Skip to content
Permalink
Browse files

Merge pull request #124 from jglick/GroovyCategorySupport-JENKINS-26481

[JENKINS-26481] [JENKINS-27421] Use GroovyCategorySupport to invoke CpsDefaultGroovyMethods (w/o DGMPatcher) & IteratorHack
  • Loading branch information
jglick committed May 30, 2017
2 parents b48af16 + eb0ad8f commit 3ec591db4bca053eb70534a92583dcd5b52bf6e5
@@ -62,7 +62,7 @@
</pluginRepository>
</pluginRepositories>
<properties>
<jenkins.version>1.642.3</jenkins.version>
<jenkins.version>2.7.3</jenkins.version>
<no-test-jar>false</no-test-jar>
<git-plugin.version>3.1.0</git-plugin.version>
<workflow-support-plugin.version>2.14</workflow-support-plugin.version>
@@ -77,7 +77,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
<artifactId>workflow-api</artifactId>
<version>2.16-20170524.173224-1</version> <!-- TODO: update when https://github.com/jenkinsci/workflow-api-plugin/pull/38 is merged and released -->
<version>2.16</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins.workflow</groupId>
@@ -92,7 +92,7 @@
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
<artifactId>script-security</artifactId>
<version>1.26</version>
<version>1.27</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.plugins</groupId>
@@ -174,7 +174,7 @@
<dependency>
<groupId>com.cloudbees</groupId>
<artifactId>groovy-cps</artifactId>
<version>1.12</version>
<version>1.13</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci.ui</groupId>
@@ -42,6 +42,7 @@
import java.util.logging.Logger;

import static java.util.logging.Level.*;
import org.jenkinsci.plugins.workflow.cps.persistence.IteratorHack;
import static org.jenkinsci.plugins.workflow.cps.persistence.PersistenceContext.*;
import org.jenkinsci.plugins.workflow.support.concurrent.Futures;
import org.jenkinsci.plugins.workflow.support.concurrent.Timeout;
@@ -146,10 +147,18 @@ public StepExecution getStep() {
this.step = step;
}

private static final List<Class> categories;
static {
categories = new ArrayList<>();
categories.addAll(Continuable.categories);
categories.add(IteratorHack.class);
}

/**
* Executes CPS code synchronously a little bit more, until it hits
* the point the workflow needs to be dehydrated.
*/
@SuppressWarnings("rawtypes")
@Nonnull Outcome runNextChunk() {
assert program!=null;

@@ -160,9 +169,9 @@ public StepExecution getStep() {

try (Timeout timeout = Timeout.limit(5, TimeUnit.MINUTES)) {
LOGGER.log(FINE, "runNextChunk on {0}", resumeValue);
Outcome o = resumeValue;
final Outcome o = resumeValue;
resumeValue = null;
outcome = program.run0(o);
outcome = program.run0(o, categories);
if (outcome.getAbnormal() != null) {
LOGGER.log(FINE, "ran and produced error", outcome.getAbnormal());
} else {
@@ -1,5 +1,6 @@
package org.jenkinsci.plugins.workflow.cps;

import com.cloudbees.groovy.cps.Continuable;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.model.Run;
import java.io.IOException;
@@ -11,6 +12,7 @@

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
@@ -87,9 +89,41 @@ public boolean permitsConstructor(Constructor<?> constructor, Object[] args) {

@Override
public boolean permitsStaticMethod(Method method, Object[] args) {
Class<?> c = method.getDeclaringClass();
String n = method.getName();
// type coercive cast. In particular, this is used to build GString. See com.cloudbees.groovy.cps.Builder.gstring
if (method.getDeclaringClass()==ScriptBytecodeAdapter.class && method.getName().equals("asType"))
if (c == ScriptBytecodeAdapter.class && n.equals("asType")) {
return true;
}

if (Continuable.categories.contains(c)) {
// Delegate permission checks to the original *GroovyMethods.
String cn = c.getName();
String driverFrom = "com.cloudbees.groovy.cps.Cps"; // cf. Driver
String driverTo = "org.codehaus.groovy.runtime.";
if (cn.startsWith(driverFrom)) {
try {
Class<?> orig = Class.forName(driverTo + cn.substring(driverFrom.length()));
Class<?>[] expectedParameterTypes = method.getParameterTypes();
String expectedName;
if (n.startsWith("$")) {
// E.g., CpsDefaultGroovyMethods.$each__java_util_List__groovy_lang_Closure
expectedName = n.substring(1).replaceFirst("__.+$", "");
} else {
expectedName = n;
}
for (Method m2 : orig.getMethods()) {
if (m2.getName().equals(expectedName) && Arrays.equals(m2.getParameterTypes(), expectedParameterTypes)) {
return Whitelist.all().permitsStaticMethod(m2, args);
}
}
} catch (ClassNotFoundException x) {
LOGGER.log(Level.WARNING, null, x); // this would be unexpected
}
} else {
LOGGER.log(Level.WARNING, "Unexpected category name {0}", cn); // as would this
}
}

return false;
}
@@ -1,20 +1,12 @@
package org.jenkinsci.plugins.workflow.cps;

import com.cloudbees.groovy.cps.impl.CpsClosure;
import groovy.lang.Closure;
import groovy.lang.GroovyClassLoader;
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;

import org.codehaus.groovy.runtime.DefaultGroovyMethods;
import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException;
import org.jenkinsci.plugins.scriptsecurity.sandbox.Whitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.EnumeratingWhitelist;
import org.jenkinsci.plugins.scriptsecurity.sandbox.whitelists.ProxyWhitelist;

@@ -49,15 +41,15 @@ private boolean permits(Class<?> declaringClass) {
}

@Override public boolean permitsMethod(Method method, Object receiver, Object[] args) {
return checkJenkins26481(args, method, method.getParameterTypes()) || delegate.permitsMethod(method, receiver, args);
return permits(method.getDeclaringClass()) || delegate.permitsMethod(method, receiver, args);
}

@Override public boolean permitsConstructor(Constructor<?> constructor, Object[] args) {
return checkJenkins26481(args, constructor, constructor.getParameterTypes()) || delegate.permitsConstructor(constructor, args);
return permits(constructor.getDeclaringClass()) || delegate.permitsConstructor(constructor, args);
}

@Override public boolean permitsStaticMethod(Method method, Object[] args) {
return checkJenkins26481(args, method, method.getParameterTypes()) || delegate.permitsStaticMethod(method, args);
return permits(method.getDeclaringClass()) || delegate.permitsStaticMethod(method, args);
}

@Override public boolean permitsFieldGet(Field field, Object receiver) {
@@ -76,21 +68,4 @@ private boolean permits(Class<?> declaringClass) {
return permits(field.getDeclaringClass()) || delegate.permitsStaticFieldSet(field, value);
}

/**
* Blocks accesses to some {@link DefaultGroovyMethods}, or any other binary method taking a closure, until JENKINS-26481 is fixed.
* Only improves diagnostics for scripts using the sandbox.
* Note that we do not simply return false for these cases, as that would throw {@link RejectedAccessException} without a meaningful explanation.
*/
private boolean checkJenkins26481(Object[] args, /* TODO Java 8: just take Executable */ Member method, Class<?>[] parameterTypes) throws UnsupportedOperationException {
if (permits(method.getDeclaringClass())) { // fine for source-defined methods to take closures
return true;
}
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof CpsClosure && parameterTypes.length > i && parameterTypes[i] == Closure.class) {
throw new UnsupportedOperationException("Calling " + method + " on a CPS-transformed closure is not yet supported (JENKINS-26481); encapsulate in a @NonCPS method, or use Java-style loops");
}
}
return false;
}

}
@@ -2,6 +2,7 @@

import com.cloudbees.groovy.cps.Continuable;
import com.cloudbees.groovy.cps.Outcome;
import java.util.List;
import org.jenkinsci.plugins.scriptsecurity.sandbox.RejectedAccessException;
import org.jenkinsci.plugins.scriptsecurity.sandbox.groovy.GroovySandbox;
import org.jenkinsci.plugins.scriptsecurity.scripts.ApprovalContext;
@@ -23,14 +24,15 @@
this.thread = thread;
}

@SuppressWarnings("rawtypes")
@Override
public Outcome run0(final Outcome cn) {
public Outcome run0(final Outcome cn, final List<Class> categories) {
try {
CpsFlowExecution e = thread.group.getExecution();
return GroovySandbox.runInSandbox(new Callable<Outcome>() {
@Override
public Outcome call() {
Outcome outcome = SandboxContinuable.super.run0(cn);
Outcome outcome = SandboxContinuable.super.run0(cn, categories);
RejectedAccessException x = findRejectedAccessException(outcome.getAbnormal());
if (x != null) {
ScriptApproval.get().accessRejected(x, ApprovalContext.create());

0 comments on commit 3ec591d

Please sign in to comment.