Wrap GroovyBugErrors in ScriptExceptions

When Groovy detects a bug in its runtime because an internal assertion
was violated, it throws an GroovyBugError. This descends from
AssertionError and if it goes uncaught will land in the uncaught
exception handler and will not deliver any useful information to the
user. This commit wraps GroovyBugErrors in ScriptExceptions so that
useful information is returned to the user.
This commit is contained in:
Jason Tedor 2016-11-19 07:05:39 -05:00
parent dce51e2062
commit 655c4fe172
2 changed files with 19 additions and 5 deletions

View File

@ -28,6 +28,7 @@ import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.util.Supplier;
import org.apache.lucene.index.LeafReaderContext;
import org.apache.lucene.search.Scorer;
import org.codehaus.groovy.GroovyBugError;
import org.codehaus.groovy.ast.ClassCodeExpressionTransformer;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.expr.ConstantExpression;
@ -67,6 +68,7 @@ import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -302,20 +304,24 @@ public class GroovyScriptEngineService extends AbstractComponent implements Scri
// NOTE: we truncate the stack because IndyInterface has security issue (needs getClassLoader)
// we don't do a security check just as a tradeoff, it cannot really escalate to anything.
return AccessController.doPrivileged((PrivilegedAction<Object>) script::run);
} catch (AssertionError ae) {
} catch (final AssertionError ae) {
if (ae instanceof GroovyBugError) {
// we encountered a bug in Groovy; we wrap this so it does not go to the uncaught exception handler and tear us down
final String message = "encountered bug in Groovy while executing script [" + compiledScript.name() + "]";
throw new ScriptException(message, ae, Collections.emptyList(), compiledScript.toString(), compiledScript.lang());
}
// Groovy asserts are not java asserts, and cannot be disabled, so we do a best-effort trying to determine if this is a
// Groovy assert (in which case we wrap it and throw), or a real Java assert, in which case we rethrow it as-is, likely
// resulting in the uncaughtExceptionHandler handling it.
final StackTraceElement[] elements = ae.getStackTrace();
if (elements.length > 0 && "org.codehaus.groovy.runtime.InvokerHelper".equals(elements[0].getClassName())) {
logger.trace((Supplier<?>) () -> new ParameterizedMessage("failed to run {}", compiledScript), ae);
throw new ScriptException("Error evaluating " + compiledScript.name(),
ae, emptyList(), "", compiledScript.lang());
logger.debug((Supplier<?>) () -> new ParameterizedMessage("failed to run {}", compiledScript), ae);
throw new ScriptException("error evaluating " + compiledScript.name(), ae, emptyList(), "", compiledScript.lang());
}
throw ae;
} catch (Exception | NoClassDefFoundError e) {
logger.trace((Supplier<?>) () -> new ParameterizedMessage("failed to run {}", compiledScript), e);
throw new ScriptException("Error evaluating " + compiledScript.name(), e, emptyList(), "", compiledScript.lang());
throw new ScriptException("error evaluating " + compiledScript.name(), e, emptyList(), "", compiledScript.lang());
}
}

View File

@ -130,6 +130,14 @@ public class GroovySecurityTests extends ESTestCase {
assertFailure("def foo=false; assert foo, \"msg2\";", NoClassDefFoundError.class);
}
public void testGroovyBugError() {
// this script throws a GroovyBugError because our security manager permissions prevent Groovy from accessing this private field
// and Groovy does not handle it gracefully; this test will likely start failing if the bug is fixed upstream so that a
// GroovyBugError no longer surfaces here in which case the script should be replaced with another script that intentionally
// surfaces a GroovyBugError
assertFailure("[1, 2].size", AssertionError.class);
}
/** runs a script */
private void doTest(String script) {
Map<String, Object> vars = new HashMap<String, Object>();