Skip to content

Commit

Permalink
No longer use SecureClassLoader, CodeSource, Permission, and Protecti…
Browse files Browse the repository at this point in the history
…onDomain
  • Loading branch information
szegedi committed Jan 5, 2025
1 parent d87cba6 commit 27a15ad
Show file tree
Hide file tree
Showing 9 changed files with 68 additions and 184 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@
import static org.openjdk.nashorn.internal.runtime.ECMAErrors.typeError;
import static org.openjdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;

import java.lang.invoke.MethodHandles;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
Expand Down Expand Up @@ -592,8 +591,8 @@ private static Class<?> arrayType(final String typeName) throws ClassNotFoundExc
* </pre>
* We can see several important concepts in the above example:
* <ul>
* <li>Every specified list of Java types will have one extender subclass in Nashorn per caller protection domain -
* repeated invocations of {@code extend} for the same list of types for scripts same protection domain will yield
* <li>Every specified list of Java types will have one extender subclass in Nashorn -
* repeated invocations of {@code extend} for the same list of types will yield
* the same extender type. It's a generic adapter that delegates to whatever JavaScript functions its implementation
* object has on a per-instance basis.</li>
* <li>If the Java method is overloaded (as in the above example {@code List.add()}), then your JavaScript adapter
Expand Down Expand Up @@ -675,18 +674,7 @@ public static Object extend(final Object self, final Object... types) {
} catch(final ClassCastException e) {
throw typeError("extend.expects.java.types");
}
// Note that while the public API documentation claims self is not used, we actually use it.
// ScriptFunction.findCallMethod will bind the lookup object into it, and we can then use that lookup when
// requesting the adapter class. Note that if Java.extend is invoked with no lookup object, it'll pass the
// public lookup which'll result in generation of a no-permissions adapter. A typical situation this can happen
// is when the extend function is bound.
final MethodHandles.Lookup lookup;
if(self instanceof MethodHandles.Lookup) {
lookup = (MethodHandles.Lookup)self;
} else {
lookup = MethodHandles.publicLookup();
}
return JavaAdapterFactory.getAdapterClassFor(stypes, classOverrides, lookup);
return JavaAdapterFactory.getAdapterClassFor(stypes, classOverrides);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,6 @@
import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.CodeSigner;
import java.security.CodeSource;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
Expand Down Expand Up @@ -162,11 +160,9 @@ static long getAnonymousInstalledScriptCount() {
*/
private abstract static class ContextCodeInstaller implements CodeInstaller {
final Context context;
final CodeSource codeSource;

ContextCodeInstaller(final Context context, final CodeSource codeSource) {
ContextCodeInstaller(final Context context) {
this.context = context;
this.codeSource = codeSource;
}

@Override
Expand Down Expand Up @@ -223,7 +219,7 @@ public StoredScript loadScript(final Source source, final String functionKey) {
public boolean isCompatibleWith(final CodeInstaller other) {
if (other instanceof ContextCodeInstaller) {
final ContextCodeInstaller cci = (ContextCodeInstaller)other;
return cci.context == context && cci.codeSource == codeSource;
return cci.context == context;
}
return false;
}
Expand All @@ -239,8 +235,8 @@ private static class NamedContextCodeInstaller extends ContextCodeInstaller {
private final static int MAX_USAGES = 10;
private final static int MAX_BYTES_DEFINED = 200_000;

private NamedContextCodeInstaller(final Context context, final CodeSource codeSource, final ScriptLoader loader) {
super(context, codeSource);
private NamedContextCodeInstaller(final Context context, final ScriptLoader loader) {
super(context);
this.loader = loader;
}

Expand All @@ -249,7 +245,7 @@ public Class<?> install(final String className, final byte[] bytecode) {
usageCount++;
bytesDefined += bytecode.length;
NAMED_INSTALLED_SCRIPT_COUNT.increment();
return loader.installClass(Compiler.binaryName(className), bytecode, codeSource);
return loader.installClass(Compiler.binaryName(className), bytecode);
}

@Override
Expand All @@ -258,7 +254,7 @@ public CodeInstaller getOnDemandCompilationInstaller() {
if (usageCount < MAX_USAGES && bytesDefined < MAX_BYTES_DEFINED) {
return this;
}
return new NamedContextCodeInstaller(context, codeSource, context.createNewLoader());
return new NamedContextCodeInstaller(context, context.createNewLoader());
}

@Override
Expand All @@ -269,7 +265,7 @@ public CodeInstaller getMultiClassCodeInstaller() {
}
}

private final WeakValueCache<CodeSource, Class<?>> anonymousHostClasses = new WeakValueCache<>();
private final WeakValueCache<URL, Class<?>> anonymousHostClasses = new WeakValueCache<>();

private static final class AnonymousContextCodeInstaller extends ContextCodeInstaller {
private static final MethodHandle DEFINE_ANONYMOUS_CLASS = getDefineAnonymousClass();
Expand All @@ -292,8 +288,8 @@ private static MethodHandle getDefineAnonymousClass() {
}
}

private AnonymousContextCodeInstaller(final Context context, final CodeSource codeSource, final Class<?> hostClass) {
super(context, codeSource);
private AnonymousContextCodeInstaller(final Context context, final Class<?> hostClass) {
super(context);
this.hostClass = hostClass;
}

Expand All @@ -320,7 +316,7 @@ public CodeInstaller getMultiClassCodeInstaller() {
// This code loader can not be used to install multiple classes that reference each other, as they
// would have no resolvable names. Therefore, in such situation we must revert to an installer that
// produces named classes.
return new NamedContextCodeInstaller(context, codeSource, context.createNewLoader());
return new NamedContextCodeInstaller(context, context.createNewLoader());
}

private static byte[] getAnonymousHostClassBytes() {
Expand Down Expand Up @@ -1335,23 +1331,22 @@ private synchronized Class<?> compile(final Source source, final ErrorManager er
return null;
}

final URL url = source.getURL();
final CodeSource cs = new CodeSource(url, (CodeSigner[])null);
final URL url = source.getURL();
final CodeInstaller installer;
if (env._persistent_cache || !env._lazy_compilation || !env.useAnonymousClasses(source.getLength(), () -> AnonymousContextCodeInstaller.initFailure) ) {
// Persistent code cache, eager compilation, or inability to use Unsafe.defineAnonymousClass (typically, JDK 17+)
// preclude use of VM anonymous classes
final ScriptLoader loader = env._loader_per_compile ? createNewLoader() : scriptLoader;
installer = new NamedContextCodeInstaller(this, cs, loader);
installer = new NamedContextCodeInstaller(this, loader);
} else {
installer = new AnonymousContextCodeInstaller(this, cs,
anonymousHostClasses.getOrCreate(cs, (key) ->
installer = new AnonymousContextCodeInstaller(this,
anonymousHostClasses.getOrCreate(url, key ->
createNewLoader().installClass(
// NOTE: we're defining these constants in AnonymousContextCodeInstaller so they are not
// initialized if we don't use AnonymousContextCodeInstaller. As this method is only ever
// invoked from AnonymousContextCodeInstaller, this is okay.
AnonymousContextCodeInstaller.ANONYMOUS_HOST_CLASS_NAME,
AnonymousContextCodeInstaller.ANONYMOUS_HOST_CLASS_BYTES, cs)));
AnonymousContextCodeInstaller.ANONYMOUS_HOST_CLASS_BYTES)));
}

if (storedScript == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,47 +34,20 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.security.CodeSource;
import java.security.Permission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.SecureClassLoader;
import java.util.Arrays;

/**
* Superclass for Nashorn class loader classes.
*/
abstract class NashornLoader extends SecureClassLoader {
protected static final String OBJECTS_PKG = "org.openjdk.nashorn.internal.objects";
protected static final String RUNTIME_PKG = "org.openjdk.nashorn.internal.runtime";
protected static final String RUNTIME_ARRAYS_PKG = "org.openjdk.nashorn.internal.runtime.arrays";
protected static final String RUNTIME_LINKER_PKG = "org.openjdk.nashorn.internal.runtime.linker";
protected static final String SCRIPTS_PKG = "org.openjdk.nashorn.internal.scripts";
abstract class NashornLoader extends ClassLoader {
protected static final String RUNTIME_PKG = "org.openjdk.nashorn.internal.runtime";
protected static final String SCRIPTS_PKG = "org.openjdk.nashorn.internal.scripts";

static final Module NASHORN_MODULE = Context.class.getModule();

private static final Permission[] SCRIPT_PERMISSIONS;

private static final String MODULE_MANIPULATOR_NAME = SCRIPTS_PKG + ".ModuleGraphManipulator";
private static final byte[] MODULE_MANIPULATOR_BYTES = readModuleManipulatorBytes();

static {
/*
* Generated classes get access to runtime, runtime.linker, objects, scripts packages.
* Note that the actual scripts can not access these because Java.type, Packages
* prevent these restricted packages. And Java reflection and JSR292 access is prevented
* for scripts. In other words, nashorn generated portions of script classes can access
* classes in these implementation packages.
*/
SCRIPT_PERMISSIONS = new Permission[] {
new RuntimePermission("accessClassInPackage." + RUNTIME_PKG),
new RuntimePermission("accessClassInPackage." + RUNTIME_LINKER_PKG),
new RuntimePermission("accessClassInPackage." + OBJECTS_PKG),
new RuntimePermission("accessClassInPackage." + SCRIPTS_PKG),
new RuntimePermission("accessClassInPackage." + RUNTIME_ARRAYS_PKG)
};
}

// addExport Method object on ModuleGraphManipulator
// class loaded by this loader
private Method addModuleExport;
Expand Down Expand Up @@ -116,15 +89,6 @@ static boolean isInNamedModule() {
return NASHORN_MODULE.isNamed();
}

@Override
protected PermissionCollection getPermissions(final CodeSource codesource) {
final Permissions permCollection = new Permissions();
for (final Permission perm : SCRIPT_PERMISSIONS) {
permCollection.add(perm);
}
return permCollection;
}

/**
* Create a secure URL class loader for the given classpath
* @param classPath classpath for the loader to search from
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@

import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Modifier;
import java.security.CodeSource;
import java.util.Objects;
import java.util.Set;

/**
* Responsible for loading script generated classes.
*
*/
final class ScriptLoader extends NashornLoader {
private static final String OBJECTS_PKG = "org.openjdk.nashorn.internal.objects";
private static final String RUNTIME_ARRAYS_PKG = "org.openjdk.nashorn.internal.runtime.arrays";
private static final String RUNTIME_LINKER_PKG = "org.openjdk.nashorn.internal.runtime.linker";
private static final String NASHORN_PKG_PREFIX = "org.openjdk.nashorn.internal.";

private volatile boolean structureAccessAdded;
Expand Down Expand Up @@ -142,11 +143,10 @@ protected Class<?> findClass(final String name) throws ClassNotFoundException {
*
* @param name Binary name of class.
* @param data Class data bytes.
* @param cs CodeSource code source of the class bytes.
*
* @return Installed class.
*/
synchronized Class<?> installClass(final String name, final byte[] data, final CodeSource cs) {
return defineClass(name, data, 0, data.length, Objects.requireNonNull(cs));
synchronized Class<?> installClass(final String name, final byte[] data) {
return defineClass(name, data, 0, data.length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@

import java.lang.module.ModuleDescriptor;
import java.lang.module.ModuleDescriptor.Modifier;
import java.security.ProtectionDomain;
import java.util.Set;
import org.openjdk.nashorn.internal.codegen.ObjectClassGenerator;

Expand Down Expand Up @@ -129,6 +128,6 @@ private Class<?> generateClass(final String name, final String descriptor, final
final Context context = Context.getContext();

final byte[] code = new ObjectClassGenerator(context, dualFields).generate(descriptor);
return defineClass(name, code, 0, code.length, new ProtectionDomain(null, getPermissions(null)));
return defineClass(name, code, 0, code.length);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,6 @@
import java.lang.reflect.Executable;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.ProtectionDomain;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
Expand Down Expand Up @@ -116,15 +115,17 @@
* to resemble Java anonymous classes) is actually equivalent to <code>new X(a, b, { ... })</code>.
* </p><p>
* It is possible to create two different adapter classes: those that can have class-level overrides, and those that can
* have instance-level overrides. When {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject, ProtectionDomain)}
* or {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject, Lookup)} is invoked
* have instance-level overrides. When {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject)}
* or {@link JavaAdapterFactory#getAdapterClassFor(Class[], ScriptObject)} is invoked
* with non-null {@code classOverrides} parameter, an adapter class is created that can have class-level overrides, and
* the passed script object will be used as the implementations for its methods, just as in the above case of the
* constructor taking a script object. Note that in the case of class-level overrides, a new adapter class is created on
* every invocation, and the implementation object is bound to the class, not to any instance. All created instances
* will share these functions. If it is required to have both class-level overrides and instance-level overrides, the
* class-level override adapter class should be subclassed with an instance-override adapter. Since adapters delegate to
* super class when an overriding method handle is not specified, this will behave as expected. It is not possible to
* super class when an overriding method handle is not specified, this will behave as expected.
* TODO: see if below described limitation could be lifted now that java.security.ProtectionDomain is no longer used.
* It is not possible to
* have both class-level and instance-level overrides in the same class for security reasons: adapter classes are
* defined with a protection domain of their creator code, and an adapter class that has both class and instance level
* overrides would need to have two potentially different protection domains: one for class-based behavior and one for
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@

package org.openjdk.nashorn.internal.runtime.linker;

import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
Expand Down Expand Up @@ -62,27 +60,23 @@ final class JavaAdapterClassLoader {
/**
* Loads the generated adapter class into the JVM.
* @param parentLoader the parent class loader for the generated class loader
* @param protectionDomain the protection domain for the generated class
* @return the generated adapter class
*/
StaticClass generateClass(final ClassLoader parentLoader, final ProtectionDomain protectionDomain) {
assert protectionDomain != null;
StaticClass generateClass(final ClassLoader parentLoader) {
try {
return StaticClass.forClass(Class.forName(className, true, createClassLoader(parentLoader, protectionDomain)));
return StaticClass.forClass(Class.forName(className, true, createClassLoader(parentLoader)));
} catch (final ClassNotFoundException e) {
throw new AssertionError(e); // cannot happen
}
}

// Note that the adapter class is created in the protection domain of the class/interface being
// extended/implemented, and only the privileged global setter action class is generated in the protection domain
// of Nashorn itself. Also note that the creation and loading of the global setter is deferred until it is
// required by JVM linker, which will only happen on first invocation of any of the adapted method. We could defer
// Note that the creation and loading of the global setter is deferred until it is
// required by JVM linker, which will only happen on first invocation of any adapted method. We could defer
// it even more by separating its invocation into a separate static method on the adapter class, but then someone
// with ability to introspect on the class and use setAccessible(true) on it could invoke the method. It's a
// security tradeoff...
private ClassLoader createClassLoader(final ClassLoader parentLoader, final ProtectionDomain protectionDomain) {
return new SecureClassLoader(parentLoader) {
private ClassLoader createClassLoader(final ClassLoader parentLoader) {
return new ClassLoader(parentLoader) {
private final ClassLoader myLoader = getClass().getClassLoader();

// the unnamed module into which adapter is loaded!
Expand Down Expand Up @@ -120,7 +114,7 @@ protected Class<?> findClass(final String name) throws ClassNotFoundException {

final Context ctx = Context.getContext();
DumpBytecode.dumpBytecode(ctx.getEnv(), ctx.getLogger(org.openjdk.nashorn.internal.codegen.Compiler.class), classBytes, name);
return defineClass(name, classBytes, 0, classBytes.length, protectionDomain);
return defineClass(name, classBytes, 0, classBytes.length);
}
throw new ClassNotFoundException(name);
}
Expand Down
Loading

0 comments on commit 27a15ad

Please sign in to comment.