8344034: Remove security manager dependency in Serialization

Reviewed-by: mullan, alanb
This commit is contained in:
Roger Riggs 2024-11-18 16:17:07 +00:00
parent d52d136486
commit 9b0ab92b16
7 changed files with 106 additions and 419 deletions

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1996, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1996, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -32,21 +32,12 @@ import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.RecordComponent;
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.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -58,13 +49,8 @@ import java.util.concurrent.ConcurrentHashMap;
import jdk.internal.event.SerializationMisdeclarationEvent;
import jdk.internal.misc.Unsafe;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import jdk.internal.reflect.ReflectionFactory;
import jdk.internal.access.SharedSecrets;
import jdk.internal.access.JavaSecurityAccess;
import jdk.internal.util.ByteArray;
import sun.reflect.misc.ReflectUtil;
/**
* Serialization's descriptor for classes. It contains the name and
@ -98,12 +84,6 @@ public final class ObjectStreamClass implements Serializable {
private static final ObjectStreamField[] serialPersistentFields =
NO_FIELDS;
/** reflection factory for obtaining serialization constructors */
@SuppressWarnings("removal")
private static final ReflectionFactory reflFactory =
AccessController.doPrivileged(
new ReflectionFactory.GetReflectionFactoryAction());
private static class Caches {
/** cache mapping local classes -> descriptors */
static final ClassCache<ObjectStreamClass> localDescs =
@ -206,8 +186,6 @@ public final class ObjectStreamClass implements Serializable {
/** session-cache of record deserialization constructor
* (in de-serialized OSC only), or null */
private MethodHandle deserializationCtr;
/** 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;
@ -280,20 +258,13 @@ public final class ObjectStreamClass implements Serializable {
*
* @return the SUID of the class described by this descriptor
*/
@SuppressWarnings("removal")
public long getSerialVersionUID() {
// REMIND: synchronize instead of relying on volatile?
if (suid == null) {
if (isRecord)
return 0L;
suid = AccessController.doPrivileged(
new PrivilegedAction<Long>() {
public Long run() {
return computeDefaultSUID(cl);
}
}
);
suid = computeDefaultSUID(cl);
}
return suid.longValue();
}
@ -304,19 +275,11 @@ public final class ObjectStreamClass implements Serializable {
*
* @return the {@code Class} instance that this descriptor represents
*/
@SuppressWarnings("removal")
@CallerSensitive
public Class<?> forClass() {
if (cl == null) {
return null;
}
requireInitialized();
if (System.getSecurityManager() != null) {
Class<?> caller = Reflection.getCallerClass();
if (ReflectUtil.needsPackageAccessCheck(caller.getClassLoader(), cl.getClassLoader())) {
ReflectUtil.checkPackageAccess(cl);
}
}
return cl;
}
@ -369,7 +332,6 @@ public final class ObjectStreamClass implements Serializable {
/**
* Creates local class descriptor representing given class.
*/
@SuppressWarnings("removal")
private ObjectStreamClass(final Class<?> cl) {
this.cl = cl;
name = cl.getName();
@ -384,53 +346,44 @@ public final class ObjectStreamClass implements Serializable {
localDesc = this;
if (serializable) {
AccessController.doPrivileged(new PrivilegedAction<>() {
public Void run() {
if (isEnum) {
suid = 0L;
fields = NO_FIELDS;
return null;
}
if (cl.isArray()) {
fields = NO_FIELDS;
return null;
}
suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
if (isEnum) {
suid = 0L;
fields = NO_FIELDS;
} else if (cl.isArray()) {
fields = NO_FIELDS;
} else {
suid = getDeclaredSUID(cl);
try {
fields = getSerialFields(cl);
computeFieldOffsets();
} catch (InvalidClassException e) {
serializeEx = deserializeEx =
new ExceptionInfo(e.classname, e.getMessage());
fields = NO_FIELDS;
}
if (isRecord) {
canonicalCtr = canonicalRecordCtr(cl);
deserializationCtrs = new DeserializationConstructorsCache();
} else if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[] { ObjectOutputStream.class },
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[] { ObjectInputStream.class },
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
domains = getProtectionDomains(cons, cl);
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
return null;
fields = NO_FIELDS;
}
});
if (isRecord) {
canonicalCtr = canonicalRecordCtr(cl);
deserializationCtrs = new DeserializationConstructorsCache();
} else if (externalizable) {
cons = getExternalizableConstructor(cl);
} else {
cons = getSerializableConstructor(cl);
writeObjectMethod = getPrivateMethod(cl, "writeObject",
new Class<?>[]{ObjectOutputStream.class},
Void.TYPE);
readObjectMethod = getPrivateMethod(cl, "readObject",
new Class<?>[]{ObjectInputStream.class},
Void.TYPE);
readObjectNoDataMethod = getPrivateMethod(
cl, "readObjectNoData", null, Void.TYPE);
hasWriteObjectData = (writeObjectMethod != null);
}
writeReplaceMethod = getInheritableMethod(
cl, "writeReplace", null, Object.class);
readResolveMethod = getInheritableMethod(
cl, "readResolve", null, Object.class);
}
} else {
suid = 0L;
fields = NO_FIELDS;
@ -474,66 +427,6 @@ public final 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}.
*/
@SuppressWarnings("removal")
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.
*/
@ -564,7 +457,6 @@ public final class ObjectStreamClass implements Serializable {
writeReplaceMethod = localDesc.writeReplaceMethod;
readResolveMethod = localDesc.readResolveMethod;
deserializeEx = localDesc.deserializeEx;
domains = localDesc.domains;
cons = localDesc.cons;
}
fieldRefl = getReflector(fields, localDesc);
@ -656,7 +548,6 @@ public final class ObjectStreamClass implements Serializable {
if (deserializeEx == null) {
deserializeEx = localDesc.deserializeEx;
}
domains = localDesc.domains;
assert cl.isRecord() ? localDesc.cons == null : true;
cons = localDesc.cons;
}
@ -1013,7 +904,6 @@ public final class ObjectStreamClass implements Serializable {
* class is non-serializable or if the appropriate no-arg constructor is
* inaccessible/unavailable.
*/
@SuppressWarnings("removal")
Object newInstance()
throws InstantiationException, InvocationTargetException,
UnsupportedOperationException
@ -1021,35 +911,7 @@ public final class ObjectStreamClass implements Serializable {
requireInitialized();
if (cons != null) {
try {
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 ie)
throw ie;
if (cause instanceof InvocationTargetException ite)
throw ite;
if (cause instanceof IllegalAccessException iae)
throw iae;
// not supposed to happen
throw x;
}
}
return cons.newInstance();
} catch (IllegalAccessException ex) {
// should not occur, as access checks have been suppressed
throw new InternalError(ex);
@ -1454,7 +1316,7 @@ public final class ObjectStreamClass implements Serializable {
* returned constructor (if any).
*/
private static Constructor<?> getSerializableConstructor(Class<?> cl) {
return reflFactory.newConstructorForSerialization(cl);
return ReflectionFactory.getReflectionFactory().newConstructorForSerialization(cl);
}
/**
@ -1462,22 +1324,18 @@ public final class ObjectStreamClass implements Serializable {
* the not found ( which should never happen for correctly generated record
* classes ).
*/
@SuppressWarnings("removal")
private static MethodHandle canonicalRecordCtr(Class<?> cls) {
assert cls.isRecord() : "Expected record, got: " + cls;
PrivilegedAction<MethodHandle> pa = () -> {
Class<?>[] paramTypes = Arrays.stream(cls.getRecordComponents())
.map(RecordComponent::getType)
.toArray(Class<?>[]::new);
try {
Constructor<?> ctr = cls.getDeclaredConstructor(paramTypes);
ctr.setAccessible(true);
return MethodHandles.lookup().unreflectConstructor(ctr);
} catch (IllegalAccessException | NoSuchMethodException e) {
return null;
}
};
return AccessController.doPrivileged(pa);
Class<?>[] paramTypes = Arrays.stream(cls.getRecordComponents())
.map(RecordComponent::getType)
.toArray(Class<?>[]::new);
try {
Constructor<?> ctr = cls.getDeclaredConstructor(paramTypes);
ctr.setAccessible(true);
return MethodHandles.lookup().unreflectConstructor(ctr);
} catch (IllegalAccessException | NoSuchMethodException e) {
return null;
}
}
/**
@ -2358,7 +2216,6 @@ public final class ObjectStreamClass implements Serializable {
* and return
* {@code Object}
*/
@SuppressWarnings("removal")
static MethodHandle deserializationCtr(ObjectStreamClass desc) {
// check the cached value 1st
MethodHandle mh = desc.deserializationCtr;
@ -2367,14 +2224,7 @@ public final class ObjectStreamClass implements Serializable {
if (mh != null) return desc.deserializationCtr = mh;
// retrieve record components
RecordComponent[] recordComponents;
try {
Class<?> cls = desc.forClass();
PrivilegedExceptionAction<RecordComponent[]> pa = cls::getRecordComponents;
recordComponents = AccessController.doPrivileged(pa);
} catch (PrivilegedActionException e) {
throw new InternalError(e.getCause());
}
RecordComponent[] recordComponents = desc.forClass().getRecordComponents();
// retrieve the canonical constructor
// (T1, T2, ..., Tn):TR