This is an automated email from the ASF dual-hosted git repository.

pradeepagrawal8184 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ranger.git


The following commit(s) were added to refs/heads/master by this push:
     new 85b8eca06 RANGER-5629: Harden GraalJS engine configuration used for 
policy condition (_expression) evaluation (#1002)
85b8eca06 is described below

commit 85b8eca0693208d435546d372edaf037a04b4c15
Author: Vyom Mani Tiwari <[email protected]>
AuthorDate: Wed Jun 17 17:29:07 2026 +0530

    RANGER-5629: Harden GraalJS engine configuration used for policy condition 
(_expression) evaluation (#1002)
    
    * RANGER-5629: Harden GraalJS engine configuration used for policy 
condition (_expression) evaluation
    
    * RANGER-5629: changed the local variable name to address review comment
---
 .../plugin/util/GraalScriptEngineCreator.java      | 172 ++++++++-------------
 .../RangerRequestScriptEvaluatorTest.java          |  60 +++++++
 2 files changed, 125 insertions(+), 107 deletions(-)

diff --git 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/GraalScriptEngineCreator.java
 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/GraalScriptEngineCreator.java
index 6fafa1039..983c0ddf7 100644
--- 
a/agents-common/src/main/java/org/apache/ranger/plugin/util/GraalScriptEngineCreator.java
+++ 
b/agents-common/src/main/java/org/apache/ranger/plugin/util/GraalScriptEngineCreator.java
@@ -19,72 +19,61 @@
 
 package org.apache.ranger.plugin.util;
 
-import org.apache.commons.lang3.StringUtils;
-import org.apache.hadoop.conf.Configuration;
-import org.apache.hadoop.fs.Path;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.script.Bindings;
-import javax.script.ScriptContext;
 import javax.script.ScriptEngine;
-import javax.script.ScriptEngineManager;
 
-import java.io.File;
-import java.io.FilenameFilter;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static java.util.Objects.requireNonNull;
+import java.lang.reflect.Executable;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.function.Predicate;
 
 public class GraalScriptEngineCreator implements ScriptEngineCreator {
     private static final Logger LOG = 
LoggerFactory.getLogger(GraalScriptEngineCreator.class);
 
-    private static final String ENGINE_NAME            = "graal.js";
-    private static final String CONFIG_PREDIX_JVM      = "polyglot";
-    private static final String CONFIG_PREDIX_PLUGIN   = 
"ranger.plugin.script.";
-    private static final String CONFIG_JAVA_CLASS_PATH = "java.class.path";
-
-    // instance variables
-    private final Map<String, Boolean> graalVmConfigs = new HashMap<>();
+    private static final String ENGINE_NAME = "graal.js";
+    private static final String CLS_HOST_ACCESS = 
"org.graalvm.polyglot.HostAccess";
+    private static final String CLS_HOST_ACCESS_BUILDER = 
"org.graalvm.polyglot.HostAccess$Builder";
+    private static final String CLS_CONTEXT = "org.graalvm.polyglot.Context";
+    private static final String CLS_CONTEXT_BUILDER = 
"org.graalvm.polyglot.Context$Builder";
+    private static final String CLS_ENGINE = "org.graalvm.polyglot.Engine";
+    private static final String CLS_GRAAL_JS_ENGINE = 
"com.oracle.truffle.js.scriptengine.GraalJSScriptEngine";
+
+    // Script-API classes whose public instance methods are exported to 
scripts.
+    // getDeclaredMethods() returns only methods declared directly in each 
class,
+    // NOT inherited ones (e.g. Object.getClass()) — so the reflection chain is
+    // closed without needing @HostAccess.Export annotations on those classes.
+    private static final String[] SCRIPT_API_CLASSES = new String[] {
+            
"org.apache.ranger.plugin.policyengine.RangerRequestScriptEvaluator",
+            "org.apache.ranger.plugin.contextenricher.RangerTagForEval"
+    };
+    private final Method createMethod;
+    private final Object ctxBuilder;
 
-    // default constructor
     public GraalScriptEngineCreator() {
-        Map<String, Boolean> graalVmConfigsDefault = new HashMap<>(4); 
//setting smallest size, which is big enough to avoid expand
-        Configuration        configuration         = new Configuration();
-        FilenameFilter       fileNameFilter        = (dir, name) -> 
name.startsWith("ranger-") && name.endsWith("security.xml");
-
-        graalVmConfigsDefault.put("polyglot.js.allowHostAccess", 
Boolean.TRUE); //default is true for backward(Nashorn) compatibility
-        graalVmConfigsDefault.put("polyglot.js.nashorn-compat", Boolean.TRUE); 
 //default is true for backward(Nashorn) compatibility
-
-        for (String file : findFiles(fileNameFilter)) {
-            configuration.addResource(new Path(file));
+        Method createMethod = null;
+        Object builder = null;
+        try {
+            Object hostAccess = buildHostAccess();
+            builder = buildContextBuilder(hostAccess);
+            Class<?> engineCls = Class.forName(CLS_ENGINE);
+            Class<?> graalJsCls = Class.forName(CLS_GRAAL_JS_ENGINE);
+            Class<?> ctxBldCls = Class.forName(CLS_CONTEXT_BUILDER);
+            createMethod = graalJsCls.getMethod("create", engineCls, 
ctxBldCls);
+        } catch (Throwable t) {
+            LOG.warn("GraalScriptEngineCreator(): failed to initialize", t);
+        } finally {
+            this.createMethod = createMethod;
+            this.ctxBuilder = builder;
         }
-
-        graalVmConfigs.putAll(getGraalVmConfigs(configuration, 
graalVmConfigsDefault));
     }
 
     public ScriptEngine getScriptEngine(ClassLoader clsLoader) {
         ScriptEngine ret = null;
-
-        if (clsLoader == null) {
-            clsLoader = getDefaultClassLoader();
-        }
-
         try {
-            ScriptEngineManager mgr = new ScriptEngineManager(clsLoader);
-
-            ret = mgr.getEngineByName(ENGINE_NAME);
-
-            if (ret != null) {
-                // enable configured script features
-                Bindings bindings = 
ret.getBindings(ScriptContext.ENGINE_SCOPE);
-                bindings.putAll(graalVmConfigs);
-                ret.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+            if (createMethod != null && ctxBuilder != null) {
+                ret = (ScriptEngine) createMethod.invoke(null, null, 
ctxBuilder);
             }
         } catch (Throwable t) {
             LOG.debug("GraalScriptEngineCreator.getScriptEngine(): failed to 
create engine type {}", ENGINE_NAME, t);
@@ -96,68 +85,37 @@ public ScriptEngine getScriptEngine(ClassLoader clsLoader) {
         return ret;
     }
 
-    private Map<String, Boolean> getGraalVmConfigs(Configuration 
configuration, Map<String, Boolean> graalVmConfigsDefault) {
-        LOG.debug("===>> GraalScriptEngineCreator.getGraalVmConfigs()");
-
-        Map<String, Boolean> ret = new HashMap<>();
-
-        // set configs from ranger security config values, if present
-        for (Map.Entry<String, String> entry : 
configuration.getPropsWithPrefix(CONFIG_PREDIX_PLUGIN).entrySet()) {
-            String key   = entry.getKey();
-            String value = entry.getValue();
-
-            if (StringUtils.isNotBlank(value)) {
-                ret.put(key, Boolean.valueOf(value));
-            }
-        }
-
-        // add JVM options if not already set
-        for (Map.Entry<Object, Object> entry : 
System.getProperties().entrySet()) {
-            if (entry.getKey().toString().startsWith(CONFIG_PREDIX_JVM)) {
-                String key   = entry.getKey().toString();
-                String value = entry.getValue().toString();
-
-                if (StringUtils.isNotBlank(value) && ret.get(key) == null) {
-                    ret.put(key, Boolean.valueOf(value));
+    private Object buildHostAccess() throws Exception {
+        Class<?> hostAccessCls = Class.forName(CLS_HOST_ACCESS);
+        Class<?> haBuilderCls = Class.forName(CLS_HOST_ACCESS_BUILDER);
+        Object haBuilder = hostAccessCls.getMethod("newBuilder").invoke(null);
+        Method allowAccessMethod = haBuilderCls.getMethod("allowAccess", 
Executable.class);
+        for (String className : SCRIPT_API_CLASSES) {
+            try {
+                for (Method m : Class.forName(className).getDeclaredMethods()) 
{
+                    if (Modifier.isPublic(m.getModifiers()) && 
!Modifier.isStatic(m.getModifiers())) {
+                        allowAccessMethod.invoke(haBuilder, m);
+                    }
                 }
+            } catch (ClassNotFoundException e) {
+                LOG.warn("GraalScriptEngineCreator.buildHostAccess(): class 
not found: {}", className);
             }
         }
-
-        // add default values if not already set
-        for (Map.Entry<String, Boolean> entry : 
graalVmConfigsDefault.entrySet()) {
-            String  key   = entry.getKey();
-            Boolean value = entry.getValue();
-
-            ret.putIfAbsent(key, value);
-        }
-
-        LOG.debug("<<=== GraalScriptEngineCreator.getGraalVmConfigs(): 
ret={}", ret);
-
-        return ret;
+        return haBuilderCls.getMethod("build").invoke(haBuilder);
     }
 
-    private Set<String> findFiles(FilenameFilter filenameFilter) {
-        String       classPath  = System.getProperty(CONFIG_JAVA_CLASS_PATH);
-        List<String> configDirs = new ArrayList<>(5);
-        Set<String>  ret        = new HashSet<>();
-
-        for (String path : classPath.split(":")) {
-            if (!path.endsWith("jar")) {  //ignore jars
-                if (path.endsWith("/")) {
-                    path = path.substring(0, path.length() - 1);
-                }
-                configDirs.add(path);
-            }
-        }
-
-        for (String configDir : configDirs) {
-            File confDir = new File(configDir);
-            if (confDir.isDirectory()) {
-                for (File file : 
requireNonNull(confDir.listFiles(filenameFilter))) {
-                    ret.add(file.getAbsolutePath());
-                }
-            }
-        }
-        return ret;
+    private Object buildContextBuilder(Object hostAccess) throws Exception {
+        Class<?> hostAccessCls = Class.forName(CLS_HOST_ACCESS);
+        Class<?> contextCls = Class.forName(CLS_CONTEXT);
+        Class<?> ctxBuilderCls = Class.forName(CLS_CONTEXT_BUILDER);
+        Object builder = contextCls.getMethod("newBuilder", 
String[].class).invoke(null, new Object[] {new String[] {"js"}});
+        builder = ctxBuilderCls.getMethod("allowExperimentalOptions", 
boolean.class).invoke(builder, true);
+        builder = ctxBuilderCls.getMethod("allowAllAccess", 
boolean.class).invoke(builder, false);
+        builder = ctxBuilderCls.getMethod("allowHostAccess", 
hostAccessCls).invoke(builder, hostAccess);
+        builder = ctxBuilderCls.getMethod("allowHostClassLookup", 
Predicate.class).invoke(builder, (Predicate<String>) s -> false);
+        builder = ctxBuilderCls.getMethod("allowHostClassLoading", 
boolean.class).invoke(builder, false);
+        builder = ctxBuilderCls.getMethod("allowCreateThread", 
boolean.class).invoke(builder, false);
+        builder = ctxBuilderCls.getMethod("allowNativeAccess", 
boolean.class).invoke(builder, false);
+        return builder;
     }
 }
diff --git 
a/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerRequestScriptEvaluatorTest.java
 
b/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerRequestScriptEvaluatorTest.java
index c84c18781..a19abe88e 100644
--- 
a/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerRequestScriptEvaluatorTest.java
+++ 
b/agents-common/src/test/java/org/apache/ranger/plugin/conditionevaluator/RangerRequestScriptEvaluatorTest.java
@@ -39,6 +39,7 @@
 import javax.script.ScriptEngineManager;
 
 import java.io.File;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
@@ -46,6 +47,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -555,6 +557,64 @@ public void testGetAllTagTypes() {
         Assertions.assertEquals(new HashSet<>(Arrays.asList("PCI", "PII")), 
evaluator.evaluateScript("ctx.getAllTagTypes()"));
     }
 
+    @Test
+    void testBlockJavaClassReferencesWithDefaultConfig() {
+        RangerRequestScriptEvaluator evaluator = createEvaluator();
+        long nonce = System.nanoTime();
+
+        String mJavaType = tempFilePath("javatype", nonce);
+        String mPackages = tempFilePath("packages", nonce);
+        String mCtxRef = tempFilePath("ctxref", nonce);
+        String mRetRef = tempFilePath("retref", nonce);
+        String mTagRef = tempFilePath("tagref", nonce);
+        String mTagAttr = tempFilePath("tagattr", nonce);
+        String mNewFile = tempFilePath("newfile", nonce);
+
+        Function<String, String> reflectExec = marker ->
+                "var Str   = 
OBJ.getClass().getClassLoader().loadClass('java.lang.String');"
+                        + "var rtClz = 
OBJ.getClass().getClassLoader().loadClass('java.lang.Runtime');"
+                        + "var rt    = 
rtClz.getMethod('getRuntime').invoke(null);"
+                        + "rtClz.getMethod('exec', Str).invoke(rt, 'touch " + 
marker + "').waitFor();";
+
+        Map<String, String[]> vectors = new HashMap<>();
+        vectors.put("Java.type('java.lang.Runtime').exec()", new String[] {
+                "var p = 
Java.type('java.lang.Runtime').getRuntime().exec('touch " + mJavaType + "'); 
p.waitFor();", mJavaType});
+        vectors.put("java.lang.Runtime (Packages namespace)", new String[] {
+                "var p = java.lang.Runtime.getRuntime().exec('touch " + 
mPackages + "'); p.waitFor();", mPackages});
+        vectors.put("reflection off ctx", new String[] {
+                reflectExec.apply(mCtxRef).replace("OBJ", "ctx"), mCtxRef});
+        vectors.put("reflection off returned ctx.getCurrentTag()", new 
String[] {
+                reflectExec.apply(mRetRef).replace("OBJ", 
"ctx.getCurrentTag()"), mRetRef});
+        vectors.put("reflection off bound 'tag'", new String[] {
+                reflectExec.apply(mTagRef).replace("OBJ", "tag"), mTagRef});
+        vectors.put("reflection off bound 'tagAttr'", new String[] {
+                reflectExec.apply(mTagAttr).replace("OBJ", "tagAttr"), 
mTagAttr});
+        vectors.put("constructor new java.io.File().createNewFile()", new 
String[] {
+                "var f = new java.io.File('" + mNewFile + "'); 
f.createNewFile();", mNewFile});
+
+        List<String> escaped = new ArrayList<>();
+        for (Map.Entry<String, String[]> e : vectors.entrySet()) {
+            File m = new File(e.getValue()[1]);
+            m.delete(); // Just to make sure file does not exist before the 
script runs. This is a clean-state guarantee
+            evaluator.evaluateScript(e.getValue()[0]); // returns null when 
blocked; never rethrows
+            if (m.exists()) { // The file existing after eval is the ground 
truth that the OS command actually ran
+                escaped.add(e.getKey() + "  -> created " + m);
+                m.delete();
+            }
+        }
+        Assertions.assertTrue(escaped.isEmpty(), "Sandbox escape OS command 
executed via:\n  " + String.join("\n  ", escaped));
+    }
+
+    private RangerRequestScriptEvaluator createEvaluator() {
+        RangerAccessRequest request = createRequest("test-user", 
Collections.emptySet(), Collections.emptySet(),
+                Collections.singletonList(new RangerTag("PII", 
Collections.singletonMap("attr1", "v1"))));
+        return new RangerRequestScriptEvaluator(request, scriptEngine, false);
+    }
+
+    private static String tempFilePath(String tag, long nonce) {
+        return new File(System.getProperty("java.io.tmpdir"), "ranger_test_" + 
tag + "_" + nonce).getAbsolutePath();
+    }
+
     RangerAccessRequest createRequest(String userName, Set<String> userGroups, 
Set<String> userRoles, List<RangerTag> resourceTags) {
         RangerAccessResource resource = mock(RangerAccessResource.class);
 

Reply via email to