Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 2FEC1200C03 for ; Fri, 6 Jan 2017 09:39:50 +0100 (CET) Received: by cust-asf.ponee.io (Postfix) id 2EA87160B49; Fri, 6 Jan 2017 08:39:50 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id A332F160B1F for ; Fri, 6 Jan 2017 09:39:48 +0100 (CET) Received: (qmail 89592 invoked by uid 500); 6 Jan 2017 08:39:47 -0000 Mailing-List: contact commits-help@cassandra.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cassandra.apache.org Delivered-To: mailing list commits@cassandra.apache.org Received: (qmail 89557 invoked by uid 99); 6 Jan 2017 08:39:47 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 06 Jan 2017 08:39:47 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id A591BDFCB6; Fri, 6 Jan 2017 08:39:47 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: snazy@apache.org To: commits@cassandra.apache.org Date: Fri, 06 Jan 2017 08:39:48 -0000 Message-Id: In-Reply-To: <0621ee253e7045aaa289e2447b87f425@git.apache.org> References: <0621ee253e7045aaa289e2447b87f425@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [2/9] cassandra git commit: Merge branch 'cassandra-2.2' into cassandra-3.0 archived-at: Fri, 06 Jan 2017 08:39:50 -0000 Merge branch 'cassandra-2.2' into cassandra-3.0 Project: http://git-wip-us.apache.org/repos/asf/cassandra/repo Commit: http://git-wip-us.apache.org/repos/asf/cassandra/commit/6e716c6d Tree: http://git-wip-us.apache.org/repos/asf/cassandra/tree/6e716c6d Diff: http://git-wip-us.apache.org/repos/asf/cassandra/diff/6e716c6d Branch: refs/heads/cassandra-3.11 Commit: 6e716c6da41900950e32a5549b3bb1e858ecad18 Parents: c0765ed 6f360b6 Author: Robert Stupp Authored: Thu Jan 5 22:20:31 2017 +0100 Committer: Robert Stupp Committed: Thu Jan 5 22:20:31 2017 +0100 ---------------------------------------------------------------------- CHANGES.txt | 1 + NEWS.txt | 3 ++ doc/cql3/CQL.textile | 2 +- lib/jsr223/clojure/README.txt | 8 --- lib/jsr223/groovy/README.txt | 35 ------------- lib/jsr223/jaskell/README.txt | 5 -- lib/jsr223/jruby/README.txt | 54 -------------------- lib/jsr223/jython/README.txt | 33 ------------ lib/jsr223/scala/README.txt | 37 -------------- .../cql3/functions/ScriptBasedUDFunction.java | 30 +++++------ .../cql3/validation/entities/UFScriptTest.java | 3 +- .../validation/entities/UFSecurityTest.java | 12 ++++- 12 files changed, 29 insertions(+), 194 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/CHANGES.txt ---------------------------------------------------------------------- diff --cc CHANGES.txt index 666a771,b41313d..77a310d --- a/CHANGES.txt +++ b/CHANGES.txt @@@ -1,23 -1,5 +1,24 @@@ -2.2.9 +3.0.11 + * Fixed flacky SSTableRewriterTest: check file counts before calling validateCFS (CASSANDRA-12348) + * Fix deserialization of 2.x DeletedCells (CASSANDRA-12620) + * Add parent repair session id to anticompaction log message (CASSANDRA-12186) + * Improve contention handling on failure to acquire MV lock for streaming and hints (CASSANDRA-12905) + * Fix DELETE and UPDATE queries with empty IN restrictions (CASSANDRA-12829) + * Mark MVs as built after successful bootstrap (CASSANDRA-12984) + * Estimated TS drop-time histogram updated with Cell.NO_DELETION_TIME (CASSANDRA-13040) + * Nodetool compactionstats fails with NullPointerException (CASSANDRA-13021) + * Thread local pools never cleaned up (CASSANDRA-13033) + * Set RPC_READY to false when draining or if a node is marked as shutdown (CASSANDRA-12781) + * CQL often queries static columns unnecessarily (CASSANDRA-12768) + * Make sure sstables only get committed when it's safe to discard commit log records (CASSANDRA-12956) + * Reject default_time_to_live option when creating or altering MVs (CASSANDRA-12868) + * Nodetool should use a more sane max heap size (CASSANDRA-12739) + * LocalToken ensures token values are cloned on heap (CASSANDRA-12651) + * AnticompactionRequestSerializer serializedSize is incorrect (CASSANDRA-12934) + * Prevent reloading of logback.xml from UDF sandbox (CASSANDRA-12535) + * Reenable HeapPool (CASSANDRA-12900) +Merged from 2.2: + * Remove support for non-JavaScript UDFs (CASSANDRA-12883) * Fix DynamicEndpointSnitch noop in multi-datacenter situations (CASSANDRA-13074) * cqlsh copy-from: encode column names to avoid primary key parsing errors (CASSANDRA-12909) * Temporarily fix bug that creates commit log when running offline tools (CASSANDRA-8616) http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/NEWS.txt ---------------------------------------------------------------------- diff --cc NEWS.txt index 32b5084,37949a1..b4e6551 --- a/NEWS.txt +++ b/NEWS.txt @@@ -13,77 -13,20 +13,80 @@@ restore snapshots created with the prev 'sstableloader' tool. You can upgrade the file format of your snapshots using the provided 'sstableupgrade' tool. -2.2.9 +3.0.11 ===== +Upgrading +--------- + - Nothing specific to this release, but please see previous versions upgrading section, + especially if you are upgrading from 2.2. + - Specifying the default_time_to_live option when creating or altering a + materialized view was erroneously accepted (and ignored). It is now + properly rejected. ++ - Only Java and JavaScript are now supported UDF languages. ++ The sandbox in 3.0 already prevented the use of script languages except Java ++ and JavaScript. + +3.0.10 +===== + +Upgrading +--------- + - memtable_allocation_type: offheap_buffers is no longer allowed to be specified in the 3.0 series. + This was an oversight that can cause segfaults. Offheap was re-introduced in 3.4 see CASSANDRA-11039 + and CASSANDRA-9472 for details. + +3.0.9 +===== + +Upgrading +--------- + - The ReversedType behaviour has been corrected for clustering columns of + BYTES type containing empty value. Scrub should be run on the existing + SSTables containing a descending clustering column of BYTES type to correct + their ordering. See CASSANDRA-12127 for more details. + +3.0.8 +===== + +Upgrading +--------- + - Ec2MultiRegionSnitch will no longer automatically set broadcast_rpc_address + to the public instance IP if this property is defined on cassandra.yaml. + +3.0.7 +===== + +Upgrading +--------- + - A maximum size for SSTables values has been introduced, to prevent out of memory + exceptions when reading corrupt SSTables. This maximum size can be set via + max_value_size_in_mb in cassandra.yaml. The default is 256MB, which matches the default + value of native_transport_max_frame_size_in_mb. SSTables will be considered corrupt if + they contain values whose size exceeds this limit. See CASSANDRA-9530 for more details. + Deprecation ----------- + - DateTieredCompactionStrategy has been deprecated - new tables should use + TimeWindowCompactionStrategy. Note that migrating an existing DTCS-table to TWCS might + cause increased compaction load for a while after the migration so make sure you run + tests before migrating. Read CASSANDRA-9666 for background on this. -(See note about the new feature User-Defined-Functions in 2.2.0.) +New features +------------ + - TimeWindowCompactionStrategy has been added. This has proven to be a better approach + to time series compaction and new tables should use this instead of DTCS. See + CASSANDRA-9666 for details. -Since the security manager added in 3.0 only allows Java and JavaScript -UDFs to be run, UDFs for other languages are deprecated and support for -non-Java and non-JavaScript UDFs is deprecated in 2.2 and has been removed -in version 3.0.11. +3.0.6 +===== + +New features +------------ + - JSON timestamps are now in UTC and contain the timezone information, see + CASSANDRA-11137 for more details. -2.2.8 +3.0.5 ===== Upgrading http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/doc/cql3/CQL.textile ---------------------------------------------------------------------- diff --cc doc/cql3/CQL.textile index 2a37452,af584d0..2544878 --- a/doc/cql3/CQL.textile +++ b/doc/cql3/CQL.textile @@@ -1977,7 -1910,7 +1977,7 @@@ SELECT AVG(players) FROM plays h2(#udfs). User-Defined Functions --User-defined functions allow execution of user-provided code in Cassandra. By default, Cassandra supports defining functions in _Java_ and _JavaScript_. Support for other JSR 223 compliant scripting languages (such as Python, Ruby, and Scala) can be added by adding a JAR to the classpath. ++User-defined functions allow execution of user-provided code in Cassandra. By default, Cassandra supports defining functions in _Java_ and _JavaScript_. Support for other JSR 223 compliant scripting languages (such as Python, Ruby, and Scala) has been removed in 3.0.11. UDFs are part of the Cassandra schema. As such, they are automatically propagated to all nodes in the cluster. http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/lib/jsr223/clojure/README.txt ---------------------------------------------------------------------- diff --cc lib/jsr223/clojure/README.txt index 7ed7551,7ed7551..0000000 deleted file mode 100644,100644 --- a/lib/jsr223/clojure/README.txt +++ /dev/null @@@ -1,8 -1,8 +1,0 @@@ --Apache Cassandra User-Defined-Functions JSR 223 scripting --========================================================= -- --Unfortunately the JSR-223 support provided by the project https://github.com/ato/clojure-jsr223 --and the related ones do not provide compileable script support. -- --The JSR-223 javax.script.Compilable implementation takes source file names or readers but not script sources --as all other JSR-223 implementations do. http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/lib/jsr223/groovy/README.txt ---------------------------------------------------------------------- diff --cc lib/jsr223/groovy/README.txt index 09fef93,09fef93..0000000 deleted file mode 100644,100644 --- a/lib/jsr223/groovy/README.txt +++ /dev/null @@@ -1,35 -1,35 +1,0 @@@ --Apache Cassandra User-Defined-Functions JSR 223 scripting --========================================================= -- --Using JSR-223 capable Groovy -- --Tested with version 2.3.6 -- --Installation -------------- -- --1. Download Groovy binary release --2. Unpack the downloaded archive into a temporary directory --3. Copy the jar groovy-all-2.3.6-indy.jar from the Groovy embeddable directory to $CASSANDRA_HOME/lib/jsr223/groovy -- "indy" means "invokedynamic" and is a JVM instruction for scripting languages new to Java 7. --4. Restart your Cassandra daemon if it's already running -- --Cassandra log should contain a line like this: -- INFO 10:49:45 Found scripting engine Groovy Scripting Engine 2.0 - Groovy 2.3.6 - language names: [groovy, Groovy] --Such a line appears when you already have scripted UDFs in your system or add a scripted UDF for the first time (see below). -- --Smoke Test ------------ -- --To test Groovy functionality, open cqlsh and execute the following command: -- CREATE OR REPLACE FUNCTION foobar ( input text ) RETURNS text LANGUAGE groovy AS 'return "foo";' ; -- --If you get the error -- code=2200 [Invalid query] message="Invalid language groovy for 'foobar'" --Groovy for Apache Cassandra has not been installed correctly. -- --Notes / Java7 invokedynamic ----------------------------- -- --Groovy provides jars that support invokedynamic bytecode instruction. These jars are whose ending with --"-indy.jar". http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/lib/jsr223/jaskell/README.txt ---------------------------------------------------------------------- diff --cc lib/jsr223/jaskell/README.txt index 53e942e,53e942e..0000000 deleted file mode 100644,100644 --- a/lib/jsr223/jaskell/README.txt +++ /dev/null @@@ -1,5 -1,5 +1,0 @@@ --Apache Cassandra User-Defined-Functions JSR 223 scripting --========================================================= -- --Unfortunately Jaskell JSR-223 support is quite old and the Jaskell engine seems to be quite --unsupported. If you find a solution, please open a ticket at Apache Cassandra JIRA. http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/lib/jsr223/jruby/README.txt ---------------------------------------------------------------------- diff --cc lib/jsr223/jruby/README.txt index cbc12dc,cbc12dc..0000000 deleted file mode 100644,100644 --- a/lib/jsr223/jruby/README.txt +++ /dev/null @@@ -1,54 -1,54 +1,0 @@@ --Apache Cassandra User-Defined-Functions JSR 223 scripting --========================================================= -- --Using JSR-223 capable JRuby -- --Tested with version 1.7.15 -- --Installation -------------- -- --1. Download JRuby binary release --2. Unpack the downloaded archive into a temporary directory --3. Copy everything from the JRuby lib directory to $CASSANDRA_HOME/lib/jsr223/jruby --4. Restart your Cassandra daemon if it's already running -- --Cassandra log should contain a line like this: -- INFO 10:29:03 Found scripting engine JSR 223 JRuby Engine 1.7.15 - ruby jruby 1.7.15 - language names: [ruby, jruby] --Such a line appears when you already have scripted UDFs in your system or add a scripted UDF for the first time (see below). -- -- --Smoke Test ------------ -- --To test JRuby functionality, open cqlsh and execute the following command: -- CREATE OR REPLACE FUNCTION foobar ( input text ) RETURNS text LANGUAGE ruby AS 'return "foo";' ; -- --If you get the error -- code=2200 [Invalid query] message="Invalid language ruby for 'foobar'" --JRuby for Apache Cassandra has not been installed correctly. -- -- --Ruby require/include ---------------------- -- --You can use Ruby require and include in your scripts as in the following example: -- -- --CREATE OR REPLACE FUNCTION foobar ( input text ) RETURNS text LANGUAGE ruby AS ' --require "bigdecimal" --require "bigdecimal/math" -- --include BigMath -- --a = BigDecimal((PI(100)/2).to_s) -- --return "foo " + a.to_s; --' ; -- -- --Notes / Java7 invokedynamic ----------------------------- -- --See JRuby wiki pages https://github.com/jruby/jruby/wiki/ConfiguringJRuby and --https://github.com/jruby/jruby/wiki/PerformanceTuning for more information and optimization tips. http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/lib/jsr223/jython/README.txt ---------------------------------------------------------------------- diff --cc lib/jsr223/jython/README.txt index bef3c83,bef3c83..0000000 deleted file mode 100644,100644 --- a/lib/jsr223/jython/README.txt +++ /dev/null @@@ -1,33 -1,33 +1,0 @@@ --Apache Cassandra User-Defined-Functions JSR 223 scripting --========================================================= -- --Using JSR-223 capable Jython -- --Tested with version 2.3.5 -- --Installation -------------- -- --1. Download Jython binary release --2. Unpack the downloaded archive into a temporary directory --3. Copy the jar jython.jar from the Jython directory to $CASSANDRA_HOME/lib/jsr223/jython --4. Restart your Cassandra daemon if it's already running -- --Cassandra log should contain a line like this: -- INFO 10:58:18 Found scripting engine jython 2.5.3 - python 2.5 - language names: [python, jython] --Such a line appears when you already have scripted UDFs in your system or add a scripted UDF for the first time (see below). -- --Smoke Test ------------ -- --To test Jython functionality, open cqlsh and execute the following command: -- CREATE OR REPLACE FUNCTION foobar ( input text ) RETURNS text LANGUAGE python AS '''foo''' ; -- --If you get the error -- code=2200 [Invalid query] message="Invalid language python for 'foobar'" --Jython for Apache Cassandra has not been installed correctly. -- --Notes / Java7 invokedynamic ----------------------------- -- --Jython currently targets Java6 only. They want to switch to Java7 + invokedynamic in Jython 3. http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/lib/jsr223/scala/README.txt ---------------------------------------------------------------------- diff --cc lib/jsr223/scala/README.txt index 7f5d6fe,7f5d6fe..0000000 deleted file mode 100644,100644 --- a/lib/jsr223/scala/README.txt +++ /dev/null @@@ -1,37 -1,37 +1,0 @@@ --Apache Cassandra User-Defined-Functions JSR 223 scripting --========================================================= -- --Using JSR-223 capable Scala -- --Tested with version 2.11.2 -- --Installation -------------- -- --1. Download Scala binary release --2. Unpack the downloaded archive into a temporary directory --3. Copy the following jars from the Scala lib directory to $CASSANDRA_HOME/lib/jsr223/scala -- scala-compiler.jar -- scala-library.jar -- scala-reflect.jar --4. Restart your Cassandra daemon if it's already running -- --Cassandra log should contain a line like this: -- INFO 11:42:35 Found scripting engine Scala Interpreter 1.0 - Scala version 2.11.2 - language names: [scala] --Such a line appears when you already have scripted UDFs in your system or add a scripted UDF for the first time (see below). -- --Smoke Test ------------ -- --To test Scala functionality, open cqlsh and execute the following command: -- CREATE OR REPLACE FUNCTION foobar ( input text ) RETURNS text LANGUAGE scala AS 'return "foo";' ; -- --If you get the error -- code=2200 [Invalid query] message="Invalid language scala for 'foobar'" --Scala for Apache Cassandra has not been installed correctly. -- --Notes / Java7 invokedynamic ----------------------------- -- --Scala 2.10 has Java6 support only. 2.11 has experimental invokedynamic support (use at your own risk!). --2.12 introduces an upgrade directly to Java8 - see https://stackoverflow.com/questions/14285894/advantages-of-scala-emitting-bytecode-for-the-jvm-1-7 http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java ---------------------------------------------------------------------- diff --cc src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java index 8743a20,0000000..47deafa mode 100644,000000..100644 --- a/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java +++ b/src/java/org/apache/cassandra/cql3/functions/ScriptBasedUDFunction.java @@@ -1,246 -1,0 +1,240 @@@ +/* + * 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.functions; + +import java.math.BigDecimal; +import java.math.BigInteger; +import java.net.*; +import java.nio.ByteBuffer; +import java.security.*; +import java.security.cert.Certificate; +import java.util.*; +import java.util.concurrent.ExecutorService; +import javax.script.*; + ++import jdk.nashorn.api.scripting.ClassFilter; ++import jdk.nashorn.api.scripting.NashornScriptEngine; ++import jdk.nashorn.api.scripting.NashornScriptEngineFactory; +import org.apache.cassandra.concurrent.NamedThreadFactory; +import org.apache.cassandra.cql3.ColumnIdentifier; +import org.apache.cassandra.db.marshal.AbstractType; +import org.apache.cassandra.exceptions.InvalidRequestException; + +final class ScriptBasedUDFunction extends UDFunction +{ - static final Map scriptEngines = new HashMap<>(); - + private static final ProtectionDomain protectionDomain; + private static final AccessControlContext accessControlContext; + + // + // For scripted UDFs we have to rely on the security mechanisms of the scripting engine and + // SecurityManager - especially SecurityManager.checkPackageAccess(). Unlike Java-UDFs, strict checking + // of class access via the UDF class loader is not possible, since e.g. Nashorn builds its own class loader + // (jdk.nashorn.internal.runtime.ScriptLoader / jdk.nashorn.internal.runtime.NashornLoader) configured with + // a system class loader. + // + private static final String[] allowedPackagesArray = + { + // following required by jdk.nashorn.internal.objects.Global.initJavaAccess() + "", + "com", + "edu", + "java", + "javax", + "javafx", + "org", + // following required by Nashorn runtime + "java.lang", + "java.lang.invoke", + "java.lang.reflect", + "java.nio.charset", + "java.util", + "java.util.concurrent", + "javax.script", + "sun.reflect", + "jdk.internal.org.objectweb.asm.commons", + "jdk.nashorn.internal.runtime", + "jdk.nashorn.internal.runtime.linker", + // following required by Java Driver + "java.math", + "java.nio", + "java.text", + "com.google.common.base", + "com.google.common.collect", + "com.google.common.reflect", + // following required by UDF + "com.datastax.driver.core", + "com.datastax.driver.core.utils" + }; + + // use a JVM standard ExecutorService as DebuggableThreadPoolExecutor references internal + // classes, which triggers AccessControlException from the UDF sandbox + private static final UDFExecutorService executor = + new UDFExecutorService(new NamedThreadFactory("UserDefinedScriptFunctions", + Thread.MIN_PRIORITY, + udfClassLoader, + new SecurityThreadGroup("UserDefinedScriptFunctions", + Collections.unmodifiableSet(new HashSet<>(Arrays.asList(allowedPackagesArray))), + UDFunction::initializeThread)), + "userscripts"); + ++ private static final ClassFilter classFilter = clsName -> secureResource(clsName.replace('.', '/') + ".class"); ++ ++ private static final NashornScriptEngine scriptEngine; ++ ++ + static + { + ScriptEngineManager scriptEngineManager = new ScriptEngineManager(); - for (ScriptEngineFactory scriptEngineFactory : scriptEngineManager.getEngineFactories()) - { - ScriptEngine scriptEngine = scriptEngineFactory.getScriptEngine(); - boolean compilable = scriptEngine instanceof Compilable; - if (compilable) - { - logger.info("Found scripting engine {} {} - {} {} - language names: {}", - scriptEngineFactory.getEngineName(), scriptEngineFactory.getEngineVersion(), - scriptEngineFactory.getLanguageName(), scriptEngineFactory.getLanguageVersion(), - scriptEngineFactory.getNames()); - for (String name : scriptEngineFactory.getNames()) - scriptEngines.put(name, (Compilable) scriptEngine); - } - } ++ ScriptEngine engine = scriptEngineManager.getEngineByName("nashorn"); ++ NashornScriptEngineFactory factory = engine != null ? (NashornScriptEngineFactory) engine.getFactory() : null; ++ scriptEngine = factory != null ? (NashornScriptEngine) factory.getScriptEngine(new String[]{}, udfClassLoader, classFilter) : null; + + try + { + protectionDomain = new ProtectionDomain(new CodeSource(new URL("udf", "localhost", 0, "/script", new URLStreamHandler() + { + protected URLConnection openConnection(URL u) + { + return null; + } + }), (Certificate[]) null), ThreadAwareSecurityManager.noPermissions); + } + catch (MalformedURLException e) + { + throw new RuntimeException(e); + } + accessControlContext = new AccessControlContext(new ProtectionDomain[]{ protectionDomain }); + } + + private final CompiledScript script; + + ScriptBasedUDFunction(FunctionName name, + List argNames, + List> argTypes, + AbstractType returnType, + boolean calledOnNullInput, + String language, + String body) + { + super(name, argNames, argTypes, returnType, calledOnNullInput, language, body); + - Compilable scriptEngine = scriptEngines.get(language); - if (scriptEngine == null) ++ if (!"JavaScript".equalsIgnoreCase(language) || scriptEngine == null) + throw new InvalidRequestException(String.format("Invalid language '%s' for function '%s'", language, name)); + + // execute compilation with no-permissions to prevent evil code e.g. via "static code blocks" / "class initialization" + try + { + this.script = AccessController.doPrivileged((PrivilegedExceptionAction) () -> scriptEngine.compile(body), + accessControlContext); + } + catch (PrivilegedActionException x) + { + Throwable e = x.getCause(); + logger.info("Failed to compile function '{}' for language {}: ", name, language, e); + throw new InvalidRequestException( + String.format("Failed to compile function '%s' for language %s: %s", name, language, e)); + } + } + + protected ExecutorService executor() + { + return executor; + } + + public ByteBuffer executeUserDefined(int protocolVersion, List parameters) + { + Object[] params = new Object[argTypes.size()]; + for (int i = 0; i < params.length; i++) + params[i] = compose(protocolVersion, i, parameters.get(i)); + + ScriptContext scriptContext = new SimpleScriptContext(); + scriptContext.setAttribute("javax.script.filename", this.name.toString(), ScriptContext.ENGINE_SCOPE); + Bindings bindings = scriptContext.getBindings(ScriptContext.ENGINE_SCOPE); + for (int i = 0; i < params.length; i++) + bindings.put(argNames.get(i).toString(), params[i]); + + Object result; + try + { + // How to prevent Class.forName() _without_ "help" from the script engine ? + // NOTE: Nashorn enforces a special permission to allow class-loading, which is not granted - so it's fine. + + result = script.eval(scriptContext); + } + catch (ScriptException e) + { + throw new RuntimeException(e); + } + if (result == null) + return null; + + Class javaReturnType = UDHelper.asJavaClass(returnCodec); + Class resultType = result.getClass(); + if (!javaReturnType.isAssignableFrom(resultType)) + { + if (result instanceof Number) + { + Number rNumber = (Number) result; + if (javaReturnType == Integer.class) + result = rNumber.intValue(); + else if (javaReturnType == Long.class) + result = rNumber.longValue(); + else if (javaReturnType == Short.class) + result = rNumber.shortValue(); + else if (javaReturnType == Byte.class) + result = rNumber.byteValue(); + else if (javaReturnType == Float.class) + result = rNumber.floatValue(); + else if (javaReturnType == Double.class) + result = rNumber.doubleValue(); + else if (javaReturnType == BigInteger.class) + { + if (javaReturnType == Integer.class) + result = rNumber.intValue(); + else if (javaReturnType == Short.class) + result = rNumber.shortValue(); + else if (javaReturnType == Byte.class) + result = rNumber.byteValue(); + else if (javaReturnType == Long.class) + result = rNumber.longValue(); + else if (javaReturnType == Float.class) + result = rNumber.floatValue(); + else if (javaReturnType == Double.class) + result = rNumber.doubleValue(); + else if (javaReturnType == BigInteger.class) + { + if (rNumber instanceof BigDecimal) + result = ((BigDecimal) rNumber).toBigInteger(); + else if (rNumber instanceof Double || rNumber instanceof Float) + result = new BigDecimal(rNumber.toString()).toBigInteger(); + else + result = BigInteger.valueOf(rNumber.longValue()); + } + else if (javaReturnType == BigDecimal.class) + // String c'tor of BigDecimal is more accurate than valueOf(double) + result = new BigDecimal(rNumber.toString()); + } + else if (javaReturnType == BigDecimal.class) + // String c'tor of BigDecimal is more accurate than valueOf(double) + result = new BigDecimal(rNumber.toString()); + } + } + + return decompose(protocolVersion, result); + } +} http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/test/unit/org/apache/cassandra/cql3/validation/entities/UFScriptTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/entities/UFScriptTest.java index af3c894,d3050a5..9c931e8 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFScriptTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFScriptTest.java @@@ -382,47 -501,4 +382,46 @@@ public class UFScriptTest extends CQLTe row(1, expected1, expected2)); } } + + @Test + public void testJavascriptDisabled() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, val double)"); + + DatabaseDescriptor.enableScriptedUserDefinedFunctions(false); + try + { - assertInvalid("double", - "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".assertNotEnabled(val double) " + ++ assertInvalid("CREATE OR REPLACE FUNCTION " + KEYSPACE + ".assertNotEnabled(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE javascript\n" + + "AS 'Math.sin(val);';"); + } + finally + { + DatabaseDescriptor.enableScriptedUserDefinedFunctions(true); + } + } + + @Test + public void testJavascriptCompileFailure() throws Throwable + { + assertInvalidMessage("Failed to compile function 'cql_test_keyspace.scrinv'", + "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE javascript\n" + + "AS 'foo bar';"); + } + + @Test + public void testScriptInvalidLanguage() throws Throwable + { + assertInvalidMessage("Invalid language 'artificial_intelligence' for function 'cql_test_keyspace.scrinv'", + "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".scrinv(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE artificial_intelligence\n" + + "AS 'question for 42?';"); + } } http://git-wip-us.apache.org/repos/asf/cassandra/blob/6e716c6d/test/unit/org/apache/cassandra/cql3/validation/entities/UFSecurityTest.java ---------------------------------------------------------------------- diff --cc test/unit/org/apache/cassandra/cql3/validation/entities/UFSecurityTest.java index 5c7ca2b,0000000..4e45a8a mode 100644,000000..100644 --- a/test/unit/org/apache/cassandra/cql3/validation/entities/UFSecurityTest.java +++ b/test/unit/org/apache/cassandra/cql3/validation/entities/UFSecurityTest.java @@@ -1,258 -1,0 +1,268 @@@ +/* + * 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.security.AccessControlException; +import java.util.List; + +import org.junit.Assert; +import org.junit.Test; + +import org.apache.cassandra.config.Config; +import org.apache.cassandra.config.DatabaseDescriptor; +import org.apache.cassandra.cql3.CQLTester; +import org.apache.cassandra.cql3.functions.UDHelper; +import org.apache.cassandra.exceptions.FunctionExecutionException; +import org.apache.cassandra.service.ClientWarn; + +public class UFSecurityTest extends CQLTester +{ + @Test + public void testSecurityPermissions() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, dval double)"); + execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); + + // Java UDFs + + try + { + String fName = createFunction(KEYSPACE_PER_TEST, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE JAVA\n" + + "AS 'System.getProperty(\"foo.bar.baz\"); return 0d;';"); + execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); + Assert.fail(); + } + catch (FunctionExecutionException e) + { + assertAccessControlException("System.getProperty(\"foo.bar.baz\"); return 0d;", e); + } + + String[][] typesAndSources = + { + {"", "try { Class.forName(\"" + UDHelper.class.getName() + "\"); } catch (Exception e) { throw new RuntimeException(e); } return 0d;"}, + {"sun.misc.Unsafe", "sun.misc.Unsafe.getUnsafe(); return 0d;"}, + {"", "try { Class.forName(\"sun.misc.Unsafe\"); } catch (Exception e) { throw new RuntimeException(e); } return 0d;"}, + {"java.nio.file.FileSystems", "try {" + + " java.nio.file.FileSystems.getDefault(); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'}, + {"java.nio.channels.FileChannel", "try {" + + " java.nio.channels.FileChannel.open(java.nio.file.FileSystems.getDefault().getPath(\"/etc/passwd\")).close(); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'}, + {"java.nio.channels.SocketChannel", "try {" + + " java.nio.channels.SocketChannel.open().close(); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'}, + {"java.io.FileInputStream", "try {" + + " new java.io.FileInputStream(\"./foobar\").close(); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'}, + {"java.lang.Runtime", "try {" + + " java.lang.Runtime.getRuntime(); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'}, + {"org.apache.cassandra.service.StorageService", + "try {" + + " org.apache.cassandra.service.StorageService v = org.apache.cassandra.service.StorageService.instance; v.isShutdown(); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'}, + {"java.net.ServerSocket", "try {" + + " new java.net.ServerSocket().bind(); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'}, + {"java.io.FileOutputStream","try {" + + " new java.io.FileOutputStream(\".foo\"); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'}, + {"java.lang.Runtime", "try {" + + " java.lang.Runtime.getRuntime().exec(\"/tmp/foo\"); return 0d;" + + "} catch (Exception t) {" + + " throw new RuntimeException(t);" + + '}'} + }; + + for (String[] typeAndSource : typesAndSources) + { + assertInvalidMessage(typeAndSource[0] + " cannot be resolved", + "CREATE OR REPLACE FUNCTION " + KEYSPACE + ".invalid_class_access(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE JAVA\n" + + "AS '" + typeAndSource[1] + "';"); + } + + // JavaScript UDFs + + try + { + String fName = createFunction(KEYSPACE_PER_TEST, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE javascript\n" + + "AS 'org.apache.cassandra.service.StorageService.instance.isShutdown(); 0;';"); + execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); + Assert.fail("Javascript security check failed"); + } + catch (FunctionExecutionException e) + { + assertAccessControlException("", e); + } + + String[] javascript = + { + "java.lang.management.ManagmentFactory.getThreadMXBean(); 0;", + "new java.io.FileInputStream(\"/tmp/foo\"); 0;", + "new java.io.FileOutputStream(\"/tmp/foo\"); 0;", + "java.nio.file.FileSystems.getDefault().createFileExclusively(\"./foo_bar_baz\"); 0;", + "java.nio.channels.FileChannel.open(java.nio.file.FileSystems.getDefault().getPath(\"/etc/passwd\")); 0;", + "java.nio.channels.SocketChannel.open(); 0;", + "new java.net.ServerSocket().bind(null); 0;", + "var thread = new java.lang.Thread(); thread.start(); 0;", + "java.lang.System.getProperty(\"foo.bar.baz\"); 0;", - "java.lang.Class.forName(\"java.lang.System\"); 0;", + "java.lang.Runtime.getRuntime().exec(\"/tmp/foo\"); 0;", + "java.lang.Runtime.getRuntime().loadLibrary(\"foobar\"); 0;", + "java.lang.Runtime.getRuntime().loadLibrary(\"foobar\"); 0;", + // TODO these (ugly) calls are still possible - these can consume CPU (as one could do with an evil loop, too) +// "java.lang.Runtime.getRuntime().traceMethodCalls(true); 0;", +// "java.lang.Runtime.getRuntime().gc(); 0;", +// "java.lang.Runtime.getRuntime(); 0;", + }; + + for (String script : javascript) + { + try + { + String fName = createFunction(KEYSPACE_PER_TEST, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE javascript\n" + + "AS '" + script + "';"); + execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); + Assert.fail("Javascript security check failed: " + script); + } + catch (FunctionExecutionException e) + { + assertAccessControlException(script, e); + } + } ++ ++ String script = "java.lang.Class.forName(\"java.lang.System\"); 0;"; ++ String fName = createFunction(KEYSPACE_PER_TEST, "double", ++ "CREATE OR REPLACE FUNCTION %s(val double) " + ++ "RETURNS NULL ON NULL INPUT " + ++ "RETURNS double " + ++ "LANGUAGE javascript\n" + ++ "AS '" + script + "';"); ++ assertInvalidThrowMessage("Java reflection not supported when class filter is present", ++ FunctionExecutionException.class, ++ "SELECT " + fName + "(dval) FROM %s WHERE key=1"); + } + + private static void assertAccessControlException(String script, FunctionExecutionException e) + { + for (Throwable t = e; t != null && t != t.getCause(); t = t.getCause()) + if (t instanceof AccessControlException) + return; + Assert.fail("no AccessControlException for " + script + " (got " + e + ')'); + } + + @Test + public void testAmokUDF() throws Throwable + { + createTable("CREATE TABLE %s (key int primary key, dval double)"); + execute("INSERT INTO %s (key, dval) VALUES (?, ?)", 1, 1d); + + long udfWarnTimeout = DatabaseDescriptor.getUserDefinedFunctionWarnTimeout(); + long udfFailTimeout = DatabaseDescriptor.getUserDefinedFunctionFailTimeout(); + int maxTries = 5; + for (int i = 1; i <= maxTries; i++) + { + try + { + // short timeout + DatabaseDescriptor.setUserDefinedFunctionWarnTimeout(10); + DatabaseDescriptor.setUserDefinedFunctionFailTimeout(250); + // don't kill the unit test... - default policy is "die" + DatabaseDescriptor.setUserFunctionTimeoutPolicy(Config.UserFunctionTimeoutPolicy.ignore); + + ClientWarn.instance.captureWarnings(); + String fName = createFunction(KEYSPACE_PER_TEST, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE JAVA\n" + + "AS 'long t=System.currentTimeMillis()+110; while (t>System.currentTimeMillis()) { }; return 0d;'"); + execute("SELECT " + fName + "(dval) FROM %s WHERE key=1"); + List warnings = ClientWarn.instance.getWarnings(); + Assert.assertNotNull(warnings); + Assert.assertFalse(warnings.isEmpty()); + ClientWarn.instance.resetWarnings(); + + // Java UDF + + fName = createFunction(KEYSPACE_PER_TEST, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE JAVA\n" + + "AS 'long t=System.currentTimeMillis()+500; while (t>System.currentTimeMillis()) { }; return 0d;';"); + assertInvalidMessage("ran longer than 250ms", "SELECT " + fName + "(dval) FROM %s WHERE key=1"); + + // Javascript UDF + + fName = createFunction(KEYSPACE_PER_TEST, "double", + "CREATE OR REPLACE FUNCTION %s(val double) " + + "RETURNS NULL ON NULL INPUT " + + "RETURNS double " + + "LANGUAGE JAVASCRIPT\n" + + "AS 'var t=java.lang.System.currentTimeMillis()+500; while (t>java.lang.System.currentTimeMillis()) { }; 0;';"); + assertInvalidMessage("ran longer than 250ms", "SELECT " + fName + "(dval) FROM %s WHERE key=1"); + + return; + } + catch (Error | RuntimeException e) + { + if (i == maxTries) + throw e; + } + finally + { + // reset to defaults + DatabaseDescriptor.setUserDefinedFunctionWarnTimeout(udfWarnTimeout); + DatabaseDescriptor.setUserDefinedFunctionFailTimeout(udfFailTimeout); + } + } + } + +}