diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java index 5888f12d6197..b6dc39add974 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/BreakpointInterceptor.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.agent; +import static com.oracle.svm.core.util.VMError.guarantee; +import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; import static com.oracle.svm.jvmtiagentbase.Support.check; import static com.oracle.svm.jvmtiagentbase.Support.checkJni; import static com.oracle.svm.jvmtiagentbase.Support.checkNoException; @@ -45,8 +47,6 @@ import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_BREAKPOINT; import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_CLASS_PREPARE; import static com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEvent.JVMTI_EVENT_NATIVE_METHOD_BIND; -import static com.oracle.svm.core.util.VMError.guarantee; -import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; import static org.graalvm.word.WordFactory.nullPointer; import java.nio.ByteBuffer; @@ -78,6 +78,17 @@ import org.graalvm.nativeimage.c.type.WordPointer; import org.graalvm.word.WordFactory; +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.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.JNIMethodId; +import com.oracle.svm.jni.nativeapi.JNINativeMethod; +import com.oracle.svm.jni.nativeapi.JNIObjectHandle; +import com.oracle.svm.jni.nativeapi.JNIValue; import com.oracle.svm.jvmtiagentbase.AgentIsolate; import com.oracle.svm.jvmtiagentbase.ConstantPoolTool; import com.oracle.svm.jvmtiagentbase.ConstantPoolTool.MethodReference; @@ -90,17 +101,6 @@ import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiEventMode; import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiFrameInfo; import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiLocationFormat; -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.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.JNIMethodId; -import com.oracle.svm.jni.nativeapi.JNINativeMethod; -import com.oracle.svm.jni.nativeapi.JNIObjectHandle; -import com.oracle.svm.jni.nativeapi.JNIValue; import jdk.vm.ci.meta.MetaUtil; @@ -182,15 +182,11 @@ private static boolean forName(JNIEnvironment jni, Breakpoint bp) { boolean allowed = (accessVerifier == null || accessVerifier.verifyForName(jni, callerClass, className)); Object result = false; if (allowed) { - boolean initializeValid = true; boolean classLoaderValid = true; - CIntPointer initializePtr = StackValue.get(CIntPointer.class); WordPointer classLoaderPtr = StackValue.get(WordPointer.class); if (bp.method == agent.handles().javaLangClassForName3) { - initializeValid = (jvmtiFunctions().GetLocalInt().invoke(jvmtiEnv(), nullHandle(), 0, 1, initializePtr) == JvmtiError.JVMTI_ERROR_NONE); classLoaderValid = (jvmtiFunctions().GetLocalObject().invoke(jvmtiEnv(), nullHandle(), 0, 2, classLoaderPtr) == JvmtiError.JVMTI_ERROR_NONE); } else { - initializePtr.write(1); classLoaderPtr.write(nullHandle()); if (callerClass.notEqual(nullHandle())) { /* @@ -202,8 +198,13 @@ private static boolean forName(JNIEnvironment jni, Breakpoint bp) { } } result = TraceWriter.UNKNOWN_VALUE; - if (initializeValid && classLoaderValid) { - result = nullHandle().notEqual(Support.callStaticObjectMethodLIL(jni, bp.clazz, agent.handles().javaLangClassForName3, name, initializePtr.read(), classLoaderPtr.read())); + if (classLoaderValid) { + /* + * Even if the original call requested class initialization, disable it because + * recursion checks keep us from seeing events of interest during initialization. + */ + int initialize = 0; + result = nullHandle().notEqual(Support.callStaticObjectMethodLIL(jni, bp.clazz, agent.handles().javaLangClassForName3, name, initialize, classLoaderPtr.read())); if (clearException(jni)) { result = false; } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java index 08a3ed76873f..e119575cb9f3 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/NativeImageAgent.java @@ -129,7 +129,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c boolean builtinCallerFilter = true; boolean builtinHeuristicFilter = true; List callerFilterFiles = new ArrayList<>(); - boolean experimentalClassLoaderSupport = false; + boolean experimentalClassLoaderSupport = true; boolean build = false; int configWritePeriod = -1; // in seconds int configWritePeriodInitialDelay = 1; // in seconds @@ -179,6 +179,8 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c callerFilterFiles.add(getTokenValue(token)); } else if (token.equals("experimental-class-loader-support")) { experimentalClassLoaderSupport = true; + } else if (token.startsWith("experimental-class-loader-support=")) { + experimentalClassLoaderSupport = Boolean.parseBoolean(getTokenValue(token)); } else if (token.startsWith("config-write-period-secs=")) { configWritePeriod = parseIntegerOrNegative(getTokenValue(token)); if (configWritePeriod <= 0) { @@ -213,7 +215,7 @@ protected int onLoadCallback(JNIJavaVM vm, JvmtiEnv jvmti, JvmtiEventCallbacks c } if (!callerFilterFiles.isEmpty()) { if (callersFilter == null) { - callersFilter = AccessAdvisor.copyBuiltinFilterTree(); + callersFilter = AccessAdvisor.copyBuiltinCallerFilterTree(); } for (String path : callerFilterFiles) { try { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/AbstractAccessVerifier.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/AbstractAccessVerifier.java index 61eafd059ca9..b93590ad9395 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/AbstractAccessVerifier.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/AbstractAccessVerifier.java @@ -40,8 +40,12 @@ class AbstractAccessVerifier { this.accessAdvisor = advisor; } - protected boolean shouldApproveWithoutChecks(JNIEnvironment env, JNIObjectHandle callerClass) { - return accessAdvisor.shouldIgnoreCaller(lazyClassNameOrNull(env, callerClass)); + protected boolean shouldApproveWithoutChecks(JNIEnvironment env, JNIObjectHandle queriedClass, JNIObjectHandle callerClass) { + return shouldApproveWithoutChecks(lazyClassNameOrNull(env, queriedClass), lazyClassNameOrNull(env, callerClass)); + } + + protected boolean shouldApproveWithoutChecks(LazyValue queriedClassName, LazyValue callerClassName) { + return accessAdvisor.shouldIgnore(queriedClassName, callerClassName); } protected static LazyValue lazyClassNameOrNull(JNIEnvironment env, JNIObjectHandle clazz) { diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/JniAccessVerifier.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/JniAccessVerifier.java index b9f75d1ecc8d..3ed672219d96 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/JniAccessVerifier.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/JniAccessVerifier.java @@ -33,20 +33,27 @@ import static com.oracle.svm.configure.trace.LazyValueUtils.lazyGet; import static com.oracle.svm.configure.trace.LazyValueUtils.lazyValue; +import org.graalvm.compiler.phases.common.LazyValue; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointer; import org.graalvm.nativeimage.c.type.CTypeConversion.CCharPointerHolder; import org.graalvm.nativeimage.c.type.WordPointer; import com.oracle.svm.agent.NativeImageAgent; -import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiError; import com.oracle.svm.configure.config.ConfigurationMethod; import com.oracle.svm.configure.trace.AccessAdvisor; import com.oracle.svm.jni.nativeapi.JNIEnvironment; import com.oracle.svm.jni.nativeapi.JNIFieldId; import com.oracle.svm.jni.nativeapi.JNIMethodId; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; +import com.oracle.svm.jvmtiagentbase.jvmti.JvmtiError; + +import jdk.vm.ci.meta.MetaUtil; +/** + * In restriction mode, decides whether to permit or deny individual accesses via JNI, using + * {@link AccessAdvisor} for additional exemptions from its own rules, such as system classes. + */ public class JniAccessVerifier extends AbstractAccessVerifier { private final TypeAccessChecker typeAccessChecker; private final TypeAccessChecker reflectTypeAccessChecker; @@ -61,39 +68,57 @@ public JniAccessVerifier(TypeAccessChecker typeAccessChecker, TypeAccessChecker @SuppressWarnings("unused") public boolean verifyDefineClass(JNIEnvironment env, CCharPointer name, JNIObjectHandle loader, CCharPointer buf, int bufLen, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + LazyValue javaName = lazyConvertFindClassName(name); + if (shouldApproveWithoutChecks(javaName, lazyClassNameOrNull(env, callerClass))) { return true; } - try (CCharPointerHolder message = toCString(NativeImageAgent.MESSAGE_PREFIX + "defining classes is not permitted.")) { + try (CCharPointerHolder message = toCString(NativeImageAgent.MESSAGE_PREFIX + "defining classes is not permitted: " + javaName.get())) { // SecurityException seems most fitting from the exceptions allowed by the JNI spec jniFunctions().getThrowNew().invoke(env, agent.handles().javaLangSecurityException, message.get()); } return false; } - public boolean verifyFindClass(JNIEnvironment env, CCharPointer cname, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + public boolean verifyFindClass(JNIEnvironment env, CCharPointer name, JNIObjectHandle callerClass) { + LazyValue javaName = lazyConvertFindClassName(name); + if (shouldApproveWithoutChecks(javaName, lazyClassNameOrNull(env, callerClass))) { return true; } - String name = fromCString(cname); - if (name != null) { - if (!name.startsWith("[") && name.length() > 1) { - name = "L" + name + ";"; // FindClass doesn't require those - } - if (typeAccessChecker.getConfiguration().getByInternalName(name) != null) { - return true; - } + if (javaName.get() != null && typeAccessChecker.getConfiguration().get(javaName.get()) != null) { + return true; } - try (CCharPointerHolder message = toCString(NativeImageAgent.MESSAGE_PREFIX + "configuration does not permit access to class: " + name)) { + try (CCharPointerHolder message = toCString(NativeImageAgent.MESSAGE_PREFIX + "configuration does not permit access to class: " + javaName.get())) { jniFunctions().getThrowNew().invoke(env, agent.handles().javaLangNoClassDefFoundError, message.get()); } return false; } + private static LazyValue lazyConvertFindClassName(CCharPointer name) { + return lazyGet(() -> { + String s = fromCString(name); + if (s != null) { + if (!s.startsWith("[") && s.length() > 1) { + s = "L" + s + ";"; + } + try { + return MetaUtil.internalNameToJava(s, true, true); + } catch (Exception ignored) { + // likely malformed input from the observed application + } + } + return null; + }); + } + public boolean verifyGetMethodID(JNIEnvironment env, JNIObjectHandle clazz, CCharPointer cname, CCharPointer csignature, JNIMethodId result, JNIObjectHandle callerClass) { + LazyValue clazzName = lazyClassNameOrNull(env, clazz); + LazyValue callerClassName = lazyClassNameOrNull(env, callerClass); + if (shouldApproveWithoutChecks(clazzName, callerClassName)) { + return true; + } assert result.isNonNull(); String name = fromCString(cname); - if (accessAdvisor.shouldIgnoreJniMethodLookup(lazyClassNameOrNull(env, clazz), lazyValue(name), lazyGet(() -> fromCString(csignature)), lazyClassNameOrNull(env, callerClass))) { + if (accessAdvisor.shouldIgnoreJniMethodLookup(clazzName, lazyValue(name), lazyGet(() -> fromCString(csignature)), callerClassName)) { return true; } WordPointer declaringPtr = StackValue.get(WordPointer.class); @@ -115,7 +140,7 @@ public boolean verifyGetFieldID(JNIEnvironment env, JNIObjectHandle clazz, CChar @SuppressWarnings("unused") CCharPointer csignature, JNIFieldId result, JNIObjectHandle callerClass) { assert result.isNonNull(); - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return true; } // Check if the member in the declaring method is registered. @@ -134,9 +159,13 @@ public boolean verifyGetFieldID(JNIEnvironment env, JNIObjectHandle clazz, CChar } public boolean verifyThrowNew(JNIEnvironment env, JNIObjectHandle clazz, JNIObjectHandle callerClass) { + LazyValue callerClassName = lazyClassNameOrNull(env, callerClass); + if (shouldApproveWithoutChecks(lazyClassNameOrNull(env, clazz), callerClassName)) { + return true; + } String name = ConfigurationMethod.CONSTRUCTOR_NAME; String signature = "(Ljava/lang/String;)V"; - if (accessAdvisor.shouldIgnoreJniMethodLookup(lazyClassNameOrNull(env, clazz), lazyValue(name), lazyValue(signature), lazyClassNameOrNull(env, callerClass))) { + if (accessAdvisor.shouldIgnoreJniMethodLookup(lazyClassNameOrNull(env, clazz), lazyValue(name), lazyValue(signature), callerClassName)) { return true; } JNIMethodId result; @@ -149,7 +178,7 @@ public boolean verifyThrowNew(JNIEnvironment env, JNIObjectHandle clazz, JNIObje public boolean verifyFromReflectedMethod(JNIEnvironment env, JNIObjectHandle declaring, String name, String signature, JNIMethodId result, JNIObjectHandle callerClass) { assert result.isNonNull(); - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, declaring, callerClass)) { return true; } return typeAccessChecker.isMethodAccessible(env, declaring, name, () -> signature, result, declaring); @@ -157,7 +186,7 @@ public boolean verifyFromReflectedMethod(JNIEnvironment env, JNIObjectHandle dec public boolean verifyFromReflectedField(JNIEnvironment env, JNIObjectHandle declaring, String name, JNIFieldId result, JNIObjectHandle callerClass) { assert result.isNonNull(); - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, declaring, callerClass)) { return true; } return typeAccessChecker.isFieldAccessible(env, declaring, () -> name, result, declaring); @@ -168,7 +197,7 @@ public boolean verifyToReflectedMethod(JNIEnvironment env, JNIObjectHandle clazz if (reflectTypeAccessChecker == null) { return true; } - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return true; } return reflectTypeAccessChecker.isMethodAccessible(env, clazz, name, () -> signature, methodId, declaring); @@ -179,20 +208,16 @@ public boolean verifyToReflectedField(JNIEnvironment env, JNIObjectHandle clazz, if (reflectTypeAccessChecker == null) { return true; } - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return true; } return reflectTypeAccessChecker.isFieldAccessible(env, clazz, () -> name, fieldId, declaring); } public boolean verifyNewObjectArray(JNIEnvironment env, JNIObjectHandle arrayClass, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { - return true; - } - if (accessAdvisor.shouldIgnoreJniNewObjectArray(lazyClassNameOrNull(env, arrayClass), lazyClassNameOrNull(env, callerClass))) { + if (shouldApproveWithoutChecks(env, arrayClass, callerClass)) { return true; } return typeAccessChecker.getType(arrayClass) != null; } - } diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ProxyAccessVerifier.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ProxyAccessVerifier.java index eedd14a5e075..8bb96eef9478 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ProxyAccessVerifier.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ProxyAccessVerifier.java @@ -24,6 +24,8 @@ */ package com.oracle.svm.agent.restrict; +import static com.oracle.svm.configure.trace.LazyValueUtils.lazyNull; + import java.util.Arrays; import com.oracle.svm.configure.config.ProxyConfiguration; @@ -48,7 +50,7 @@ public boolean verifyGetProxyClass(JNIEnvironment env, Object interfaceNames, JN } private boolean verifyProxyAccess(JNIEnvironment env, Object interfaceNames, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(lazyNull(), lazyClassNameOrNull(env, callerClass))) { return true; } return (interfaceNames instanceof String[]) && configuration.contains(Arrays.asList((String[]) interfaceNames)); diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ReflectAccessVerifier.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ReflectAccessVerifier.java index b63ea2741170..2c896ab41a0c 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ReflectAccessVerifier.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ReflectAccessVerifier.java @@ -24,17 +24,19 @@ */ package com.oracle.svm.agent.restrict; +import static com.oracle.svm.configure.trace.LazyValueUtils.lazyValue; +import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; import static com.oracle.svm.jvmtiagentbase.Support.clearException; import static com.oracle.svm.jvmtiagentbase.Support.fromCString; import static com.oracle.svm.jvmtiagentbase.Support.fromJniString; import static com.oracle.svm.jvmtiagentbase.Support.jniFunctions; import static com.oracle.svm.jvmtiagentbase.Support.jvmtiEnv; import static com.oracle.svm.jvmtiagentbase.Support.jvmtiFunctions; -import static com.oracle.svm.jni.JNIObjectHandles.nullHandle; import static org.graalvm.word.WordFactory.nullPointer; import java.util.function.Supplier; +import org.graalvm.compiler.phases.common.LazyValue; import org.graalvm.nativeimage.StackValue; import org.graalvm.nativeimage.c.type.CCharPointerPointer; import org.graalvm.nativeimage.c.type.WordPointer; @@ -51,6 +53,10 @@ import com.oracle.svm.jni.nativeapi.JNIMethodId; import com.oracle.svm.jni.nativeapi.JNIObjectHandle; +/** + * In restriction mode, decides whether to permit or deny individual reflective accesses, using + * {@link AccessAdvisor} to decide additional exemptions from its own rules, such as system classes. + */ public class ReflectAccessVerifier extends AbstractAccessVerifier { private final TypeAccessChecker typeAccessChecker; private final NativeImageAgent agent; @@ -62,18 +68,26 @@ public ReflectAccessVerifier(TypeAccessChecker typeAccessChecker, AccessAdvisor } public boolean verifyForName(JNIEnvironment env, JNIObjectHandle callerClass, String className) { - return verifyLoadClass(env, callerClass, className); + if (shouldApproveWithoutChecks(lazyValue(className), lazyClassNameOrNull(env, callerClass))) { + return true; + } + return className == null || typeAccessChecker.getConfiguration().get(className) != null; } public boolean verifyLoadClass(JNIEnvironment env, JNIObjectHandle callerClass, String className) { - if (shouldApproveWithoutChecks(env, callerClass)) { + LazyValue lazyName = lazyValue(className); + LazyValue callerClassName = lazyClassNameOrNull(env, callerClass); + if (shouldApproveWithoutChecks(lazyName, callerClassName)) { + return true; + } + if (accessAdvisor.shouldIgnoreLoadClass(lazyName, callerClassName)) { return true; } return className == null || typeAccessChecker.getConfiguration().get(className) != null; } public boolean verifyGetField(JNIEnvironment env, JNIObjectHandle clazz, JNIObjectHandle name, JNIObjectHandle result, JNIObjectHandle declaring, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return true; } JNIFieldId field = jniFunctions().getFromReflectedField().invoke(env, result); @@ -81,14 +95,14 @@ public boolean verifyGetField(JNIEnvironment env, JNIObjectHandle clazz, JNIObje } public boolean verifyObjectFieldOffset(JNIEnvironment env, JNIObjectHandle name, JNIObjectHandle declaring, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, declaring, callerClass)) { return true; } return typeAccessChecker.isFieldUnsafeAccessible(() -> fromJniString(env, name), declaring); } public boolean verifyGetMethod(JNIEnvironment env, JNIObjectHandle clazz, String name, Supplier signature, JNIObjectHandle result, JNIObjectHandle declaring, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return true; } JNIMethodId method = jniFunctions().getFromReflectedMethod().invoke(env, result); @@ -100,21 +114,21 @@ public boolean verifyGetConstructor(JNIEnvironment env, JNIObjectHandle clazz, S } public boolean verifyNewInstance(JNIEnvironment env, JNIObjectHandle clazz, String name, String signature, JNIMethodId result, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return true; } return verifyGetMethod0(env, clazz, name, () -> signature, result, clazz); } public boolean verifyNewArray(JNIEnvironment env, JNIObjectHandle arrayClass, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, arrayClass, callerClass)) { return true; } return typeAccessChecker.getType(arrayClass) != null; } public boolean verifyGetEnclosingMethod(JNIEnvironment env, JNIObjectHandle clazz, String name, String signature, JNIObjectHandle result, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return true; } JNIMethodId method = jniFunctions().getFromReflectedMethod().invoke(env, result); @@ -126,7 +140,7 @@ private boolean verifyGetMethod0(JNIEnvironment env, JNIObjectHandle clazz, Stri } public JNIObjectHandle filterGetFields(JNIEnvironment env, JNIObjectHandle clazz, JNIObjectHandle array, boolean declaredOnly, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return array; } WordPredicate predicate = f -> shouldRetainField(env, clazz, f, declaredOnly); @@ -136,7 +150,7 @@ public JNIObjectHandle filterGetFields(JNIEnvironment env, JNIObjectHandle clazz public JNIObjectHandle filterGetMethods(JNIEnvironment env, JNIObjectHandle clazz, JNIObjectHandle array, WordSupplier elementClass, boolean declaredOnly, JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(env, clazz, callerClass)) { return array; } WordPredicate predicate = m -> shouldRetainMethod(env, clazz, m, declaredOnly); diff --git a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ResourceAccessVerifier.java b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ResourceAccessVerifier.java index 0e4febd32da3..0bdc958dc1ac 100644 --- a/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ResourceAccessVerifier.java +++ b/substratevm/src/com.oracle.svm.agent/src/com/oracle/svm/agent/restrict/ResourceAccessVerifier.java @@ -24,6 +24,7 @@ */ package com.oracle.svm.agent.restrict; +import static com.oracle.svm.configure.trace.LazyValueUtils.lazyNull; import static com.oracle.svm.jvmtiagentbase.Support.fromJniString; import com.oracle.svm.configure.config.ResourceConfiguration; @@ -54,7 +55,7 @@ private boolean verifyGetResources0(JNIEnvironment env, JNIObjectHandle name, @S } public boolean verifyGetBundle(JNIEnvironment env, JNIObjectHandle baseName, @SuppressWarnings("unused") JNIObjectHandle callerClass) { - if (shouldApproveWithoutChecks(env, callerClass)) { + if (shouldApproveWithoutChecks(lazyNull(), lazyClassNameOrNull(env, callerClass))) { return true; } String bundleName = fromJniString(env, baseName); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java index 11f4c494c53c..f23e2cd939ee 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/ConfigurationTool.java @@ -201,7 +201,7 @@ private static void generate(Iterator argsIter, boolean acceptTraceFileA } if (!callerFilterFiles.isEmpty()) { if (callersFilter == null) { - callersFilter = AccessAdvisor.copyBuiltinFilterTree(); + callersFilter = AccessAdvisor.copyBuiltinCallerFilterTree(); } for (Path path : callerFilterFiles) { try { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java index 807779f422db..91160a52ecc5 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/ConfigurationType.java @@ -51,6 +51,7 @@ public class ConfigurationType implements JsonPrintable { public ConfigurationType(String qualifiedJavaName) { assert qualifiedJavaName.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; + assert !qualifiedJavaName.startsWith("[") : "Requires Java source array syntax, for example java.lang.String[]"; this.qualifiedJavaName = qualifiedJavaName; } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java index 3060aa42f8d3..de17efeb3f89 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/config/TypeConfiguration.java @@ -44,8 +44,8 @@ public ConfigurationType get(String qualifiedJavaName) { return types.get(qualifiedJavaName); } - public ConfigurationType getByInternalName(String name) { - return types.get(MetaUtil.internalNameToJava(name, true, false)); + public ConfigurationType getByInternalName(String internalName) { + return types.get(MetaUtil.internalNameToJava(internalName, true, false)); } public void add(ConfigurationType type) { @@ -53,8 +53,23 @@ public void add(ConfigurationType type) { UserError.guarantee(previous == null || previous == type, "Cannot replace existing type %s with %s", previous, type); } - public ConfigurationType getOrCreateType(String qualifiedJavaName) { - return types.computeIfAbsent(qualifiedJavaName, ConfigurationType::new); + public ConfigurationType getOrCreateType(String qualifiedForNameString) { + assert qualifiedForNameString.indexOf('/') == -1 : "Requires qualified Java name, not internal representation"; + assert !qualifiedForNameString.endsWith("[]") : "Requires Class.forName syntax, for example '[Ljava.lang.String;'"; + String s = qualifiedForNameString; + int n = 0; + while (n < s.length() && s.charAt(n) == '[') { + n++; + } + if (n > 0) { // transform to Java source syntax + StringBuilder sb = new StringBuilder(s.length() + n); + sb.append(s, n + 1, s.length() - 1); // cut off leading '[' and 'L' and trailing ';' + for (int i = 0; i < n; i++) { + sb.append("[]"); + } + s = sb.toString(); + } + return types.computeIfAbsent(s, ConfigurationType::new); } @Override diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RuleNode.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RuleNode.java index 424368b4c163..bc99c26a3d73 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RuleNode.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/filters/RuleNode.java @@ -285,9 +285,6 @@ public boolean treeIncludes(String qualifiedName) { inheritedInclusion = current.descendantsInclusion; } String part = tokenizer.nextToken(); - if (part.indexOf('*') != -1) { - throw new IllegalArgumentException("Patterns are not allowed for querying"); - } RuleNode child = (current.children != null) ? current.children.get(part) : null; if (child == null) { boolean isDirectChild = !tokenizer.hasMoreTokens(); diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java index e1b1d984874e..cb7342eddb65 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/AccessAdvisor.java @@ -28,28 +28,46 @@ import com.oracle.svm.configure.filters.RuleNode; +/** + * Decides if a recorded access should be included in a configuration. Also advises the agent's + * {@code AccessVerifier} classes which accesses to ignore when the agent is in restriction mode. + */ public final class AccessAdvisor { - private static final RuleNode internalsFilter; + /** Filter to ignore accesses that originate in methods of these internal classes. */ + private static final RuleNode internalCallerFilter; + + /** Filter to ignore accesses of these classes and their members without a caller. */ + private static final RuleNode accessWithoutCallerFilter; + static { - internalsFilter = RuleNode.createRoot(); - internalsFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); - internalsFilter.addOrGetChildren("java.**", RuleNode.Inclusion.Exclude); - internalsFilter.addOrGetChildren("javax.**", RuleNode.Inclusion.Exclude); - internalsFilter.addOrGetChildren("javax.security.auth.**", RuleNode.Inclusion.Include); - internalsFilter.addOrGetChildren("sun.**", RuleNode.Inclusion.Exclude); - internalsFilter.addOrGetChildren("com.sun.**", RuleNode.Inclusion.Exclude); - internalsFilter.addOrGetChildren("jdk.**", RuleNode.Inclusion.Exclude); - internalsFilter.addOrGetChildren("org.graalvm.compiler.**", RuleNode.Inclusion.Exclude); - internalsFilter.addOrGetChildren("org.graalvm.libgraal.**", RuleNode.Inclusion.Exclude); - internalsFilter.removeRedundantNodes(); + internalCallerFilter = RuleNode.createRoot(); + internalCallerFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); + internalCallerFilter.addOrGetChildren("java.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("javax.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("javax.security.auth.**", RuleNode.Inclusion.Include); + internalCallerFilter.addOrGetChildren("sun.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("com.sun.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("jdk.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("org.graalvm.compiler.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.addOrGetChildren("org.graalvm.libgraal.**", RuleNode.Inclusion.Exclude); + internalCallerFilter.removeRedundantNodes(); + + accessWithoutCallerFilter = RuleNode.createRoot(); + accessWithoutCallerFilter.addOrGetChildren("**", RuleNode.Inclusion.Include); + accessWithoutCallerFilter.addOrGetChildren("jdk.vm.ci.**", RuleNode.Inclusion.Exclude); + accessWithoutCallerFilter.addOrGetChildren("org.graalvm.compiler.**", RuleNode.Inclusion.Exclude); + accessWithoutCallerFilter.addOrGetChildren("org.graalvm.libgraal.**", RuleNode.Inclusion.Exclude); + accessWithoutCallerFilter.addOrGetChildren("[Ljava.lang.String;", RuleNode.Inclusion.Exclude); + // ^ String[]: for command-line argument arrays created before Java main method is called + accessWithoutCallerFilter.removeRedundantNodes(); } - public static RuleNode copyBuiltinFilterTree() { - return internalsFilter.copy(); + public static RuleNode copyBuiltinCallerFilterTree() { + return internalCallerFilter.copy(); } - private RuleNode callerFilter = internalsFilter; + private RuleNode callerFilter = internalCallerFilter; private boolean heuristicsEnabled = true; private boolean isInLivePhase = false; private int launchPhase = 0; @@ -71,14 +89,21 @@ public void setInLivePhase(boolean live) { isInLivePhase = live; } - public boolean shouldIgnoreCaller(LazyValue qualifiedClass) { - return (heuristicsEnabled && !isInLivePhase) || filterExcludesCaller(qualifiedClass.get()); + public boolean shouldIgnore(LazyValue queriedClass, LazyValue callerClass) { + if (heuristicsEnabled && !isInLivePhase) { + return true; + } + if (filterExcludesCaller(callerClass.get())) { + return true; + } + if (callerClass.get() == null) { + return !accessWithoutCallerFilter.treeIncludes(queriedClass.get()); + } + return false; } public boolean shouldIgnoreJniMethodLookup(LazyValue queriedClass, LazyValue name, LazyValue signature, LazyValue callerClass) { - if (shouldIgnoreCaller(callerClass)) { - return true; - } + assert !shouldIgnore(queriedClass, callerClass) : "must have been checked before"; if (!heuristicsEnabled) { return false; } @@ -102,16 +127,6 @@ public boolean shouldIgnoreJniMethodLookup(LazyValue queriedClass, LazyV return true; } } - // Ignore libjvmcicompiler internal JNI calls: - // * jdk.vm.ci.services.Services.getJVMCIClassLoader - // * org.graalvm.compiler.hotspot.management.libgraal.runtime.SVMToHotSpotEntryPoints - if (callerClass.get() == null && "jdk.vm.ci.services.Services".equals(queriedClass.get()) && "getJVMCIClassLoader".equals(name.get()) && - "()Ljava/lang/ClassLoader;".equals(signature.get())) { - return true; - } - if (callerClass.get() == null && "org.graalvm.compiler.hotspot.management.libgraal.runtime.SVMToHotSpotEntryPoints".equals(queriedClass.get())) { - return true; - } /* * NOTE: JVM invocations cannot be reliably filtered with callerClass == null because these * could also be calls in a manually launched thread which is attached to JNI, but is not @@ -120,34 +135,17 @@ public boolean shouldIgnoreJniMethodLookup(LazyValue queriedClass, LazyV return false; } - public boolean shouldIgnoreJniClassLookup(LazyValue name, LazyValue callerClass) { - if (shouldIgnoreCaller(callerClass)) { - return true; - } - if (!heuristicsEnabled) { - return false; - } - // Ignore libjvmcicompiler internal JNI calls: jdk.vm.ci.services.Services - if (callerClass.get() == null && "jdk.vm.ci.services.Services".equals(name.get())) { - return true; - } - return false; - } - - public boolean shouldIgnoreJniNewObjectArray(LazyValue arrayClass, LazyValue callerClass) { - if (shouldIgnoreCaller(callerClass)) { - return true; - } + public boolean shouldIgnoreLoadClass(LazyValue queriedClass, LazyValue callerClass) { + assert !shouldIgnore(queriedClass, callerClass) : "must have been checked before"; if (!heuristicsEnabled) { return false; } - if (callerClass.get() == null && "[Ljava.lang.String;".equals(arrayClass.get())) { - /* - * For command-line argument arrays created before the Java main method is called. We - * cannot detect this only via launchPhase on Java 8. - */ - return true; - } - return false; + /* + * Without a caller, we always assume that the class loader was invoked directly by the VM, + * which indicates a system class (compiler, JVMCI, etc.) that we shouldn't need in our + * configuration. The class loader could also have been called via JNI in a manually + * attached native thread without Java frames, but that is unusual. + */ + return callerClass.get() == null; } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java index d06ab4277a59..d0d37c7b84f2 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/JniProcessor.java @@ -60,37 +60,32 @@ void processEntry(Map entry) { return; } String function = (String) entry.get("function"); - String clazz = (String) entry.get("class"); - String declaringClass = (String) entry.get("declaring_class"); String callerClass = (String) entry.get("caller_class"); List args = (List) entry.get("args"); LazyValue callerClassLazyValue = lazyValue(callerClass); - if (advisor.shouldIgnoreCaller(callerClassLazyValue)) { + // Special: FindClass and DefineClass take the class in question as a string argument + if (function.equals("FindClass") || function.equals("DefineClass")) { + String lookupName = singleElement(args); + String internalName = (lookupName.charAt(0) != '[') ? ('L' + lookupName + ';') : lookupName; + String forNameString = MetaUtil.internalNameToJava(internalName, true, true); + if (!advisor.shouldIgnore(lazyValue(forNameString), callerClassLazyValue)) { + if (function.equals("FindClass")) { + configuration.getOrCreateType(forNameString); + } else if (!lookupName.startsWith("com/sun/proxy/$Proxy")) { // DefineClass + logWarning("Unsupported JNI function DefineClass used to load class " + forNameString); + } + } return; } + String clazz = (String) entry.get("class"); + if (advisor.shouldIgnore(lazyValue(clazz), callerClassLazyValue)) { + return; + } + String declaringClass = (String) entry.get("declaring_class"); String declaringClassOrClazz = (declaringClass != null) ? declaringClass : clazz; ConfigurationMemberKind memberKind = (declaringClass != null) ? ConfigurationMemberKind.DECLARED : ConfigurationMemberKind.PRESENT; TypeConfiguration config = configuration; switch (function) { - case "DefineClass": { - String name = singleElement(args); - if (name.startsWith("com/sun/proxy/$Proxy")) { - break; // implementation detail of Proxy support - } - logWarning("Unsupported JNI function DefineClass used to load class " + name); - break; - } - case "FindClass": { - String name = singleElement(args); - if (name.charAt(0) != '[') { - name = "L" + name + ";"; - } - String qualifiedJavaName = MetaUtil.internalNameToJava(name, true, false); - if (!advisor.shouldIgnoreJniClassLookup(lazyValue(qualifiedJavaName), callerClassLazyValue)) { - config.getOrCreateType(qualifiedJavaName); - } - break; - } case "GetStaticMethodID": case "GetMethodID": { expectSize(args, 2); @@ -136,10 +131,8 @@ void processEntry(Map entry) { } case "NewObjectArray": { expectSize(args, 0); - if (!advisor.shouldIgnoreJniNewObjectArray(lazyValue(clazz), callerClassLazyValue)) { - String arrayQualifiedJavaName = MetaUtil.internalNameToJava(clazz, true, false); - config.getOrCreateType(arrayQualifiedJavaName); - } + String arrayQualifiedJavaName = MetaUtil.internalNameToJava(clazz, true, true); + config.getOrCreateType(arrayQualifiedJavaName); break; } } diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/LazyValueUtils.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/LazyValueUtils.java index cd024d5330fd..a3222e527014 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/LazyValueUtils.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/LazyValueUtils.java @@ -29,8 +29,15 @@ import org.graalvm.compiler.phases.common.LazyValue; public class LazyValueUtils { + static final LazyValue NULL_VALUE = new LazyValue<>(() -> null); + public static LazyValue lazyValue(T value) { - return new LazyValue<>(() -> value); + return (value != null) ? new LazyValue<>(() -> value) : lazyNull(); + } + + @SuppressWarnings("unchecked") + public static LazyValue lazyNull() { + return (LazyValue) NULL_VALUE; } public static LazyValue lazyGet(Supplier supplier) { diff --git a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java index e5f09d324f23..dd866d3a749f 100644 --- a/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java +++ b/substratevm/src/com.oracle.svm.configure/src/com/oracle/svm/configure/trace/ReflectionProcessor.java @@ -88,24 +88,27 @@ public void processEntry(Map entry) { resourceConfiguration.addResourcePattern(regex); return; } - String clazz = (String) entry.get("class"); String callerClass = (String) entry.get("caller_class"); - if (advisor.shouldIgnoreCaller(lazyValue(callerClass))) { + boolean isLoadClass = function.equals("loadClass"); + if (isLoadClass || function.equals("forName")) { + String name = singleElement(args); + if (isLoadClass) { // different array syntax + name = MetaUtil.internalNameToJava(MetaUtil.toInternalName(name), true, true); + } + if (!advisor.shouldIgnore(lazyValue(name), lazyValue(callerClass)) && + !(isLoadClass && advisor.shouldIgnoreLoadClass(lazyValue(name), lazyValue(callerClass)))) { + configuration.getOrCreateType(name); + } + return; + } + String clazz = (String) entry.get("class"); + if (advisor.shouldIgnore(lazyValue(clazz), lazyValue(callerClass))) { return; } ConfigurationMemberKind memberKind = ConfigurationMemberKind.PUBLIC; boolean unsafeAccess = false; String clazzOrDeclaringClass = entry.containsKey("declaring_class") ? (String) entry.get("declaring_class") : clazz; switch (function) { - case "loadClass": - case "forName": { - assert clazz.equals("java.lang.Class"); - expectSize(args, 1); - String name = (String) args.get(0); - configuration.getOrCreateType(name); - break; - } - case "getDeclaredFields": { configuration.getOrCreateType(clazz).setAllDeclaredFields(); break; @@ -190,7 +193,7 @@ public void processEntry(Map entry) { case "newInstance": { if (clazz.equals("java.lang.reflect.Array")) { // reflective array instantiation - String qualifiedJavaName = MetaUtil.internalNameToJava((String) args.get(0), true, false); + String qualifiedJavaName = MetaUtil.internalNameToJava((String) args.get(0), true, true); configuration.getOrCreateType(qualifiedJavaName); } else { configuration.getOrCreateType(clazz).addMethod(ConfigurationMethod.CONSTRUCTOR_NAME, "()V", ConfigurationMemberKind.DECLARED); diff --git a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java index 2c2625686b6d..36a5cb3b73ed 100644 --- a/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java +++ b/substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/jdk/Target_java_lang_ClassLoader.java @@ -155,6 +155,9 @@ final class Target_java_lang_ClassLoader { @Alias @RecomputeFieldValue(kind = Kind.Reset)// private Vector> classes; + @Alias @RecomputeFieldValue(kind = Kind.Reset)// + private ConcurrentHashMap parallelLockMap; + /** * Reset ClassLoader.packages; accessing packages via ClassLoader is currently not supported and * the SystemClassLoader may capture some hosted packages.