8331671: Implement JEP 472: Prepare to Restrict the Use of JNI

Reviewed-by: jpai, prr, ihse, kcr, alanb
This commit is contained in:
Maurizio Cimadamore 2024-08-26 09:17:45 +00:00
parent ce83f6af64
commit 20d8f58c92
107 changed files with 551 additions and 182 deletions

View file

@ -2442,10 +2442,27 @@ public abstract class ClassLoader {
" in java.library.path: " + StaticProperty.javaLibraryPath());
}
/*
/**
* Invoked in the VM class linking code.
* @param loader the class loader used to look up the native library symbol
* @param clazz the class in which the native method is declared
* @param entryName the native method's mangled name (this is the name used for the native lookup)
* @param javaName the native method's declared name
*/
static long findNative(ClassLoader loader, String entryName) {
static long findNative(ClassLoader loader, Class<?> clazz, String entryName, String javaName) {
long addr = findNativeInternal(loader, entryName);
if (addr != 0 && loader != null) {
Reflection.ensureNativeAccess(clazz, clazz, javaName, true);
}
return addr;
}
/*
* This is also called by SymbolLookup::loaderLookup. In that case, we need
* to avoid a restricted check, as that check has already been performed when
* obtaining the lookup.
*/
static long findNativeInternal(ClassLoader loader, String entryName) {
if (loader == null) {
return BootLoader.getNativeLibraries().find(entryName);
} else {

View file

@ -62,7 +62,9 @@ import jdk.internal.loader.BootLoader;
import jdk.internal.loader.ClassLoaders;
import jdk.internal.misc.CDS;
import jdk.internal.misc.Unsafe;
import jdk.internal.misc.VM;
import jdk.internal.module.ModuleBootstrap;
import jdk.internal.module.ModuleBootstrap.IllegalNativeAccess;
import jdk.internal.module.ModuleLoaderMap;
import jdk.internal.module.ServicesCatalog;
import jdk.internal.module.Resources;
@ -300,26 +302,43 @@ public final class Module implements AnnotatedElement {
}
// This is invoked from Reflection.ensureNativeAccess
void ensureNativeAccess(Class<?> owner, String methodName, Class<?> currentClass) {
void ensureNativeAccess(Class<?> owner, String methodName, Class<?> currentClass, boolean jni) {
// The target module whose enableNativeAccess flag is ensured
Module target = moduleForNativeAccess();
if (!EnableNativeAccess.isNativeAccessEnabled(target)) {
if (ModuleBootstrap.hasEnableNativeAccessFlag()) {
throw new IllegalCallerException("Illegal native access from: " + this);
ModuleBootstrap.IllegalNativeAccess illegalNativeAccess = ModuleBootstrap.illegalNativeAccess();
if (illegalNativeAccess != ModuleBootstrap.IllegalNativeAccess.ALLOW &&
!EnableNativeAccess.isNativeAccessEnabled(target)) {
String mod = isNamed() ? "module " + getName() : "an unnamed module";
if (currentClass != null) {
// try to extract location of the current class (e.g. jar or folder)
URL url = System.codeSource(currentClass);
if (url != null) {
mod += " (" + url + ")";
}
}
if (EnableNativeAccess.trySetEnableNativeAccess(target)) {
if (illegalNativeAccess == ModuleBootstrap.IllegalNativeAccess.DENY) {
throw new IllegalCallerException("Illegal native access from " + mod);
} else if (EnableNativeAccess.trySetEnableNativeAccess(target)) {
// warn and set flag, so that only one warning is reported per module
String cls = owner.getName();
String mtd = cls + "::" + methodName;
String mod = isNamed() ? "module " + getName() : "an unnamed module";
String modflag = isNamed() ? getName() : "ALL-UNNAMED";
String caller = currentClass != null ? currentClass.getName() : "code";
System.err.printf("""
WARNING: A restricted method in %s has been called
WARNING: %s has been called by %s in %s
WARNING: Use --enable-native-access=%s to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
%n""", cls, mtd, caller, mod, modflag);
if (jni) {
VM.initialErr().printf("""
WARNING: A native method in %s has been bound
WARNING: %s is declared in %s
WARNING: Use --enable-native-access=%s to avoid a warning for native methods declared in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
%n""", cls, mtd, mod, modflag);
} else {
VM.initialErr().printf("""
WARNING: A restricted method in %s has been called
WARNING: %s has been called by %s in %s
WARNING: Use --enable-native-access=%s to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
%n""", cls, mtd, caller, mod, modflag);
}
}
}
}

View file

@ -323,7 +323,7 @@ public final class ModuleLayer {
public Controller enableNativeAccess(Module target) {
ensureInLayer(target);
Reflection.ensureNativeAccess(Reflection.getCallerClass(), Module.class,
"enableNativeAccess");
"enableNativeAccess", false);
target.implAddEnableNativeAccess();
return this;
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1995, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1995, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2019, Azul Systems, Inc. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
@ -36,6 +36,7 @@ import java.util.Optional;
import java.util.StringTokenizer;
import jdk.internal.access.SharedSecrets;
import jdk.internal.javac.Restricted;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
@ -828,14 +829,19 @@ public class Runtime {
* a native library image by the host system.
* @throws NullPointerException if {@code filename} is
* {@code null}
* @throws IllegalCallerException if the caller is in a module that
* does not have native access enabled.
* @spec jni/index.html Java Native Interface Specification
* @see java.lang.Runtime#getRuntime()
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkLink(java.lang.String)
*/
@CallerSensitive
@Restricted
public void load(String filename) {
load0(Reflection.getCallerClass(), filename);
Class<?> caller = Reflection.getCallerClass();
Reflection.ensureNativeAccess(caller, Runtime.class, "load", false);
load0(caller, filename);
}
void load0(Class<?> fromClass, String filename) {
@ -894,13 +900,18 @@ public class Runtime {
* native library image by the host system.
* @throws NullPointerException if {@code libname} is
* {@code null}
* @throws IllegalCallerException if the caller is in a module that
* does not have native access enabled.
* @spec jni/index.html Java Native Interface Specification
* @see java.lang.SecurityException
* @see java.lang.SecurityManager#checkLink(java.lang.String)
*/
@CallerSensitive
@Restricted
public void loadLibrary(String libname) {
loadLibrary0(Reflection.getCallerClass(), libname);
Class<?> caller = Reflection.getCallerClass();
Reflection.ensureNativeAccess(caller, Runtime.class, "loadLibrary", false);
loadLibrary0(caller, libname);
}
void loadLibrary0(Class<?> fromClass, String libname) {

View file

@ -69,6 +69,7 @@ import java.util.function.Supplier;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Stream;
import jdk.internal.javac.Restricted;
import jdk.internal.logger.LoggerFinderLoader.TemporaryLoggerFinder;
import jdk.internal.misc.Blocker;
import jdk.internal.misc.CarrierThreadLocal;
@ -355,7 +356,7 @@ public final class System {
= Collections.synchronizedMap(new WeakHashMap<>());
}
private static URL codeSource(Class<?> clazz) {
static URL codeSource(Class<?> clazz) {
PrivilegedAction<ProtectionDomain> pa = clazz::getProtectionDomain;
@SuppressWarnings("removal")
CodeSource cs = AccessController.doPrivileged(pa).getCodeSource();
@ -2017,14 +2018,19 @@ public final class System {
* linked with the VM, or the library cannot be mapped to
* a native library image by the host system.
* @throws NullPointerException if {@code filename} is {@code null}
* @throws IllegalCallerException if the caller is in a module that
* does not have native access enabled.
*
* @spec jni/index.html Java Native Interface Specification
* @see java.lang.Runtime#load(java.lang.String)
* @see java.lang.SecurityManager#checkLink(java.lang.String)
*/
@CallerSensitive
@Restricted
public static void load(String filename) {
Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
Class<?> caller = Reflection.getCallerClass();
Reflection.ensureNativeAccess(caller, System.class, "load", false);
Runtime.getRuntime().load0(caller, filename);
}
/**
@ -2055,14 +2061,19 @@ public final class System {
* linked with the VM, or the library cannot be mapped to a
* native library image by the host system.
* @throws NullPointerException if {@code libname} is {@code null}
* @throws IllegalCallerException if the caller is in a module that
* does not have native access enabled.
*
* @spec jni/index.html Java Native Interface Specification
* @see java.lang.Runtime#loadLibrary(java.lang.String)
* @see java.lang.SecurityManager#checkLink(java.lang.String)
*/
@CallerSensitive
@Restricted
public static void loadLibrary(String libname) {
Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);
Class<?> caller = Reflection.getCallerClass();
Reflection.ensureNativeAccess(caller, System.class, "loadLibrary", false);
Runtime.getRuntime().loadLibrary0(caller, libname);
}
/**
@ -2539,8 +2550,8 @@ public final class System {
public void addEnableNativeAccessToAllUnnamed() {
Module.implAddEnableNativeAccessToAllUnnamed();
}
public void ensureNativeAccess(Module m, Class<?> owner, String methodName, Class<?> currentClass) {
m.ensureNativeAccess(owner, methodName, currentClass);
public void ensureNativeAccess(Module m, Class<?> owner, String methodName, Class<?> currentClass, boolean jni) {
m.ensureNativeAccess(owner, methodName, currentClass, jni);
}
public ServicesCatalog getServicesCatalog(ModuleLayer layer) {
return layer.getServicesCatalog();
@ -2645,7 +2656,7 @@ public final class System {
@Override
public long findNative(ClassLoader loader, String entry) {
return ClassLoader.findNative(loader, entry);
return ClassLoader.findNativeInternal(loader, entry);
}
@Override

View file

@ -108,7 +108,7 @@ public sealed interface AddressLayout extends ValueLayout permits ValueLayouts.O
* @param layout the target layout
* @return an address layout with same characteristics as this layout, but with the
* provided target layout
* @throws IllegalCallerException If the caller is in a module that does not have
* @throws IllegalCallerException if the caller is in a module that does not have
* native access enabled
* @see #targetLayout()
*/

View file

@ -613,7 +613,7 @@ public sealed interface Linker permits AbstractLinker {
* {@code address.equals(MemorySegment.NULL)}
* @throws IllegalArgumentException if an invalid combination of linker options
* is given
* @throws IllegalCallerException If the caller is in a module that does not have
* @throws IllegalCallerException if the caller is in a module that does not have
* native access enabled
*
* @see SymbolLookup
@ -684,7 +684,7 @@ public sealed interface Linker permits AbstractLinker {
* supported by this linker
* @throws IllegalArgumentException if an invalid combination of linker options
* is given
* @throws IllegalCallerException If the caller is in a module that does not have
* @throws IllegalCallerException if the caller is in a module that does not have
* native access enabled
*/
@CallerSensitive
@ -733,7 +733,7 @@ public sealed interface Linker permits AbstractLinker {
* @throws IllegalStateException if {@code arena.scope().isAlive() == false}
* @throws WrongThreadException if {@code arena} is a confined arena, and this method
* is called from a thread {@code T}, other than the arena's owner thread
* @throws IllegalCallerException If the caller is in a module that does not have
* @throws IllegalCallerException if the caller is in a module that does not have
* native access enabled
*/
@CallerSensitive

View file

@ -285,14 +285,14 @@ public interface SymbolLookup {
* @throws WrongThreadException if {@code arena} is a confined arena, and this method
* is called from a thread {@code T}, other than the arena's owner thread
* @throws IllegalArgumentException if {@code name} does not identify a valid library
* @throws IllegalCallerException If the caller is in a module that does not have
* @throws IllegalCallerException if the caller is in a module that does not have
* native access enabled
*/
@CallerSensitive
@Restricted
static SymbolLookup libraryLookup(String name, Arena arena) {
Reflection.ensureNativeAccess(Reflection.getCallerClass(),
SymbolLookup.class, "libraryLookup");
SymbolLookup.class, "libraryLookup", false);
if (Utils.containsNullChars(name)) {
throw new IllegalArgumentException("Cannot open library: " + name);
}
@ -319,14 +319,14 @@ public interface SymbolLookup {
* is called from a thread {@code T}, other than the arena's owner thread
* @throws IllegalArgumentException if {@code path} does not point to a valid library
* in the default file system
* @throws IllegalCallerException If the caller is in a module that does not have
* @throws IllegalCallerException if the caller is in a module that does not have
* native access enabled
*/
@CallerSensitive
@Restricted
static SymbolLookup libraryLookup(Path path, Arena arena) {
Reflection.ensureNativeAccess(Reflection.getCallerClass(),
SymbolLookup.class, "libraryLookup");
SymbolLookup.class, "libraryLookup", false);
if (path.getFileSystem() != FileSystems.getDefault()) {
throw new IllegalArgumentException("Path not in default file system: " + path);
}

View file

@ -165,10 +165,11 @@
* In the reference implementation, access to restricted methods can be granted to
* specific modules using the command line option {@code --enable-native-access=M1,M2, ... Mn},
* where {@code M1}, {@code M2}, {@code ... Mn} are module names (for the unnamed module,
* the special value {@code ALL-UNNAMED} can be used). If this option is specified,
* access to restricted methods are only granted to the modules listed by that option.
* If this option is not specified, access to restricted methods is enabled for all
* modules, but access to restricted methods will result in runtime warnings.
* the special value {@code ALL-UNNAMED} can be used). Access to restricted methods
* from modules not listed by that option is deemed <em>illegal</em>. Clients can
* control how access to restricted methods is handled, using the command line
* option {@code --illegal-native-access}. If this option is not specified,
* illegal access to restricted methods will result in runtime warnings.
*
* @spec jni/index.html Java Native Interface Specification
*

View file

@ -281,10 +281,14 @@ public interface JavaLangAccess {
void addEnableNativeAccessToAllUnnamed();
/**
* Ensure that the given module has native access. If not, warn or
* throw exception depending on the configuration.
* Ensure that the given module has native access. If not, warn or throw exception depending on the configuration.
* @param m the module in which native access occurred
* @param owner the owner of the restricted method being called (or the JNI method being bound)
* @param methodName the name of the restricted method being called (or the JNI method being bound)
* @param currentClass the class calling the restricted method (for JNI, this is the same as {@code owner})
* @param jni {@code true}, if this event is related to a JNI method being bound
*/
void ensureNativeAccess(Module m, Class<?> owner, String methodName, Class<?> currentClass);
void ensureNativeAccess(Module m, Class<?> owner, String methodName, Class<?> currentClass, boolean jni);
/**
* Returns the ServicesCatalog for the given Layer.

View file

@ -152,7 +152,7 @@ public abstract sealed class AbstractMemorySegmentImpl
}
public MemorySegment reinterpretInternal(Class<?> callerClass, long newSize, Scope scope, Consumer<MemorySegment> cleanup) {
Reflection.ensureNativeAccess(callerClass, MemorySegment.class, "reinterpret");
Reflection.ensureNativeAccess(callerClass, MemorySegment.class, "reinterpret", false);
Utils.checkNonNegativeArgument(newSize, "newSize");
if (!isNative()) throw new UnsupportedOperationException("Not a native segment");
Runnable action = cleanup != null ?

View file

@ -80,7 +80,7 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
@Override
@CallerSensitive
public final MethodHandle downcallHandle(MemorySegment symbol, FunctionDescriptor function, Option... options) {
Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle");
Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle", false);
SharedUtils.checkSymbol(symbol);
return downcallHandle0(function, options).bindTo(symbol);
}
@ -88,7 +88,7 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
@Override
@CallerSensitive
public final MethodHandle downcallHandle(FunctionDescriptor function, Option... options) {
Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle");
Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "downcallHandle", false);
return downcallHandle0(function, options);
}
@ -115,7 +115,7 @@ public abstract sealed class AbstractLinker implements Linker permits LinuxAArch
@Override
@CallerSensitive
public final MemorySegment upcallStub(MethodHandle target, FunctionDescriptor function, Arena arena, Linker.Option... options) {
Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "upcallStub");
Reflection.ensureNativeAccess(Reflection.getCallerClass(), Linker.class, "upcallStub", false);
Objects.requireNonNull(arena);
Objects.requireNonNull(target);
Objects.requireNonNull(function);

View file

@ -36,7 +36,7 @@ final class LibFallback {
static final boolean SUPPORTED = tryLoadLibrary();
@SuppressWarnings("removal")
@SuppressWarnings({"removal", "restricted"})
private static boolean tryLoadLibrary() {
return java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<>() {

View file

@ -332,7 +332,7 @@ public final class ValueLayouts {
@Override
@CallerSensitive
public AddressLayout withTargetLayout(MemoryLayout layout) {
Reflection.ensureNativeAccess(Reflection.getCallerClass(), AddressLayout.class, "withTargetLayout");
Reflection.ensureNativeAccess(Reflection.getCallerClass(), AddressLayout.class, "withTargetLayout", false);
Objects.requireNonNull(layout);
return new OfAddressImpl(order(), byteSize(), byteAlignment(), layout, name());
}

View file

@ -38,6 +38,7 @@ class NativeImageBuffer {
static {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction<Void>() {
@SuppressWarnings("restricted")
public Void run() {
System.loadLibrary("jimage");
return null;

View file

@ -787,17 +787,23 @@ public final class ModuleBootstrap {
}
}
private static final boolean HAS_ENABLE_NATIVE_ACCESS_FLAG;
private static final Set<String> USER_NATIVE_ACCESS_MODULES;
private static final Set<String> JDK_NATIVE_ACCESS_MODULES;
private static final IllegalNativeAccess ILLEGAL_NATIVE_ACCESS;
public static boolean hasEnableNativeAccessFlag() {
return HAS_ENABLE_NATIVE_ACCESS_FLAG;
public enum IllegalNativeAccess {
ALLOW,
WARN,
DENY
}
public static IllegalNativeAccess illegalNativeAccess() {
return ILLEGAL_NATIVE_ACCESS;
}
static {
ILLEGAL_NATIVE_ACCESS = addIllegalNativeAccess();
USER_NATIVE_ACCESS_MODULES = decodeEnableNativeAccess();
HAS_ENABLE_NATIVE_ACCESS_FLAG = !USER_NATIVE_ACCESS_MODULES.isEmpty();
JDK_NATIVE_ACCESS_MODULES = ModuleLoaderMap.nativeAccessModules();
}
@ -847,6 +853,27 @@ public final class ModuleBootstrap {
return modules;
}
/**
* Process the --illegal-native-access option (and its default).
*/
private static IllegalNativeAccess addIllegalNativeAccess() {
String value = getAndRemoveProperty("jdk.module.illegal.native.access");
// don't use a switch: bootstrapping issues!
if (value == null) {
return IllegalNativeAccess.WARN; // default
} else if (value.equals("deny")) {
return IllegalNativeAccess.DENY;
} else if (value.equals("allow")) {
return IllegalNativeAccess.ALLOW;
} else if (value.equals("warn")) {
return IllegalNativeAccess.WARN;
} else {
fail("Value specified to --illegal-native-access not recognized:"
+ " '" + value + "'");
return null;
}
}
/**
* Decodes the values of --add-reads, -add-exports, --add-opens or
* --patch-modules options that are encoded in system properties.

View file

@ -111,7 +111,7 @@ public class Reflection {
}
@ForceInline
public static void ensureNativeAccess(Class<?> currentClass, Class<?> owner, String methodName) {
public static void ensureNativeAccess(Class<?> currentClass, Class<?> owner, String methodName, boolean jni) {
// if there is no caller class, act as if the call came from unnamed module of system class loader
Module module = currentClass != null ?
currentClass.getModule() :
@ -119,7 +119,10 @@ public class Reflection {
class Holder {
static final JavaLangAccess JLA = SharedSecrets.getJavaLangAccess();
}
Holder.JLA.ensureNativeAccess(module, owner, methodName, currentClass);
if (module != null) {
// not in init phase
Holder.JLA.ensureNativeAccess(module, owner, methodName, currentClass, jni);
}
}
/**

View file

@ -65,6 +65,11 @@ java.launcher.opt.footer = \
\ --enable-native-access <module name>[,<module name>...]\n\
\ allow code in modules to access code and data outside the Java runtime.\n\
\ <module name> can also be ALL-UNNAMED to indicate code on the class path.\n\
\ --illegal-native-access=<value>\n\
\ allow or deny access to code and data outside the Java runtime\n\
\ by code in modules for which native access is not explicitly enabled.\n\
\ <value> is one of "deny", "warn" or "allow". The default value is "warn".\n\
\ This option will be removed in a future release.\n\
\ --list-modules\n\
\ list observable modules and exit\n\
\ -d <module name>\n\

View file

@ -552,15 +552,45 @@ of the release.
Native access involves access to code or data outside the Java runtime.
This is generally unsafe and, if done incorrectly, might crash the JVM
or result in memory corruption.
Methods that provide native access are restricted, and by default their
use causes warnings.
This option allows code in the specified modules to use restricted
methods without warnings.
\f[I]module\f[R] can be \f[V]ALL-UNNAMED\f[R] to indicate code on the
class path.
When this option is present, any use of restricted methods by code
outside the specified modules causes an
Native access can occur as a result of calling a method that is either
\f[B]restricted\f[R] [https://openjdk.org/jeps/454#Safety], or
\f[V]native\f[R].
This option allows code in the specified modules to perform native
access.
Native access occurring in a module that has not been explicitly enabled
is deemed \f[I]illegal\f[R].
.RS
.PP
\f[I]module\f[R] can be a module name, or \f[V]ALL-UNNAMED\f[R] to
indicate code on the class path.
.RE
.TP
-\f[V]--illegal-native-access=\f[R]\f[I]parameter\f[R]
This option specifies a mode for how illegal native access is handled:
.RS
.RS
.PP
\f[B]Note:\f[R] This option will be removed in a future release.
.RE
.IP \[bu] 2
\f[V]allow\f[R]: This mode allows illegal native access in all modules,
without any warings.
.IP \[bu] 2
\f[V]warn\f[R]: This mode is identical to \f[V]allow\f[R] except that a
warning message is issued for the first illegal native access found in a
module.
This mode is the default for the current JDK but will change in a future
release.
.IP \[bu] 2
\f[V]deny\f[R]: This mode disables illegal native access.
That is, any illegal native access causes an
\f[V]IllegalCallerException\f[R].
This mode will become the default in a future release.
.PP
To verify that your application is ready for a future version of the
JDK, run it with \f[V]--illegal-native-access=deny\f[R] along with any
necessary \f[V]--enable-native-access\f[R] options.
.RE
.TP
\f[V]--finalization=\f[R]\f[I]value\f[R]
Controls whether the JVM performs finalization of objects.