Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8307341

Prepare to Restrict The Use of JNI

    XMLWordPrintable

Details

    • JEP
    • Status: Draft
    • P3
    • Resolution: Unresolved
    • None
    • core-libs
    • None
    • Ron Pressler
    • Feature
    • Open
    • SE

    Description

      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 avoided by enabling the use of JNI on the command line. In a future release, invoking native code will cause an exception unless the use of JNI 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 the use of JNI on the command line.

      • Align JNI and the Foreign Function & Memory (FFM) API, so that libraries can migrate from JNI to FFM without changing the command line option used to enable each of them.

      Non-Goals

      • There is no plan to remove JNI from the Java Platform.

      • It is not a goal to restrict the behavior of native code called via JNI. For example, all the native JNI functions will remain usable by native code.

      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 <code class="prettyprint" data-shared-secret="1695759071168-0.5634843703604367">String</code> 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 <code class="prettyprint" data-shared-secret="1695759071168-0.5634843703604367">GetPrimitiveArrayCritical</code> and <code class="prettyprint" data-shared-secret="1695759071168-0.5634843703604367">GetStringCritical</code>) 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.

      A future JDK release will restrict the loading of native libraries and the binding of native methods by throwing an exception when Java code tries to perform these activities. It will be possible to avoid the exception by enabling the use of JNI on the command line.

      To prepare developers for these restrictions, JDK NN issues a warning the first time that Java code loads a native library or binds a native method. The warning can be avoided in JDK NN by enabling the use of JNI on the command line; this also prepares the Java code to run without exceptions on a future JDK release.

      Restrictions on loading native libraries

      The following methods in java.lang.System and java.lang.Runtime load a native library and invoke its <code class="prettyprint" data-shared-secret="1695759071168-0.5634843703604367">JNI_OnLoad</code> function, which contains arbitrary native code:

      In addition, the native library may define initialization functions that are run by the operating system, and these also contain arbitrary native code.

      Because of the risks of native code, these four methods are restricted in JDK NN. When a restricted method is called, the JVM runs the method but gives a warning that identifies the caller:

      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 class path 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 the native code is described here.)

      When a native method is bound, the JVM binds the method but gives a warning that identifies the caller:

      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 the first time that a native method declared in a particular module is bound. Afterward, binding other native methods declared in the same module will not cause a warning.

      Enabling use of JNI

      Warnings about restricted methods and native methods can be avoided by enabling the use of JNI, in effect acknowledging the program's need 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 should be allowed to load native libraries and bind native methods. To avoid warnings for code on the class path, use:

      java --enable-native-access=ALL-UNNAMED ...

      When the --enable-native-access option is present in JDK NN, 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 rather than a warning.

      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, its use of JNI can be enabled via the <code class="prettyprint" data-shared-secret="1695759071168-0.5634843703604367">ModuleLayer.Controller.enableNativeAccess</code> method.

      Disabling use of JNI

      Given the risks of native code, some developers would like to ensure that their application's dependencies do not use native code. One way to achieve this is to run with:

      java --enable-native-access=java.base ...

      This enables the use of JNI by the java.base module only; any other Java code that tries to load native libraries or bind native methods will cause an IllegalCallerException in JDK NN. In effect, this simulates the behavior of the future JDK release which will throw an exception unless Java code is identified explicitly via --enable-native-access.

      JNI Invocation API

      The JNI Invocation API allows a native application 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.

      A 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=....

      Future work

      A release after JDK NN may include the following functionality:

      • To help identify libraries that use JNI, the jdeps tool may be extended to report where Java code loads native libraries or binds native methods, similar to how the tool's static analysis currently reports dependencies on JDK modules.

      • To promote reliable configuration, allow a module's declaration to express that the module needs to use JNI. At startup, the Java runtime would check if the use of JNI was enabled on the command line, and if not, would refuse to load any module which needs to use JNI.

      • JNI allows native code to break the encapsulation of Java code, which could interfere with future JVM optimizations in ways that use of the FFM API does not. A future JDK release may offer an additional flag that enables the use of only FFM and not JNI.

      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 <code class="prettyprint" data-shared-secret="1695759071168-0.5634843703604367">SymbolLookup.libraryLookup</code>.

      • 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.

      Attachments

        Issue Links

          Activity

            People

              rpressler Ron Pressler
              rpressler Ron Pressler
              Ron Pressler Ron Pressler
              Alex Buckley, Jorn Vernee
              Votes:
              0 Vote for this issue
              Watchers:
              11 Start watching this issue

              Dates

                Created:
                Updated: