8221642: AccessibleObject::setAccessible throws NPE when invoked by JNI code with no java frame on stack

Reviewed-by: alanb
This commit is contained in:
Mandy Chung 2022-02-01 00:09:35 +00:00
parent 4dbebb62aa
commit 9c0104b9c9
3 changed files with 174 additions and 27 deletions

View File

@ -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,25 +360,37 @@ public class AccessibleObject implements AnnotatedElement {
}
if (throwExceptionIfDenied) {
// not accessible
String msg = "Unable to make ";
if (this instanceof Field)
msg += "field ";
msg += this + " accessible: " + declaringModule + " does not \"";
if (isClassPublic && Modifier.isPublic(modifiers))
msg += "exports";
else
msg += "opens";
msg += " " + pn + "\" to " + callerModule;
InaccessibleObjectException e = new InaccessibleObjectException(msg);
if (printStackTraceWhenAccessFails()) {
e.printStackTrace(System.err);
}
throw e;
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";
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 + "\"" ;
if (caller != null)
msg += " to " + caller.getModule();
InaccessibleObjectException e = new InaccessibleObjectException(msg);
if (printStackTraceWhenAccessFails()) {
e.printStackTrace(System.err);
}
throw e;
}
private boolean isSubclassOf(Class<?> queryClass, Class<?> ofClass) {
while (queryClass != null) {
if (queryClass == ofClass) {
@ -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

View File

@ -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);
}
}

View File

@ -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;
}