This is an automated email from the ASF dual-hosted git repository.
henrib pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/commons-jexl.git
The following commit(s) were added to refs/heads/master by this push:
new c0b85019 JEXL-466: ensure JexlEngine is thread-local accessible when
creating TemplateScript and TemplateExpressions;
c0b85019 is described below
commit c0b850195aab78c392f7c809ec3afbe50f799961
Author: Henrib <[email protected]>
AuthorDate: Fri Jul 24 15:32:31 2026 +0200
JEXL-466: ensure JexlEngine is thread-local accessible when creating
TemplateScript and TemplateExpressions;
---
.../org/apache/commons/jexl3/internal/Engine.java | 19 +++---
.../commons/jexl3/internal/TemplateEngine.java | 4 ++
.../commons/jexl3/internal/TemplateScript.java | 38 ++++++-----
.../org/apache/commons/jexl3/Issues400Test.java | 77 ++++++++++++++++++++++
4 files changed, 114 insertions(+), 24 deletions(-)
diff --git a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
index d26a015e..2de56103 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/Engine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/Engine.java
@@ -345,7 +345,7 @@ public class Engine extends JexlEngine implements
JexlUberspect.ConstantResolver
* The set of caches created by this engine.
* <p>Caches are soft-referenced by the engine so they can be cleaned on
class loader change.</p>
*/
- protected final MetaCache metaCache;
+ final MetaCache metaCache;
/**
* Creates an engine with default arguments.
@@ -847,7 +847,7 @@ public class Engine extends JexlEngine implements
JexlUberspect.ConstantResolver
if (parsing.compareAndSet(false, true)) {
synchronized (parsing) {
try {
- // lets parse
+ // let's parse
script = parser.parse(ninfo, features, src, scope);
} finally {
// no longer in use
@@ -862,7 +862,7 @@ public class Engine extends JexlEngine implements
JexlUberspect.ConstantResolver
cache.put(source, script);
}
} finally {
- // restore thread local engine
+ // restore thread local engine (most likely null)
putThreadEngine(se);
}
return script;
@@ -1018,15 +1018,18 @@ public class Engine extends JexlEngine implements
JexlUberspect.ConstantResolver
}
/**
- * Swaps the current thread local engine.
+ * Swaps the current thread local engine if it differs from argument.
*
* @param jexl The engine or null
* @return The previous thread local engine
*/
- protected JexlEngine putThreadEngine(final JexlEngine jexl) {
- final JexlEngine pjexl = ENGINE.get();
- ENGINE.set(jexl);
- return pjexl;
+ protected static JexlEngine putThreadEngine(final JexlEngine jexl) {
+ final JexlEngine engine = ENGINE.get();
+ if (engine != jexl) {
+ ENGINE.set(jexl);
+ return engine;
+ }
+ return jexl;
}
/**
diff --git
a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
index cd4a6a07..36d0bba7 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateEngine.java
@@ -29,6 +29,7 @@ import java.util.Set;
import org.apache.commons.jexl3.JexlCache;
import org.apache.commons.jexl3.JexlContext;
+import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlFeatures;
import org.apache.commons.jexl3.JexlInfo;
@@ -917,6 +918,7 @@ public final class TemplateEngine extends JxltEngine {
final JexlFeatures features = noscript ? jexl.expressionFeatures :
jexl.scriptFeatures;
// do not cache interpolation expression, they are stored in AST node
final boolean cached = cache != null && expression.length() <
jexl.cacheThreshold;
+ final JexlEngine je = Engine.putThreadEngine(jexl);
try {
if (!cached) {
stmt = parseExpression(info, expression, scope);
@@ -932,6 +934,8 @@ public final class TemplateEngine extends JxltEngine {
}
} catch (final JexlException xjexl) {
xuel = new Exception(xjexl.getInfo(), "failed to parse '" +
expression + "'", xjexl);
+ } finally {
+ Engine.putThreadEngine(je);
}
if (xuel != null) {
if (!jexl.isSilent()) {
diff --git
a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
index 92550b1e..5653797b 100644
--- a/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
+++ b/src/main/java/org/apache/commons/jexl3/internal/TemplateScript.java
@@ -25,6 +25,7 @@ import java.util.Objects;
import java.util.Set;
import org.apache.commons.jexl3.JexlContext;
+import org.apache.commons.jexl3.JexlEngine;
import org.apache.commons.jexl3.JexlException;
import org.apache.commons.jexl3.JexlInfo;
import org.apache.commons.jexl3.JexlOptions;
@@ -213,22 +214,27 @@ public final class TemplateScript implements
JxltEngine.Template {
verbatims += 1;
}
}
- final String scriptSource = callerScript(blocks);
- // allow lambda defining params
- final JexlInfo info = jexlInfo == null ? jexl.createInfo() : jexlInfo;
- final Scope scope = parms == null ? null : new Scope(null, parms);
- final JexlInfo templateInfo =
jexl.scriptFeatures.isIgnoreTemplatePrefix()
- ? new TemplateInfo(info.at(1, 1), Collections.singleton(prefix))
- : info.at(1, 1);
- final ASTJexlScript callerScript = jexl.jxltParse(templateInfo, false,
scriptSource, scope).script();
- // seek the map of expression number to scope so we can parse Unified
- // expression blocks with the appropriate symbols
- final JexlNode.Info[] callSites = new JexlNode.Info[verbatims];
- collectPrintScope(callerScript.script(), callSites);
- // create the expressions from the blocks
- this.exprs = calleeScripts(scope, blocks, callSites);
- this.script = callerScript;
- this.source = blocks;
+ final JexlEngine je = Engine.putThreadEngine(jexl);
+ try {
+ final String scriptSource = callerScript(blocks);
+ // allow lambda defining params
+ final JexlInfo info = jexlInfo == null ? jexl.createInfo() :
jexlInfo;
+ final Scope scope = parms == null ? null : new Scope(null, parms);
+ final JexlInfo templateInfo =
jexl.scriptFeatures.isIgnoreTemplatePrefix()
+ ? new TemplateInfo(info.at(1, 1),
Collections.singleton(prefix))
+ : info.at(1, 1);
+ final ASTJexlScript callerScript = jexl.jxltParse(templateInfo,
false, scriptSource, scope).script();
+ // seek the map of expression number to scope so we can parse
Unified
+ // expression blocks with the appropriate symbols
+ final JexlNode.Info[] callSites = new JexlNode.Info[verbatims];
+ collectPrintScope(callerScript.script(), callSites);
+ // create the expressions from the blocks
+ this.exprs = calleeScripts(scope, blocks, callSites);
+ this.script = callerScript;
+ this.source = blocks;
+ } finally {
+ Engine.putThreadEngine(je);
+ }
}
/**
diff --git a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
index a39e4ffc..4c8500ec 100644
--- a/src/test/java/org/apache/commons/jexl3/Issues400Test.java
+++ b/src/test/java/org/apache/commons/jexl3/Issues400Test.java
@@ -1258,5 +1258,82 @@ public class Issues400Test {
pw.flush();
assertTrue(sw.toString().contains("hello"));
}
+
+ @Test
+ void test462a() {
+ // JEXL-462: under RESTRICTED, java.io.PrintWriter and
java.util.Formatter are reachable and their methods are allowed,
+ // but their constructors are denied - they cannot be instantiated
from a script.
+ final JexlEngine jexl = new
JexlBuilder().silent(false).permissions(RESTRICTED).create();
+ for (final String src : new String[] {
+ "new('java.io.PrintWriter', '/tmp/pwn0.php')",
+ "import java.io.PrintWriter; new PrintWriter('/tmp/pwn1.php')"}) {
+ final JexlScript script = jexl.createScript(src);
+ final JexlException.Method xctor =
assertThrows(JexlException.Method.class,
+ () -> script.execute(null), () -> "PrintWriter::new should be
denied: " + src);
+ assertTrue(xctor.getMethod().contains("PrintWriter"));
+ }
+ // a PrintWriter method (println) is allowed on a provided instance
+ StringWriter sw = new StringWriter();
+ final java.io.PrintWriter pw = new java.io.PrintWriter(sw);
+ jexl.createScript("p.println('hello')", "p").execute(null, pw);
+ pw.flush();
+ assertTrue(sw.toString().contains("hello"));
+ for (final String src : new String[] {
+ "new('java.util.Formatter', '/tmp/pwn2.php')",
+ "import java.util.Formatter; new Formatter('/tmp/pwn3.php')" }) {
+ final JexlScript script = jexl.createScript(src);
+ final JexlException.Method xctor =
assertThrows(JexlException.Method.class,
+ () -> script.execute(null), () -> "Formatter::new should be
denied: " + src);
+ assertTrue(xctor.getMethod().contains("Formatter"));
+ }
+
+ // a PrintWriter method (println) is allowed on a provided instance
+ sw = new StringWriter();
+ final java.util.Formatter pf = new java.util.Formatter(sw);
+ jexl.createScript("pf.format('%s', 'bonjour')", "pf").execute(null,
pf);
+ assertTrue(sw.toString().contains("bonjour"));
+ }
+
+ @Test
+ void test463() {
+ final JexlEngine jexl = new JexlBuilder()
+ .silent(false)
+ .features(JexlFeatures.createDefault())
+ .permissions(RESTRICTED)
+ .create();
+ String src = "addOne = (f) -> {\n return f + 1;\n}";
+ JexlScript script = jexl.createScript(src);
+ assertNotNull(script);
+ JexlContext ctxt = new MapContext();
+ Object result = script.execute(ctxt);
+ assertNotNull(result);
+ JexlScript rs = (JexlScript) result;
+ result = rs.execute(ctxt, 1);
+ assertEquals(2, result);
+ }
+
+ @Test
+ void test466() {
+ final JexlEngine jexl = new JexlBuilder()
+ .silent(false)
+ .features(JexlFeatures.createDefault())
+ .permissions(RESTRICTED)
+ .create();
+ JxltEngine jxlt = jexl.createJxltEngine();
+
+ String src = "$$ const a = 'a';\n" + "$$ const b = `${a}` \n" +
"b:${b}";
+ JxltEngine.Template template = jxlt.createTemplate(src);
+ StringWriter writer = new StringWriter();
+ template.evaluate(null, writer);
+ assertEquals("b:a", writer.toString());
+
+ String expr = "forty-two: ${b + a}";
+ JexlContext ctxt = new MapContext();
+ ctxt.set("b", 20);
+ ctxt.set("a", 22);
+ JxltEngine.Expression expression = jxlt.createExpression(expr);
+ Object value = expression.evaluate(ctxt);
+ assertEquals("forty-two: 42", value.toString());
+ }
}