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.lang.ClassNotFoundException 
136 	 * @throws java.io.InvalidObjectException if invariant check fails
137 	 * @throws NullPointerException if arg or type is null.
138 	 */
139 	public static <T> T instantiate(final Class<T> type, final GetArg arg)
140 		throws IOException, ClassNotFoundException {
141 	    if (arg == null) throw new NullPointerException();
142 	    if (type == null) throw new NullPointerException();
143 	    final Class[] param = { GetArg.class };
144 	    Object[] args = { arg };
145 	    Constructor<T> c;
146 	    try {
147 		c = AccessController.doPrivileged(
148 		    new PrivilegedExceptionAction<Constructor<T>>(){
149 
150 			@Override
151 			public Constructor<T> run() throws Exception {
152 			    Constructor<T> c = type.getDeclaredConstructor(param);
153 			    int mods = c.getModifiers();
154 			    switch (mods){
155 				case Modifier.PUBLIC:
156 				    c.setAccessible(true); //In case constructor is public but class not.
157 				    return c;
158 				case Modifier.PROTECTED:
159 				    throw new InvalidClassException( type.getCanonicalName(),
160 					"protected constructor cannot be called by de-serializer");
161 				case Modifier.PRIVATE:
162 				    throw new InvalidClassException( type.getCanonicalName(),
163 					"private constructor cannot be called by de-serializer");
164 				default: // Package private
165 				    c.setAccessible(true);
166 				    return c;
167 			    }
168 			}
169 
170 		    });
171 		return c.newInstance(args);
172 	    } catch (PrivilegedActionException ex) {
173 		Exception e = ex.getException();
174 		if (e instanceof NoSuchMethodException) throw new InvalidClassException(type.getCanonicalName(), "No matching AtomicSerial constructor signature found");
175 		if (e instanceof SecurityException ) throw (SecurityException) e;
176 		if (e instanceof InvalidClassException ) throw (InvalidClassException) e;
177 		InvalidClassException ice = new InvalidClassException("Unexpected exception while attempting to access constructor");
178 		ice.initCause(ex);
179 		throw ice;
180 	    } catch (InvocationTargetException ex) {
181 		Throwable e = ex.getCause();
182 		if (e instanceof InvalidObjectException) throw (InvalidObjectException) e;
183 		if (e instanceof IOException) throw (IOException) e;
184 		if (e instanceof ClassNotFoundException) throw (ClassNotFoundException) e;
185 		if (e instanceof RuntimeException) throw (RuntimeException) e;
186 		InvalidObjectException ioe = new InvalidObjectException(
187 		    "Construction failed: " + type);
188 		ioe.initCause(ex);
189 		throw ioe;
190 	    } catch (IllegalAccessException ex) {
191 		throw new AssertionError("This shouldn't happen ", ex);
192 	    } catch (IllegalArgumentException ex) {
193 		throw new AssertionError("This shouldn't happen ", ex);
194 	    } catch (InstantiationException ex) {
195 		throw new InvalidClassException(type.getCanonicalName(), ex.getMessage());
196 	    }
197 	}
198 	
199 	/**
200 	 * Convenience method to test retrieval of a new ReadObject instance from
201 	 * a class static method annotated with @ReadInput
202 	 * 
203 	 * @see ReadInput
204 	 * @param streamClass
205 	 * @return
206 	 * @throws IOException 
207 	 */
208 	public static ReadObject streamReader( final Class<?> streamClass) throws IOException {
209 	    if (streamClass == null) throw new NullPointerException();
210 	    try {
211 		Method readerMethod = AccessController.doPrivileged(
212 		    new PrivilegedExceptionAction<Method>(){
213 			@Override
214 			public Method run() throws Exception {
215 			    for (Method m : streamClass.getDeclaredMethods()){
216 				if (m.isAnnotationPresent(ReadInput.class)){
217 				    m.setAccessible(true);
218 				    return m;
219 				}
220 			    }
221 			    return null;
222 			}
223 		    }
224 		);
225 		if (readerMethod != null){
226 		    ReadObject result = (ReadObject) readerMethod.invoke(null, (Object []) null);
227 		    return result;
228 		}
229 	    } catch (PrivilegedActionException ex) {
230 		Exception e = ex.getException();
231 		if (e instanceof SecurityException ) throw (SecurityException) e;
232 		InvalidClassException ice = new InvalidClassException("Unexpected exception while attempting to obtain Reader");
233 		ice.initCause(ex);
234 		throw ice;
235 	    } catch (IllegalAccessException ex) {
236 		throw new AssertionError("This shouldn't happen ", ex);
237 	    } catch (IllegalArgumentException ex) {
238 		throw new AssertionError("This shouldn't happen ", ex);
239 	    } catch (InvocationTargetException ex) {
240 		InvalidClassException ice = new InvalidClassException("Unexpected exception while attempting to obtain Reader");
241 		ice.initCause(ex);
242 		throw ice;
243 	    }
244 	    return null;
245 	}
246     }
247 
248     /**
249      * If an object wishes to read from the stream during construction
250      * it must provide a class static method with the following annotation.
251      * <p>
252      * The Serializer will use this static method to obtain a ReadObject instance
253      * that will be invoked at the time of the streams choosing.
254      * @see ReadObject
255      */
256     @Retention(value = RetentionPolicy.RUNTIME)
257     @Target(value = ElementType.METHOD)
258     public static @interface ReadInput {
259     }
260 
261     /**
262      * GetArg is the single argument to AtomicSerial's constructor
263      * 
264      * @author peter
265      */
266     public static abstract class GetArg extends ObjectInputStream.GetField 
267 					implements ObjectStreamContext {
268 	
269 	private static Guard enableSubclassImplementation 
270 		= new SerializablePermission("enableSubclassImplementation");
271 	
272 	
273 
274 	private static boolean check() {
275 	    enableSubclassImplementation.checkGuard(null);
276 	    return true;
277 	}
278 	
279 	/**
280          * Not intended for general construction, however may be extended
281          * by an ObjectInput implementation or for testing purposes.
282          * 
283          * @throws SecurityException if caller doesn't have permission java.io.SerializablePermission "enableSubclassImplementation";
284          */
285 	protected GetArg() {
286 	    this(check());
287 	}
288 	
289 	GetArg(boolean check){
290 	    super();
291 	}
292 
293 	/**
294 	 * Provides access to stream classes that belong to the Object under
295 	 * construction, ordered from superclass to child class.
296 	 *
297 	 * @return stream classes that belong to the object currently being
298 	 * de-serialized.
299 	 */
300 	public abstract Class[] serialClasses();
301 
302 	/**
303 	 * If an AtomicSerial implementation annotates a static method that returns
304 	 * a Reader instance, with {@link ReadInput}, then the stream will provide
305 	 * the ReadObject access to the stream at a time that suits the stream.  
306 	 * This method provides a way for an object under construction to 
307 	 * retrieve information from the stream.  This is provided to retain
308 	 * compatibility with writeObject methods that write directly to the
309 	 * stream.
310 	 *
311 	 * @return ReadObject instance provided by static class method after it has
312 	 * read from the stream, or null.
313 	 */
314 	public abstract ReadObject getReader();
315 	
316 	
317 	/**
318          * Get the value of the named Object field from the persistent field.
319 	 * Convenience method to avoid type casts, that also performs a type check.
320 	 * <p>
321 	 * Instances of java.util.Collection will be replaced in the stream
322 	 * by a safe limited functionality immutable Collection instance 
323 	 * that must be passed to a collection instance constructor.  It is
324 	 * advisable to pass a Collections empty collection instance for the
325 	 * val parameter, to prevent a NullPointerException, in this case.
326          *
327 	 * @param <T> Type of object, note if T is an instance of Class&lt;? extends SomeClass&gt;
328          * the you must validate it, as this method can't.
329          * @param  name the name of the field
330          * @param  val the default value to use if <code>name</code> does not
331          *         have a value
332 	 * @param type check to be performed, prior to returning.
333          * @return the value of the named <code>Object</code> field
334          * @throws IOException if there are I/O errors while reading from the
335          *         underlying <code>InputStream</code>
336 	 * @throws java.lang.ClassNotFoundException if class is not resolvable
337 	 *	   from the default loader.
338          * @throws IllegalArgumentException if type of <code>name</code> is
339          *         not serializable or if the field type is incorrect
340 	 * @throws InvalidObjectException containing a ClassCastException cause 
341 	 *	   if object to be returned is not an instance of type.
342 	 * @throws NullPointerException if type is null.
343          */
344         public abstract <T> T get(String name, T val, Class<T> type) 
345 		throws IOException, ClassNotFoundException;
346 	
347 	public abstract GetArg validateInvariants(  String[] fields, 
348 						    Class[] types,
349 						    boolean[] nonNull) 
350 							throws IOException;	  
351 	
352 	}
353     
354 }
355 
356