- groovy脚本中while退出判断条件错误,线程陷入死循环
- 未对System.exit和Runtime.getRuntime.exec方法做限制
代码实现:org.codehaus.groovy groovy 3.0.9 org.craftercms groovy-sandbox 1.27.6
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.script;
import groovy.transform.ConditionalInterrupt;
import groovy.transform.ThreadInterrupt;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.expr.Classexpression;
import org.codehaus.groovy.ast.expr.Closureexpression;
import org.codehaus.groovy.ast.expr.Constantexpression;
import org.codehaus.groovy.ast.expr.MethodCallexpression;
import org.codehaus.groovy.ast.stmt.expressionStatement;
import org.codehaus.groovy.control.CompilerConfiguration;
import org.codehaus.groovy.control.customizers.ASTTransformationCustomizer;
import org.junit.Test;
import org.kohsuke.groovy.sandbox.GroovyInterceptor;
import org.kohsuke.groovy.sandbox.SandboxTransformer;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class GroovyShellTest {
private static final Map script_CACHE = new ConcurrentHashMap<>();
@Test
public void scriptTest(){
// 脚本名称,key
String scriptId = "test";
// groovy脚本,value
String scriptext = "def o = new Object();ndef t = o.class";
script script = script_CACHE.get(scriptId);
if (script == null) {
// 缓存脚本,提高执行性能
GroovyShell shell = createGroovyShell();
script = shell.parse(scriptext);
script_CACHE.put(scriptId, script);
}
// 变量绑定,自己看情况加
Binding binding = doBinding(scriptext, new HashMap());
script.setBinding(binding);
// 重置调用时间
ThreadLocalUtils.setStartTime();
// 添加脚本名称
ThreadLocalUtils.set("scriptName", scriptId);
Object result = script.run();
System.err.println(result);
}
private GroovyShell createGroovyShell() {
// 自定义配置
CompilerConfiguration config = new CompilerConfiguration();
// 保存生成的class文件
// config.setTargetDirectory("./");
// System.err.println(config.getTargetDirectory().getAbsolutePath());
// 添加线程中断拦截器,可拦截循环体(for,while)、方法和闭包的首指令
config.addCompilationCustomizers(new ASTTransformationCustomizer(ThreadInterrupt.class));
// 添加线程中断拦截器,可中断超时线程
// TimedInterrupt是线程监控的,不适合缓存脚本超时判断
// Map timeoutArgs = ImmutableMap.of("value", 300,
// "checkOnMethodStart", false);
// config.addCompilationCustomizers(new ASTTransformationCustomizer(timeoutArgs, TimedInterrupt.class));
Map timeoutArgs = new HashMap<>();
timeoutArgs.put("value", new Closureexpression(
Parameter.EMPTY_ARRAY,
new expressionStatement(
new MethodCallexpression(
new Classexpression(ClassHelper.make(ConditionInterceptor.class)),
"checkTimeout",
new Constantexpression(10))
)
));
config.addCompilationCustomizers(new ASTTransformationCustomizer(timeoutArgs, ConditionalInterrupt.class));
// 沙盒环境
config.addCompilationCustomizers(new SandboxTransformer());
GroovyShell shell = new GroovyShell(config);
// 注册方法拦截
new GroovyNotSupportInterceptor().register();
return shell;
}
private Binding doBinding(String scriptText, Object params) {
Binding binding = new Binding();
binding.setVariable("utils", new Object());
return binding;
}
}
class ConditionInterceptor {
static boolean checkTimeout(int timeout) {
boolean flag = ThreadLocalUtils.getStartTime() + timeout * 1000 < System.currentTimeMillis();
if (flag) {
System.err.printf("[%s] Execution timed out after %s seconds. Start Time:%s%n",
ThreadLocalUtils.get("scriptName"),
timeout,
LocalDateTime.ofInstant(Instant.ofEpochMilli(ThreadLocalUtils.getStartTime()), ZoneOffset.ofHours(8))
.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
}
return flag;
}
}
class ThreadLocalUtils {
// 存储脚本开始执行时间
private static final ThreadLocal
Class文件
测试脚本:
while(true){
Thread.sleep(1000)
}
System.exit(1)
return 1
class文件内容:
import groovy.lang.Binding;
import groovy.lang.script;
import org.codehaus.groovy.runtime.ArrayUtil;
import org.codehaus.groovy.runtime.InvokerHelper;
import org.codehaus.groovy.runtime.scriptBytecodeAdapter;
import org.codehaus.groovy.runtime.callsite.CallSite;
import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation;
import org.kohsuke.groovy.sandbox.impl.Checker;
public class script1 extends script {
public script1() {
CallSite[] var1 = $getCallSiteArray();
super();
}
public script1(Binding context) {
CallSite[] var2 = $getCallSiteArray();
super(context);
}
public static void main(String... args) {
CallSite[] var1 = $getCallSiteArray();
if (DefaultTypeTransformation.booleanUnbox(var1[0].callStatic(Checker.class, ArrayUtil.createArray(var1[1].callStatic(Checker.class, ArrayUtil.createArray(Thread.class, false, false, "currentThread", var1[2].call(scriptBytecodeAdapter.createList(new Object[0])))), false, false, "isInterrupted", var1[3].call(scriptBytecodeAdapter.createList(new Object[0])))))) {
throw (Throwable) var1[4].callStatic(Checker.class, InterruptedException.class, var1[5].call(scriptBytecodeAdapter.createList(new Object[]{"Execution interrupted. The current thread has been interrupted."})));
} else {
var1[6].callStatic(Checker.class, InvokerHelper.class, "runscript", var1[7].call(scriptBytecodeAdapter.createList(new Object[]{script1.class, args})));
}
}
public Object run() {
CallSite[] var1 = $getCallSiteArray();
if (DefaultTypeTransformation.booleanUnbox(var1[8].callStatic(Checker.class, ArrayUtil.createArray(var1[9].callStatic(Checker.class, ArrayUtil.createArray(Thread.class, false, false, "currentThread", var1[10].call(scriptBytecodeAdapter.createList(new Object[0])))), false, false, "isInterrupted", var1[11].call(scriptBytecodeAdapter.createList(new Object[0])))))) {
throw (Throwable) var1[12].callStatic(Checker.class, InterruptedException.class, var1[13].call(scriptBytecodeAdapter.createList(new Object[]{"Execution interrupted. The current thread has been interrupted."})));
} else {
while (!DefaultTypeTransformation.booleanUnbox(var1[14].callStatic(Checker.class, ArrayUtil.createArray(this, false, false, "conditionalTransform550147359$condition", var1[15].call(scriptBytecodeAdapter.createList(new Object[0])))))) {
if (DefaultTypeTransformation.booleanUnbox(var1[18].callStatic(Checker.class, ArrayUtil.createArray(var1[19].callStatic(Checker.class, ArrayUtil.createArray(Thread.class, false, false, "currentThread", var1[20].call(scriptBytecodeAdapter.createList(new Object[0])))), false, false, "isInterrupted", var1[21].call(scriptBytecodeAdapter.createList(new Object[0])))))) {
throw (Throwable) var1[22].callStatic(Checker.class, InterruptedException.class, var1[23].call(scriptBytecodeAdapter.createList(new Object[]{"Execution interrupted. The current thread has been interrupted."})));
}
var1[24].callStatic(Checker.class, ArrayUtil.createArray(Thread.class, false, false, "sleep", var1[25].call(scriptBytecodeAdapter.createList(new Object[]{1000}))));
}
throw (Throwable) var1[16].callStatic(Checker.class, InterruptedException.class, var1[17].call(scriptBytecodeAdapter.createList(new Object[]{"Execution interrupted. The following condition failed: Error calculating source code for expression. Trying to read line 0 from class org.codehaus.groovy.control.io.StringReaderSource"})));
}
}
}
有点长…主要信息在run方法中。有一个线程中断判断(ThreadInterrupt),有一个自定义条件判断(while中ConditionalInterrupt)。



