https://openjdk.org/jeps/8307341 JEP draft: Prepare to Restrict The Use of JNI Owner Ron Pressler Type Feature Scope SE Status Draft Component core-libs Reviewed by Alex Buckley, Jorn Vernee Created 2023/05/03 09:08 Updated 2023/09/20 13:50 Issue 8307341 Summary Support the goal of integrity-by-default by issuing warnings when native code is invoked through the Java Native Interface (JNI). Warnings may be suppressed by enabling native access on the command line. In a future release, invoking native code will cause an error unless native access is enabled. Goals * Prepare for a future release that restricts the use of JNI to load native libraries and call native code. Restricting the use of JNI means that the user will have to enable native access on the command line. * Align JNI and the Foreign Function & Memory (FFM) API, so that libraries can migrate from JNI to FFM by employing the same command line option to enable native access for both. Non-Goals * There is no plan to remove JNI from the Java Platform. Motivation The Java Native Interface (JNI) was added in JDK 1.1 as the primary means for interoperating between Java code and native code, typically written in C. JNI lets Java code call native code (a "downcall") and lets native code call Java code (an "upcall"). Unfortunately, any interaction at all between Java code and native code is risky because it can compromise the integrity of applications and the Java Platform itself. According to the policy of integrity by default, all JDK features that are capable of breaking integrity must obtain explicit approval from the end user or the application assembler. Here are four common interactions and their risks: 1. Calling native code can lead to arbitrary undefined behavior, including JVM crashes. Such problems cannot be prevented by the Java runtime or caught by Java code. For example, the following C function takes a long value passed from Java and treats it as an address in memory, storing a value at that address: void Java_pkg_C_setPointerToThree__J(jlong ptr) { *(int*)ptr = 3; } Calling this C function could corrupt memory used by the JVM, causing the JVM to crash at an unpredictable time, long after the C function has returned. Such crashes (and other unexpected behaviors) cannot be easily localized to their cause. 2. Native code and Java code often exchange data through byte buffers, which are regions of memory not managed by the JVM's garbage collector. Native code can produce a byte buffer that is backed by an invalid region of memory, and using the byte buffer in Java code is practically certain to cause undefined behavior. For example, the following C code constructs a 10-element byte buffer starting at address 0, and returns it to Java code. The JVM will crash when Java code attempts to read or write the byte buffer: ... return (*env)->NewDirectByteBuffer(env, NULL, 10); // a buffer wrapping an illegal address 3. Native code could use the JNI API to access fields and call methods without any access checks by the JVM or even change the values of final fields long after they are initialized because the implementation of JNI does not perform the appropriate checks. Java code that uses JNI could therefore circumvent any of the invariants established through strong encapsulation of any other Java code. For example, Java code frequently relies on the invariant that String objects are immutable, but the following C code mutates a String object by writing to an array referenced by a private field: jclass clazz = (*env)->FindClass(env, "java/lang/String"); jfieldID fid = (*env)->GetFieldID(env, clazz , "value", "[B"); jbyteArray contents = (jbyteArray)(*env)->GetObjectField(env, str, fid); jbyte b = 0; (*env)->SetByteArrayRegion(env, contents, 0, 1, &b); Additionally, the following code can violate the JVM's integrity invariant of never writing past the end of an array: jbyte *a = (*env)->GetPrimitiveArrayCritical(env, arr, 0); a[500] = 3; // may be out of bounds (*env)->ReleasePrimitiveArrayCritical(env, arr, a, 0); 4. Native code which uses certain functions of the JNI API incorrectly (principally GetPrimitiveArrayCritical and GetStringCritical) may cause undesirable behavior by the garbage collector that can manifest at any time during the program's lifetime. The Foreign Function & Memory (FFM) API, introduced in JDK 19 as the preferred alternative to JNI, shares the first and second risks. FFM took a proactive approach to mitigate these risks, and separated actions that risk integrity from those that do not. Some parts of the FFM API are classified as restricted methods, which means the end user must approve their use and opt in via a command line option. JNI should follow FFM's example toward achieving integrity by default. Description Restricting the use of JNI means: 1. Restricting the loading of native libraries via java.lang.System and java.lang.Runtime. 2. Restricting the binding of native methods. To prepare for these restrictions, the Java runtime in JDK NN issues a warning the first time that Java code loads a native library or binds a native method. In a release after JDK NN, the Java runtime will give an error unless the user explicitly grants permission to use JNI. There are no changes to JNI functions or the JNI Invocation API. In future releases we may do some or all of the following (and perhaps others), as deemed appropriate to improve the user experience: * To identify libraries that use JNI statically, without running the program, the jdeps tool may be extended to report the use of restricted and native methods in a library, i.e. to report the dependence of a library on native code, similar to how the tool's static analysis currently reports the dependence on specific modules. * Offer a way for a module to declare that it requires native access so that we could also offer reliable configuration. I.e. it will allow the platform to immediately detect whether all the required native access has been granted, and fail to launch if not. We may additionally offer a way to automate the selection of modules to be granted native access. * Because, unlike FFM, JNI allows breaking Java encapsulation, which could interfere with future JVM optimizations in ways that FFM does not, a future release may offer an additional flag that grants the privilege of using only FFM and not JNI. In a future release, when it's determined that sufficient time has passed for the ecosystem to adapt and that the user experience is satisfactory, the warning will be replaced by an exception, and the --enable-native-access option (or the corresponding manifest attribute) will be required to load native libraries and bind native methods. Restrictions on loading native libraries The following methods in java.lang.System and java.lang.Runtime load a native library and invoke its JNI_OnLoad function, which contains arbitrary native code. In addition, the library may define initialization functions that are run by the operating system, and these also contain arbitrary native code. * System.load * Runtime.load * System.loadLibrary * Runtime.loadLibrary Because of the risks of native code, these methods are restricted in JDK NN. When a restricted method is called, the JVM gives a warning that identifies the caller: (unless the call is permitted, as described later) WARNING: A restricted method in java.lang.System has been called WARNING: System::load has been called by com.foo.Server in an unnamed module WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module WARNING: Restricted methods will be blocked in a future release unless native access is enabled The warning is issued to the standard error stream. The warning is given at most once for each module whose code calls a restricted method. (Code on the classpath resides in the unnamed module, mentioned in the example above.) Restrictions on binding native methods When a native method in Java is first called by the Java program, the native method is linked to the corresponding native code in a native library. This linkage is called "binding" the native method. (The correspondence between the native method and native code is described here.) When a native method is bound, the JVM gives a warning that identifies the caller: (unless the binding is permitted, as described later) WARNING: A native method in org.baz.services.Controller has been bound WARNING: Controller::getData in module org.baz has been called by com.foo.Server in an unnamed module WARNING: Use --enable-native-access=org.baz to avoid a warning for native methods declared in org.baz WARNING: Native methods will be blocked in a future release unless native access is enabled This warning is given at most once for a module which declares native methods. Specifically: * The warning is given only when a native method is bound, which happens the first time that the native method is called. The warning is not given every time that the native method is called. * The warning is given only the first time that a native method declared in a module is bound. After that, binding other native methods declared in the same module will not cause a warning. Granting permission to access native code Warnings about restricted methods and native methods can be avoided by giving permission to the Java program to load native libraries and bind native methods. This is done at startup, via a command line option: java --enable-native-access=M ... where M is a comma-separated list of modules that are permitted to load native libraries or bind native methods. Code on the class path lives in the unnamed module, so to avoid warnings for code on the class path, use: java --enable-native-access=ALL-UNNAMED ... When the --enable-native-access option is present, any attempt to load a native library or bind a native method by a module outside the list of specified modules will cause an IllegalCallerException to be thrown, rather than a warning to be issued. As an alternative to the --enable-native-access option, the following attribute may be added to the manifest of a JAR file that is used with the -jar flag (an "executable JAR"): Enable-Native-Access: ALL-UNNAMED ALL-UNNAMED is the only value supported in the attribute. Other module names will be ignored. When a module is created programmatically, it can be granted native access with the ModuleLayer.Controller.enableNativeAccess method. JNI Invocation API An application written in native code can use the JNI Invocation API to host the JVM in-process. In JDK NN, Java code that runs in a hosted JVM is by default restricted in its use of JNI (i.e., the use causes warnings), as if the JVM had been launched from the command line without enabling native access. The native application which uses the JNI Invocation API can choose to enable native access for the hosted JVM by passing the --enable-native-access=... option when creating the JVM. If this occurs, then Java code that runs in the hosted JVM can use JNI without warnings, as if the JVM had been launched from the command line with --enable-native-access=.... Disabling permissions Running with: java --enable-native-access=java.base will cause code on the classpath of JDK NN to receive an IllegalCallerException when trying to load a native library or call a native method Risks and Assumptions JNI has been part of the Java Platform since JDK 1.1, so there is a risk that some applications will be impacted by restrictions on the use of JNI. The restrictions are designed to be analogous to those placed on use of the FFM API: * Loading a native library: The restriction on System.loadLibrary and related methods is analogous to the restriction on FFM's SymbolLookup.libraryLookup. * Binding a native method: The restriction on binding a native method declared in a Java class -- which is implicitly done the first time the method is called -- is analogous to a Java class obtaining a "downcall handle" with FFM's Linker.downcallHandle, which is a restricted method. * Calling a native method: There is no restriction on calling a native method (after it has been bound), and this is analogous to how invoking a downcall handle in the FFM API is not restricted. Alternatives * Rather than restrict the loading of native libraries and the binding of native methods, the JVM could apply access control rules when native code uses JNI functions to access Java fields and methods. However, this is insufficient to maintain integrity because any use of native code is risky, and can lead to undefined behavior. Portions of the FFM API are restricted for the same reason, even though the FFM API does not offer access to Java objects from native code. OpenJDK logo Installing Contributing Sponsoring Developers' Guide Vulnerabilities JDK GA/EA Builds Mailing lists Wiki * IRC Bylaws * Census Legal Workshop JEP Process Source code Mercurial GitHub Tools Git jtreg harness Groups (overview) Adoption Build Client Libraries Compatibility & Specification Review Compiler Conformance Core Libraries Governing Board HotSpot IDE Tooling & Support Internationalization JMX Members Networking Porters Quality Security Serviceability Vulnerability Web Projects (overview, archive) Amber Audio Engine CRaC Caciocavallo Closures Code Tools Coin Common VM Interface Compiler Grammar Detroit Developers' Guide Device I/O Duke Font Scaler Galahad Graal Graphics Rasterizer IcedTea JDK 7 JDK 8 JDK 8 Updates JDK 9 JDK (..., 21, 22) JDK Updates JavaDoc.Next Jigsaw Kona Kulla Lambda Lanai Leyden Lilliput Locale Enhancement Loom Memory Model Update Metropolis Mission Control Modules Multi-Language VM Nashorn New I/O OpenJFX Panama Penrose Port: AArch32 Port: AArch64 Port: BSD Port: Haiku Port: Mac OS X Port: MIPS Port: Mobile Port: PowerPC/AIX Port: RISC-V Port: s390x Portola SCTP Shenandoah Skara Sumatra Tiered Attribution Tsan Type Annotations Valhalla Verona VisualVM Wakefield Zero ZGC Oracle logo (c) Oracle Corporation and/or its affiliates Terms of Use * License: GPLv2 * Privacy * Trademarks