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:
parent
ad6bc5b94c
commit
6d8c035f70
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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() {
|
||||
|
|
Loading…
Reference in New Issue