Add SpecialPermission to guard exceptions to security policy.

Closes #13854

Squashed commit of the following:

commit 42c1166efc55adda0d13fed77de583c0973e44b3
Author: Robert Muir <rmuir@apache.org>
Date:   Tue Sep 29 11:59:43 2015 -0400

    Add paranoia

    Groovy holds on to a classloader, so check it before compilation too.
    I have not reviewed yet what Rhino is doing, but just be safe.

commit b58668a81428e964dd5ffa712872c0a34897fc91
Author: Robert Muir <rmuir@apache.org>
Date:   Tue Sep 29 11:46:06 2015 -0400

    Add SpecialPermission to guard exceptions to security policy.

    In some cases (e.g. buggy cloud libraries, scripting engines), we must
    grant dangerous permissions to contained cases. Those AccessController blocks
    are dangerous, since they truncate the stack, and can allow privilege escalation.

    This PR adds a simple permission to check before each one, so that unprivileged code
    like groovy scripts, can't do anything they shouldn't be allowed to do otherwise.
This commit is contained in:
Robert Muir 2015-09-29 17:32:56 -04:00
parent ad6bc5b94c
commit 6d8c035f70
10 changed files with 179 additions and 4 deletions

View File

@ -0,0 +1,82 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch;
import java.security.BasicPermission;
/**
* Elasticsearch-specific permission to check before entering
* {@code AccessController.doPrivileged()} blocks.
* <p>
* We try to avoid these blocks in our code and keep security simple,
* but we need them for a few special places to contain hacks for third
* party code, or dangerous things used by scripting engines.
* <p>
* All normal code has this permission, but checking this before truncating the stack
* prevents unprivileged code (e.g. scripts), which do not have it, from gaining elevated
* privileges.
* <p>
* In other words, don't do this:
* <br>
* <pre><code>
* // throw away all information about caller and run with our own privs
* AccessController.doPrivileged(
* ...
* );
* </code></pre>
* <br>
* Instead do this;
* <br>
* <pre><code>
* // check caller first, to see if they should be allowed to do this
* SecurityManager sm = System.getSecurityManager();
* if (sm != null) {
* sm.checkPermission(new SpecialPermission());
* }
* // throw away all information about caller and run with our own privs
* AccessController.doPrivileged(
* ...
* );
* </code></pre>
*/
public final class SpecialPermission extends BasicPermission {
private static final long serialVersionUID = -4129500096157408168L;
/**
* Creates a new SpecialPermision object.
*/
public SpecialPermission() {
// TODO: if we really need we can break out name (e.g. "hack" or "scriptEngineService" or whatever).
// but let's just keep it simple if we can.
super("*");
}
/**
* Creates a new SpecialPermission object.
* This constructor exists for use by the {@code Policy} object to instantiate new Permission objects.
*
* @param name ignored
* @param actions ignored
*/
public SpecialPermission(String name, String actions) {
this();
}
}

View File

@ -69,6 +69,8 @@ grant codeBase "${es.security.plugin.lang-groovy}" {
permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
// needed by GroovyScriptEngineService to close its classloader (why?)
permission java.lang.RuntimePermission "closeClassLoader";
// Allow executing groovy scripts with codesource of /groovy/script
permission groovy.security.GroovyCodeSourcePermission "/groovy/script";
};
grant codeBase "${es.security.plugin.lang-javascript}" {
@ -119,10 +121,9 @@ grant codeBase "${es.security.jar.randomizedtesting.junit4}" {
grant {
// Allow executing groovy scripts with codesource of /groovy/script
// TODO: make our own general ScriptServicePermission we check instead and
// check-before-createClassLoader for all scripting engines.
permission groovy.security.GroovyCodeSourcePermission "/groovy/script";
// checked by scripting engines, and before hacks and other issues in
// third party code, to safeguard these against unprivileged code like scripts.
permission org.elasticsearch.SpecialPermission;
// Allow connecting to the internet anywhere
permission java.net.SocketPermission "*", "accept,listen,connect,resolve";

View File

@ -0,0 +1,38 @@
/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch;
import org.elasticsearch.test.ESTestCase;
import java.security.AllPermission;
/** Very simple sanity checks for {@link SpecialPermission} */
public class SpecialPermissionTests extends ESTestCase {
public void testEquals() {
assertEquals(new SpecialPermission(), new SpecialPermission());
assertFalse(new SpecialPermission().equals(new AllPermission()));
}
public void testImplies() {
assertTrue(new SpecialPermission().implies(new SpecialPermission()));
assertFalse(new SpecialPermission().implies(new AllPermission()));
}
}

View File

@ -28,6 +28,7 @@ import com.google.api.services.compute.Compute;
import com.google.api.services.compute.model.Instance;
import com.google.api.services.compute.model.InstanceList;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.common.component.AbstractLifecycleComponent;
import org.elasticsearch.common.inject.Inject;
@ -62,6 +63,10 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent<GceCompute
try {
// hack around code messiness in GCE code
// TODO: get this fixed
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
InstanceList instanceList = AccessController.doPrivileged(new PrivilegedExceptionAction<InstanceList>() {
@Override
public InstanceList run() throws Exception {
@ -135,6 +140,10 @@ public class GceComputeServiceImpl extends AbstractLifecycleComponent<GceCompute
// hack around code messiness in GCE code
// TODO: get this fixed
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws IOException {

View File

@ -19,6 +19,7 @@
package org.elasticsearch.plugin.discovery.ec2;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.cloud.aws.AwsEc2ServiceImpl;
import org.elasticsearch.cloud.aws.Ec2Module;
import org.elasticsearch.common.component.LifecycleComponent;
@ -42,6 +43,10 @@ public class Ec2DiscoveryPlugin extends Plugin {
// This internal config is deserialized but with wrong access modifiers,
// cannot work without suppressAccessChecks permission right now. We force
// a one time load with elevated privileges as a workaround.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {

View File

@ -26,6 +26,7 @@ import org.apache.lucene.expressions.js.VariableContext;
import org.apache.lucene.queries.function.ValueSource;
import org.apache.lucene.queries.function.valuesource.DoubleConstValueSource;
import org.apache.lucene.search.SortField;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
@ -94,6 +95,10 @@ public class ExpressionScriptEngineService extends AbstractComponent implements
@Override
public Object compile(String script) {
// classloader created here
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
return AccessController.doPrivileged(new PrivilegedAction<Expression>() {
@Override
public Expression run() {

View File

@ -40,6 +40,7 @@ import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
import org.codehaus.groovy.control.customizers.ImportCustomizer;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.ExceptionsHelper;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractComponent;
@ -105,6 +106,10 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
// Groovy class loader to isolate Groovy-land code
// classloader created here
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
this.loader = AccessController.doPrivileged(new PrivilegedAction<GroovyClassLoader>() {
@Override
public GroovyClassLoader run() {
@ -117,6 +122,10 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
public void close() {
loader.clearCache();
// close classloader here (why do we do this?)
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {
@ -158,6 +167,11 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
@Override
public Object compile(String script) {
try {
// we reuse classloader, so do a security check just in case.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
return loader.parseClass(script, Hashing.sha1().hashString(script, StandardCharsets.UTF_8).toString());
} catch (Throwable e) {
if (logger.isTraceEnabled()) {

View File

@ -21,6 +21,7 @@ package org.elasticsearch.script.javascript;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
@ -94,6 +95,12 @@ public class JavaScriptScriptEngineService extends AbstractComponent implements
@Override
public Object compile(String script) {
// we don't know why kind of safeguards rhino has,
// but just be safe
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
Context ctx = Context.enter();
try {
ctx.setWrapFactory(wrapFactory);

View File

@ -26,6 +26,7 @@ import java.util.Map;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Scorer;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
@ -57,6 +58,10 @@ public class PythonScriptEngineService extends AbstractComponent implements Scri
super(settings);
// classloader created here
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
this.interp = AccessController.doPrivileged(new PrivilegedAction<PythonInterpreter> () {
@Override
public PythonInterpreter run() {
@ -83,6 +88,10 @@ public class PythonScriptEngineService extends AbstractComponent implements Scri
@Override
public Object compile(String script) {
// classloader created here
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
return AccessController.doPrivileged(new PrivilegedAction<PyCode>() {
@Override
public PyCode run() {

View File

@ -19,6 +19,7 @@
package org.elasticsearch.plugin.repository.s3;
import org.elasticsearch.SpecialPermission;
import org.elasticsearch.cloud.aws.S3Module;
import org.elasticsearch.common.component.LifecycleComponent;
import org.elasticsearch.common.inject.Module;
@ -42,6 +43,10 @@ public class S3RepositoryPlugin extends Plugin {
// This internal config is deserialized but with wrong access modifiers,
// cannot work without suppressAccessChecks permission right now. We force
// a one time load with elevated privileges as a workaround.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
sm.checkPermission(new SpecialPermission());
}
AccessController.doPrivileged(new PrivilegedAction<Void>() {
@Override
public Void run() {