8050078: Nashorn ClassFilter Support

Reviewed-by: lagergren, hannesw
This commit is contained in:
Athijegannathan Sundararajan 2014-08-20 20:04:19 +05:30
parent fa78f33af1
commit 16bef5aa2a
12 changed files with 401 additions and 17 deletions

View File

@ -0,0 +1,41 @@
/*
* Copyright (c) 2014, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.nashorn.api.scripting;
/**
* Class filter (optional) to be used by nashorn script engine.
* jsr-223 program embedding nashorn script can set ClassFilter instance
* to be used when an engine instance is created.
*/
public interface ClassFilter {
/**
* Should the Java class of the specified name be exposed to scripts?
* @param className is the fully qualified name of the java class being
* checked. This will not be null. Only non-array class names will be
* passed.
* @return true if the java class can be exposed to scripts false otherwise
*/
public boolean exposeToScripts(String className);
}

View File

@ -93,9 +93,6 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
// This is used as "shared" global if above option is true.
private final Global global;
// default options passed to Nashorn Options object
private static final String[] DEFAULT_OPTIONS = new String[] { "-doe" };
// Nashorn script engine error message management
private static final String MESSAGES_RESOURCE = "jdk.nashorn.api.scripting.resources.Messages";
@ -113,11 +110,8 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
}
}
NashornScriptEngine(final NashornScriptEngineFactory factory, final ClassLoader appLoader) {
this(factory, DEFAULT_OPTIONS, appLoader);
}
NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args, final ClassLoader appLoader) {
NashornScriptEngine(final NashornScriptEngineFactory factory, final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) {
assert args != null : "null argument array";
this.factory = factory;
final Options options = new Options("nashorn");
options.process(args);
@ -129,7 +123,7 @@ public final class NashornScriptEngine extends AbstractScriptEngine implements C
@Override
public Context run() {
try {
return new Context(options, errMgr, appLoader);
return new Context(options, errMgr, appLoader, classFilter);
} catch (final RuntimeException e) {
if (Context.DEBUG) {
e.printStackTrace();

View File

@ -135,10 +135,13 @@ public final class NashornScriptEngineFactory implements ScriptEngineFactory {
return sb.toString();
}
// default options passed to Nashorn script engine
private static final String[] DEFAULT_OPTIONS = new String[] { "-doe" };
@Override
public ScriptEngine getScriptEngine() {
try {
return new NashornScriptEngine(this, getAppClassLoader());
return new NashornScriptEngine(this, DEFAULT_OPTIONS, getAppClassLoader(), null);
} catch (final RuntimeException e) {
if (Context.DEBUG) {
e.printStackTrace();
@ -152,10 +155,27 @@ public final class NashornScriptEngineFactory implements ScriptEngineFactory {
*
* @param appLoader class loader to be used as script "app" class loader.
* @return newly created script engine.
* @throws SecurityException
* if the security manager's {@code checkPermission}
* denies {@code RuntimePermission("nashorn.setConfig")}
*/
public ScriptEngine getScriptEngine(final ClassLoader appLoader) {
checkConfigPermission();
return new NashornScriptEngine(this, appLoader);
return newEngine(DEFAULT_OPTIONS, appLoader, null);
}
/**
* Create a new Script engine initialized by given class filter.
*
* @param classFilter class filter to use.
* @return newly created script engine.
* @throws NullPointerException if {@code classFilter} is {@code null}
* @throws SecurityException
* if the security manager's {@code checkPermission}
* denies {@code RuntimePermission("nashorn.setConfig")}
*/
public ScriptEngine getScriptEngine(final ClassFilter classFilter) {
classFilter.getClass(); // null check
return newEngine(DEFAULT_OPTIONS, getAppClassLoader(), classFilter);
}
/**
@ -163,10 +183,14 @@ public final class NashornScriptEngineFactory implements ScriptEngineFactory {
*
* @param args arguments array passed to script engine.
* @return newly created script engine.
* @throws NullPointerException if {@code args} is {@code null}
* @throws SecurityException
* if the security manager's {@code checkPermission}
* denies {@code RuntimePermission("nashorn.setConfig")}
*/
public ScriptEngine getScriptEngine(final String... args) {
checkConfigPermission();
return new NashornScriptEngine(this, args, getAppClassLoader());
args.getClass(); // null check
return newEngine(args, getAppClassLoader(), null);
}
/**
@ -175,10 +199,44 @@ public final class NashornScriptEngineFactory implements ScriptEngineFactory {
* @param args arguments array passed to script engine.
* @param appLoader class loader to be used as script "app" class loader.
* @return newly created script engine.
* @throws NullPointerException if {@code args} is {@code null}
* @throws SecurityException
* if the security manager's {@code checkPermission}
* denies {@code RuntimePermission("nashorn.setConfig")}
*/
public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader) {
args.getClass(); // null check
return newEngine(args, appLoader, null);
}
/**
* Create a new Script engine initialized by given arguments.
*
* @param args arguments array passed to script engine.
* @param appLoader class loader to be used as script "app" class loader.
* @param classFilter class filter to use.
* @return newly created script engine.
* @throws NullPointerException if {@code args} or {@code classFilter} is {@code null}
* @throws SecurityException
* if the security manager's {@code checkPermission}
* denies {@code RuntimePermission("nashorn.setConfig")}
*/
public ScriptEngine getScriptEngine(final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) {
args.getClass(); // null check
classFilter.getClass(); // null check
return newEngine(args, appLoader, classFilter);
}
private ScriptEngine newEngine(final String[] args, final ClassLoader appLoader, final ClassFilter classFilter) {
checkConfigPermission();
return new NashornScriptEngine(this, args, appLoader);
try {
return new NashornScriptEngine(this, args, appLoader, classFilter);
} catch (final RuntimeException e) {
if (Context.DEBUG) {
e.printStackTrace();
}
throw e;
}
}
// -- Internals only below this point

View File

@ -50,6 +50,7 @@ import javax.script.ScriptContext;
import javax.script.ScriptEngine;
import jdk.internal.dynalink.linker.GuardedInvocation;
import jdk.internal.dynalink.linker.LinkRequest;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.codegen.ApplySpecialization;
import jdk.nashorn.internal.codegen.CompilerConstants.Call;
@ -542,6 +543,14 @@ public final class Global extends ScriptObject implements Scope {
// Runtime interface to Global
/**
* Is there a class filter in the current Context?
* @return class filter
*/
public ClassFilter getClassFilter() {
return context.getClassFilter();
}
/**
* Is this global of the given Context?
* @param ctxt the context

View File

@ -65,6 +65,7 @@ import java.util.logging.Level;
import javax.script.ScriptEngine;
import jdk.internal.org.objectweb.asm.ClassReader;
import jdk.internal.org.objectweb.asm.util.CheckClassAdapter;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.ScriptObjectMirror;
import jdk.nashorn.internal.codegen.Compiler;
import jdk.nashorn.internal.codegen.Compiler.CompilationPhases;
@ -349,6 +350,9 @@ public final class Context {
/** Unique id for 'eval' */
private final AtomicLong uniqueEvalId;
/** Optional class filter to use for Java classes. Can be null. */
private final ClassFilter classFilter;
private static final ClassLoader myLoader = Context.class.getClassLoader();
private static final StructureLoader sharedLoader;
@ -403,7 +407,19 @@ public final class Context {
* @param appLoader application class loader
*/
public Context(final Options options, final ErrorManager errors, final ClassLoader appLoader) {
this(options, errors, new PrintWriter(System.out, true), new PrintWriter(System.err, true), appLoader);
this(options, errors, appLoader, (ClassFilter)null);
}
/**
* Constructor
*
* @param options options from command line or Context creator
* @param errors error manger
* @param appLoader application class loader
* @param classFilter class filter to use
*/
public Context(final Options options, final ErrorManager errors, final ClassLoader appLoader, final ClassFilter classFilter) {
this(options, errors, new PrintWriter(System.out, true), new PrintWriter(System.err, true), appLoader, classFilter);
}
/**
@ -416,11 +432,26 @@ public final class Context {
* @param appLoader application class loader
*/
public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader) {
this(options, errors, out, err, appLoader, (ClassFilter)null);
}
/**
* Constructor
*
* @param options options from command line or Context creator
* @param errors error manger
* @param out output writer for this Context
* @param err error writer for this Context
* @param appLoader application class loader
* @param classFilter class filter to use
*/
public Context(final Options options, final ErrorManager errors, final PrintWriter out, final PrintWriter err, final ClassLoader appLoader, final ClassFilter classFilter) {
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new RuntimePermission(NASHORN_CREATE_CONTEXT));
}
this.classFilter = classFilter;
this.env = new ScriptEnvironment(options, out, err);
this._strict = env._strict;
this.appLoader = appLoader;
@ -473,6 +504,15 @@ public final class Context {
initLoggers();
}
/**
* Get the class filter for this context
* @return class filter
*/
public ClassFilter getClassFilter() {
return classFilter;
}
/**
* Get the error manager for this context
* @return error manger
@ -890,6 +930,11 @@ public final class Context {
throw new ClassNotFoundException(fullName);
}
// give chance to ClassFilter to filter out, if present
if (classFilter != null && !classFilter.exposeToScripts(fullName)) {
throw new ClassNotFoundException(fullName);
}
// check package access as soon as possible!
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {

View File

@ -25,6 +25,8 @@
package jdk.nashorn.internal.runtime.linker;
import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
import java.lang.reflect.Modifier;
import java.lang.reflect.Proxy;
import jdk.internal.dynalink.CallSiteDescriptor;
@ -33,7 +35,9 @@ import jdk.internal.dynalink.linker.LinkRequest;
import jdk.internal.dynalink.linker.LinkerServices;
import jdk.internal.dynalink.linker.TypeBasedGuardingDynamicLinker;
import jdk.internal.dynalink.support.CallSiteDescriptorFactory;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.internal.runtime.Context;
import jdk.nashorn.internal.objects.Global;
/**
* Check java reflection permission for java reflective and java.lang.invoke access from scripts
@ -100,6 +104,12 @@ final class ReflectionCheckLinker implements TypeBasedGuardingDynamicLinker{
}
static void checkReflectionAccess(final Class<?> clazz, final boolean isStatic) {
final Global global = Context.getGlobal();
final ClassFilter cf = global.getClassFilter();
if (cf != null && isReflectiveCheckNeeded(clazz, isStatic)) {
throw typeError("no.reflection.with.classfilter");
}
final SecurityManager sm = System.getSecurityManager();
if (sm != null && isReflectiveCheckNeeded(clazz, isStatic)) {
checkReflectionPermission(sm);
@ -107,6 +117,12 @@ final class ReflectionCheckLinker implements TypeBasedGuardingDynamicLinker{
}
private static void checkLinkRequest(final LinkRequest origRequest) {
final Global global = Context.getGlobal();
final ClassFilter cf = global.getClassFilter();
if (cf != null) {
throw typeError("no.reflection.with.classfilter");
}
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
final LinkRequest requestWithoutContext = origRequest.withoutRuntimeContext(); // Nashorn has no runtime context

View File

@ -81,6 +81,7 @@ type.error.not.a.file={0} is not a File
type.error.not.a.numeric.array={0} is not a numeric array
type.error.not.a.bytebuffer={0} is not a java.nio.ByteBuffer
type.error.not.an.arraybuffer.in.dataview=First arg to DataView constructor must be an ArrayBuffer
type.error.no.reflection.with.classfilter=Java reflection not supported when class filter is present
# operations not permitted on undefined
type.error.cant.call.undefined=Cannot call undefined

View File

@ -36,6 +36,24 @@ e.eval(<<EOF
'use strict';
try {
context = 444;
print("FAILED!! context write should have thrown error");
} catch (e) {
if (! (e instanceof TypeError)) {
print("TypeError expected but got " + e);
}
}
try {
engine = "hello";
print("FAILED!! engine write should have thrown error");
} catch (e) {
if (! (e instanceof TypeError)) {
print("TypeError expected but got " + e);
}
}
try {
delete context;
print("FAILED!! context delete should have thrown error");

View File

@ -0,0 +1,72 @@
/*
* Copyright (c) 2014, 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
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* ClassFilter to filter out java classes in a script engine.
*
* @test
* @run
*/
var NashornScriptEngineFactory = Java.type("jdk.nashorn.api.scripting.NashornScriptEngineFactory");
var fac = new NashornScriptEngineFactory();
// allow only "java.*" classes to be accessed
var e = fac.getScriptEngine(
function(name) name.startsWith("java."));
function evalIt(str) {
print(str + " evalutes to " + e.eval(str));
}
function evalExpectError(str) {
try {
print(e.eval(str));
fail("expected error for: " + str);
} catch(exp) {
print(str + " throws " + exp);
}
}
evalIt("typeof javax.script.ScriptContext");
evalIt("typeof javax.script.ScriptEngine");
evalIt("typeof java.util.Vector");
evalIt("typeof java.util.Map");
evalIt("typeof java.util.HashMap");
// should be able to call methods, create objects of java.* classes
evalIt("var m = new java.util.HashMap(); m.put('foo', 42); m");
evalIt("java.lang.System.out.println");
evalIt("java.lang.System.exit");
evalExpectError("new javax.script.SimpleBindings");
evalExpectError("Java.type('javax.script.ScriptContext')");
evalExpectError("java.lang.Class.forName('javax.script.ScriptContext')");
try {
fac["getScriptEngine(ClassFilter)"](null);
fail("should have thrown NPE");
} catch (e) {
if (! (e instanceof java.lang.NullPointerException)) {
fail("NPE expected, got " + e);
}
}

View File

@ -0,0 +1,11 @@
typeof javax.script.ScriptContext evalutes to object
typeof javax.script.ScriptEngine evalutes to object
typeof java.util.Vector evalutes to function
typeof java.util.Map evalutes to function
typeof java.util.HashMap evalutes to function
var m = new java.util.HashMap(); m.put('foo', 42); m evalutes to {foo=42}
java.lang.System.out.println evalutes to [jdk.internal.dynalink.beans.OverloadedDynamicMethod java.io.PrintStream.println]
java.lang.System.exit evalutes to [jdk.internal.dynalink.beans.SimpleDynamicMethod void java.lang.System.exit(int)]
new javax.script.SimpleBindings throws java.lang.RuntimeException: java.lang.ClassNotFoundException: javax.script.SimpleBindings
Java.type('javax.script.ScriptContext') throws java.lang.RuntimeException: java.lang.ClassNotFoundException: javax.script.ScriptContext
java.lang.Class.forName('javax.script.ScriptContext') throws javax.script.ScriptException: TypeError: Java reflection not supported when class filter is present in <eval> at line number 1

View File

@ -629,6 +629,34 @@ public class ScriptEngineTest {
assertEquals(enumerable, Boolean.FALSE);
}
@Test
public void nashornConfigSecurityTest() {
final NashornScriptEngineFactory fac = new NashornScriptEngineFactory();
try {
fac.getScriptEngine(new ClassFilter() {
@Override
public boolean exposeToScripts(final String name) {
return true;
}
});
fail("SecurityException should have been thrown");
} catch (final SecurityException exp) {}
}
@Test
public void nashornConfigSecurityTest2() {
final NashornScriptEngineFactory fac = new NashornScriptEngineFactory();
try {
fac.getScriptEngine(new String[0], null, new ClassFilter() {
@Override
public boolean exposeToScripts(final String name) {
return true;
}
});
fail("SecurityException should have been thrown");
} catch (final SecurityException exp) {}
}
private static void checkProperty(final ScriptEngine e, final String name)
throws ScriptException {
final String value = System.getProperty(name);

View File

@ -35,6 +35,7 @@ import javax.script.ScriptEngineFactory;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import javax.script.SimpleScriptContext;
import jdk.nashorn.api.scripting.ClassFilter;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import org.testng.annotations.Test;
@ -220,8 +221,98 @@ public class TrustedScriptEngineTest {
assertTrue(e.eval("typeof bar").equals("function"));
}
@Test
public void classFilterTest() throws ScriptException {
final NashornScriptEngineFactory fac = new NashornScriptEngineFactory();
final ScriptEngine e = fac.getScriptEngine(new ClassFilter() {
@Override
public boolean exposeToScripts(final String fullName) {
// don't allow anything that is not "java."
return fullName.startsWith("java.");
}
});
@Test public void nashornSwallowsConstKeyword() throws Exception {
assertEquals(e.eval("typeof javax.script.ScriptEngine"), "object");
assertEquals(e.eval("typeof java.util.Vector"), "function");
try {
e.eval("Java.type('javax.script.ScriptContext')");
fail("should not reach here");
} catch (final ScriptException | RuntimeException se) {
if (! (se.getCause() instanceof ClassNotFoundException)) {
fail("ClassNotFoundException expected");
}
}
}
@Test
public void classFilterTest2() throws ScriptException {
final NashornScriptEngineFactory fac = new NashornScriptEngineFactory();
final ScriptEngine e = fac.getScriptEngine(new String[0], Thread.currentThread().getContextClassLoader(),
new ClassFilter() {
@Override
public boolean exposeToScripts(final String fullName) {
// don't allow anything that is not "java."
return fullName.startsWith("java.");
}
});
assertEquals(e.eval("typeof javax.script.ScriptEngine"), "object");
assertEquals(e.eval("typeof java.util.Vector"), "function");
try {
e.eval("Java.type('javax.script.ScriptContext')");
fail("should not reach here");
} catch (final ScriptException | RuntimeException se) {
if (! (se.getCause() instanceof ClassNotFoundException)) {
fail("ClassNotFoundException expected");
}
}
}
@Test
public void nullClassFilterTest() {
final NashornScriptEngineFactory fac = new NashornScriptEngineFactory();
try {
fac.getScriptEngine((ClassFilter)null);
fail("should have thrown NPE");
} catch (NullPointerException npe) {}
}
@Test
public void nullClassFilterTest2() {
final NashornScriptEngineFactory fac = new NashornScriptEngineFactory();
try {
fac.getScriptEngine(new String[0], null, null);
fail("should have thrown NPE");
} catch (NullPointerException npe) {}
}
@Test
public void nullArgsTest() {
final NashornScriptEngineFactory fac = new NashornScriptEngineFactory();
try {
fac.getScriptEngine((String[])null);
fail("should have thrown NPE");
} catch (NullPointerException npe) {}
}
@Test
public void nullArgsTest2() {
final NashornScriptEngineFactory fac = new NashornScriptEngineFactory();
try {
fac.getScriptEngine(null, null, new ClassFilter() {
@Override
public boolean exposeToScripts(final String name) {
return true;
}
});
fail("should have thrown NPE");
} catch (NullPointerException npe) {}
}
@Test
public void nashornSwallowsConstKeyword() throws Exception {
final NashornScriptEngineFactory f = new NashornScriptEngineFactory();
final String[] args = new String[] { "--const-as-var" };
final ScriptEngine engine = f.getScriptEngine(args);