Skip to content

Commit d8da62b

Browse files
committed
Support JDK serialization
1 parent 14ddf78 commit d8da62b

File tree

31 files changed

+993
-44
lines changed

31 files changed

+993
-44
lines changed

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java

Lines changed: 124 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOr;
3838
import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOrNull;
3939
import static com.oracle.svm.jvmtiagentbase.Support.getDirectCallerClass;
40+
import static com.oracle.svm.jvmtiagentbase.Support.getIntArgument;
4041
import static com.oracle.svm.jvmtiagentbase.Support.getMethodDeclaringClass;
4142
import static com.oracle.svm.jvmtiagentbase.Support.getObjectArgument;
4243
import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions;
@@ -49,6 +50,7 @@
4950
import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND;
5051
import static org.graalvm.word.WordFactory.nullPointer;
5152

53+
import java.lang.reflect.Modifier;
5254
import java.nio.ByteBuffer;
5355
import java.nio.ByteOrder;
5456
import java.util.ArrayList;
@@ -81,10 +83,12 @@
8183
import com.oracle.svm.agent.restrict.ProxyAccessVerifier;
8284
import com.oracle.svm.agent.restrict.ReflectAccessVerifier;
8385
import com.oracle.svm.agent.restrict.ResourceAccessVerifier;
86+
import com.oracle.svm.agent.restrict.SerializationAccessVerifier;
8487
import com.oracle.svm.configure.config.ConfigurationMethod;
8588
import com.oracle.svm.core.c.function.CEntryPointOptions;
8689
import com.oracle.svm.core.util.VMError;
8790
import com.oracle.svm.jni.nativeapi.JNIEnvironment;
91+
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes;
8892
import com.oracle.svm.jni.nativeapi.JNIMethodId;
8993
import com.oracle.svm.jni.nativeapi.JNINativeMethod;
9094
import com.oracle.svm.jni.nativeapi.JNIObjectHandle;
@@ -129,6 +133,7 @@ final class BreakpointInterceptor {
129133
private static ReflectAccessVerifier accessVerifier;
130134
private static ProxyAccessVerifier proxyVerifier;
131135
private static ResourceAccessVerifier resourceVerifier;
136+
private static SerializationAccessVerifier serializationAccessVerifier;
132137
private static NativeImageAgent agent;
133138

134139
private static Map<Long, Breakpoint> installedBreakpoints;
@@ -913,6 +918,119 @@ private static String asInternalSignature(Object paramTypesArray) {
913918
return null;
914919
}
915920

921+
@SuppressWarnings("unused")
922+
private static boolean generateSerializationConstructor(JNIEnvironment jni, Breakpoint bp) {
923+
JNIObjectHandle self = getObjectArgument(0);
924+
JNIObjectHandle serializeTargetClass = getObjectArgument(1);
925+
String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass);
926+
JNIObjectHandle parameterTypes = getObjectArgument(2);
927+
Object parameterTypeNames = getClassArrayNames(jni, parameterTypes);
928+
JNIObjectHandle checkedExceptions = getObjectArgument(3);
929+
Object checkedExceptionNames = getClassArrayNames(jni, checkedExceptions);
930+
int modifiers = getIntArgument(4);
931+
JNIObjectHandle targetConstructorClass = getObjectArgument(5);
932+
String targetConstructorClassName = getClassNameOrNull(jni, targetConstructorClass);
933+
boolean allowed = (serializationAccessVerifier == null ||
934+
serializationAccessVerifier.verifyGenerateSerializationConstructor(jni, serializeTargetClassName, parameterTypeNames,
935+
checkedExceptionNames, modifiers, targetConstructorClassName));
936+
Object result = false;
937+
if (allowed) {
938+
JNIValue args = StackValue.get(6, JNIValue.class);
939+
args.addressOf(0).setObject(self);
940+
args.addressOf(1).setObject(serializeTargetClass);
941+
args.addressOf(2).setObject(parameterTypes);
942+
args.addressOf(3).setObject(checkedExceptions);
943+
args.addressOf(4).setInt(modifiers);
944+
args.addressOf(5).setObject(targetConstructorClass);
945+
result = nullHandle().notEqual(jniFunctions().getCallObjectMethodA().invoke(jni, bp.clazz, bp.method, args));
946+
if (clearException(jni)) {
947+
result = false;
948+
}
949+
}
950+
JNIObjectHandle callerClass = getDirectCallerClass();
951+
if (traceWriter != null) {
952+
traceWriter.traceCall("serialization",
953+
"generateSerializationConstructor",
954+
null,
955+
null,
956+
null,
957+
result,
958+
serializeTargetClassName, parameterTypeNames,
959+
checkedExceptionNames, modifiers, targetConstructorClassName);
960+
JNIFunctionPointerTypes.CallIntMethodFunctionPointer noArgRetIntCall = jniFunctions().getCallIntMethod();
961+
int privateStaticFinalMask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
962+
int staticFinalMask = Modifier.STATIC | Modifier.FINAL;
963+
964+
// call serializationTargetClass.getDeclaredFields();
965+
JNIObjectHandle javaLangClass = agent.handles().findClass(jni, "java/lang/Class");
966+
JNIMethodId getDeclaredFieldsMI = agent.handles().getMethodId(jni, javaLangClass, "getDeclaredFields",
967+
"()[Ljava/lang/reflect/Field;", false);
968+
JNIFunctionPointerTypes.CallObjectMethod0FunctionPointer noArgRetObjectCall = jniFunctions().getCallObjectMethod();
969+
JNIObjectHandle fieldsJArray = noArgRetObjectCall.invoke(jni, serializeTargetClass, getDeclaredFieldsMI);
970+
971+
// Prepare JNIMethodIds for later calls
972+
JNIObjectHandle javaLangReflectField = agent.handles().findClass(jni, "java/lang/reflect/Field");
973+
JNIMethodId getFieldNameId = agent.handles().getMethodId(jni, javaLangReflectField, "getName", "()Ljava/lang/String;", false);
974+
JNIMethodId getFieldModifiersId = agent.handles().getMethodId(jni, javaLangReflectField, "getModifiers", "()I", false);
975+
JNIMethodId getFieldTypeId = agent.handles().getMethodId(jni, javaLangReflectField, "getType", "()Ljava/lang/Class;", false);
976+
// Add serialize and deserialize fields into reflection configs
977+
// Check each field
978+
int fieldArrayLength = jniFunctions().getGetArrayLength().invoke(jni, fieldsJArray);
979+
for (int i = 0; i < fieldArrayLength; i++) {
980+
// Get field object from array
981+
JNIObjectHandle field = jniFunctions().getGetObjectArrayElement().invoke(jni, fieldsJArray, i);
982+
// call field.getName() to get field's name
983+
String fieldName = fromJniString(jni, noArgRetObjectCall.invoke(jni, field, getFieldNameId));
984+
// call field.getModifiers tp get field's modifiers
985+
int fieldModifiers = noArgRetIntCall.invoke(jni, field, getFieldModifiersId);
986+
if (fieldName.equals("serialPersistentFields") &&
987+
(fieldModifiers & privateStaticFinalMask) == privateStaticFinalMask) {
988+
traceWriter.traceCall("reflect",
989+
"getDeclaredField",
990+
serializeTargetClassName,
991+
null,
992+
null,
993+
result, "serialPersistentFields");
994+
} else if (fieldName.equals("serialVersionUID") &&
995+
(fieldModifiers & staticFinalMask) == staticFinalMask) {
996+
traceWriter.traceCall("reflect",
997+
"getDeclaredField",
998+
serializeTargetClassName,
999+
null,
1000+
null,
1001+
result, "serialVersionUID");
1002+
} else if ((fieldModifiers & staticFinalMask) != staticFinalMask) {
1003+
// Set the field's allowWrite and unsafeAccess properties
1004+
traceWriter.traceCall("reflect",
1005+
"getDeclaredField",
1006+
serializeTargetClassName,
1007+
null,
1008+
null,
1009+
result, (modifiers & Modifier.FINAL) == Modifier.FINAL, (modifiers & Modifier.STATIC) == 0, fieldName);
1010+
}
1011+
// Add field's class in config
1012+
// call field.getType()
1013+
JNIObjectHandle fieldClass = noArgRetObjectCall.invoke(jni, field, getFieldTypeId);
1014+
traceWriter.traceCall("reflect",
1015+
"forName",
1016+
serializeTargetClassName,
1017+
null,
1018+
null,
1019+
result, getClassNameOrNull(jni, fieldClass));
1020+
}
1021+
guarantee(!testException(jni));
1022+
}
1023+
1024+
if (!allowed) {
1025+
try (CCharPointerHolder message = toCString(
1026+
NativeImageAgent.MESSAGE_PREFIX + "configuration does not permit SerializationConstructorAccessor class for class: " + serializeTargetClassName +
1027+
" with first unserializable super class " + targetConstructorClassName)) {
1028+
jniFunctions().getThrowNew().invoke(jni, agent.handles().javaLangSecurityException, message.get());
1029+
}
1030+
}
1031+
return allowed;
1032+
}
1033+
9161034
@CEntryPoint
9171035
@CEntryPointOptions(prologue = AgentIsolate.Prologue.class)
9181036
private static void onBreakpoint(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni,
@@ -991,12 +1109,14 @@ private static void installBreakpointIfClassLoader(JNIEnvironment jni, JNIObject
9911109
JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class);
9921110

9931111
public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, TraceWriter writer, ReflectAccessVerifier verifier,
994-
ProxyAccessVerifier prverifier, ResourceAccessVerifier resverifier, NativeImageAgent nativeImageTracingAgent, boolean exptlClassLoaderSupport) {
1112+
ProxyAccessVerifier prverifier, ResourceAccessVerifier resverifier, SerializationAccessVerifier serializationAccessVerifier, NativeImageAgent nativeImageTracingAgent,
1113+
boolean exptlClassLoaderSupport) {
9951114

9961115
BreakpointInterceptor.traceWriter = writer;
9971116
BreakpointInterceptor.accessVerifier = verifier;
9981117
BreakpointInterceptor.proxyVerifier = prverifier;
9991118
BreakpointInterceptor.resourceVerifier = resverifier;
1119+
BreakpointInterceptor.serializationAccessVerifier = serializationAccessVerifier;
10001120
BreakpointInterceptor.agent = nativeImageTracingAgent;
10011121
BreakpointInterceptor.experimentalClassLoaderSupport = exptlClassLoaderSupport;
10021122

@@ -1218,6 +1338,9 @@ private interface BreakpointHandler {
12181338
brk("java/lang/reflect/Proxy", "newProxyInstance",
12191339
"(Ljava/lang/ClassLoader;[Ljava/lang/Class;Ljava/lang/reflect/InvocationHandler;)Ljava/lang/Object;", BreakpointInterceptor::newProxyInstance),
12201340

1341+
brk("sun/reflect/MethodAccessorGenerator", "generateSerializationConstructor",
1342+
"(Ljava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Class;ILjava/lang/Class;)Lsun/reflect/SerializationConstructorAccessorImpl;",
1343+
BreakpointInterceptor::generateSerializationConstructor),
12211344
optionalBrk("java/util/ResourceBundle",
12221345
"getBundleImpl",
12231346
"(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;",

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.function.Function;
5454
import java.util.regex.Pattern;
5555

56+
import com.oracle.svm.agent.restrict.SerializationAccessVerifier;
5657
import org.graalvm.compiler.options.OptionKey;
5758
import org.graalvm.nativeimage.ProcessProperties;
5859

@@ -92,6 +93,7 @@ public final class NativeImageAgent extends JvmtiAgentBase<NativeImageAgentJNIHa
9293
private static final String oHReflectionConfigurationResources = oH(ConfigurationFiles.Options.ReflectionConfigurationResources);
9394
private static final String oHDynamicProxyConfigurationResources = oH(ConfigurationFiles.Options.DynamicProxyConfigurationResources);
9495
private static final String oHResourceConfigurationResources = oH(ConfigurationFiles.Options.ResourceConfigurationResources);
96+
private static final String oHSerializationConfigurationResources = oH(ConfigurationFiles.Options.SerializationConfigurationResources);
9597
private static final String oHConfigurationResourceRoots = oH(ConfigurationFiles.Options.ConfigurationResourceRoots);
9698

9799
private static <T> String oH(OptionKey<T> option) {
@@ -256,7 +258,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
256258
// They should use the same filter sets, however.
257259
AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter);
258260
TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler),
259-
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler));
261+
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler));
260262
traceWriter = new TraceProcessorWriterAdapter(processor);
261263
} catch (Throwable t) {
262264
System.err.println(MESSAGE_PREFIX + t);
@@ -298,7 +300,11 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
298300
if (!restrictConfigs.getResourceConfigPaths().isEmpty()) {
299301
resourceVerifier = new ResourceAccessVerifier(restrictConfigs.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION), accessAdvisor);
300302
}
301-
BreakpointInterceptor.onLoad(jvmti, callbacks, traceWriter, verifier, proxyVerifier, resourceVerifier, this, experimentalClassLoaderSupport);
303+
SerializationAccessVerifier serializationAccessVerifier = null;
304+
if (!restrictConfigs.getSerializationConfigPaths().isEmpty()) {
305+
serializationAccessVerifier = new SerializationAccessVerifier(restrictConfigs.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION), accessAdvisor);
306+
}
307+
BreakpointInterceptor.onLoad(jvmti, callbacks, traceWriter, verifier, proxyVerifier, resourceVerifier, serializationAccessVerifier, this, experimentalClassLoaderSupport);
302308
} catch (Throwable t) {
303309
System.err.println(MESSAGE_PREFIX + t);
304310
return 3;
@@ -439,12 +445,15 @@ private static boolean addRestrictConfigs(JvmtiEnv jvmti, ConfigurationSet restr
439445
addURI.add(restrictConfigs.getProxyConfigPaths(), cpEntry, optionParts[1]);
440446
} else if (oHResourceConfigurationResources.equals(argName)) {
441447
addURI.add(restrictConfigs.getResourceConfigPaths(), cpEntry, optionParts[1]);
448+
} else if (oHSerializationConfigurationResources.equals(argName)) {
449+
addURI.add(restrictConfigs.getSerializationConfigPaths(), cpEntry, optionParts[1]);
442450
} else if (oHConfigurationResourceRoots.equals(argName)) {
443451
String resourceLocation = optionParts[1];
444452
addURI.add(restrictConfigs.getJniConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.JNI_NAME);
445453
addURI.add(restrictConfigs.getReflectConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.REFLECTION_NAME);
446454
addURI.add(restrictConfigs.getProxyConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.DYNAMIC_PROXY_NAME);
447455
addURI.add(restrictConfigs.getResourceConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.RESOURCES_NAME);
456+
addURI.add(restrictConfigs.getSerializationConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.SERIALIZATION_NAME);
448457
}
449458
}
450459
});
@@ -557,6 +566,7 @@ private void writeConfigurationFiles() {
557566
allConfigFiles.put(ConfigurationFiles.JNI_NAME, p.getJniConfiguration());
558567
allConfigFiles.put(ConfigurationFiles.DYNAMIC_PROXY_NAME, p.getProxyConfiguration());
559568
allConfigFiles.put(ConfigurationFiles.RESOURCES_NAME, p.getResourceConfiguration());
569+
allConfigFiles.put(ConfigurationFiles.SERIALIZATION_NAME, p.getSerializationConfiguration());
560570

561571
for (Map.Entry<String, JsonPrintable> configFile : allConfigFiles.entrySet()) {
562572
Path tempPath = tempDirectory.resolve(configFile.getKey());

substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/TraceWriter.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,23 @@ public void tracePhaseChange(String phase) {
8989
* @param args Arguments to the call, which may contain arrays (which can contain more arrays)
9090
*/
9191
public void traceCall(String tracer, String function, Object clazz, Object declaringClass, Object callerClass, Object result, Object... args) {
92+
Map<String, Object> entry = createTraceEntry(tracer, function, clazz, declaringClass, callerClass, result,
93+
args);
94+
traceEntry(entry);
95+
}
96+
97+
public void traceCall(String tracer, String function, Object clazz, Object declaringClass, Object callerClass,
98+
Object result, boolean allowWrite, boolean unsafeAccess, String fieldName) {
99+
Map<String, Object> entry = createTraceEntry(tracer, function, clazz, declaringClass, callerClass, result,
100+
fieldName);
101+
entry.put("allowWrite", allowWrite);
102+
entry.put("unsafeAccess", unsafeAccess);
103+
traceEntry(entry);
104+
}
105+
106+
@SuppressWarnings("static-method")
107+
private Map<String, Object> createTraceEntry(String tracer, String function, Object clazz, Object declaringClass,
108+
Object callerClass, Object result, Object... args) {
92109
Map<String, Object> entry = new HashMap<>();
93110
entry.put("tracer", tracer);
94111
entry.put("function", function);
@@ -107,7 +124,7 @@ public void traceCall(String tracer, String function, Object clazz, Object decla
107124
if (args != null) {
108125
entry.put("args", handleSpecialValue(args));
109126
}
110-
traceEntry(entry);
127+
return entry;
111128
}
112129

113130
abstract void traceEntry(Map<String, Object> entry);

0 commit comments

Comments
 (0)