Skip to content

Commit

Permalink
Support JDK serialization
Browse files Browse the repository at this point in the history
  • Loading branch information
ziyilin committed Aug 18, 2020
1 parent 00e5f77 commit b7d56d1
Show file tree
Hide file tree
Showing 30 changed files with 992 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOr;
import static com.oracle.svm.jvmtiagentbase.Support.getClassNameOrNull;
import static com.oracle.svm.jvmtiagentbase.Support.getDirectCallerClass;
import static com.oracle.svm.jvmtiagentbase.Support.getIntArgument;
import static com.oracle.svm.jvmtiagentbase.Support.getMethodDeclaringClass;
import static com.oracle.svm.jvmtiagentbase.Support.getObjectArgument;
import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions;
Expand All @@ -49,6 +50,7 @@
import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND;
import static org.graalvm.word.WordFactory.nullPointer;

import java.lang.reflect.Modifier;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
Expand Down Expand Up @@ -81,10 +83,12 @@
import com.oracle.svm.agent.restrict.ProxyAccessVerifier;
import com.oracle.svm.agent.restrict.ReflectAccessVerifier;
import com.oracle.svm.agent.restrict.ResourceAccessVerifier;
import com.oracle.svm.agent.restrict.SerializationAccessVerifier;
import com.oracle.svm.configure.config.ConfigurationMethod;
import com.oracle.svm.core.c.function.CEntryPointOptions;
import com.oracle.svm.core.util.VMError;
import com.oracle.svm.jni.nativeapi.JNIEnvironment;
import com.oracle.svm.jni.nativeapi.JNIFunctionPointerTypes;
import com.oracle.svm.jni.nativeapi.JNIMethodId;
import com.oracle.svm.jni.nativeapi.JNINativeMethod;
import com.oracle.svm.jni.nativeapi.JNIObjectHandle;
Expand Down Expand Up @@ -129,6 +133,7 @@ final class BreakpointInterceptor {
private static ReflectAccessVerifier accessVerifier;
private static ProxyAccessVerifier proxyVerifier;
private static ResourceAccessVerifier resourceVerifier;
private static SerializationAccessVerifier serializationAccessVerifier;
private static NativeImageAgent agent;

private static Map<Long, Breakpoint> installedBreakpoints;
Expand Down Expand Up @@ -913,6 +918,119 @@ private static String asInternalSignature(Object paramTypesArray) {
return null;
}

@SuppressWarnings("unused")
private static boolean generateSerializationConstructor(JNIEnvironment jni, Breakpoint bp) {
JNIObjectHandle self = getObjectArgument(0);
JNIObjectHandle serializeTargetClass = getObjectArgument(1);
String serializeTargetClassName = getClassNameOrNull(jni, serializeTargetClass);
JNIObjectHandle parameterTypes = getObjectArgument(2);
Object parameterTypeNames = getClassArrayNames(jni, parameterTypes);
JNIObjectHandle checkedExceptions = getObjectArgument(3);
Object checkedExceptionNames = getClassArrayNames(jni, checkedExceptions);
int modifiers = getIntArgument(4);
JNIObjectHandle targetConstructorClass = getObjectArgument(5);
String targetConstructorClassName = getClassNameOrNull(jni, targetConstructorClass);
boolean allowed = (serializationAccessVerifier == null ||
serializationAccessVerifier.verifyGenerateSerializationConstructor(jni, serializeTargetClassName, parameterTypeNames,
checkedExceptionNames, modifiers, targetConstructorClassName));
Object result = false;
if (allowed) {
JNIValue args = StackValue.get(6, JNIValue.class);
args.addressOf(0).setObject(self);
args.addressOf(1).setObject(serializeTargetClass);
args.addressOf(2).setObject(parameterTypes);
args.addressOf(3).setObject(checkedExceptions);
args.addressOf(4).setInt(modifiers);
args.addressOf(5).setObject(targetConstructorClass);
result = nullHandle().notEqual(jniFunctions().getCallObjectMethodA().invoke(jni, bp.clazz, bp.method, args));
if (clearException(jni)) {
result = false;
}
}
JNIObjectHandle callerClass = getDirectCallerClass();
if (traceWriter != null) {
traceWriter.traceCall("serialization",
"generateSerializationConstructor",
null,
null,
null,
result,
serializeTargetClassName, parameterTypeNames,
checkedExceptionNames, modifiers, targetConstructorClassName);
JNIFunctionPointerTypes.CallIntMethodFunctionPointer noArgRetIntCall = jniFunctions().getCallIntMethod();
int privateStaticFinalMask = Modifier.PRIVATE | Modifier.STATIC | Modifier.FINAL;
int staticFinalMask = Modifier.STATIC | Modifier.FINAL;

// call serializationTargetClass.getDeclaredFields();
JNIObjectHandle javaLangClass = agent.handles().findClass(jni, "java/lang/Class");
JNIMethodId getDeclaredFieldsMI = agent.handles().getMethodId(jni, javaLangClass, "getDeclaredFields",
"()[Ljava/lang/reflect/Field;", false);
JNIFunctionPointerTypes.CallObjectMethod0FunctionPointer noArgRetObjectCall = jniFunctions().getCallObjectMethod();
JNIObjectHandle fieldsJArray = noArgRetObjectCall.invoke(jni, serializeTargetClass, getDeclaredFieldsMI);

// Prepare JNIMethodIds for later calls
JNIObjectHandle javaLangReflectField = agent.handles().findClass(jni, "java/lang/reflect/Field");
JNIMethodId getFieldNameId = agent.handles().getMethodId(jni, javaLangReflectField, "getName", "()Ljava/lang/String;", false);
JNIMethodId getFieldModifiersId = agent.handles().getMethodId(jni, javaLangReflectField, "getModifiers", "()I", false);
JNIMethodId getFieldTypeId = agent.handles().getMethodId(jni, javaLangReflectField, "getType", "()Ljava/lang/Class;", false);
// Add serialize and deserialize fields into reflection configs
// Check each field
int fieldArrayLength = jniFunctions().getGetArrayLength().invoke(jni, fieldsJArray);
for (int i = 0; i < fieldArrayLength; i++) {
// Get field object from array
JNIObjectHandle field = jniFunctions().getGetObjectArrayElement().invoke(jni, fieldsJArray, i);
// call field.getName() to get field's name
String fieldName = fromJniString(jni, noArgRetObjectCall.invoke(jni, field, getFieldNameId));
// call field.getModifiers tp get field's modifiers
int fieldModifiers = noArgRetIntCall.invoke(jni, field, getFieldModifiersId);
if (fieldName.equals("serialPersistentFields") &&
(fieldModifiers & privateStaticFinalMask) == privateStaticFinalMask) {
traceWriter.traceCall("reflect",
"getDeclaredField",
serializeTargetClassName,
null,
null,
result, "serialPersistentFields");
} else if (fieldName.equals("serialVersionUID") &&
(fieldModifiers & staticFinalMask) == staticFinalMask) {
traceWriter.traceCall("reflect",
"getDeclaredField",
serializeTargetClassName,
null,
null,
result, "serialVersionUID");
} else if ((fieldModifiers & staticFinalMask) != staticFinalMask) {
// Set the field's allowWrite and unsafeAccess properties
traceWriter.traceCall("reflect",
"getDeclaredField",
serializeTargetClassName,
null,
null,
result, (modifiers & Modifier.FINAL) == Modifier.FINAL, (modifiers & Modifier.STATIC) == 0, fieldName);
}
// Add field's class in config
// call field.getType()
JNIObjectHandle fieldClass = noArgRetObjectCall.invoke(jni, field, getFieldTypeId);
traceWriter.traceCall("reflect",
"forName",
serializeTargetClassName,
null,
null,
result, getClassNameOrNull(jni, fieldClass));
}
guarantee(!testException(jni));
}

if (!allowed) {
try (CCharPointerHolder message = toCString(
NativeImageAgent.MESSAGE_PREFIX + "configuration does not permit SerializationConstructorAccessor class for class: " + serializeTargetClassName +
" with first unserializable super class " + targetConstructorClassName)) {
jniFunctions().getThrowNew().invoke(jni, agent.handles().javaLangSecurityException, message.get());
}
}
return allowed;
}

@CEntryPoint
@CEntryPointOptions(prologue = AgentIsolate.Prologue.class)
private static void onBreakpoint(@SuppressWarnings("unused") JvmtiEnv jvmti, JNIEnvironment jni,
Expand Down Expand Up @@ -991,12 +1109,14 @@ private static void installBreakpointIfClassLoader(JNIEnvironment jni, JNIObject
JvmtiEnv.class, JNIEnvironment.class, JNIObjectHandle.class, JNIObjectHandle.class);

public static void onLoad(JvmtiEnv jvmti, JvmtiEventCallbacks callbacks, TraceWriter writer, ReflectAccessVerifier verifier,
ProxyAccessVerifier prverifier, ResourceAccessVerifier resverifier, NativeImageAgent nativeImageTracingAgent, boolean exptlClassLoaderSupport) {
ProxyAccessVerifier prverifier, ResourceAccessVerifier resverifier, SerializationAccessVerifier serializationAccessVerifier, NativeImageAgent nativeImageTracingAgent,
boolean exptlClassLoaderSupport) {

BreakpointInterceptor.traceWriter = writer;
BreakpointInterceptor.accessVerifier = verifier;
BreakpointInterceptor.proxyVerifier = prverifier;
BreakpointInterceptor.resourceVerifier = resverifier;
BreakpointInterceptor.serializationAccessVerifier = serializationAccessVerifier;
BreakpointInterceptor.agent = nativeImageTracingAgent;
BreakpointInterceptor.experimentalClassLoaderSupport = exptlClassLoaderSupport;

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

brk("sun/reflect/MethodAccessorGenerator", "generateSerializationConstructor",
"(Ljava/lang/Class;[Ljava/lang/Class;[Ljava/lang/Class;ILjava/lang/Class;)Lsun/reflect/SerializationConstructorAccessorImpl;",
BreakpointInterceptor::generateSerializationConstructor),
optionalBrk("java/util/ResourceBundle",
"getBundleImpl",
"(Ljava/lang/String;Ljava/util/Locale;Ljava/lang/ClassLoader;Ljava/util/ResourceBundle$Control;)Ljava/util/ResourceBundle;",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
import java.util.function.Function;
import java.util.regex.Pattern;

import com.oracle.svm.agent.restrict.SerializationAccessVerifier;
import org.graalvm.compiler.options.OptionKey;
import org.graalvm.nativeimage.ProcessProperties;

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

private static <T> String oH(OptionKey<T> option) {
Expand Down Expand Up @@ -256,7 +258,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
// They should use the same filter sets, however.
AccessAdvisor advisor = createAccessAdvisor(builtinHeuristicFilter, callerFilter, accessFilter);
TraceProcessor processor = new TraceProcessor(advisor, mergeConfigs.loadJniConfig(handler), mergeConfigs.loadReflectConfig(handler),
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler));
mergeConfigs.loadProxyConfig(handler), mergeConfigs.loadResourceConfig(handler), mergeConfigs.loadSerializationConfig(handler));
traceWriter = new TraceProcessorWriterAdapter(processor);
} catch (Throwable t) {
System.err.println(MESSAGE_PREFIX + t);
Expand Down Expand Up @@ -298,7 +300,11 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c
if (!restrictConfigs.getResourceConfigPaths().isEmpty()) {
resourceVerifier = new ResourceAccessVerifier(restrictConfigs.loadResourceConfig(ConfigurationSet.FAIL_ON_EXCEPTION), accessAdvisor);
}
BreakpointInterceptor.onLoad(jvmti, callbacks, traceWriter, verifier, proxyVerifier, resourceVerifier, this, experimentalClassLoaderSupport);
SerializationAccessVerifier serializationAccessVerifier = null;
if (!restrictConfigs.getSerializationConfigPaths().isEmpty()) {
serializationAccessVerifier = new SerializationAccessVerifier(restrictConfigs.loadSerializationConfig(ConfigurationSet.FAIL_ON_EXCEPTION), accessAdvisor);
}
BreakpointInterceptor.onLoad(jvmti, callbacks, traceWriter, verifier, proxyVerifier, resourceVerifier, serializationAccessVerifier, this, experimentalClassLoaderSupport);
} catch (Throwable t) {
System.err.println(MESSAGE_PREFIX + t);
return 3;
Expand Down Expand Up @@ -439,12 +445,15 @@ private static boolean addRestrictConfigs(JvmtiEnv jvmti, ConfigurationSet restr
addURI.add(restrictConfigs.getProxyConfigPaths(), cpEntry, optionParts[1]);
} else if (oHResourceConfigurationResources.equals(argName)) {
addURI.add(restrictConfigs.getResourceConfigPaths(), cpEntry, optionParts[1]);
} else if (oHSerializationConfigurationResources.equals(argName)) {
addURI.add(restrictConfigs.getSerializationConfigPaths(), cpEntry, optionParts[1]);
} else if (oHConfigurationResourceRoots.equals(argName)) {
String resourceLocation = optionParts[1];
addURI.add(restrictConfigs.getJniConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.JNI_NAME);
addURI.add(restrictConfigs.getReflectConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.REFLECTION_NAME);
addURI.add(restrictConfigs.getProxyConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.DYNAMIC_PROXY_NAME);
addURI.add(restrictConfigs.getResourceConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.RESOURCES_NAME);
addURI.add(restrictConfigs.getSerializationConfigPaths(), cpEntry, resourceLocation + "/" + ConfigurationFiles.SERIALIZATION_NAME);
}
}
});
Expand Down Expand Up @@ -557,6 +566,7 @@ private void writeConfigurationFiles() {
allConfigFiles.put(ConfigurationFiles.JNI_NAME, p.getJniConfiguration());
allConfigFiles.put(ConfigurationFiles.DYNAMIC_PROXY_NAME, p.getProxyConfiguration());
allConfigFiles.put(ConfigurationFiles.RESOURCES_NAME, p.getResourceConfiguration());
allConfigFiles.put(ConfigurationFiles.SERIALIZATION_NAME, p.getSerializationConfiguration());

for (Map.Entry<String, JsonPrintable> configFile : allConfigFiles.entrySet()) {
Path tempPath = tempDirectory.resolve(configFile.getKey());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,23 @@ public void tracePhaseChange(String phase) {
* @param args Arguments to the call, which may contain arrays (which can contain more arrays)
*/
public void traceCall(String tracer, String function, Object clazz, Object declaringClass, Object callerClass, Object result, Object... args) {
Map<String, Object> entry = createTraceEntry(tracer, function, clazz, declaringClass, callerClass, result,
args);
traceEntry(entry);
}

public void traceCall(String tracer, String function, Object clazz, Object declaringClass, Object callerClass,
Object result, boolean allowWrite, boolean unsafeAccess, String fieldName) {
Map<String, Object> entry = createTraceEntry(tracer, function, clazz, declaringClass, callerClass, result,
fieldName);
entry.put("allowWrite", allowWrite);
entry.put("unsafeAccess", unsafeAccess);
traceEntry(entry);
}

@SuppressWarnings("static-method")
private Map<String, Object> createTraceEntry(String tracer, String function, Object clazz, Object declaringClass,
Object callerClass, Object result, Object... args) {
Map<String, Object> entry = new HashMap<>();
entry.put("tracer", tracer);
entry.put("function", function);
Expand All @@ -107,7 +124,7 @@ public void traceCall(String tracer, String function, Object clazz, Object decla
if (args != null) {
entry.put("args", handleSpecialValue(args));
}
traceEntry(entry);
return entry;
}

abstract void traceEntry(Map<String, Object> entry);
Expand Down
Loading

0 comments on commit b7d56d1

Please sign in to comment.