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