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