SOLR-4882: Restrict SolrResourceLoader to only allow access to resource files below the instance dir

git-svn-id: https://svn.apache.org/repos/asf/lucene/dev/trunk@1525246 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Uwe Schindler 2013-09-21 14:51:51 +00:00
parent 243e614e52
commit da34b18cb3
7 changed files with 99 additions and 40 deletions

View File

@ -71,6 +71,17 @@ New Features
* SOLR-5167: Add support for AnalyzingInfixSuggester (AnalyzingInfixLookupFactory). * SOLR-5167: Add support for AnalyzingInfixSuggester (AnalyzingInfixLookupFactory).
(Areek Zillur, Varun Thacker via Robert Muir) (Areek Zillur, Varun Thacker via Robert Muir)
Security
----------------------
* SOLR-4882: SolrResourceLoader was restricted to only allow access to resource
files below the instance dir. The reason for this is security related: Some
Solr components allow to pass in resource paths via REST parameters
(e.g. XSL stylesheets, velocity templates,...) and load them via resource
loader. For backwards compatibility, this security feature can be disabled
by a new system property: solr.allow.unsafe.resourceloading=true
(Uwe Schindler)
Other Changes Other Changes
---------------------- ----------------------

View File

@ -67,7 +67,6 @@ public class VelocityResponseWriter implements QueryResponseWriter {
} catch (ClassCastException e) { } catch (ClassCastException e) {
// known edge case where QueryResponse's extraction assumes "response" is a SolrDocumentList // known edge case where QueryResponse's extraction assumes "response" is a SolrDocumentList
// (AnalysisRequestHandler emits a "response") // (AnalysisRequestHandler emits a "response")
e.printStackTrace();
rsp = new SolrResponseBase(); rsp = new SolrResponseBase();
rsp.setResponse(parsedResponse); rsp.setResponse(parsedResponse);
} }
@ -121,25 +120,7 @@ public class VelocityResponseWriter implements QueryResponseWriter {
SolrVelocityResourceLoader resourceLoader = SolrVelocityResourceLoader resourceLoader =
new SolrVelocityResourceLoader(request.getCore().getSolrConfig().getResourceLoader()); new SolrVelocityResourceLoader(request.getCore().getSolrConfig().getResourceLoader());
engine.setProperty("solr.resource.loader.instance", resourceLoader); engine.setProperty("solr.resource.loader.instance", resourceLoader);
File fileResourceLoaderBaseDir = null;
try {
String template_root = request.getParams().get("v.base_dir");
fileResourceLoaderBaseDir = new File(request.getCore().getResourceLoader().getConfigDir(), "velocity");
if (template_root != null) {
fileResourceLoaderBaseDir = new File(template_root);
}
} catch (SolrException e) {
// no worries... probably in ZooKeeper mode and getConfigDir() isn't available, so we'll just ignore omit
// the file system resource loader
}
if (fileResourceLoaderBaseDir != null) {
engine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, fileResourceLoaderBaseDir.getAbsolutePath());
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "params,file,solr");
} else {
engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "params,solr"); engine.setProperty(RuntimeConstants.RESOURCE_LOADER, "params,solr");
}
// TODO: Externalize Velocity properties // TODO: Externalize Velocity properties
String propFile = request.getParams().get("v.properties"); String propFile = request.getParams().get("v.properties");

View File

@ -18,6 +18,7 @@ package org.apache.solr.cloud;
*/ */
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.List; import java.util.List;
@ -75,7 +76,7 @@ public class ZkSolrResourceLoader extends SolrResourceLoader {
String file = collectionZkPath + "/" + resource; String file = collectionZkPath + "/" + resource;
try { try {
if (zkController.pathExists(file)) { if (zkController.pathExists(file)) {
byte[] bytes = zkController.getZkClient().getData(collectionZkPath + "/" + resource, null, null, true); byte[] bytes = zkController.getZkClient().getData(file, null, null, true);
return new ByteArrayInputStream(bytes); return new ByteArrayInputStream(bytes);
} }
} catch (Exception e) { } catch (Exception e) {
@ -83,7 +84,7 @@ public class ZkSolrResourceLoader extends SolrResourceLoader {
} }
try { try {
// delegate to the class loader (looking into $INSTANCE_DIR/lib jars) // delegate to the class loader (looking into $INSTANCE_DIR/lib jars)
is = classLoader.getResourceAsStream(resource); is = classLoader.getResourceAsStream(resource.replace(File.separatorChar, '/'));
} catch (Exception e) { } catch (Exception e) {
throw new IOException("Error opening " + resource, e); throw new IOException("Error opening " + resource, e);
} }

View File

@ -55,6 +55,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
@ -250,7 +251,7 @@ public class SolrResourceLoader implements ResourceLoader,Closeable
} }
public String getConfigDir() { public String getConfigDir() {
return instanceDir + "conf/"; return instanceDir + "conf" + File.separator;
} }
public String getDataDir() { public String getDataDir() {
@ -299,27 +300,46 @@ public class SolrResourceLoader implements ResourceLoader,Closeable
public InputStream openResource(String resource) throws IOException { public InputStream openResource(String resource) throws IOException {
InputStream is=null; InputStream is=null;
try { try {
File f0 = new File(resource); File f0 = new File(resource), f = f0;
File f = f0;
if (!f.isAbsolute()) { if (!f.isAbsolute()) {
// try $CWD/$configDir/$resource // try $CWD/$configDir/$resource
f = new File(getConfigDir() + resource); f = new File(getConfigDir() + resource).getAbsoluteFile();
} }
if (f.isFile() && f.canRead()) { boolean found = f.isFile() && f.canRead();
if (!found) { // no success with $CWD/$configDir/$resource
f = f0.getAbsoluteFile();
found = f.isFile() && f.canRead();
}
// check that we don't escape instance dir
if (found) {
if (!Boolean.parseBoolean(System.getProperty("solr.allow.unsafe.resourceloading", "false"))) {
final URI instanceURI = new File(getInstanceDir()).getAbsoluteFile().toURI().normalize();
final URI fileURI = f.toURI().normalize();
if (instanceURI.relativize(fileURI) == fileURI) {
// no URI relativize possible, so they don't share same base folder
throw new IOException("For security reasons, SolrResourceLoader cannot load files from outside the instance's directory: " + f +
"; if you want to override this safety feature and you are sure about the consequences, you can pass the system property "+
"-Dsolr.allow.unsafe.resourceloading=true to your JVM");
}
}
// relativize() returned a relative, new URI, so we are fine!
return new FileInputStream(f); return new FileInputStream(f);
} else if (f != f0) { // no success with $CWD/$configDir/$resource
if (f0.isFile() && f0.canRead())
return new FileInputStream(f0);
} }
// delegate to the class loader (looking into $INSTANCE_DIR/lib jars) // Delegate to the class loader (looking into $INSTANCE_DIR/lib jars).
is = classLoader.getResourceAsStream(resource); // We need a ClassLoader-compatible (forward-slashes) path here!
if (is == null) is = classLoader.getResourceAsStream(resource.replace(File.separatorChar, '/'));
is = classLoader.getResourceAsStream(getConfigDir() + resource); // This is a hack just for tests (it is not done in ZKResourceLoader)!
// -> the getConfigDir's path must not be absolute!
if (is == null && System.getProperty("jetty.testMode") != null && !new File(getConfigDir()).isAbsolute()) {
is = classLoader.getResourceAsStream((getConfigDir() + resource).replace(File.separatorChar, '/'));
}
} catch (IOException ioe) {
throw ioe;
} catch (Exception e) { } catch (Exception e) {
throw new IOException("Error opening " + resource, e); throw new IOException("Error opening " + resource, e);
} }
if (is==null) { if (is==null) {
throw new IOException("Can't find resource '" + resource + "' in classpath or '" + getConfigDir() + "', cwd="+System.getProperty("user.dir")); throw new IOException("Can't find resource '" + resource + "' in classpath or '" + new File(getConfigDir()).getAbsolutePath() + "'");
} }
return is; return is;
} }

View File

@ -19,10 +19,10 @@ package org.apache.solr.core;
import junit.framework.Assert; import junit.framework.Assert;
import org.apache.lucene.util.LuceneTestCase;
import org.apache.lucene.analysis.core.KeywordTokenizerFactory; import org.apache.lucene.analysis.core.KeywordTokenizerFactory;
import org.apache.lucene.analysis.ngram.NGramFilterFactory; import org.apache.lucene.analysis.ngram.NGramFilterFactory;
import org.apache.lucene.util._TestUtil; import org.apache.lucene.util._TestUtil;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.common.SolrException; import org.apache.solr.common.SolrException;
import org.apache.solr.handler.admin.LukeRequestHandler; import org.apache.solr.handler.admin.LukeRequestHandler;
import org.apache.solr.handler.component.FacetComponent; import org.apache.solr.handler.component.FacetComponent;
@ -33,6 +33,7 @@ import org.apache.solr.util.plugin.SolrCoreAware;
import java.io.File; import java.io.File;
import java.io.FileFilter; import java.io.FileFilter;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.CharacterCodingException; import java.nio.charset.CharacterCodingException;
import java.util.Arrays; import java.util.Arrays;
@ -41,23 +42,46 @@ import java.util.List;
import java.util.jar.JarEntry; import java.util.jar.JarEntry;
import java.util.jar.JarOutputStream; import java.util.jar.JarOutputStream;
public class ResourceLoaderTest extends LuceneTestCase public class ResourceLoaderTest extends SolrTestCaseJ4
{ {
public void testInstanceDir() throws Exception { public void testInstanceDir() throws Exception {
SolrResourceLoader loader = new SolrResourceLoader(null); SolrResourceLoader loader = new SolrResourceLoader(null);
String instDir = loader.getInstanceDir(); String instDir = loader.getInstanceDir();
assertTrue(instDir + " is not equal to " + "solr/", instDir.equals("solr/") == true); assertTrue(instDir + " is not equal to " + "solr/", instDir.equals("solr/") == true);
loader.close();
loader = new SolrResourceLoader("solr"); loader = new SolrResourceLoader("solr");
instDir = loader.getInstanceDir(); instDir = loader.getInstanceDir();
assertTrue(instDir + " is not equal to " + "solr/", instDir.equals("solr" + File.separator) == true); assertTrue(instDir + " is not equal to " + "solr/", instDir.equals("solr" + File.separator) == true);
loader.close();
} }
public void testAwareCompatibility() public void testEscapeInstanceDir() throws Exception {
File temp = _TestUtil.getTempDir("testEscapeInstanceDir");
try {
temp.mkdirs();
new File(temp, "dummy.txt").createNewFile();
File instanceDir = new File(temp, "instance");
instanceDir.mkdir();
new File(instanceDir, "conf").mkdir();
SolrResourceLoader loader = new SolrResourceLoader(instanceDir.getAbsolutePath());
try {
loader.openResource("../../dummy.txt").close();
fail();
} catch (IOException ioe) {
assertTrue(ioe.getMessage().startsWith("For security reasons, SolrResourceLoader"));
}
loader.close();
} finally {
_TestUtil.rmDir(temp);
}
}
public void testAwareCompatibility() throws Exception
{ {
SolrResourceLoader loader = new SolrResourceLoader( "." ); SolrResourceLoader loader = new SolrResourceLoader( "." );
Class clazz = ResourceLoaderAware.class; Class<?> clazz = ResourceLoaderAware.class;
// Check ResourceLoaderAware valid objects // Check ResourceLoaderAware valid objects
loader.assertAwareCompatibility( clazz, new NGramFilterFactory(new HashMap<String,String>()) ); loader.assertAwareCompatibility( clazz, new NGramFilterFactory(new HashMap<String,String>()) );
loader.assertAwareCompatibility( clazz, new KeywordTokenizerFactory(new HashMap<String,String>()) ); loader.assertAwareCompatibility( clazz, new KeywordTokenizerFactory(new HashMap<String,String>()) );
@ -97,6 +121,8 @@ public class ResourceLoaderTest extends LuceneTestCase
} }
catch( SolrException ex ) { } // OK catch( SolrException ex ) { } // OK
} }
loader.close();
} }
public void testBOMMarkers() throws Exception { public void testBOMMarkers() throws Exception {
@ -123,6 +149,8 @@ public class ResourceLoaderTest extends LuceneTestCase
List<String> lines = loader.getLines(fileWithBom); List<String> lines = loader.getLines(fileWithBom);
assertEquals(1, lines.size()); assertEquals(1, lines.size());
assertEquals("BOMsAreEvil", lines.get(0)); assertEquals("BOMsAreEvil", lines.get(0));
loader.close();
} }
public void testWrongEncoding() throws Exception { public void testWrongEncoding() throws Exception {
@ -130,11 +158,12 @@ public class ResourceLoaderTest extends LuceneTestCase
SolrResourceLoader loader = new SolrResourceLoader("solr/collection1"); SolrResourceLoader loader = new SolrResourceLoader("solr/collection1");
// ensure we get our exception // ensure we get our exception
try { try {
List<String> lines = loader.getLines(wrongEncoding); loader.getLines(wrongEncoding);
fail(); fail();
} catch (SolrException expected) { } catch (SolrException expected) {
assertTrue(expected.getCause() instanceof CharacterCodingException); assertTrue(expected.getCause() instanceof CharacterCodingException);
} }
loader.close();
} }
public void testClassLoaderLibs() throws Exception { public void testClassLoaderLibs() throws Exception {

View File

@ -43,11 +43,18 @@ public class PrimitiveFieldTypeTest extends SolrTestCaseJ4 {
System.setProperty("enable.update.log", "false"); // schema12 doesn't support _version_ System.setProperty("enable.update.log", "false"); // schema12 doesn't support _version_
System.setProperty("solr.test.sys.prop1", "propone"); System.setProperty("solr.test.sys.prop1", "propone");
System.setProperty("solr.test.sys.prop2", "proptwo"); System.setProperty("solr.test.sys.prop2", "proptwo");
System.setProperty("solr.allow.unsafe.resourceloading", "true");
initMap = new HashMap<String,String>(); initMap = new HashMap<String,String>();
config = new SolrConfig(new SolrResourceLoader("solr/collection1"), testConfHome + "solrconfig.xml", null); config = new SolrConfig(new SolrResourceLoader("solr/collection1"), testConfHome + "solrconfig.xml", null);
} }
@Override
public void tearDown() throws Exception {
System.clearProperty("solr.allow.unsafe.resourceloading");
super.tearDown();
}
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@Test @Test
public void testDefaultOmitNorms() throws Exception { public void testDefaultOmitNorms() throws Exception {

View File

@ -28,6 +28,16 @@ import org.apache.commons.io.IOUtils;
public class TestSystemIdResolver extends LuceneTestCase { public class TestSystemIdResolver extends LuceneTestCase {
public void setUp() throws Exception {
super.setUp();
System.setProperty("solr.allow.unsafe.resourceloading", "true");
}
public void tearDown() throws Exception {
System.clearProperty("solr.allow.unsafe.resourceloading");
super.tearDown();
}
private void assertEntityResolving(SystemIdResolver resolver, String expectedSystemId, String base, String systemId) throws Exception { private void assertEntityResolving(SystemIdResolver resolver, String expectedSystemId, String base, String systemId) throws Exception {
final InputSource is = resolver.resolveEntity(null, null, base, systemId); final InputSource is = resolver.resolveEntity(null, null, base, systemId);
try { try {