View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  
18  package org.apache.river.api.io;
19  
20  import java.io.IOException;
21  import java.io.InvalidClassException;
22  import java.io.InvalidObjectException;
23  import java.io.ObjectInput;
24  import java.io.ObjectInputStream;
25  import java.io.SerializablePermission;
26  import java.lang.annotation.ElementType;
27  import java.lang.annotation.Retention;
28  import java.lang.annotation.RetentionPolicy;
29  import java.lang.annotation.Target;
30  import java.lang.reflect.Constructor;
31  import java.lang.reflect.InvocationTargetException;
32  import java.lang.reflect.Method;
33  import java.lang.reflect.Modifier;
34  import java.security.AccessController;
35  import java.security.Guard;
36  import java.security.PrivilegedActionException;
37  import java.security.PrivilegedExceptionAction;
38  import net.jini.io.ObjectStreamContext;
39  
40  /**
41   * Traditional java de-serialization cannot be used over untrusted connections
42   * for the following reasons:
43   * <p>
44   * The serial stream can be manipulated to allow the attacker to instantiate
45   * any Serializable object available on the CLASSPATH or any object that
46   * has a default constructor, such as ClassLoader.
47   * <p>
48   * Failure to validate invariants during construction, or as a result of 
49   * an exception, objects can remain in an invalid state after construction. 
50   * During traditional de-serialization, an objects state is written after it's
51   * creation, thus an attacker can steal a reference to the object without
52   * any invariant check protection, by manipulating the stream.
53   * <p>
54   * In addition many java objects, including ObjectInputStream itself, read 
55   * integer length values from the stream and instantiate arrays without checking 
56   * the size first, so an attacker can easily cause an Error that brings
57   * down the JVM. 
58   * <p>
59   * A requirement of implementing this interface is to implement a constructor
60   * that accepts a single GetArg parameter.  This constructor may be
61   * public or have default visibility, even in this case, the constructor
62   * must be treated as a public constructor.
63   * <p>
64   * <code>
65   * public AtomicSerialImpl(GetArg arg) throws InvalidObjectException{<br>
66   *	super(check(arg)); // If super also implements @AtomicSerial<br>
67   *	// Set fields here<br>
68   * }<br>
69   * </code>
70   * In addition, before calling a superclass constructor, the class must
71   * also implement a static invariant check method, for example:
72   * <p>
73   * static GetArg check(GetArg) throws InvalidObjectException;
74   * <p>
75   * Atomic stands for atomic failure, if invariants cannot be satisfied an 
76   * instance cannot be created and hence a reference cannot be stolen.
77   * <p>
78   * The serial form of AtomicSerial is backward compatible with Serializable
79   * classes that do not define a writeObject method.  It is also compatible 
80   * with Serializable classes that define a writeObject method that calls
81   * defaultWriteObject.  AtomicSerial provides backward compatibility with 
82   * Serializable classes that implement writeObject and write other Objects
83   * or primitives to the stream when {@link ReadObject} and {@link ReadInput}
84   * are implemented by the class.
85   * 
86   * @author peter
87   * @see ReadObject
88   * @see ReadInput
89   * @see GetArg
90   */
91  @Retention(RetentionPolicy.RUNTIME)
92  @Target(ElementType.TYPE)
93  public @interface AtomicSerial {
94       
95      
96      /**
97       * ReadObject that can be used to read in data and Objects written
98       * to the stream by writeObject() methods.
99       * 
100      * @see  ReadInput
101      */
102     public interface ReadObject {
103 	void read(ObjectInput input) throws IOException, ClassNotFoundException;
104     }
105     
106     /**
107      * Factory to test AtomicSerial instantiation compliance.
108      */
109     public static final class Factory {
110 	private Factory(){} // Non instantiable.
111 	
112 	/**
113 	 * Convenience method for testing implementing class constructor
114 	 * signature compliance.
115 	 * <p>
116 	 * De-serializers are free to implement higher performance instantiation
117 	 * that complies with this contract.
118 	 * <p>
119 	 * Only public and package default constructors can be called by 
120 	 * de-serializers.  Package default constructors have been provided
121 	 * to prevent implementations from polluting public api,
122 	 * but should be treated as public constructors.
123 	 * <p>
124 	 * Constructors with private visibility cannot be called.
125 	 * <p>
126 	 * Constructors with protected visibility can only be called by 
127 	 * subclasses, not de-serializers.
128 	 * 
129 	 * @param <T> AtomicSerial implementation type.
130 	 * @param type AtomicSerial implementing class.
131 	 * @param arg GetArg caller sensitive arguments used by implementing constructor.
132 	 * @return new instance of T.
133 	 * @throws java.io.InvalidClassException if constructor is non compliant
134 	 * or doesn't exist. 
135 	 * @throws java.io.InvalidObjectException if invariant check fails
136 	 * @throws NullPointerException if arg or type is null.
137 	 */
138 	public static <T> T instantiate(final Class<T> type, final GetArg arg)
139 		throws IOException {
140 	    if (arg == null) throw new NullPointerException();
141 	    if (type == null) throw new NullPointerException();
142 	    final Class[] param = { GetArg.class };
143 	    Object[] args = { arg };
144 	    Constructor<T> c;
145 	    try {
146 		c = AccessController.doPrivileged(
147 		    new PrivilegedExceptionAction<Constructor<T>>(){
148 
149 			@Override
150 			public Constructor<T> run() throws Exception {
151 			    Constructor<T> c = type.getDeclaredConstructor(param);
152 			    int mods = c.getModifiers();
153 			    switch (mods){
154 				case Modifier.PUBLIC:
155 				    c.setAccessible(true); //In case constructor is public but class not.
156 				    return c;
157 				case Modifier.PROTECTED:
158 				    throw new InvalidClassException( type.getCanonicalName(),
159 					"protected constructor cannot be called by de-serializer");
160 				case Modifier.PRIVATE:
161 				    throw new InvalidClassException( type.getCanonicalName(),
162 					"private constructor cannot be called by de-serializer");
163 				default: // Package private
164 				    c.setAccessible(true);
165 				    return c;
166 			    }
167 			}
168 
169 		    });
170 		return c.newInstance(args);
171 	    } catch (PrivilegedActionException ex) {
172 		Exception e = ex.getException();
173 		if (e instanceof NoSuchMethodException) throw new InvalidClassException(type.getCanonicalName(), "No matching AtomicSerial constructor signature found");
174 		if (e instanceof SecurityException ) throw (SecurityException) e;
175 		if (e instanceof InvalidClassException ) throw (InvalidClassException) e;
176 		InvalidClassException ice = new InvalidClassException("Unexpected exception while attempting to access constructor");
177 		ice.initCause(ex);
178 		throw ice;
179 	    } catch (InvocationTargetException ex) {
180 		Throwable e = ex.getCause();
181 		if (e instanceof InvalidObjectException) throw (InvalidObjectException) e;
182 		if (e instanceof IOException) throw (IOException) e;
183 		if (e instanceof RuntimeException) throw (RuntimeException) e;
184 		InvalidObjectException ioe = new InvalidObjectException(
185 		    "Construction failed: " + type);
186 		ioe.initCause(ex);
187 		throw ioe;
188 	    } catch (IllegalAccessException ex) {
189 		throw new AssertionError("This shouldn't happen ", ex);
190 	    } catch (IllegalArgumentException ex) {
191 		throw new AssertionError("This shouldn't happen ", ex);
192 	    } catch (InstantiationException ex) {
193 		throw new InvalidClassException(type.getCanonicalName(), ex.getMessage());
194 	    }
195 	}
196 	
197 	/**
198 	 * Convenience method to test retrieval of a new ReadObject instance from
199 	 * a class static method annotated with @ReadInput
200 	 * 
201 	 * @see ReadInput
202 	 * @param streamClass
203 	 * @return
204 	 * @throws IOException 
205 	 */
206 	public static ReadObject streamReader( final Class<?> streamClass) throws IOException {
207 	    if (streamClass == null) throw new NullPointerException();
208 	    try {
209 		Method readerMethod = AccessController.doPrivileged(
210 		    new PrivilegedExceptionAction<Method>(){
211 			@Override
212 			public Method run() throws Exception {
213 			    for (Method m : streamClass.getDeclaredMethods()){
214 				if (m.isAnnotationPresent(ReadInput.class)){
215 				    m.setAccessible(true);
216 				    return m;
217 				}
218 			    }
219 			    return null;
220 			}
221 		    }
222 		);
223 		if (readerMethod != null){
224 		    ReadObject result = (ReadObject) readerMethod.invoke(null, (Object []) null);
225 		    return result;
226 		}
227 	    } catch (PrivilegedActionException ex) {
228 		Exception e = ex.getException();
229 		if (e instanceof SecurityException ) throw (SecurityException) e;
230 		InvalidClassException ice = new InvalidClassException("Unexpected exception while attempting to obtain Reader");
231 		ice.initCause(ex);
232 		throw ice;
233 	    } catch (IllegalAccessException ex) {
234 		throw new AssertionError("This shouldn't happen ", ex);
235 	    } catch (IllegalArgumentException ex) {
236 		throw new AssertionError("This shouldn't happen ", ex);
237 	    } catch (InvocationTargetException ex) {
238 		InvalidClassException ice = new InvalidClassException("Unexpected exception while attempting to obtain Reader");
239 		ice.initCause(ex);
240 		throw ice;
241 	    }
242 	    return null;
243 	}
244     }
245 
246     /**
247      * If an object wishes to read from the stream during construction
248      * it must provide a class static method with the following annotation.
249      * <p>
250      * The Serializer will use this static method to obtain a ReadObject instance
251      * that will be invoked at the time of the streams choosing.
252      * @see ReadObject
253      */
254     @Retention(value = RetentionPolicy.RUNTIME)
255     @Target(value = ElementType.METHOD)
256     public static @interface ReadInput {
257     }
258 
259     /**
260      * GetArg is the single argument to AtomicSerial's constructor
261      * 
262      * @author peter
263      */
264     public static abstract class GetArg extends ObjectInputStream.GetField 
265 					implements ObjectStreamContext {
266 	
267 	private static Guard enableSubclassImplementation 
268 		= new SerializablePermission("enableSubclassImplementation");
269 	
270 	
271 
272 	private static boolean check() {
273 	    enableSubclassImplementation.checkGuard(null);
274 	    return true;
275 	}
276 	
277 	/**
278          * Not intended for general construction, however may be extended
279          * by an ObjectInput implementation or for testing purposes.
280          * 
281          * @throws SecurityException if caller doesn't have permission java.io.SerializablePermission "enableSubclassImplementation";
282          */
283 	protected GetArg() {
284 	    this(check());
285 	}
286 	
287 	GetArg(boolean check){
288 	    super();
289 	}
290 
291 	/**
292 	 * Provides access to stream classes that belong to the Object under
293 	 * construction, ordered from superclass to child class.
294 	 *
295 	 * @return stream classes that belong to the object currently being
296 	 * de-serialized.
297 	 */
298 	public abstract Class[] serialClasses();
299 
300 	/**
301 	 * If an AtomicSerial implementation annotates a static method that returns
302 	 * a Reader instance, with {@link ReadInput}, then the stream will provide
303 	 * the ReadObject access to the stream at a time that suits the stream.  
304 	 * This method provides a way for an object under construction to 
305 	 * retrieve information from the stream.  This is provided to retain
306 	 * compatibility with writeObject methods that write directly to the
307 	 * stream.
308 	 *
309 	 * @return ReadObject instance provided by static class method after it has
310 	 * read from the stream, or null.
311 	 */
312 	public abstract ReadObject getReader();
313 	
314 	
315 	/**
316          * Get the value of the named Object field from the persistent field.
317 	 * Convenience method to avoid type casts, that also performs a type check.
318 	 * <p>
319 	 * Instances of java.util.Collection will be replaced in the stream
320 	 * by a safe limited functionality immutable Collection instance 
321 	 * that must be passed to a collection instance constructor.  It is
322 	 * advisable to pass a Collections empty collection instance for the
323 	 * val parameter, to prevent a NullPointerException, in this case.
324          *
325 	 * @param <T> Type of object, note if T is an instance of Class&lt;? extends SomeClass&gt;
326          * the you must validate it, as this method can't.
327          * @param  name the name of the field
328          * @param  val the default value to use if <code>name</code> does not
329          *         have a value
330 	 * @param type check to be performed, prior to returning.
331          * @return the value of the named <code>Object</code> field
332          * @throws IOException if there are I/O errors while reading from the
333          *         underlying <code>InputStream</code>
334          * @throws IllegalArgumentException if type of <code>name</code> is
335          *         not serializable or if the field type is incorrect
336 	 * @throws InvalidObjectException containing a ClassCastException cause 
337 	 *	   if object to be returned is not an instance of type.
338 	 * @throws NullPointerException if type is null.
339          */
340         public abstract <T> T get(String name, T val, Class<T> type) 
341 		throws IOException;
342 	
343 	public abstract GetArg validateInvariants(  String[] fields, 
344 						    Class[] types,
345 						    boolean[] nonNull) 
346 							throws IOException;	  
347 	
348 	}
349     
350 }
351 
352