8180024: Improve construction of objects during deserialization

Reviewed-by: rriggs, skoivu, ahgross, rhalade
This commit is contained in:
Daniel Fuchs 2017-05-19 11:18:49 +01:00
parent d36d599779
commit 020204a972
4 changed files with 270 additions and 6 deletions

View File

@ -32,14 +32,19 @@ import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -53,7 +58,8 @@ import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.internal.reflect.ReflectionFactory;
import sun.reflect.misc.ReflectUtil;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.misc.JavaSecurityAccess;
import static java.io.ObjectStreamField.*;
/**
@ -176,6 +182,9 @@ public class ObjectStreamClass implements Serializable {
/** serialization-appropriate constructor, or null if none */
private Constructor<?> cons;
/** protection domains that need to be checked when calling the constructor */
private ProtectionDomain[] domains;
/** class-defined writeObject method, or null if none */
private Method writeObjectMethod;
/** class-defined readObject method, or null if none */
@ -508,6 +517,7 @@ public class ObjectStreamClass implements Serializable {
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
@ -550,6 +560,65 @@ public class ObjectStreamClass implements Serializable {
ObjectStreamClass() {
}
/**
* Creates a PermissionDomain that grants no permission.
*/
private ProtectionDomain noPermissionsDomain() {
PermissionCollection perms = new Permissions();
perms.setReadOnly();
return new ProtectionDomain(null, perms);
}
/**
* Aggregate the ProtectionDomains of all the classes that separate
* a concrete class {@code cl} from its ancestor's class declaring
* a constructor {@code cons}.
*
* If {@code cl} is defined by the boot loader, or the constructor
* {@code cons} is declared by {@code cl}, or if there is no security
* manager, then this method does nothing and {@code null} is returned.
*
* @param cons A constructor declared by {@code cl} or one of its
* ancestors.
* @param cl A concrete class, which is either the class declaring
* the constructor {@code cons}, or a serializable subclass
* of that class.
* @return An array of ProtectionDomain representing the set of
* ProtectionDomain that separate the concrete class {@code cl}
* from its ancestor's declaring {@code cons}, or {@code null}.
*/
private ProtectionDomain[] getProtectionDomains(Constructor<?> cons,
Class<?> cl) {
ProtectionDomain[] domains = null;
if (cons != null && cl.getClassLoader() != null
&& System.getSecurityManager() != null) {
Class<?> cls = cl;
Class<?> fnscl = cons.getDeclaringClass();
Set<ProtectionDomain> pds = null;
while (cls != fnscl) {
ProtectionDomain pd = cls.getProtectionDomain();
if (pd != null) {
if (pds == null) pds = new HashSet<>();
pds.add(pd);
}
cls = cls.getSuperclass();
if (cls == null) {
// that's not supposed to happen
// make a ProtectionDomain with no permission.
// should we throw instead?
if (pds == null) pds = new HashSet<>();
else pds.clear();
pds.add(noPermissionsDomain());
break;
}
}
if (pds != null) {
domains = pds.toArray(new ProtectionDomain[0]);
}
}
return domains;
}
/**
* Initializes class descriptor representing a proxy class.
*/
@ -580,6 +649,7 @@ public class ObjectStreamClass implements Serializable {
writeReplaceMethod = localDesc.writeReplaceMethod;
readResolveMethod = localDesc.readResolveMethod;
deserializeEx = localDesc.deserializeEx;
domains = localDesc.domains;
cons = localDesc.cons;
}
fieldRefl = getReflector(fields, localDesc);
@ -666,6 +736,7 @@ public class ObjectStreamClass implements Serializable {
if (deserializeEx == null) {
deserializeEx = localDesc.deserializeEx;
}
domains = localDesc.domains;
cons = localDesc.cons;
}
@ -1006,7 +1077,35 @@ public class ObjectStreamClass implements Serializable {
requireInitialized();
if (cons != null) {
try {
return cons.newInstance();
if (domains == null || domains.length == 0) {
return cons.newInstance();
} else {
JavaSecurityAccess jsa = SharedSecrets.getJavaSecurityAccess();
PrivilegedAction<?> pea = () -> {
try {
return cons.newInstance();
} catch (InstantiationException
| InvocationTargetException
| IllegalAccessException x) {
throw new UndeclaredThrowableException(x);
}
}; // Can't use PrivilegedExceptionAction with jsa
try {
return jsa.doIntersectionPrivilege(pea,
AccessController.getContext(),
new AccessControlContext(domains));
} catch (UndeclaredThrowableException x) {
Throwable cause = x.getCause();
if (cause instanceof InstantiationException)
throw (InstantiationException) cause;
if (cause instanceof InvocationTargetException)
throw (InvocationTargetException) cause;
if (cause instanceof IllegalAccessException)
throw (IllegalAccessException) cause;
// not supposed to happen
throw x;
}
}
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);

View File

@ -38,7 +38,10 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.DigestOutputStream;
import java.security.AccessController;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.lang.reflect.Modifier;
import java.lang.reflect.Field;
@ -57,6 +60,8 @@ import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import com.sun.corba.se.impl.util.RepositoryId;
@ -443,6 +448,65 @@ public class ObjectStreamClass implements java.io.Serializable {
private static final PersistentFieldsValue persistentFieldsValue =
new PersistentFieldsValue();
/**
* Creates a PermissionDomain that grants no permission.
*/
private ProtectionDomain noPermissionsDomain() {
PermissionCollection perms = new Permissions();
perms.setReadOnly();
return new ProtectionDomain(null, perms);
}
/**
* Aggregate the ProtectionDomains of all the classes that separate
* a concrete class {@code cl} from its ancestor's class declaring
* a constructor {@code cons}.
*
* If {@code cl} is defined by the boot loader, or the constructor
* {@code cons} is declared by {@code cl}, or if there is no security
* manager, then this method does nothing and {@code null} is returned.
*
* @param cons A constructor declared by {@code cl} or one of its
* ancestors.
* @param cl A concrete class, which is either the class declaring
* the constructor {@code cons}, or a serializable subclass
* of that class.
* @return An array of ProtectionDomain representing the set of
* ProtectionDomain that separate the concrete class {@code cl}
* from its ancestor's declaring {@code cons}, or {@code null}.
*/
private ProtectionDomain[] getProtectionDomains(Constructor<?> cons,
Class<?> cl) {
ProtectionDomain[] domains = null;
if (cons != null && cl.getClassLoader() != null
&& System.getSecurityManager() != null) {
Class<?> cls = cl;
Class<?> fnscl = cons.getDeclaringClass();
Set<ProtectionDomain> pds = null;
while (cls != fnscl) {
ProtectionDomain pd = cls.getProtectionDomain();
if (pd != null) {
if (pds == null) pds = new HashSet<>();
pds.add(pd);
}
cls = cls.getSuperclass();
if (cls == null) {
// that's not supposed to happen
// make a ProtectionDomain with no permission.
// should we throw instead?
if (pds == null) pds = new HashSet<>();
else pds.clear();
pds.add(noPermissionsDomain());
break;
}
}
if (pds != null) {
domains = pds.toArray(new ProtectionDomain[0]);
}
}
return domains;
}
/*
* Initialize class descriptor. This method is only invoked on class
* descriptors created via calls to lookupInternal(). This method is kept
@ -568,11 +632,15 @@ public class ObjectStreamClass implements java.io.Serializable {
readResolveObjectMethod = bridge.readResolveForSerialization(cl);
domains = new ProtectionDomain[] {noPermissionsDomain()};
if (externalizable)
cons = getExternalizableConstructor(cl) ;
else
cons = getSerializableConstructor(cl) ;
domains = getProtectionDomains(cons, cl);
if (serializable && !forProxyClass) {
writeObjectMethod = bridge.writeObjectForSerialization(cl) ;
readObjectMethod = bridge.readObjectForSerialization(cl);
@ -910,7 +978,7 @@ public class ObjectStreamClass implements java.io.Serializable {
{
if (cons != null) {
try {
return cons.newInstance();
return bridge.newInstanceForSerialization(cons, domains);
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
InternalError ie = new InternalError();
@ -1506,6 +1574,7 @@ public class ObjectStreamClass implements java.io.Serializable {
private transient MethodHandle writeReplaceObjectMethod;
private transient MethodHandle readResolveObjectMethod;
private transient Constructor<?> cons;
private transient ProtectionDomain[] domains;
/**
* Beginning in Java to IDL ptc/02-01-12, RMI-IIOP has a

View File

@ -27,8 +27,9 @@ package sun.corba ;
import java.io.OptionalDataException;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field ;
import java.lang.reflect.Constructor ;
import java.lang.reflect.Field;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.StackWalker;
import java.lang.StackWalker.StackFrame;
import java.util.Optional;
@ -37,6 +38,7 @@ import java.util.stream.Stream;
import java.security.AccessController;
import java.security.Permission;
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import sun.misc.Unsafe;
import sun.reflect.ReflectionFactory;
@ -340,6 +342,36 @@ public final class Bridge
return reflectionFactory.newConstructorForExternalization( cl ) ;
}
/**
* Invokes the supplied constructor, adding the provided protection domains
* to the invocation stack before invoking {@code Constructor::newInstance}.
*
* This is equivalent to calling
* {@code ReflectionFactory.newInstanceForSerialization(cons,domains)}.
*
* @param cons A constructor obtained from {@code
* newConstructorForSerialization} or {@code
* newConstructorForExternalization}.
*
* @param domains An array of protection domains that limit the privileges
* with which the constructor is invoked. Can be {@code null}
* or empty, in which case privileges are only limited by the
* {@linkplain AccessController#getContext() current context}.
*
* @return A new object built from the provided constructor.
*
* @throws NullPointerException if {@code cons} is {@code null}.
* @throws InstantiationException if thrown by {@code cons.newInstance()}.
* @throws InvocationTargetException if thrown by {@code cons.newInstance()}.
* @throws IllegalAccessException if thrown by {@code cons.newInstance()}.
*/
public final Object newInstanceForSerialization(Constructor<?> cons,
ProtectionDomain[] domains)
throws InstantiationException, InvocationTargetException, IllegalAccessException
{
return reflectionFactory.newInstanceForSerialization(cons, domains);
}
/**
* Returns true if the given class defines a static initializer method,
* false otherwise.

View File

@ -29,9 +29,14 @@ import java.io.OptionalDataException;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.UndeclaredThrowableException;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.Permission;
import java.security.ProtectionDomain;
import java.security.PrivilegedAction;
import jdk.internal.misc.SharedSecrets;
import jdk.internal.misc.JavaSecurityAccess;
/**
* ReflectionFactory supports custom serialization.
@ -139,6 +144,66 @@ public class ReflectionFactory {
return delegate.readObjectForSerialization(cl);
}
/**
* Invokes the supplied constructor, adding the provided protection domains
* to the invocation stack before invoking {@code Constructor::newInstance}.
* If no {@linkplain System#getSecurityManager() security manager} is present,
* or no domains are provided, then this method simply calls
* {@code cons.newInstance()}. Otherwise, it invokes the provided constructor
* with privileges at the intersection of the current context and the provided
* protection domains.
*
* @param cons A constructor obtained from {@code
* newConstructorForSerialization} or {@code
* newConstructorForExternalization}.
* @param domains An array of protection domains that limit the privileges
* with which the constructor is invoked. Can be {@code null}
* or empty, in which case privileges are only limited by the
* {@linkplain AccessController#getContext() current context}.
*
* @return A new object built from the provided constructor.
*
* @throws NullPointerException if {@code cons} is {@code null}.
* @throws InstantiationException if thrown by {@code cons.newInstance()}.
* @throws InvocationTargetException if thrown by {@code cons.newInstance()}.
* @throws IllegalAccessException if thrown by {@code cons.newInstance()}.
*/
public final Object newInstanceForSerialization(Constructor<?> cons,
ProtectionDomain[] domains)
throws InstantiationException, InvocationTargetException, IllegalAccessException
{
SecurityManager sm = System.getSecurityManager();
if (sm == null || domains == null || domains.length == 0) {
return cons.newInstance();
} else {
JavaSecurityAccess jsa = SharedSecrets.getJavaSecurityAccess();
PrivilegedAction<?> pea = () -> {
try {
return cons.newInstance();
} catch (InstantiationException
| InvocationTargetException
| IllegalAccessException x) {
throw new UndeclaredThrowableException(x);
}
}; // Can't use PrivilegedExceptionAction with jsa
try {
return jsa.doIntersectionPrivilege(pea,
AccessController.getContext(),
new AccessControlContext(domains));
} catch (UndeclaredThrowableException x) {
Throwable cause = x.getCause();
if (cause instanceof InstantiationException)
throw (InstantiationException) cause;
if (cause instanceof InvocationTargetException)
throw (InvocationTargetException) cause;
if (cause instanceof IllegalAccessException)
throw (IllegalAccessException) cause;
// not supposed to happen
throw x;
}
}
}
/**
* Returns a direct MethodHandle for the {@code readObjectNoData} method on
* a Serializable class.
@ -224,4 +289,3 @@ public class ReflectionFactory {
}
}
}