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