Repository: cassandra Updated Branches: refs/heads/trunk ed65ff99c -> 247bc2e67
UFPureScriptTest fails with pre-3.0 java-driver patch by Robert Stupp; reviewed by Joshua McKenzie for CASSANDRA-10141 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/3ae01ddc Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/3ae01ddc Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/3ae01ddc Branch: refs/heads/trunk Commit: 3ae01ddcc0c11a77f34eb48d334e7d3746a26381 Parents: 8bb7077 Author: Robert Stupp <[email protected]> Authored: Wed Sep 30 13:24:55 2015 +0200 Committer: Robert Stupp <[email protected]> Committed: Wed Sep 30 13:24:55 2015 +0200 ---------------------------------------------------------------------- .../cql3/functions/JavaBasedUDFunction.java | 2 +- .../cql3/functions/ScriptBasedUDFunction.java | 4 +- .../cql3/functions/SecurityThreadGroup.java | 19 ++- .../functions/ThreadAwareSecurityManager.java | 57 +++++--- .../cassandra/cql3/functions/UDFunction.java | 12 +- .../validation/entities/UFPureScriptTest.java | 99 ------------- .../UFPureScriptTupleCollectionTest.java | 138 +++++++++++++++++++ 7 files changed, 207 insertions(+), 124 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/3ae01ddc/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java b/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java index 1db13e3..0fb8123 100644 --- a/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/JavaBasedUDFunction.java @@ -70,7 +70,7 @@ final class JavaBasedUDFunction extends UDFunction new UDFExecutorService(new NamedThreadFactory("UserDefinedFunctions", Thread.MIN_PRIORITY, udfClassLoader, - new SecurityThreadGroup("UserDefinedFunctions", null)), + new SecurityThreadGroup("UserDefinedFunctions", null, UDFunction::initializeThread)), "userfunction"); private static final EcjTargetClassLoader targetClassLoader = new EcjTargetClassLoader(); http://git-wip-us.apache.org/repos/asf/cassandra/blob/3ae01ddc/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java index ce4ea5e..4ffb992 100644 --- a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java @@ -86,7 +86,9 @@ final class ScriptBasedUDFunction extends UDFunction new UDFExecutorService(new NamedThreadFactory("UserDefinedScriptFunctions", Thread.MIN_PRIORITY, udfClassLoader, - new SecurityThreadGroup("UserDefinedScriptFunctions", Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowedPackagesArray))))), + new SecurityThreadGroup("UserDefinedScriptFunctions", + Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowedPackagesArray))), + UDFunction::initializeThread)), "userscripts"); static http://git-wip-us.apache.org/repos/asf/cassandra/blob/3ae01ddc/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java b/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java index fb821d5..8f50dc8 100644 --- a/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java +++ b/src/java/org/apache/cassandra/cql3/functions/SecurityThreadGroup.java @@ -26,15 +26,28 @@ import java.util.Set; public final class SecurityThreadGroup extends ThreadGroup { private final Set<String> allowedPackages; + private final ThreadInitializer threadInitializer; - public SecurityThreadGroup(String name, Set<String> allowedPackages) + public SecurityThreadGroup(String name, Set<String> allowedPackages, ThreadInitializer threadInitializer) { super(name); this.allowedPackages = allowedPackages; + this.threadInitializer = threadInitializer; } - public Set<String> getAllowedPackages() + public void initializeThread() { - return allowedPackages; + threadInitializer.initializeThread(); + } + + public boolean isPackageAllowed(String pkg) + { + return allowedPackages == null || allowedPackages.contains(pkg); + } + + @FunctionalInterface + interface ThreadInitializer + { + void initializeThread(); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/3ae01ddc/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java b/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java index edc03a7..b96c80f 100644 --- a/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java +++ b/src/java/org/apache/cassandra/cql3/functions/ThreadAwareSecurityManager.java @@ -28,9 +28,6 @@ import java.security.Policy; import java.security.ProtectionDomain; import java.util.Collections; import java.util.Enumeration; -import java.util.Set; - -import sun.security.util.SecurityConstants; /** * Custom {@link SecurityManager} and {@link Policy} implementation that only performs access checks @@ -61,6 +58,10 @@ public final class ThreadAwareSecurityManager extends SecurityManager } }; + private static final RuntimePermission CHECK_MEMBER_ACCESS_PERMISSION = new RuntimePermission("accessDeclaredMembers"); + private static final RuntimePermission MODIFY_THREAD_PERMISSION = new RuntimePermission("modifyThread"); + private static final RuntimePermission MODIFY_THREADGROUP_PERMISSION = new RuntimePermission("modifyThreadGroup"); + private static volatile boolean installed; public static void install() @@ -131,37 +132,59 @@ public final class ThreadAwareSecurityManager extends SecurityManager }); } + private static final ThreadLocal<Boolean> initializedThread = new ThreadLocal<>(); + private ThreadAwareSecurityManager() { } private static boolean isSecuredThread() { - return Thread.currentThread().getThreadGroup() instanceof SecurityThreadGroup; + ThreadGroup tg = Thread.currentThread().getThreadGroup(); + if (!(tg instanceof SecurityThreadGroup)) + return false; + Boolean threadInitialized = initializedThread.get(); + if (threadInitialized == null) + { + initializedThread.set(false); + ((SecurityThreadGroup) tg).initializeThread(); + initializedThread.set(true); + threadInitialized = true; + } + return threadInitialized; } public void checkAccess(Thread t) { - // need to override since the default implementation is kind of ... + // need to override since the default implementation only checks the permission if the current thread's + // in the root-thread-group if (isSecuredThread()) - throw new AccessControlException("access denied: " + SecurityConstants.MODIFY_THREAD_PERMISSION, SecurityConstants.MODIFY_THREAD_PERMISSION); + throw new AccessControlException("access denied: " + MODIFY_THREAD_PERMISSION, MODIFY_THREAD_PERMISSION); super.checkAccess(t); } public void checkAccess(ThreadGroup g) { - // need to override since the default implementation is kind of ... + // need to override since the default implementation only checks the permission if the current thread's + // in the root-thread-group if (isSecuredThread()) - throw new AccessControlException("access denied: " + SecurityConstants.MODIFY_THREADGROUP_PERMISSION, SecurityConstants.MODIFY_THREADGROUP_PERMISSION); + throw new AccessControlException("access denied: " + MODIFY_THREADGROUP_PERMISSION, MODIFY_THREADGROUP_PERMISSION); super.checkAccess(g); } public void checkPermission(Permission perm) { - if (isSecuredThread()) - super.checkPermission(perm); + if (!isSecuredThread()) + return; + + // required by JavaDriver 2.2.0-rc3 and 3.0.0-a2 or newer + // code in com.datastax.driver.core.CodecUtils uses Guava stuff, which in turns requires this permission + if (CHECK_MEMBER_ACCESS_PERMISSION.equals(perm)) + return; + + super.checkPermission(perm); } public void checkPermission(Permission perm, Object context) @@ -172,13 +195,15 @@ public final class ThreadAwareSecurityManager extends SecurityManager public void checkPackageAccess(String pkg) { - ThreadGroup threadGroup = Thread.currentThread().getThreadGroup(); - if (threadGroup instanceof SecurityThreadGroup) + if (!isSecuredThread()) + return; + + if (!((SecurityThreadGroup) Thread.currentThread().getThreadGroup()).isPackageAllowed(pkg)) { - Set<String> allowedPackages = ((SecurityThreadGroup) threadGroup).getAllowedPackages(); - if (allowedPackages != null && !allowedPackages.contains(pkg)) - throw new AccessControlException("access denied: " + new RuntimePermission("accessClassInPackage." + pkg), new RuntimePermission("accessClassInPackage." + pkg)); - super.checkPackageAccess(pkg); + RuntimePermission perm = new RuntimePermission("accessClassInPackage." + pkg); + throw new AccessControlException("access denied: " + perm, perm); } + + super.checkPackageAccess(pkg); } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/3ae01ddc/src/java/org/apache/cassandra/cql3/functions/UDFunction.java ---------------------------------------------------------------------- diff --git a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java index a07852d..36cdb15 100644 --- a/src/java/org/apache/cassandra/cql3/functions/UDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/UDFunction.java @@ -296,6 +296,14 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct throw new InvalidRequestException("Scripted user-defined functions are disabled in cassandra.yaml - set enable_scripted_user_defined_functions=true to enable if you are aware of the security risks"); } + static void initializeThread() + { + // Get the TypeCodec stuff in Java Driver initialized. + // This is to get the classes loaded outside of the restricted sandbox's security context of a UDF. + UDHelper.codecRegistry.codecFor(DataType.inet()).format(InetAddress.getLoopbackAddress()); + UDHelper.codecRegistry.codecFor(DataType.ascii()).format(""); + } + private static final class ThreadIdAndCpuTime extends CompletableFuture<Object> { long threadId; @@ -309,10 +317,6 @@ public abstract class UDFunction extends AbstractFunction implements ScalarFunct // because class loading would be deferred until setup() is executed - but setup() is called with // limited privileges. threadMXBean.getCurrentThreadCpuTime(); - // - // Get the TypeCodec stuff in Java Driver initialized. - UDHelper.codecRegistry.codecFor(DataType.inet()).format(InetAddress.getLoopbackAddress()); - UDHelper.codecRegistry.codecFor(DataType.ascii()).format(""); } void setup() http://git-wip-us.apache.org/repos/asf/cassandra/blob/3ae01ddc/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTest.java index ef06dbf..82ed63d 100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTest.java @@ -32,15 +32,11 @@ import java.util.UUID; import org.junit.Assert; import org.junit.Test; -import com.datastax.driver.core.DataType; -import com.datastax.driver.core.TupleType; -import com.datastax.driver.core.TupleValue; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.cql3.CQLTester; import org.apache.cassandra.cql3.UntypedResultSet; import org.apache.cassandra.cql3.functions.FunctionName; import org.apache.cassandra.exceptions.FunctionExecutionException; -import org.apache.cassandra.transport.Server; import org.apache.cassandra.utils.UUIDGen; public class UFPureScriptTest extends CQLTester @@ -119,101 +115,6 @@ public class UFPureScriptTest extends CQLTester } @Test - public void testJavascriptTupleTypeCollection() throws Throwable - { - String tupleTypeDef = "tuple<double, list<double>, set<text>, map<int, boolean>>"; - createTable("CREATE TABLE %s (key int primary key, tup frozen<" + tupleTypeDef + ">)"); - - String fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS tuple<double, list<double>, set<text>, map<int, boolean>> " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup;$$;"); - String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS double " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup.getDouble(0);$$;"); - String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS list<double> " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup.getList(1, java.lang.Double.class);$$;"); - String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS set<text> " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup.getSet(2, java.lang.String.class);$$;"); - String fTup5 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, - "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + - "RETURNS NULL ON NULL INPUT " + - "RETURNS map<int, boolean> " + - "LANGUAGE javascript\n" + - "AS $$" + - " tup.getMap(3, java.lang.Integer.class, java.lang.Boolean.class);$$;"); - - List<Double> list = Arrays.asList(1d, 2d, 3d); - Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two")); - Map<Integer, Boolean> map = new TreeMap<>(); - map.put(1, true); - map.put(2, false); - map.put(3, true); - - Object t = tuple(1d, list, set, map); - - execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t); - - assertRows(execute("SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"), - row(t)); - assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"), - row(1d)); - assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"), - row(list)); - assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"), - row(set)); - assertRows(execute("SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"), - row(map)); - - // same test - but via native protocol - // we use protocol V3 here to encode the expected version because the server - // always serializes Collections using V3 - see CollectionSerializer's - // serialize and deserialize methods. - TupleType tType = tupleTypeOf(Server.VERSION_3, - DataType.cdouble(), - DataType.list(DataType.cdouble()), - DataType.set(DataType.text()), - DataType.map(DataType.cint(), - DataType.cboolean())); - TupleValue tup = tType.newValue(1d, list, set, map); - for (int version : PROTOCOL_VERSIONS) - { - assertRowsNet(version, - executeNet(version, "SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"), - row(tup)); - assertRowsNet(version, - executeNet(version, "SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"), - row(1d)); - assertRowsNet(version, - executeNet(version, "SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"), - row(list)); - assertRowsNet(version, - executeNet(version, "SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"), - row(set)); - assertRowsNet(version, - executeNet(version, "SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"), - row(map)); - } - } - - @Test public void testJavascriptUserType() throws Throwable { String type = createType("CREATE TYPE %s (txt text, i int)"); http://git-wip-us.apache.org/repos/asf/cassandra/blob/3ae01ddc/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTupleCollectionTest.java ---------------------------------------------------------------------- diff --git a/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTupleCollectionTest.java b/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTupleCollectionTest.java new file mode 100644 index 0000000..7465a2a --- /dev/null +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFPureScriptTupleCollectionTest.java @@ -0,0 +1,138 @@ +/* + * 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.cassandra.cql3.validation.entities; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.TreeSet; + +import org.junit.Test; + +import com.datastax.driver.core.DataType; +import com.datastax.driver.core.TupleType; +import com.datastax.driver.core.TupleValue; +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.transport.Server; + +public class UFPureScriptTupleCollectionTest extends CQLTester +{ + // Just JavaScript UDFs to check how UDF - especially security/class-loading/sandboxing stuff - + // behaves, if no Java UDF has been executed before. + + // Do not add any other test here! + // See CASSANDRA-10141 + + @Test + public void testJavascriptTupleTypeCollection() throws Throwable + { + String tupleTypeDef = "tuple<double, list<double>, set<text>, map<int, boolean>>"; + createTable("CREATE TABLE %s (key int primary key, tup frozen<" + tupleTypeDef + ">)"); + + String fTup1 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, + "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS tuple<double, list<double>, set<text>, map<int, boolean>> " + + "LANGUAGE javascript\n" + + "AS $$" + + " tup;$$;"); + String fTup2 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, + "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE javascript\n" + + "AS $$" + + " tup.getDouble(0);$$;"); + String fTup3 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, + "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS list<double> " + + "LANGUAGE javascript\n" + + "AS $$" + + " tup.getList(1, java.lang.Double.class);$$;"); + String fTup4 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, + "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS set<text> " + + "LANGUAGE javascript\n" + + "AS $$" + + " tup.getSet(2, java.lang.String.class);$$;"); + String fTup5 = createFunction(KEYSPACE_PER_TEST, tupleTypeDef, + "CREATE FUNCTION %s( tup " + tupleTypeDef + " ) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS map<int, boolean> " + + "LANGUAGE javascript\n" + + "AS $$" + + " tup.getMap(3, java.lang.Integer.class, java.lang.Boolean.class);$$;"); + + List<Double> list = Arrays.asList(1d, 2d, 3d); + Set<String> set = new TreeSet<>(Arrays.asList("one", "three", "two")); + Map<Integer, Boolean> map = new TreeMap<>(); + map.put(1, true); + map.put(2, false); + map.put(3, true); + + Object t = tuple(1d, list, set, map); + + execute("INSERT INTO %s (key, tup) VALUES (1, ?)", t); + + assertRows(execute("SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"), + row(t)); + assertRows(execute("SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"), + row(1d)); + assertRows(execute("SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"), + row(list)); + assertRows(execute("SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"), + row(set)); + assertRows(execute("SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"), + row(map)); + + // same test - but via native protocol + // we use protocol V3 here to encode the expected version because the server + // always serializes Collections using V3 - see CollectionSerializer's + // serialize and deserialize methods. + TupleType tType = tupleTypeOf(Server.VERSION_3, + DataType.cdouble(), + DataType.list(DataType.cdouble()), + DataType.set(DataType.text()), + DataType.map(DataType.cint(), + DataType.cboolean())); + TupleValue tup = tType.newValue(1d, list, set, map); + for (int version : PROTOCOL_VERSIONS) + { + assertRowsNet(version, + executeNet(version, "SELECT " + fTup1 + "(tup) FROM %s WHERE key = 1"), + row(tup)); + assertRowsNet(version, + executeNet(version, "SELECT " + fTup2 + "(tup) FROM %s WHERE key = 1"), + row(1d)); + assertRowsNet(version, + executeNet(version, "SELECT " + fTup3 + "(tup) FROM %s WHERE key = 1"), + row(list)); + assertRowsNet(version, + executeNet(version, "SELECT " + fTup4 + "(tup) FROM %s WHERE key = 1"), + row(set)); + assertRowsNet(version, + executeNet(version, "SELECT " + fTup5 + "(tup) FROM %s WHERE key = 1"), + row(map)); + } + } +}
