8221642: AccessibleObject::setAccessible throws NPE when invoked by JNI code with no java frame on stack
Reviewed-by: alanb
This commit is contained in:
parent
4dbebb62aa
commit
9c0104b9c9
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 1997, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 1997, 2022, 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
|
||||
@ -168,6 +168,15 @@ public class AccessibleObject implements AnnotatedElement {
|
||||
* open module. </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> This method may be used by <a href="{@docRoot}/../specs/jni/index.html">JNI code</a>
|
||||
* with no caller class on the stack to enable access to a {@link Member member}
|
||||
* of {@link Member#getDeclaringClass() declaring class} {@code D} if and only if:
|
||||
* <ul>
|
||||
* <li> The member is {@code public} and {@code D} is {@code public} in
|
||||
* a package that the module containing {@code D} {@link
|
||||
* Module#isExported(String,Module) exports} unconditionally. </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> This method cannot be used to enable access to private members,
|
||||
* members with default (package) access, protected instance members, or
|
||||
* protected constructors when the declaring class is in a different module
|
||||
@ -246,6 +255,11 @@ public class AccessibleObject implements AnnotatedElement {
|
||||
* }
|
||||
* }</pre>
|
||||
*
|
||||
* <p> If this method is invoked by <a href="{@docRoot}/../specs/jni/index.html">JNI code</a>
|
||||
* with no caller class on the stack, the {@code accessible} flag can
|
||||
* only be set if the member and the declaring class are public, and
|
||||
* the class is in a package that is exported unconditionally. </p>
|
||||
*
|
||||
* <p> If there is a security manager, its {@code checkPermission} method
|
||||
* is first called with a {@code ReflectPermission("suppressAccessChecks")}
|
||||
* permission. </p>
|
||||
@ -304,6 +318,16 @@ public class AccessibleObject implements AnnotatedElement {
|
||||
throw new IllegalCallerException(); // should not happen
|
||||
}
|
||||
|
||||
if (caller == null) {
|
||||
// No caller frame when a native thread attaches to the VM
|
||||
// only allow access to a public accessible member
|
||||
boolean canAccess = Reflection.verifyPublicMemberAccess(declaringClass, declaringClass.getModifiers());
|
||||
if (!canAccess && throwExceptionIfDenied) {
|
||||
throwInaccessibleObjectException(caller, declaringClass);
|
||||
}
|
||||
return canAccess;
|
||||
}
|
||||
|
||||
Module callerModule = caller.getModule();
|
||||
Module declaringModule = declaringClass.getModule();
|
||||
|
||||
@ -312,12 +336,7 @@ public class AccessibleObject implements AnnotatedElement {
|
||||
if (!declaringModule.isNamed()) return true;
|
||||
|
||||
String pn = declaringClass.getPackageName();
|
||||
int modifiers;
|
||||
if (this instanceof Executable) {
|
||||
modifiers = ((Executable) this).getModifiers();
|
||||
} else {
|
||||
modifiers = ((Field) this).getModifiers();
|
||||
}
|
||||
int modifiers = ((Member)this).getModifiers();
|
||||
|
||||
// class is public and package is exported to caller
|
||||
boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
|
||||
@ -341,24 +360,36 @@ public class AccessibleObject implements AnnotatedElement {
|
||||
}
|
||||
|
||||
if (throwExceptionIfDenied) {
|
||||
throwInaccessibleObjectException(caller, declaringClass);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void throwInaccessibleObjectException(Class<?> caller, Class<?> declaringClass) {
|
||||
boolean isClassPublic = Modifier.isPublic(declaringClass.getModifiers());
|
||||
String pn = declaringClass.getPackageName();
|
||||
int modifiers = ((Member)this).getModifiers();
|
||||
|
||||
// not accessible
|
||||
String msg = "Unable to make ";
|
||||
if (this instanceof Field)
|
||||
msg += "field ";
|
||||
msg += this + " accessible: " + declaringModule + " does not \"";
|
||||
msg += this + " accessible";
|
||||
msg += caller == null ? " by JNI attached native thread with no caller frame: " : ": ";
|
||||
msg += declaringClass.getModule() + " does not \"";
|
||||
if (isClassPublic && Modifier.isPublic(modifiers))
|
||||
msg += "exports";
|
||||
else
|
||||
msg += "opens";
|
||||
msg += " " + pn + "\" to " + callerModule;
|
||||
msg += " " + pn + "\"" ;
|
||||
if (caller != null)
|
||||
msg += " to " + caller.getModule();
|
||||
InaccessibleObjectException e = new InaccessibleObjectException(msg);
|
||||
if (printStackTraceWhenAccessFails()) {
|
||||
e.printStackTrace(System.err);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isSubclassOf(Class<?> queryClass, Class<?> ofClass) {
|
||||
while (queryClass != null) {
|
||||
@ -409,7 +440,11 @@ public class AccessibleObject implements AnnotatedElement {
|
||||
* is set to {@code true}, i.e. the checks for Java language access control
|
||||
* are suppressed, or if the caller can access the member as
|
||||
* specified in <cite>The Java Language Specification</cite>,
|
||||
* with the variation noted in the class description. </p>
|
||||
* with the variation noted in the class description.
|
||||
* If this method is invoked by <a href="{@docRoot}/../specs/jni/index.html">JNI code</a>
|
||||
* with no caller class on the stack, this method returns {@code true}
|
||||
* if the member and the declaring class are public, and the class is in
|
||||
* a package that is exported unconditionally. </p>
|
||||
*
|
||||
* @param obj an instance object of the declaring class of this reflected
|
||||
* object if it is an instance method or field
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, 2021, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2022, 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
|
||||
@ -24,7 +24,7 @@
|
||||
|
||||
/**
|
||||
* @test
|
||||
* @bug 8221530
|
||||
* @bug 8221530 8221642
|
||||
* @summary Test uses custom launcher that starts VM using JNI that verifies
|
||||
* reflection API with null caller class
|
||||
* @library /test/lib
|
||||
@ -61,7 +61,10 @@ public class CallerAccessTest {
|
||||
|
||||
System.out.println("Launching: " + launcher + " shared library path: " +
|
||||
env.get(sharedLibraryPathEnvName));
|
||||
new OutputAnalyzer(pb.start()).shouldHaveExitValue(0);
|
||||
new OutputAnalyzer(pb.start())
|
||||
.outputTo(System.out)
|
||||
.errorTo(System.err)
|
||||
.shouldHaveExitValue(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved.
|
||||
* Copyright (c) 2019, 2022, 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,9 +32,15 @@ static jclass iaeClass;
|
||||
static jmethodID mid_Class_forName;
|
||||
static jmethodID mid_Class_getField;
|
||||
static jmethodID mid_Field_get;
|
||||
static jmethodID mid_Field_canAccess;
|
||||
static jmethodID mid_Field_trySetAccessible;
|
||||
static jmethodID mid_Field_setAccessible;
|
||||
|
||||
int getField(JNIEnv *env, char* declaringClass_name, char* field_name);
|
||||
int checkAndClearIllegalAccessExceptionThrown(JNIEnv *env);
|
||||
int setAccessible(JNIEnv *env, char* declaringClass_name, char* field_name);
|
||||
int trySetAccessible(JNIEnv *env, char* declaringClass_name, char* field_name, jboolean canAccess);
|
||||
int checkAccess(JNIEnv *env, char* declaringClass_name, char* field_name, jboolean canAccess);
|
||||
|
||||
int main(int argc, char** args) {
|
||||
JavaVM *jvm;
|
||||
@ -65,6 +71,12 @@ int main(int argc, char** args) {
|
||||
jclass fieldClass = (*env)->FindClass(env, "java/lang/reflect/Field");
|
||||
mid_Field_get = (*env)->GetMethodID(env, fieldClass, "get", "(Ljava/lang/Object;)Ljava/lang/Object;");
|
||||
assert(mid_Class_getField != NULL);
|
||||
mid_Field_canAccess = (*env)->GetMethodID(env, fieldClass, "canAccess", "(Ljava/lang/Object;)Z");
|
||||
assert(mid_Field_canAccess != NULL);
|
||||
mid_Field_setAccessible = (*env)->GetMethodID(env, fieldClass, "setAccessible", "(Z)V");
|
||||
assert(mid_Field_setAccessible != NULL);
|
||||
mid_Field_trySetAccessible = (*env)->GetMethodID(env, fieldClass, "trySetAccessible", "()Z");
|
||||
assert(mid_Field_trySetAccessible != NULL);
|
||||
|
||||
// can access to public member of an exported type
|
||||
if ((rc = getField(env, "java.lang.Integer", "TYPE")) != 0) {
|
||||
@ -92,6 +104,32 @@ int main(int argc, char** args) {
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
// expect IAE to jdk.internal.misc.Unsafe class
|
||||
if ((rc = setAccessible(env, "jdk.internal.misc.Unsafe", "INVALID_FIELD_OFFSET")) == 0) {
|
||||
printf("ERROR: IAE not thrown\n");
|
||||
exit(-1);
|
||||
}
|
||||
if (checkAndClearIllegalAccessExceptionThrown(env) != JNI_TRUE) {
|
||||
printf("ERROR: exception is not an instance of IAE\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if ((rc = trySetAccessible(env, "java.lang.reflect.Modifier", "PUBLIC", JNI_TRUE)) != 0) {
|
||||
printf("ERROR: unexpected result from trySetAccessible on Modifier::PUBLIC field\n");
|
||||
exit(-1);
|
||||
}
|
||||
if ((rc = trySetAccessible(env, "jdk.internal.misc.Unsafe", "INVALID_FIELD_OFFSET", JNI_FALSE)) != 0) {
|
||||
printf("ERROR: unexpected result from trySetAccessible on Unsafe public field\n");
|
||||
exit(-1);
|
||||
}
|
||||
|
||||
if ((rc = checkAccess(env, "java.lang.reflect.Modifier", "PUBLIC", JNI_TRUE)) != 0) {
|
||||
printf("ERROR: unexpected result from trySetAccessible on Modifier::PUBLIC field\n");
|
||||
exit(-1);
|
||||
}
|
||||
if ((rc = checkAccess(env, "jdk.internal.misc.Unsafe", "INVALID_FIELD_OFFSET", JNI_FALSE)) != 0) {
|
||||
printf("ERROR: unexpected result from trySetAccessible on Unsafe public field\n");
|
||||
}
|
||||
(*jvm)->DestroyJavaVM(jvm);
|
||||
return 0;
|
||||
}
|
||||
@ -127,3 +165,74 @@ int getField(JNIEnv *env, char* declaringClass_name, char* field_name) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int setAccessible(JNIEnv *env, char* declaringClass_name, char* field_name) {
|
||||
jobject c = (*env)->CallStaticObjectMethod(env, classClass, mid_Class_forName,
|
||||
(*env)->NewStringUTF(env, declaringClass_name));
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 1;
|
||||
}
|
||||
|
||||
jobject f = (*env)->CallObjectMethod(env, c, mid_Class_getField, (*env)->NewStringUTF(env, field_name));
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 2;
|
||||
}
|
||||
|
||||
(*env)->CallVoidMethod(env, f, mid_Field_setAccessible, JNI_TRUE);
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 3;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int trySetAccessible(JNIEnv *env, char* declaringClass_name, char* field_name, jboolean canAccess) {
|
||||
jobject c = (*env)->CallStaticObjectMethod(env, classClass, mid_Class_forName,
|
||||
(*env)->NewStringUTF(env, declaringClass_name));
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 1;
|
||||
}
|
||||
|
||||
jobject f = (*env)->CallObjectMethod(env, c, mid_Class_getField, (*env)->NewStringUTF(env, field_name));
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 2;
|
||||
}
|
||||
|
||||
jboolean rc = (*env)->CallBooleanMethod(env, f, mid_Field_trySetAccessible);
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 3;
|
||||
}
|
||||
if (rc != canAccess) {
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int checkAccess(JNIEnv *env, char* declaringClass_name, char* field_name, jboolean canAccess) {
|
||||
jobject c = (*env)->CallStaticObjectMethod(env, classClass, mid_Class_forName,
|
||||
(*env)->NewStringUTF(env, declaringClass_name));
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 1;
|
||||
}
|
||||
|
||||
jobject f = (*env)->CallObjectMethod(env, c, mid_Class_getField, (*env)->NewStringUTF(env, field_name));
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 2;
|
||||
}
|
||||
|
||||
jboolean rc = (*env)->CallBooleanMethod(env, f, mid_Field_canAccess, NULL);
|
||||
if ((*env)->ExceptionOccurred(env) != NULL) {
|
||||
(*env)->ExceptionDescribe(env);
|
||||
return 3;
|
||||
}
|
||||
if (rc != canAccess) {
|
||||
return 4;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user