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 84f4e885 JEXL: getting ready for 3.3; - doc, expose 3.3 release notes to be clear about what can break on upgrade 84f4e885 is described below commit 84f4e88531b5d3e242fbefbefc5164669d168ada Author: henrib <hen...@apache.org> AuthorDate: Fri Mar 10 00:00:54 2023 +0100 JEXL: getting ready for 3.3; - doc, expose 3.3 release notes to be clear about what can break on upgrade --- RELEASE-NOTES.txt | 11 +++ .../jexl3/introspection/JexlPermissions.java | 75 ++++++++++++++- .../commons/jexl3/scripting/JexlScriptEngine.java | 30 ++++-- src/site/site.xml | 3 +- src/site/xdoc/relnotes33.xml | 106 +++++++++++++++++++++ .../org/apache/commons/jexl3/ClassPermissions.java | 77 --------------- .../apache/commons/jexl3/examples/StreamTest.java | 3 +- .../jexl3/scripting/JexlScriptEngineTest.java | 8 +- 8 files changed, 220 insertions(+), 93 deletions(-) diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt index 1021cb07..043edcdb 100644 --- a/RELEASE-NOTES.txt +++ b/RELEASE-NOTES.txt @@ -27,6 +27,17 @@ Version 3.3 is a minor release. Compatibility with previous releases ==================================== Version 3.3 is source and binary compatible with 3.2. +However, the default setting for permissions that determine which packages, classes and methods are accessible +to scripts has been reduced to a very narrow set. When migrating from previous version of JEXL, this may result +in breaking your application behavior ; this breaking change requires remediation in your code. +Despite the obvious inconvenience - our sincere apologies on the matter -, how much functional and semantic power is +accessible through scripts has a real impact on your application security and stability ; that potential risk requires +an informed review and conscious choice on your end. +To mitigate the change, you can revert to the previous behavior with one line of code (see JexlBuilder) or use this +opportunity to reduce exposure. Whether Files, URLs, networking, processes, class-loaders or reflection features are +accessible are part of the choice to make. +Any CVE linked to JEXL should try resolution by upgrading to 3.3. + What's new in 3.3: ================== diff --git a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java index c4b7d276..d405b6cd 100644 --- a/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java +++ b/src/main/java/org/apache/commons/jexl3/introspection/JexlPermissions.java @@ -22,6 +22,12 @@ import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; /** * This interface describes permissions used by JEXL introspection that constrain which @@ -39,10 +45,22 @@ import java.lang.reflect.Modifier; * processed.</p> * <p>A simple textual configuration can be used to create user defined permissions using * {@link JexlPermissions#parse(String...)}.</p> + * *<p>To instantiate a JEXL engine using permissions, one should use a {@link org.apache.commons.jexl3.JexlBuilder} * and call {@link org.apache.commons.jexl3.JexlBuilder#permissions(JexlPermissions)}. Another approach would * be to instantiate a {@link JexlUberspect} with those permissions and call * {@link org.apache.commons.jexl3.JexlBuilder#uberspect(JexlUberspect)}.</p> + * + * <p> + * To help migration from earlier versions, it is possible to revert to the JEXL 3.2 default lenient behavior + * by calling {@link org.apache.commons.jexl3.JexlBuilder#setDefaultPermissions(JexlPermissions)} with + * {@link #UNRESTRICTED} as parameter. + * </p> + * <p> + * For the same reason, using JEXL through scripting, it is possible to revert the underlying JEXL behaviour to + * JEXL 3.2 default by calling {@link org.apache.commons.jexl3.scripting.JexlScriptEngine#setPermissions(JexlPermissions)} + * with {@link #UNRESTRICTED} as parameter. + * </p> * @since 3.3 */ public interface JexlPermissions { @@ -315,7 +333,7 @@ public interface JexlPermissions { } /** - * A base for permission delegation allowing greater functional malleability. + * A base for permission delegation allowing functional refinement. * Overloads should call the appropriate validate() method early in their body. */ class Delegate implements JexlPermissions { @@ -356,4 +374,59 @@ public interface JexlPermissions { return new Delegate(base.compose(src)); } } + + /** + * A permission delegation that augments the RESTRICTED permission with an explicit + * set of classes. + * <p>Typical use case is to deny access to a package - and thus all its classes - but allow + * a few specific classes.</p> + */ + class ClassPermissions extends JexlPermissions.Delegate { + /** The set of explicitly allowed classes, overriding the delegate permissions. */ + private final Set<String> allowedClasses; + + /** + * Creates permissions based on the RESTRICTED set but allowing an explicit set. + * @param allow the set of allowed classes + */ + public ClassPermissions(Class... allow) { + this(JexlPermissions.RESTRICTED, + Arrays.asList(Objects.requireNonNull(allow)) + .stream().map(Class::getCanonicalName).collect(Collectors.toList())); + } + + /** + * Required for compose(). + * @param delegate the base to delegate to + * @param allow the list of class canonical names + */ + public ClassPermissions(JexlPermissions delegate, Collection<String> allow) { + super(Objects.requireNonNull(delegate)); + allowedClasses = new HashSet<>(Objects.requireNonNull(allow)); + } + + private boolean isClassAllowed(Class<?> clazz) { + return allowedClasses.contains(clazz.getCanonicalName()); + } + + @Override + public boolean allow(Class<?> clazz) { + return (validate(clazz) && isClassAllowed(clazz)) || super.allow(clazz); + } + + @Override + public boolean allow(Method method) { + return (validate(method) && isClassAllowed(method.getDeclaringClass())) || super.allow(method); + } + + @Override + public boolean allow(Constructor constructor) { + return (validate(constructor) && isClassAllowed(constructor.getDeclaringClass())) || super.allow(constructor); + } + + @Override + public JexlPermissions compose(String... src) { + return new ClassPermissions(base.compose(src), allowedClasses); + } + } } diff --git a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java index 56642fd4..c9dfeb27 100644 --- a/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java +++ b/src/main/java/org/apache/commons/jexl3/scripting/JexlScriptEngine.java @@ -72,11 +72,30 @@ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable */ private static Reference<JexlEngine> ENGINE = null; + /** + * The permissions used to create the script engine. + */ + private static JexlPermissions PERMISSIONS = null; + /** + * Sets the permissions instance used to create the script engine. + * <p>Calling this method will force engine instance re-creation.</p> + * <p>To restore 3.2 script behavior:</p> + * <code> + * JexlScriptEngine.setPermissions(JexlPermissions.UNRESTRICTED); + * </code> + * @param permissions the permissions instance to use or null to use the {@link JexlBuilder} default + * @since 3.3 + */ + public static void setPermissions(final JexlPermissions permissions) { + PERMISSIONS = permissions; + ENGINE = null; // will force recreation + } + /** * Sets the shared instance used for the script engine. * <p>This should be called early enough to have an effect, ie before any * {@link javax.script.ScriptEngineManager} features.</p> - * <p>To restore 3.2 script permeability:</p> + * <p>To restore 3.2 script behavior:</p> * <code> * JexlScriptEngine.setInstance(new JexlBuilder() * .cache(512) @@ -84,12 +103,6 @@ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable * .permissions(JexlPermissions.UNRESTRICTED) * .create()); * </code> - * <p>Alternatively, setting the default {@link JexlBuilder#setDefaultPermissions(JexlPermissions)} using - * {@link org.apache.commons.jexl3.introspection.JexlPermissions#UNRESTRICTED} will also restore JEXL 3.2 - * behavior.</p> - * <code> - * JexlBuilder.setDefaultPermissions(JexlPermissions.UNRESTRICTED); - * </code> * @param engine the JexlEngine instance to use * @since 3.3 */ @@ -111,6 +124,9 @@ public class JexlScriptEngine extends AbstractScriptEngine implements Compilable .safe(false) .logger(JexlScriptEngine.LOG) .cache(JexlScriptEngine.CACHE_SIZE); + if (PERMISSIONS != null ) { + builder.permissions(PERMISSIONS); + } engine = builder.create(); ENGINE = new SoftReference<>(engine); } diff --git a/src/site/site.xml b/src/site/site.xml index 17b337dc..eec9a22f 100644 --- a/src/site/site.xml +++ b/src/site/site.xml @@ -25,7 +25,8 @@ <body> <menu name="JEXL"> <item name="Overview" href="index.html" /> - <item name="Javadoc 3.2.1" href="apidocs/index.html"/> + <item name="JEXL 3.3 Release notes" href="relnotes33.html"/> + <item name="Javadoc 3.3" href="apidocs/index.html"/> <item name="Javadoc 2.1.1" href="javadocs/apidocs-2.1.1/index.html"/> <item name="Javadoc 1.1" href="javadocs/apidocs-1.1/index.html"/> <item name="Download" href="download_jexl.cgi"/> diff --git a/src/site/xdoc/relnotes33.xml b/src/site/xdoc/relnotes33.xml new file mode 100644 index 00000000..982cfa89 --- /dev/null +++ b/src/site/xdoc/relnotes33.xml @@ -0,0 +1,106 @@ +<?xml version="1.0"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You under the Apache License, Version 2.0 + (the "License"); you may not use this file except in compliance with + the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +--> + +<document> + <properties> + <title>Apache Commons JEXL 3.3 Release Notes</title> + </properties> + + <body> + <section name="Compatibility with previous releases"> + <p> + Version 3.3 is source and binary compatible with 3.2. + </p><p> + However, the default setting for permissions that determine which packages, classes and methods are accessible + to scripts has been reduced to a very narrow set. When migrating from previous version of JEXL, this may result + in breaking your application behavior ; this breaking change requires remediation in your code. + </p><p> + Despite the obvious inconvenience - our sincere apologies on the matter -, how much functional and semantic power is + accessible through scripts has a real impact on your application security and stability ; that potential risk requires + an informed review and conscious choice on your end. + </p><p> + To mitigate the change, you can revert to the previous behavior with one line of code + (see <a href="apidocs/org/apache/commons/jexl3/introspection/JexlPermissions.html">JexlPermissions</a>, + <a href="apidocs/org/apache/commons/jexl3/JexlBuilder.html">JexlBuilder</a> and + <a href="apidocs/org/apache/commons/jexl3/scripting/JexlScriptEngine.html">JexlScriptEngine</a> ) or use this + opportunity to reduce exposure. Whether Files, URLs, networking, processes, class-loaders or reflection features are + accessible are part of the choice to make.</p><p> + <strong>Any CVE linked to JEXL should try resolution by first upgrading to JEXL 3.3.</strong> + </p> + </section> + <section name="What's new in 3.3:"> + <p> + JEXL 3.3 brings the ability to configure permissions on libraries in the manner pioneered + with the @NoJexl annotation on source code. This is achieved through a crude but light mechanism akin to + a security manager that controls what JEXL can introspect and thus expose to scripts. + </p><p> + Used in conjunction with options (JexlOptions) and features (JexlFeatures), the permissions (JexlPermissions) + allow fine-tuning the scripting integration into any project. + </p><p> + JEXL 3.3 also adds some syntactic (ECMAScript) features (let, const, =>, for, ...) to further reduce + the skill set required to write scripts. + </p> + </section> + + <section name="New Features in 3.3:"> + <p> + <table> + <tr><td>JEXL-392:</td><td>Enable namespace declaration based on scripts</td></tr> + <tr><td>JEXL-391:</td><td>Improve in/=~ operator when arguments are arrays and collections</td></tr> + <tr><td>JEXL-390:</td><td>Pragmas should not be statements</td></tr> + <tr><td>JEXL-389:</td><td>Improve parsing timings</td></tr> + <tr><td>JEXL-385:</td><td>Support disabling fortran-style relational operators syntax</td></tr> + <tr><td>JEXL-382:</td><td>Simplify grammar and lexical state management</td></tr> + <tr><td>JEXL-380:</td><td>Multiple values per pragma key</td></tr> + <tr><td>JEXL-379:</td><td>Allow new to use class identifier</td></tr> + <tr><td>JEXL-373:</td><td>Add support for prefix/postfix increment/decrement operators</td></tr> + <tr><td>JEXL-372:</td><td>Add support for 'standard' for loop</td></tr> + <tr><td>JEXL-369:</td><td>Add 'let' and 'const' variable declarations</td></tr> + <tr><td>JEXL-367:</td><td>Named function and fat-arrow (=>) lambda syntax</td></tr> + <tr><td>JEXL-366:</td><td>Fail to evaluate string and number comparison</td></tr> + <tr><td>JEXL-365:</td><td>Lambda expressions</td></tr> + <tr><td>JEXL-363:</td><td>Allow retrieving captured variables in script</td></tr> + <tr><td>JEXL-360:</td><td>Add missing bitshift operators ( >>>, >>, <<)</td></tr> + <tr><td>JEXL-359:</td><td>Allow per-operator arithmetic handling of null arguments</td></tr> + <tr><td>JEXL-357:</td><td>Configure accessible packages/classes/methods/fields</td></tr> + </table> + </p> + </section> + + <section name="Bugs Fixed in 3.3:"> + <p> + <table> + <tr><td>JEXL-386:</td><td>Non-inheritable permissions on interfaces are ignored in an inheritable sandbox</td></tr> + <tr><td>JEXL-384:</td><td>Improve control over JexlArithmetic null argument handling</td></tr> + <tr><td>JEXL-378:</td><td>Incremental operator and decremental operator do not honor the side-effect flag</td></tr> + <tr><td>JEXL-376:</td><td>Introspector captures methods on non-exported classes (modules, java9+)</td></tr> + <tr><td>JEXL-375:</td><td>Cannot access enums by their name when using sandbox</td></tr> + <tr><td>JEXL-374:</td><td>No exception if dereferencing null object using safe(false) and antish(false)</td></tr> + <tr><td>JEXL-371:</td><td>Override of a protected method with public visibility is not callable</td></tr> + <tr><td>JEXL-370:</td><td>Cannot check if variable is defined using ObjectContext if the value is null</td></tr> + <tr><td>JEXL-368:</td><td>Namespace functor resolution is not cached</td></tr> + <tr><td>JEXL-364:</td><td>Evaluator options not propagated in closures</td></tr> + <tr><td>JEXL-362:</td><td>JexlInfo position reporting is off</td></tr> + <tr><td>JEXL-361:</td><td>Null may be used as operand silently even in arithmetic strict(true) mode</td></tr> + <tr><td>JEXL-354:</td><td>#pragma does not handle negative integer or real literals</td></tr> + <tr><td>JEXL-353:</td><td>Documentation error for not-in/not-match operator</td></tr> + </table> + </p> + </section> + </body> +</document> diff --git a/src/test/java/org/apache/commons/jexl3/ClassPermissions.java b/src/test/java/org/apache/commons/jexl3/ClassPermissions.java deleted file mode 100644 index 0484dfe5..00000000 --- a/src/test/java/org/apache/commons/jexl3/ClassPermissions.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.commons.jexl3; - -import org.apache.commons.jexl3.introspection.JexlPermissions; - -import java.lang.reflect.Method; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -/** - * An example of permission delegation that augments the RESTRICTED permission with an explicit - * set of classes. - * <p>Typical use case is to deny access to a package - and thus all its classes - but allow - * a few specific classes.</p> - */ -public class ClassPermissions extends JexlPermissions.Delegate { - /** The set of explicitly allowed classes, overriding the delegate permissions. */ - private final Set<String> allowedClasses; - - /** - * Creates permissions based on the RESTRICTED set but allowing an explicit set. - * @param allow the set of allowed classes - */ - public ClassPermissions(Class... allow) { - this(JexlPermissions.RESTRICTED, - Arrays.asList(Objects.requireNonNull(allow)) - .stream().map(Class::getCanonicalName) .collect(Collectors.toList())); - } - - /** - * Required for compose(). - * @param delegate the base to delegate to - * @param allow the list of class canonical names - */ - public ClassPermissions(JexlPermissions delegate, Collection<String> allow) { - super(delegate); - allowedClasses = new HashSet<>(Objects.requireNonNull(allow)); - } - - private boolean isClassAllowed(Class<?> clazz) { - return allowedClasses.contains(clazz.getCanonicalName()); - } - - @Override - public boolean allow(Class<?> clazz) { - return (validate(clazz) && isClassAllowed(clazz)) || super.allow(clazz); - } - - @Override - public boolean allow(Method method) { - return (validate(method) && isClassAllowed(method.getDeclaringClass())) || super.allow(method); - } - - @Override - public JexlPermissions compose(String... src) { - return new ClassPermissions(base.compose(src), allowedClasses); - } -} diff --git a/src/test/java/org/apache/commons/jexl3/examples/StreamTest.java b/src/test/java/org/apache/commons/jexl3/examples/StreamTest.java index e5fb7fe2..57ee335a 100644 --- a/src/test/java/org/apache/commons/jexl3/examples/StreamTest.java +++ b/src/test/java/org/apache/commons/jexl3/examples/StreamTest.java @@ -16,8 +16,6 @@ */ package org.apache.commons.jexl3.examples; -import org.apache.commons.jexl3.ClassPermissions; -import org.apache.commons.jexl3.JexlArithmetic; import org.apache.commons.jexl3.JexlBuilder; import org.apache.commons.jexl3.JexlContext; import org.apache.commons.jexl3.JexlEngine; @@ -25,6 +23,7 @@ import org.apache.commons.jexl3.JexlFeatures; import org.apache.commons.jexl3.JexlScript; import org.apache.commons.jexl3.MapContext; import org.apache.commons.jexl3.introspection.JexlPermissions; +import org.apache.commons.jexl3.introspection.JexlPermissions.ClassPermissions; import org.junit.Assert; import org.junit.Test; diff --git a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java index e119eb65..9a38936c 100644 --- a/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java +++ b/src/test/java/org/apache/commons/jexl3/scripting/JexlScriptEngineTest.java @@ -55,6 +55,7 @@ public class JexlScriptEngineTest { public void tearDown() { JexlBuilder.setDefaultPermissions(null); JexlScriptEngine.setInstance(null); + JexlScriptEngine.setPermissions(null); } @Test @@ -125,12 +126,8 @@ public class JexlScriptEngineTest { @Test public void testScriptingInstance0() throws Exception { + JexlScriptEngine.setPermissions(JexlPermissions.UNRESTRICTED); final ScriptEngineManager manager = new ScriptEngineManager(); - JexlScriptEngine.setInstance(new JexlBuilder() - .cache(512) - .logger(LogFactory.getLog(JexlScriptEngine.class)) - .permissions(JexlPermissions.UNRESTRICTED) - .create()); final ScriptEngine engine = manager.getEngineByName("jexl3"); Long time2 = (Long) engine.eval( "sys=context.class.forName(\"java.lang.System\");" @@ -141,6 +138,7 @@ public class JexlScriptEngineTest { @Test public void testScriptingPermissions1() throws Exception { JexlBuilder.setDefaultPermissions(JexlPermissions.UNRESTRICTED); + JexlScriptEngine.setPermissions(null); final ScriptEngineManager manager = new ScriptEngineManager(); final ScriptEngine engine = manager.getEngineByName("jexl3"); Long time2 = (Long) engine.eval(