Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 58 additions & 23 deletions src/java.base/share/classes/java/lang/reflect/AccessibleObject.java
Original file line number Diff line number Diff line change
@@ -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 static void setAccessible(AccessibleObject[] array, boolean flag) {
* 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 @@ boolean setAccessible0(boolean flag) {
* }
* }</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 @@ private boolean checkCanSetAccessible(Class<?> caller,
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 @@ private boolean checkCanSetAccessible(Class<?> caller,
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 @@ && isSubclassOf(caller, declaringClass)) {
}

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 boolean isAccessible() {
* 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
Original file line number Diff line number Diff line change
@@ -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 static void main(String[] args) throws IOException {

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

111 changes: 110 additions & 1 deletion test/jdk/java/lang/reflect/exeCallerAccessTest/exeCallerAccessTest.c
Original file line number Diff line number Diff line change
@@ -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;
}