Skip to content

Commit 6d8c035

Browse files
committed
Add SpecialPermission to guard exceptions to security policy.
Closes #13854 Squashed commit of the following: commit 42c1166efc55adda0d13fed77de583c0973e44b3 Author: Robert Muir <[email protected]> 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 <[email protected]> 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.
1 parent ad6bc5b commit 6d8c035

File tree

10 files changed

+179
-4
lines changed

10 files changed

+179
-4
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch;
21+
22+
import java.security.BasicPermission;
23+
24+
/**
25+
* Elasticsearch-specific permission to check before entering
26+
* {@code AccessController.doPrivileged()} blocks.
27+
* <p>
28+
* We try to avoid these blocks in our code and keep security simple,
29+
* but we need them for a few special places to contain hacks for third
30+
* party code, or dangerous things used by scripting engines.
31+
* <p>
32+
* All normal code has this permission, but checking this before truncating the stack
33+
* prevents unprivileged code (e.g. scripts), which do not have it, from gaining elevated
34+
* privileges.
35+
* <p>
36+
* In other words, don't do this:
37+
* <br>
38+
* <pre><code>
39+
* // throw away all information about caller and run with our own privs
40+
* AccessController.doPrivileged(
41+
* ...
42+
* );
43+
* </code></pre>
44+
* <br>
45+
* Instead do this;
46+
* <br>
47+
* <pre><code>
48+
* // check caller first, to see if they should be allowed to do this
49+
* SecurityManager sm = System.getSecurityManager();
50+
* if (sm != null) {
51+
* sm.checkPermission(new SpecialPermission());
52+
* }
53+
* // throw away all information about caller and run with our own privs
54+
* AccessController.doPrivileged(
55+
* ...
56+
* );
57+
* </code></pre>
58+
*/
59+
public final class SpecialPermission extends BasicPermission {
60+
61+
private static final long serialVersionUID = -4129500096157408168L;
62+
63+
/**
64+
* Creates a new SpecialPermision object.
65+
*/
66+
public SpecialPermission() {
67+
// TODO: if we really need we can break out name (e.g. "hack" or "scriptEngineService" or whatever).
68+
// but let's just keep it simple if we can.
69+
super("*");
70+
}
71+
72+
/**
73+
* Creates a new SpecialPermission object.
74+
* This constructor exists for use by the {@code Policy} object to instantiate new Permission objects.
75+
*
76+
* @param name ignored
77+
* @param actions ignored
78+
*/
79+
public SpecialPermission(String name, String actions) {
80+
this();
81+
}
82+
}

core/src/main/resources/org/elasticsearch/bootstrap/security.policy

+5-4
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,8 @@ grant codeBase "${es.security.plugin.lang-groovy}" {
6969
permission java.lang.RuntimePermission "accessClassInPackage.sun.reflect";
7070
// needed by GroovyScriptEngineService to close its classloader (why?)
7171
permission java.lang.RuntimePermission "closeClassLoader";
72+
// Allow executing groovy scripts with codesource of /groovy/script
73+
permission groovy.security.GroovyCodeSourcePermission "/groovy/script";
7274
};
7375

7476
grant codeBase "${es.security.plugin.lang-javascript}" {
@@ -119,10 +121,9 @@ grant codeBase "${es.security.jar.randomizedtesting.junit4}" {
119121

120122
grant {
121123

122-
// Allow executing groovy scripts with codesource of /groovy/script
123-
// TODO: make our own general ScriptServicePermission we check instead and
124-
// check-before-createClassLoader for all scripting engines.
125-
permission groovy.security.GroovyCodeSourcePermission "/groovy/script";
124+
// checked by scripting engines, and before hacks and other issues in
125+
// third party code, to safeguard these against unprivileged code like scripts.
126+
permission org.elasticsearch.SpecialPermission;
126127

127128
// Allow connecting to the internet anywhere
128129
permission java.net.SocketPermission "*", "accept,listen,connect,resolve";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch;
21+
22+
import org.elasticsearch.test.ESTestCase;
23+
24+
import java.security.AllPermission;
25+
26+
/** Very simple sanity checks for {@link SpecialPermission} */
27+
public class SpecialPermissionTests extends ESTestCase {
28+
29+
public void testEquals() {
30+
assertEquals(new SpecialPermission(), new SpecialPermission());
31+
assertFalse(new SpecialPermission().equals(new AllPermission()));
32+
}
33+
34+
public void testImplies() {
35+
assertTrue(new SpecialPermission().implies(new SpecialPermission()));
36+
assertFalse(new SpecialPermission().implies(new AllPermission()));
37+
}
38+
}

plugins/cloud-gce/src/main/java/org/elasticsearch/cloud/gce/GceComputeServiceImpl.java

+9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import com.google.api.services.compute.model.Instance;
2929
import com.google.api.services.compute.model.InstanceList;
3030

31+
import org.elasticsearch.SpecialPermission;
3132
import org.elasticsearch.ElasticsearchException;
3233
import org.elasticsearch.common.component.AbstractLifecycleComponent;
3334
import org.elasticsearch.common.inject.Inject;
@@ -62,6 +63,10 @@ public Collection<Instance> instances() {
6263
try {
6364
// hack around code messiness in GCE code
6465
// TODO: get this fixed
66+
SecurityManager sm = System.getSecurityManager();
67+
if (sm != null) {
68+
sm.checkPermission(new SpecialPermission());
69+
}
6570
InstanceList instanceList = AccessController.doPrivileged(new PrivilegedExceptionAction<InstanceList>() {
6671
@Override
6772
public InstanceList run() throws Exception {
@@ -135,6 +140,10 @@ public synchronized Compute client() {
135140

136141
// hack around code messiness in GCE code
137142
// TODO: get this fixed
143+
SecurityManager sm = System.getSecurityManager();
144+
if (sm != null) {
145+
sm.checkPermission(new SpecialPermission());
146+
}
138147
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
139148
@Override
140149
public Void run() throws IOException {

plugins/discovery-ec2/src/main/java/org/elasticsearch/plugin/discovery/ec2/Ec2DiscoveryPlugin.java

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.plugin.discovery.ec2;
2121

22+
import org.elasticsearch.SpecialPermission;
2223
import org.elasticsearch.cloud.aws.AwsEc2ServiceImpl;
2324
import org.elasticsearch.cloud.aws.Ec2Module;
2425
import org.elasticsearch.common.component.LifecycleComponent;
@@ -42,6 +43,10 @@ public class Ec2DiscoveryPlugin extends Plugin {
4243
// This internal config is deserialized but with wrong access modifiers,
4344
// cannot work without suppressAccessChecks permission right now. We force
4445
// a one time load with elevated privileges as a workaround.
46+
SecurityManager sm = System.getSecurityManager();
47+
if (sm != null) {
48+
sm.checkPermission(new SpecialPermission());
49+
}
4550
AccessController.doPrivileged(new PrivilegedAction<Void>() {
4651
@Override
4752
public Void run() {

plugins/lang-expression/src/main/java/org/elasticsearch/script/expression/ExpressionScriptEngineService.java

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.apache.lucene.queries.function.ValueSource;
2727
import org.apache.lucene.queries.function.valuesource.DoubleConstValueSource;
2828
import org.apache.lucene.search.SortField;
29+
import org.elasticsearch.SpecialPermission;
2930
import org.elasticsearch.common.Nullable;
3031
import org.elasticsearch.common.component.AbstractComponent;
3132
import org.elasticsearch.common.inject.Inject;
@@ -94,6 +95,10 @@ public boolean sandboxed() {
9495
@Override
9596
public Object compile(String script) {
9697
// classloader created here
98+
SecurityManager sm = System.getSecurityManager();
99+
if (sm != null) {
100+
sm.checkPermission(new SpecialPermission());
101+
}
97102
return AccessController.doPrivileged(new PrivilegedAction<Expression>() {
98103
@Override
99104
public Expression run() {

plugins/lang-groovy/src/main/java/org/elasticsearch/script/groovy/GroovyScriptEngineService.java

+14
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
import org.codehaus.groovy.control.SourceUnit;
4141
import org.codehaus.groovy.control.customizers.CompilationCustomizer;
4242
import org.codehaus.groovy.control.customizers.ImportCustomizer;
43+
import org.elasticsearch.SpecialPermission;
4344
import org.elasticsearch.ExceptionsHelper;
4445
import org.elasticsearch.common.Nullable;
4546
import org.elasticsearch.common.component.AbstractComponent;
@@ -105,6 +106,10 @@ public GroovyScriptEngineService(Settings settings) {
105106

106107
// Groovy class loader to isolate Groovy-land code
107108
// classloader created here
109+
SecurityManager sm = System.getSecurityManager();
110+
if (sm != null) {
111+
sm.checkPermission(new SpecialPermission());
112+
}
108113
this.loader = AccessController.doPrivileged(new PrivilegedAction<GroovyClassLoader>() {
109114
@Override
110115
public GroovyClassLoader run() {
@@ -117,6 +122,10 @@ public GroovyClassLoader run() {
117122
public void close() {
118123
loader.clearCache();
119124
// close classloader here (why do we do this?)
125+
SecurityManager sm = System.getSecurityManager();
126+
if (sm != null) {
127+
sm.checkPermission(new SpecialPermission());
128+
}
120129
AccessController.doPrivileged(new PrivilegedAction<Void>() {
121130
@Override
122131
public Void run() {
@@ -158,6 +167,11 @@ public boolean sandboxed() {
158167
@Override
159168
public Object compile(String script) {
160169
try {
170+
// we reuse classloader, so do a security check just in case.
171+
SecurityManager sm = System.getSecurityManager();
172+
if (sm != null) {
173+
sm.checkPermission(new SpecialPermission());
174+
}
161175
return loader.parseClass(script, Hashing.sha1().hashString(script, StandardCharsets.UTF_8).toString());
162176
} catch (Throwable e) {
163177
if (logger.isTraceEnabled()) {

plugins/lang-javascript/src/main/java/org/elasticsearch/script/javascript/JavaScriptScriptEngineService.java

+7
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
import org.apache.lucene.index.LeafReaderContext;
2323
import org.apache.lucene.search.Scorer;
24+
import org.elasticsearch.SpecialPermission;
2425
import org.elasticsearch.common.Nullable;
2526
import org.elasticsearch.common.component.AbstractComponent;
2627
import org.elasticsearch.common.inject.Inject;
@@ -94,6 +95,12 @@ public boolean sandboxed() {
9495

9596
@Override
9697
public Object compile(String script) {
98+
// we don't know why kind of safeguards rhino has,
99+
// but just be safe
100+
SecurityManager sm = System.getSecurityManager();
101+
if (sm != null) {
102+
sm.checkPermission(new SpecialPermission());
103+
}
97104
Context ctx = Context.enter();
98105
try {
99106
ctx.setWrapFactory(wrapFactory);

plugins/lang-python/src/main/java/org/elasticsearch/script/python/PythonScriptEngineService.java

+9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626

2727
import org.apache.lucene.index.LeafReaderContext;
2828
import org.apache.lucene.search.Scorer;
29+
import org.elasticsearch.SpecialPermission;
2930
import org.elasticsearch.common.Nullable;
3031
import org.elasticsearch.common.component.AbstractComponent;
3132
import org.elasticsearch.common.inject.Inject;
@@ -57,6 +58,10 @@ public PythonScriptEngineService(Settings settings) {
5758
super(settings);
5859

5960
// classloader created here
61+
SecurityManager sm = System.getSecurityManager();
62+
if (sm != null) {
63+
sm.checkPermission(new SpecialPermission());
64+
}
6065
this.interp = AccessController.doPrivileged(new PrivilegedAction<PythonInterpreter> () {
6166
@Override
6267
public PythonInterpreter run() {
@@ -83,6 +88,10 @@ public boolean sandboxed() {
8388
@Override
8489
public Object compile(String script) {
8590
// classloader created here
91+
SecurityManager sm = System.getSecurityManager();
92+
if (sm != null) {
93+
sm.checkPermission(new SpecialPermission());
94+
}
8695
return AccessController.doPrivileged(new PrivilegedAction<PyCode>() {
8796
@Override
8897
public PyCode run() {

plugins/repository-s3/src/main/java/org/elasticsearch/plugin/repository/s3/S3RepositoryPlugin.java

+5
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.plugin.repository.s3;
2121

22+
import org.elasticsearch.SpecialPermission;
2223
import org.elasticsearch.cloud.aws.S3Module;
2324
import org.elasticsearch.common.component.LifecycleComponent;
2425
import org.elasticsearch.common.inject.Module;
@@ -42,6 +43,10 @@ public class S3RepositoryPlugin extends Plugin {
4243
// This internal config is deserialized but with wrong access modifiers,
4344
// cannot work without suppressAccessChecks permission right now. We force
4445
// a one time load with elevated privileges as a workaround.
46+
SecurityManager sm = System.getSecurityManager();
47+
if (sm != null) {
48+
sm.checkPermission(new SpecialPermission());
49+
}
4550
AccessController.doPrivileged(new PrivilegedAction<Void>() {
4651
@Override
4752
public Void run() {

0 commit comments

Comments
 (0)