SOLR-14110: sandbox javax.script usage in tests

This commit is contained in:
Robert Muir 2019-12-18 06:30:24 -05:00
parent 71a5714e29
commit 612cba38ca
6 changed files with 121 additions and 4 deletions

View File

@ -19,6 +19,12 @@ package org.apache.solr.handler.dataimport;
import static org.apache.solr.handler.dataimport.DataImportHandlerException.wrapAndThrow; import static org.apache.solr.handler.dataimport.DataImportHandlerException.wrapAndThrow;
import static org.apache.solr.handler.dataimport.DataImportHandlerException.SEVERE; import static org.apache.solr.handler.dataimport.DataImportHandlerException.SEVERE;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.Map; import java.util.Map;
import javax.script.Invocable; import javax.script.Invocable;
@ -46,7 +52,16 @@ public class ScriptTransformer extends Transformer {
private String functionName; private String functionName;
@Override @Override
public Object transformRow(Map<String, Object> row, Context context) { public Object transformRow(Map<String,Object> row, Context context) {
return AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
return transformRowUnsafe(row, context);
}
}, SCRIPT_SANDBOX);
}
public Object transformRowUnsafe(Map<String, Object> row, Context context) {
try { try {
if (engine == null) if (engine == null)
initEngine(context); initEngine(context);
@ -84,7 +99,17 @@ public class ScriptTransformer extends Transformer {
+ scriptEngine.getClass().getName()); + scriptEngine.getClass().getName());
} }
try { try {
scriptEngine.eval(scriptText); try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws ScriptException {
scriptEngine.eval(scriptText);
return null;
}
}, SCRIPT_SANDBOX);
} catch (PrivilegedActionException e) {
throw (ScriptException) e.getException();
}
} catch (ScriptException e) { } catch (ScriptException e) {
wrapAndThrow(SEVERE, e, "'eval' failed with language: " + scriptLang wrapAndThrow(SEVERE, e, "'eval' failed with language: " + scriptLang
+ " and script: \n" + scriptText); + " and script: \n" + scriptText);
@ -99,4 +124,8 @@ public class ScriptTransformer extends Transformer {
return functionName; return functionName;
} }
// sandbox for script code: zero permissions
private static final AccessControlContext SCRIPT_SANDBOX =
new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, null) });
} }

View File

@ -56,6 +56,28 @@ public class TestScriptTransformer extends AbstractDataImportHandlerTestCase {
} }
} }
@Test
public void testEvil() {
assumeTrue("This test only works with security manager", System.getSecurityManager() != null);
String script = "function f1(row) {"
+ "var os = Packages.java.lang.System.getProperty('os.name');"
+ "row.put('name', os);"
+ "return row;\n"
+ "}";
Context context = getContext("f1", script);
Map<String, Object> map = new HashMap<>();
map.put("name", "Scott");
EntityProcessorWrapper sep = new EntityProcessorWrapper(new SqlEntityProcessor(), null, null);
sep.init(context);
DataImportHandlerException expected = expectThrows(DataImportHandlerException.class, () -> {
sep.applyTransformer(map);
});
assumeFalse("This JVM does not have JavaScript installed. Test Skipped.",
expected.getMessage().startsWith("Cannot load Script Engine for language"));
assertTrue(expected.getCause().toString(), SecurityException.class.isAssignableFrom(expected.getCause().getClass()));
}
private Context getContext(String funcName, String script) { private Context getContext(String funcName, String script) {
List<Map<String, String>> fields = new ArrayList<>(); List<Map<String, String>> fields = new ArrayList<>();
Map<String, String> entity = new HashMap<>(); Map<String, String> entity = new HashMap<>();

View File

@ -41,6 +41,12 @@ import java.io.InputStream;
import java.io.Reader; import java.io.Reader;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.security.ProtectionDomain;
import java.util.Set; import java.util.Set;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.ArrayList; import java.util.ArrayList;
@ -269,7 +275,7 @@ public class StatelessScriptUpdateProcessorFactory extends UpdateRequestProcesso
} }
for (ScriptFile scriptFile : scriptFiles) { for (ScriptFile scriptFile : scriptFiles) {
ScriptEngine engine = null; final ScriptEngine engine;
if (null != engineName) { if (null != engineName) {
engine = scriptEngineManager.getEngineByName(engineName); engine = scriptEngineManager.getEngineByName(engineName);
if (engine == null) { if (engine == null) {
@ -314,7 +320,17 @@ public class StatelessScriptUpdateProcessorFactory extends UpdateRequestProcesso
Reader scriptSrc = scriptFile.openReader(resourceLoader); Reader scriptSrc = scriptFile.openReader(resourceLoader);
try { try {
engine.eval(scriptSrc); try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws ScriptException {
engine.eval(scriptSrc);
return null;
}
}, SCRIPT_SANDBOX);
} catch (PrivilegedActionException e) {
throw (ScriptException) e.getException();
}
} catch (ScriptException e) { } catch (ScriptException e) {
throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
"Unable to evaluate script: " + "Unable to evaluate script: " +
@ -424,6 +440,15 @@ public class StatelessScriptUpdateProcessorFactory extends UpdateRequestProcesso
* cast to a java Boolean. * cast to a java Boolean.
*/ */
private boolean invokeFunction(String name, Object... cmd) { private boolean invokeFunction(String name, Object... cmd) {
return AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
return invokeFunctionUnsafe(name, cmd);
}
}, SCRIPT_SANDBOX);
}
private boolean invokeFunctionUnsafe(String name, Object... cmd) {
for (EngineInfo engine : engines) { for (EngineInfo engine : engines) {
try { try {
@ -496,4 +521,8 @@ public class StatelessScriptUpdateProcessorFactory extends UpdateRequestProcesso
(input, StandardCharsets.UTF_8); (input, StandardCharsets.UTF_8);
} }
} }
// sandbox for script code: zero permissions
private static final AccessControlContext SCRIPT_SANDBOX =
new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, null) });
} }

View File

@ -0,0 +1,21 @@
var sys = Packages.java.lang.System;
function processAdd(cmd) {
sys.getProperty("os.name");
}
function processDelete(cmd) {
}
function processMergeIndexes(cmd) {
}
function processCommit(cmd) {
}
function processRollback(cmd) {
}
function finish() {
}

View File

@ -117,4 +117,10 @@
</processor> </processor>
</updateRequestProcessorChain> </updateRequestProcessorChain>
<updateRequestProcessorChain name="evil">
<processor class="solr.StatelessScriptUpdateProcessorFactory">
<str name="script">evil.js</str>
</processor>
</updateRequestProcessorChain>
</config> </config>

View File

@ -259,4 +259,14 @@ public class StatelessScriptUpdateProcessorFactoryTest extends UpdateProcessorTe
} }
public void testScriptSandbox() throws Exception {
assumeTrue("This test only works with security manager", System.getSecurityManager() != null);
expectThrows(SecurityException.class, () -> {
processAdd("evil",
doc(f("id", "5"),
f("name", " foo "),
f("subject", "BAR")));
});
}
} }