From 0d9c2b3c9ddfdcbd6774ed4bed30cb3ea3f81e3f Mon Sep 17 00:00:00 2001 From: contrueCT Date: Tue, 27 Oct 2026 02:36:05 +0800 Subject: [PATCH 1/2] fix(server): allow gRPC allocator property access - Scope the property exception to the actual gRPC allocator call. - Retain restrictions on direct Gremlin system-property access. - Cover both worker types and script denials with real gRPC tests. Refs #3412 --- .../security/HugeSecurityManager.java | 10 +- .../security/HugeSecurityManagerGrpcTest.java | 140 ++++++++++++++++++ 2 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 hugegraph-server/hugegraph-core/src/test/java/org/apache/hugegraph/security/HugeSecurityManagerGrpcTest.java diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/security/HugeSecurityManager.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/security/HugeSecurityManager.java index f435ea7b7f..7e9060a6d9 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/security/HugeSecurityManager.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/security/HugeSecurityManager.java @@ -318,7 +318,8 @@ public void checkPropertiesAccess() { public void checkPropertyAccess(String key) { if (!callFromAcceptClassLoaders() && callFromGremlin() && !WHITE_SYSTEM_PROPERTIES.contains(key) && !callFromBackendHbase() && - !callFromSnapshot() && !callFromRaft() && !callFromSofaRpc()) { + !callFromSnapshot() && !callFromRaft() && !callFromSofaRpc() && + !callFromGrpcNettyAllocator(key)) { throw newSecurityException("Not allowed to access system property(%s) via Gremlin", key); } @@ -411,6 +412,13 @@ private static boolean callFromSofaRpc() { return callFromWorkerWithClass(SOFA_RPC_CLASSES); } + private static boolean callFromGrpcNettyAllocator(String key) { + // gRPC reads this configuration on every new transport, even after + // initialization. Keep direct script reads and other properties denied. + return "io.grpc.netty.shaded.io.grpc.netty.useCustomAllocator".equals(key) && + callFromMethod("io.grpc.netty.shaded.io.grpc.netty.Utils", "getByteBufAllocator"); + } + private static boolean callFromNewSecurityException() { return callFromMethods(NEW_SECURITY_EXCEPTION); } diff --git a/hugegraph-server/hugegraph-core/src/test/java/org/apache/hugegraph/security/HugeSecurityManagerGrpcTest.java b/hugegraph-server/hugegraph-core/src/test/java/org/apache/hugegraph/security/HugeSecurityManagerGrpcTest.java new file mode 100644 index 0000000000..82ef958e48 --- /dev/null +++ b/hugegraph-server/hugegraph-core/src/test/java/org/apache/hugegraph/security/HugeSecurityManagerGrpcTest.java @@ -0,0 +1,140 @@ +/* + * 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.hugegraph.security; + +import java.lang.reflect.Method; + +import javax.script.Bindings; +import javax.script.ScriptException; + +import org.apache.hugegraph.util.Log; +import org.apache.tinkerpop.gremlin.groovy.jsr223.GremlinGroovyScriptEngine; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +@SuppressWarnings("removal") +public class HugeSecurityManagerGrpcTest { + + private static final String ALLOCATOR_PROPERTY = + "io.grpc.netty.shaded.io.grpc.netty.useCustomAllocator"; + + private static GremlinGroovyScriptEngine engine; + private static Bindings bindings; + + @BeforeClass + public static void init() throws Exception { + Log.logger(HugeSecurityManager.class); + Method method = Class.forName("io.grpc.netty.shaded.io.grpc.netty.Utils") + .getDeclaredMethod("getByteBufAllocator", boolean.class); + method.setAccessible(true); + engine = new GremlinGroovyScriptEngine(); + bindings = engine.createBindings(); + bindings.put("allocator", new AllocatorAccess(method)); + // Initialize gRPC outside the sandbox: the regression is a property + // read on every allocator call, including after initialization. + engine.eval("allocator.allocate(false)", bindings); + engine.eval("allocator.allocate(true)", bindings); + } + + @Test + public void testGrpcAllocatorFromGremlinWorker() throws Exception { + assertAllocatorAllowed("gremlin-server-exec-grpc-regression"); + } + + @Test + public void testGrpcAllocatorFromTaskWorker() throws Exception { + assertAllocatorAllowed("task-worker-grpc-regression"); + } + + @Test + public void testScriptCannotReadAllocatorProperty() { + assertDenied("System.getProperty('" + ALLOCATOR_PROPERTY + "')", + "system property(" + ALLOCATOR_PROPERTY + ")"); + } + + @Test + public void testScriptCannotReadSensitiveProperty() { + assertDenied("System.getProperty('user.home')", "system property(user.home)"); + } + + @Test + public void testScriptCannotReadOtherGrpcProperty() { + String key = "io.grpc.netty.shaded.io.grpc.netty.unrelated"; + assertDenied("System.getProperty('" + key + "')", "system property(" + key + ")"); + } + + @Test + public void testScriptCannotEnumerateProperties() { + assertDenied("System.getProperties()", "system properties"); + } + + @Test + public void testAllocatorAccessDoesNotAllowSubsequentScriptRead() { + assertDenied("allocator.allocate(false); System.getProperty('" + ALLOCATOR_PROPERTY + "')", + "system property(" + ALLOCATOR_PROPERTY + ")"); + } + + private static void assertAllocatorAllowed(String worker) throws Exception { + for (boolean heap : new boolean[]{false, true}) { + Object result = eval("allocator.allocate(" + heap + ")", worker); + Assert.assertTrue(String.valueOf(result).contains("ByteBufAllocator")); + } + } + + private static void assertDenied(String script, String property) { + Exception error = Assert.assertThrows(Exception.class, () -> + eval(script, "gremlin-server-exec-grpc-regression")); + Throwable cause = error; + while (cause.getCause() != null) { + cause = cause.getCause(); + } + Assert.assertTrue(cause.toString(), cause instanceof SecurityException); + Assert.assertEquals("Not allowed to access " + property + " via Gremlin", cause.getMessage()); + } + + private static Object eval(String script, String worker) throws ScriptException { + engine.compile(script); + Thread current = Thread.currentThread(); + String previousName = current.getName(); + SecurityManager previousManager = System.getSecurityManager(); + try { + current.setName(worker); + System.setSecurityManager(new HugeSecurityManager()); + // Calling the engine is essential: CompiledScript.eval() omits the + // engine frame that HugeSecurityManager uses to identify Gremlin. + return engine.eval(script, bindings); + } finally { + System.setSecurityManager(previousManager); + current.setName(previousName); + } + } + + public static class AllocatorAccess { + + private final Method method; + + public AllocatorAccess(Method method) { + this.method = method; + } + + public String allocate(boolean heap) throws Exception { + return this.method.invoke(null, heap).getClass().getName(); + } + } +} From 796789eed0fea54c4b9bfdb30c969dbfeacda6ad Mon Sep 17 00:00:00 2001 From: contrueCT Date: Tue, 27 Oct 2026 19:57:23 +0800 Subject: [PATCH 2/2] refactor(server): whitelist gRPC allocator flag - Replace internal gRPC stack matching with an exact property whitelist. - Permit direct flag reads while retaining other property restrictions. - Update real gRPC regressions and the runtime policy documentation. Refs #3412 --- docs/server-runtime.md | 9 +++++++++ .../security/HugeSecurityManager.java | 14 ++++--------- .../security/HugeSecurityManagerGrpcTest.java | 20 +++++++++++-------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/docs/server-runtime.md b/docs/server-runtime.md index 42acf3f347..872f83396f 100644 --- a/docs/server-runtime.md +++ b/docs/server-runtime.md @@ -378,6 +378,15 @@ without a preceding REST graph query, and subsequent REST queries must remain us Gremlin scripts still cannot read restricted system properties such as `java.version` or disable the security manager. +### HStore transport configuration + +HStore's gRPC transport reads the boolean configuration property +`io.grpc.netty.shaded.io.grpc.netty.useCustomAllocator` on every allocator invocation. +The server's existing property-read whitelist allows this exact, non-sensitive flag, +including direct reads from Gremlin scripts. Other restricted property reads and bulk +system-property enumeration remain denied; this entry grants no wildcard access to +the gRPC property namespace. + ### Property-query index coverage For type-compatible values, a global property-index query such as diff --git a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/security/HugeSecurityManager.java b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/security/HugeSecurityManager.java index 7e9060a6d9..5c5d336c6a 100644 --- a/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/security/HugeSecurityManager.java +++ b/hugegraph-server/hugegraph-core/src/main/java/org/apache/hugegraph/security/HugeSecurityManager.java @@ -63,7 +63,9 @@ public class HugeSecurityManager extends SecurityManager { "line.separator", "file.separator", // Sofa - "java.specification.version" + "java.specification.version", + // Non-sensitive gRPC allocator selection; scripts may read this flag too. + "io.grpc.netty.shaded.io.grpc.netty.useCustomAllocator" ); private static final Set ASYNC_SCHEMA_METHODS = ImmutableSet.of( @@ -318,8 +320,7 @@ public void checkPropertiesAccess() { public void checkPropertyAccess(String key) { if (!callFromAcceptClassLoaders() && callFromGremlin() && !WHITE_SYSTEM_PROPERTIES.contains(key) && !callFromBackendHbase() && - !callFromSnapshot() && !callFromRaft() && !callFromSofaRpc() && - !callFromGrpcNettyAllocator(key)) { + !callFromSnapshot() && !callFromRaft() && !callFromSofaRpc()) { throw newSecurityException("Not allowed to access system property(%s) via Gremlin", key); } @@ -412,13 +413,6 @@ private static boolean callFromSofaRpc() { return callFromWorkerWithClass(SOFA_RPC_CLASSES); } - private static boolean callFromGrpcNettyAllocator(String key) { - // gRPC reads this configuration on every new transport, even after - // initialization. Keep direct script reads and other properties denied. - return "io.grpc.netty.shaded.io.grpc.netty.useCustomAllocator".equals(key) && - callFromMethod("io.grpc.netty.shaded.io.grpc.netty.Utils", "getByteBufAllocator"); - } - private static boolean callFromNewSecurityException() { return callFromMethods(NEW_SECURITY_EXCEPTION); } diff --git a/hugegraph-server/hugegraph-core/src/test/java/org/apache/hugegraph/security/HugeSecurityManagerGrpcTest.java b/hugegraph-server/hugegraph-core/src/test/java/org/apache/hugegraph/security/HugeSecurityManagerGrpcTest.java index 82ef958e48..20cd0e7fbd 100644 --- a/hugegraph-server/hugegraph-core/src/test/java/org/apache/hugegraph/security/HugeSecurityManagerGrpcTest.java +++ b/hugegraph-server/hugegraph-core/src/test/java/org/apache/hugegraph/security/HugeSecurityManagerGrpcTest.java @@ -63,9 +63,11 @@ public void testGrpcAllocatorFromTaskWorker() throws Exception { } @Test - public void testScriptCannotReadAllocatorProperty() { - assertDenied("System.getProperty('" + ALLOCATOR_PROPERTY + "')", - "system property(" + ALLOCATOR_PROPERTY + ")"); + public void testScriptCanReadAllocatorProperty() throws Exception { + String expected = System.getProperty(ALLOCATOR_PROPERTY, "true"); + String script = "System.getProperty('" + ALLOCATOR_PROPERTY + "', 'true')"; + Assert.assertEquals(expected, eval(script, "gremlin-server-exec-grpc-regression")); + Assert.assertEquals(expected, eval(script, "task-worker-grpc-regression")); } @Test @@ -75,8 +77,10 @@ public void testScriptCannotReadSensitiveProperty() { @Test public void testScriptCannotReadOtherGrpcProperty() { - String key = "io.grpc.netty.shaded.io.grpc.netty.unrelated"; - assertDenied("System.getProperty('" + key + "')", "system property(" + key + ")"); + for (String key : new String[]{"io.grpc.netty.shaded.io.grpc.netty.unrelated", + ALLOCATOR_PROPERTY + ".unrelated"}) { + assertDenied("System.getProperty('" + key + "')", "system property(" + key + ")"); + } } @Test @@ -85,9 +89,9 @@ public void testScriptCannotEnumerateProperties() { } @Test - public void testAllocatorAccessDoesNotAllowSubsequentScriptRead() { - assertDenied("allocator.allocate(false); System.getProperty('" + ALLOCATOR_PROPERTY + "')", - "system property(" + ALLOCATOR_PROPERTY + ")"); + public void testAllocatorAccessDoesNotAllowSubsequentSensitiveRead() { + assertDenied("allocator.allocate(false); System.getProperty('user.home')", + "system property(user.home)"); } private static void assertAllocatorAllowed(String worker) throws Exception {