You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by rm...@apache.org on 2019/12/05 06:37:12 UTC

[lucene-solr] branch branch_8x updated: SOLR-13993: sandbox velocity template render (if security manager is enabled)

This is an automated email from the ASF dual-hosted git repository.

rmuir pushed a commit to branch branch_8x
in repository https://gitbox.apache.org/repos/asf/lucene-solr.git


The following commit(s) were added to refs/heads/branch_8x by this push:
     new e6728cd  SOLR-13993: sandbox velocity template render (if security manager is enabled)
e6728cd is described below

commit e6728cdf64472e91186b3cc080d2d1c5de774196
Author: Robert Muir <rm...@apache.org>
AuthorDate: Thu Dec 5 01:06:38 2019 -0500

    SOLR-13993: sandbox velocity template render (if security manager is enabled)
    
    The solr permissions are weak sauce due to the huge number of features, third-party dependencies, etc.
    
    Hence they have access to do many things. For "scripting" such as velocity we have to look at a more aggressive stance:
    
    Step 1: Can we wrap a sandbox around the whole goddamn thing and call it a day?
    Step 2: Let's separate the "engine" from "untrusted code" and only be an asshole to the latter.
    Step 3: Java's security is shit, Lets contain that classloader and whitelist access.
---
 lucene/tools/junit4/solr-tests.policy              |  2 ++
 .../solr/response/VelocityResponseWriter.java      | 41 +++++++++++++++++++++
 .../collection1/conf/velocity/outside_the_box.vm   |  4 +++
 .../conf/velocity/sandbox_intersection.vm          |  5 +++
 .../solr/velocity/VelocityResponseWriterTest.java  | 42 ++++++++++++++++++++++
 5 files changed, 94 insertions(+)

diff --git a/lucene/tools/junit4/solr-tests.policy b/lucene/tools/junit4/solr-tests.policy
index c3cd0e8..4d60f13 100644
--- a/lucene/tools/junit4/solr-tests.policy
+++ b/lucene/tools/junit4/solr-tests.policy
@@ -166,4 +166,6 @@ grant {
   // java 8 accessibility requires this perm - should not after 8 I believe (rrd4j is the root reason we hit an accessibility code path)
   permission java.awt.AWTPermission "*";
 
+  // used by solr to create sandboxes (e.g. script execution)
+  permission java.security.SecurityPermission "createAccessControlContext";
 };
diff --git a/solr/contrib/velocity/src/java/org/apache/solr/response/VelocityResponseWriter.java b/solr/contrib/velocity/src/java/org/apache/solr/response/VelocityResponseWriter.java
index b3f022a..39b0a69 100644
--- a/solr/contrib/velocity/src/java/org/apache/solr/response/VelocityResponseWriter.java
+++ b/solr/contrib/velocity/src/java/org/apache/solr/response/VelocityResponseWriter.java
@@ -17,6 +17,7 @@
 package org.apache.solr.response;
 
 import java.io.File;
+import java.io.FilePermission;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
@@ -24,11 +25,18 @@ import java.io.StringWriter;
 import java.io.Writer;
 import java.lang.invoke.MethodHandles;
 import java.nio.charset.StandardCharsets;
+import java.security.AccessControlContext;
+import java.security.AccessController;
+import java.security.Permissions;
+import java.security.PrivilegedActionException;
+import java.security.PrivilegedExceptionAction;
+import java.security.ProtectionDomain;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Properties;
+import java.util.PropertyPermission;
 import java.util.ResourceBundle;
 
 import org.apache.solr.client.solrj.SolrResponse;
@@ -147,6 +155,39 @@ public class VelocityResponseWriter implements QueryResponseWriter, SolrCoreAwar
 
   @Override
   public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
+    // run doWrite() with the velocity sandbox
+    try {
+      AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
+        @Override
+        public Void run() throws IOException {
+          doWrite(writer, request, response);
+          return null;
+        }
+      }, VELOCITY_SANDBOX);
+    } catch (PrivilegedActionException e) {
+      throw (IOException) e.getException();
+    }
+  }
+
+  // sandbox for velocity code
+  // TODO: we could read in a policy file instead, in case someone needs to tweak it?
+  private static final AccessControlContext VELOCITY_SANDBOX;
+  static {
+    Permissions permissions = new Permissions();
+    // TODO: restrict the scope of this! we probably only need access to classpath
+    permissions.add(new FilePermission("<<ALL FILES>>", "read,readlink"));
+    // properties needed by SolrResourceLoader (called from velocity code)
+    permissions.add(new PropertyPermission("jetty.testMode", "read"));
+    permissions.add(new PropertyPermission("solr.allow.unsafe.resourceloading", "read"));
+    // properties needed by log4j (called from velocity code)
+    permissions.add(new PropertyPermission("java.version", "read"));
+    // needed by velocity duck-typing
+    permissions.add(new RuntimePermission("accessDeclaredMembers"));
+    permissions.setReadOnly();
+    VELOCITY_SANDBOX = new AccessControlContext(new ProtectionDomain[] { new ProtectionDomain(null, permissions) });
+  }
+
+  private void doWrite(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
     VelocityEngine engine = createEngine(request);  // TODO: have HTTP headers available for configuring engine
 
     Template template = getTemplate(engine, request);
diff --git a/solr/contrib/velocity/src/test-files/velocity/solr/collection1/conf/velocity/outside_the_box.vm b/solr/contrib/velocity/src/test-files/velocity/solr/collection1/conf/velocity/outside_the_box.vm
new file mode 100644
index 0000000..2842823
--- /dev/null
+++ b/solr/contrib/velocity/src/test-files/velocity/solr/collection1/conf/velocity/outside_the_box.vm
@@ -0,0 +1,4 @@
+#set($x='')
+#set($sys=$x.class.forName('java.lang.System'))
+#set($ex=$sys.getProperty('os.name'))
+$ex
diff --git a/solr/contrib/velocity/src/test-files/velocity/solr/collection1/conf/velocity/sandbox_intersection.vm b/solr/contrib/velocity/src/test-files/velocity/solr/collection1/conf/velocity/sandbox_intersection.vm
new file mode 100644
index 0000000..75d97a18
--- /dev/null
+++ b/solr/contrib/velocity/src/test-files/velocity/solr/collection1/conf/velocity/sandbox_intersection.vm
@@ -0,0 +1,5 @@
+#set($x='')
+#set($sys=$x.class.forName('java.nio.file.Paths'))
+#set($path=$sys.get('/dumbass/denied_location'))
+#set($ex=$path.resolve($path).toRealPath())
+$ex
diff --git a/solr/contrib/velocity/src/test/org/apache/solr/velocity/VelocityResponseWriterTest.java b/solr/contrib/velocity/src/test/org/apache/solr/velocity/VelocityResponseWriterTest.java
index 33f1b02..c139379 100644
--- a/solr/contrib/velocity/src/test/org/apache/solr/velocity/VelocityResponseWriterTest.java
+++ b/solr/contrib/velocity/src/test/org/apache/solr/velocity/VelocityResponseWriterTest.java
@@ -18,6 +18,7 @@ package org.apache.solr.velocity;
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.security.AccessControlException;
 
 import org.apache.solr.SolrTestCaseJ4;
 import org.apache.solr.common.SolrException;
@@ -27,6 +28,7 @@ import org.apache.solr.response.QueryResponseWriter;
 import org.apache.solr.response.SolrParamResourceLoader;
 import org.apache.solr.response.SolrQueryResponse;
 import org.apache.solr.response.VelocityResponseWriter;
+import org.apache.velocity.exception.MethodInvocationException;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -53,6 +55,46 @@ public class VelocityResponseWriterTest extends SolrTestCaseJ4 {
   }
 
   @Test
+  public void testTemplateSandbox() throws Exception {
+    assumeTrue("This test only works with security manager", System.getSecurityManager() != null);
+    VelocityResponseWriter vrw = new VelocityResponseWriter();
+    NamedList<String> nl = new NamedList<>();
+    nl.add("template.base.dir", getFile("velocity").getAbsolutePath());
+    vrw.init(nl);
+    SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"outside_the_box");
+    SolrQueryResponse rsp = new SolrQueryResponse();
+    StringWriter buf = new StringWriter();
+    try {
+      vrw.write(buf, req, rsp);
+      fail("template broke outside the box, retrieved OS: " + buf);
+    } catch (MethodInvocationException e) {
+      assertNotNull(e.getCause());
+      assertEquals(AccessControlException.class, e.getCause().getClass());
+      // expected failure, can't get outside the box
+    }
+  }
+
+  @Test
+  public void testSandboxIntersection() throws Exception {
+    assumeTrue("This test only works with security manager", System.getSecurityManager() != null);
+    VelocityResponseWriter vrw = new VelocityResponseWriter();
+    NamedList<String> nl = new NamedList<>();
+    nl.add("template.base.dir", getFile("velocity").getAbsolutePath());
+    vrw.init(nl);
+    SolrQueryRequest req = req(VelocityResponseWriter.TEMPLATE,"sandbox_intersection");
+    SolrQueryResponse rsp = new SolrQueryResponse();
+    StringWriter buf = new StringWriter();
+    try {
+      vrw.write(buf, req, rsp);
+      fail("template broke outside the box, retrieved OS: " + buf);
+    } catch (MethodInvocationException e) {
+      assertNotNull(e.getCause());
+      assertEquals(AccessControlException.class, e.getCause().getClass());
+      // expected failure, can't get outside the box
+    }
+  }
+
+  @Test
   public void testCustomParamTemplate() throws Exception {
     org.apache.solr.response.VelocityResponseWriter vrw = new VelocityResponseWriter();
     NamedList<String> nl = new NamedList<>();