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<? extends SomeClass>
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