You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@commons.apache.org by he...@apache.org on 2023/03/09 23:01:02 UTC

[commons-jexl] branch master updated: JEXL: getting ready for 3.3; - doc, expose 3.3 release notes to be clear about what can break on upgrade

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 <he...@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 ( &gt;&gt;&gt;, &gt;&gt;, &lt;&lt;)</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(