Skip to content

Commit

Permalink
[SECURITY-1266] Block problematic AST transforms from sandbox
Browse files Browse the repository at this point in the history
  • Loading branch information
abayer committed Jan 2, 2019
1 parent a559061 commit 2c5122e
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,12 @@
package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import groovy.grape.GrabAnnotationTransformation;
import groovy.lang.GroovyShell;
import groovy.lang.Script;

import java.util.Collections;
import java.util.HashSet;
import java.util.concurrent.Callable;
import javax.annotation.Nonnull;
import org.codehaus.groovy.control.CompilerConfiguration;
Expand Down Expand Up @@ -58,11 +61,21 @@ public class GroovySandbox {
* @return a compiler configuration set up to use the sandbox
*/
public static @Nonnull CompilerConfiguration createSecureCompilerConfiguration() {
CompilerConfiguration cc = new CompilerConfiguration();
CompilerConfiguration cc = createBaseCompilerConfiguration();
cc.addCompilationCustomizers(new SandboxTransformer());
return cc;
}

/**
* Prepares a compiler configuration that rejects certain AST transformations. Used by {@link #createSecureCompilerConfiguration()}.
*/
public static @Nonnull CompilerConfiguration createBaseCompilerConfiguration() {
CompilerConfiguration cc = new CompilerConfiguration();
cc.addCompilationCustomizers(new RejectASTTransformsCustomizer());
cc.setDisabledGlobalASTTransformations(new HashSet<>(Collections.singletonList(GrabAnnotationTransformation.class.getName())));
return cc;
}

/**
* Prepares a classloader for Groovy shell for sandboxing.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* The MIT License
*
* Copyright (c) 2018, CloudBees, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/

package org.jenkinsci.plugins.scriptsecurity.sandbox.groovy;

import com.google.common.collect.ImmutableList;
import groovy.lang.Grab;
import groovy.transform.ASTTest;
import org.codehaus.groovy.ast.AnnotatedNode;
import org.codehaus.groovy.ast.AnnotationNode;
import org.codehaus.groovy.ast.ClassCodeVisitorSupport;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.classgen.GeneratorContext;
import org.codehaus.groovy.control.CompilationFailedException;
import org.codehaus.groovy.control.CompilePhase;
import org.codehaus.groovy.control.SourceUnit;
import org.codehaus.groovy.control.customizers.CompilationCustomizer;

import java.lang.annotation.Annotation;
import java.util.List;

public class RejectASTTransformsCustomizer extends CompilationCustomizer {
private static final List<Class<? extends Annotation>> BLOCKED_TRANSFORMS = ImmutableList.of(ASTTest.class, Grab.class);

public RejectASTTransformsCustomizer() {
super(CompilePhase.CONVERSION);
}

@Override
public void call(final SourceUnit source, GeneratorContext context, ClassNode classNode) throws CompilationFailedException {
new RejectASTTransformsVisitor(source).visitClass(classNode);
}

private static class RejectASTTransformsVisitor extends ClassCodeVisitorSupport {
private SourceUnit source;

public RejectASTTransformsVisitor(SourceUnit source) {
this.source = source;
}

@Override
protected SourceUnit getSourceUnit() {
return source;
}

/**
* If the node is annotated with one of the blocked transform annotations, throw a security exception.
*
* @param node the node to process
*/
@Override
public void visitAnnotations(AnnotatedNode node) {
for (AnnotationNode an : node.getAnnotations()) {
for (Class<? extends Annotation> blockedAnnotation : BLOCKED_TRANSFORMS) {
if (blockedAnnotation.getSimpleName().equals(an.getClassNode().getName())) {
throw new SecurityException("Annotation " + blockedAnnotation.getSimpleName() + " cannot be used in the sandbox.");
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,15 @@
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
import org.junit.rules.ExpectedException;
import org.jvnet.hudson.test.Issue;

public class SandboxInterceptorTest {

@Rule public ErrorCollector errors = new ErrorCollector();

@Rule public ExpectedException thrown = ExpectedException.none();

@Test public void genericWhitelist() throws Exception {
assertEvaluate(new GenericWhitelist(), 3, "'foo bar baz'.split(' ').length");
assertEvaluate(new GenericWhitelist(), false, "def x = null; x != null");
Expand Down Expand Up @@ -818,6 +821,30 @@ public void explode() {}
assertRejected(new StaticWhitelist(), "staticMethod hudson.model.Hudson getInstance", "hudson.model.Hudson.instance");
}

@Issue("SECURITY-1266")
@Test
public void blockedASTTransformsASTTest() throws Exception {
GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration());

thrown.expect(MultipleCompilationErrorsException.class);
thrown.expectMessage("Annotation ASTTest cannot be used in the sandbox");

shell.parse("import groovy.transform.*\n" +
"@ASTTest(value={ assert true })\n" +
"@Field int x\n");
}

@Issue("SECURITY-1266")
@Test
public void blockedASTTransformsGrab() throws Exception {
GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration());
thrown.expect(MultipleCompilationErrorsException.class);
thrown.expectMessage("Annotation Grab cannot be used in the sandbox");

shell.parse("@Grab(group='foo', module='bar', version='1.0')\n" +
"def foo\n");
}

private static Object evaluate(Whitelist whitelist, String script) {
GroovyShell shell = new GroovyShell(GroovySandbox.createSecureCompilerConfiguration());
Object actual = GroovySandbox.run(shell.parse(script), whitelist);
Expand Down

0 comments on commit 2c5122e

Please sign in to comment.