February 11, 2022·6 min read·engineeringcassandrasecurityudf

Cassandra scripted UDFs and the security cost of disabling threads

CVE-2021-44521 concerns scripted functions with the UDF executor disabled. The fix adds explicit consent for unsafe execution and restricts access to the JVM security manager.

Disabling Cassandra's dedicated threads for user-defined functions changes more than scheduling. CVE-2021-44521 exposes the security consequences when scripted functions run in that configuration. An attacker who can create those functions can execute arbitrary code on the host when the required non-default settings are present. The important operational question is whether the function execution configuration preserves the restrictions that the deployment expects.

The CVE record identifies the vulnerable combination. Marcus Eriksson's February 11 disclosure gives the mitigation and credits Omer Kaspi of JFrog with the discovery. The implementation change for CASSANDRA-17352 explains how Cassandra tightens the configuration and code checks. Reading that patch clarifies why upgrading does not turn the previously unsafe execution mode into a generally safe choice.

I am attracted to Cassandra for systems that actually need its distributed architecture. Executing application-supplied code inside a database adds a separate requirement for isolation, and that requirement deserves its own justification. The capacity to distribute data does not establish that every extension to server execution is appropriate for the same deployment.

The affected configuration is a conjunction

The disclosure names the following configuration. This is an illustration of the affected state, not a configuration to deploy.

enable_user_defined_functions: true
enable_scripted_user_defined_functions: true
enable_user_defined_functions_threads: false

All of those settings belong in the description of this vulnerability. UDF support must be enabled, scripted functions must be enabled, and the normal threaded execution must be disabled. The attacker also needs enough database permissions to create user-defined functions. Omitting the permission requirement would make the advisory sound like an unauthenticated attack against every Cassandra listener, which is not what the disclosure says.

The 4.0.1 configuration file supplies useful context for the first two switches. UDFs are disabled by default. Scripted UDFs have their own disabled default, and the comments say that enabling them has no effect if UDFs themselves remain disabled. The scripted option covers JavaScript functions and custom JSR-223 providers. Java UDFs do not require that scripted option once general UDF support is enabled.

These distinctions make the configuration an authorization decision as well as a runtime decision. Enabling a language runtime determines what kinds of submitted function Cassandra will execute. Granting permission to create functions determines who can introduce that code. Changing the threading mode determines part of the environment in which the code runs. An exposure assessment needs to retain all of those conditions rather than treating the presence of the Cassandra package as sufficient evidence of exploitability.

The security manager checks depend on execution context

The patch changes ThreadAwareSecurityManager, whose permission checking distinguishes secured threads from other threads. That distinction is directly relevant to a configuration that disables the dedicated UDF threads. The existing method returns without its later checks when the current thread is not a secured thread. The fix inserts a new check before that return.

Specifically, the patch defines a RuntimePermission for setSecurityManager. When UDF threads are disabled and the extra unsafe override is false, a request for that permission now raises an AccessControlException. Because this check precedes the secured-thread test, it does not depend on the caller belonging to the normal secured execution context.

The ordering is the significant implementation detail. A restriction placed only after the early return would not cover a thread that the existing code treats as unsecured. The new branch checks the combination of global UDF configuration and the requested permission before that distinction can skip the remaining method. This explains how an apparently local scheduling choice affects enforcement inside the JVM process.

The patch also changes UDFunction.secureResource. That method examines allowed resource patterns and rejects explicitly disallowed resources. The new code adds java/lang/System.class to a separate list for synchronous UDF execution. When threads are disabled and the extra unsafe override is false, the method rejects resources matching that list even when an earlier allowed pattern would otherwise admit them.

The two checks operate at different points in execution. Resource filtering controls which class a function can access, while the permission check controls a sensitive operation even before the secured-thread branch. Both changes restrict the disabled-thread mode. One checks permission to replace the security manager. The other blocks access to the System class through the resource filtering path. The patch does not justify treating unrestricted synchronous functions as an equivalent substitute for the dedicated execution environment. Its configuration comments explicitly retain the warning about arbitrary code execution when the extra override permits access to System methods.

Unsafe execution now requires explicit configuration

Cassandra adds allow_insecure_udfs and allow_extra_insecure_udfs, both defaulting to false. They control different decisions. The first permits disabling UDF threads. The second permits the additional access that the new security checks otherwise restrict in that mode.

The startup validation in DatabaseDescriptor rejects a configuration when enable_user_defined_functions_threads is false and allow_insecure_udfs is also false. Its exception message explains that disabling the threads now requires enabling the unsafe override and that the configuration is not recommended. This is an intentional compatibility consequence for a deployment carrying the old setting forward.

That validation has no condition requiring scripted functions to be enabled. Its predicate checks the threading switch and the unsafe override directly. The difference matters when interpreting the patch. The conditions that identify this disclosed attack and the conditions that make the new configuration validator reject startup are not identical. A configuration can encounter the validator without establishing every prerequisite of the disclosed scripted-function attack.

The extra override has a separate warning. If allow_extra_insecure_udfs is true, startup logs that allowing java.lang.System access in UDFs is dangerous. The permission and resource checks also consult that option. It is therefore not merely a way to suppress a diagnostic. Enabling it changes enforcement in the execution paths that the patch modifies.

I support requiring explicit configuration for these exceptions. An operator reading a file that disables execution threads should be able to see that the choice affects security. The new names make that consequence more apparent, and startup validation prevents the older configuration from continuing unchanged under the default values of the new switches.

Threading also affects control over long-running functions

The configuration comment modified by the patch explains another consequence of disabling asynchronous UDF execution. Operators must pay attention to read timeouts because those timeouts can indicate functions that run too long or forever. The comment warns that such functions can destabilize the cluster. The threading option therefore belongs to the database's resource control policy as well as its code execution restrictions.

That connection helps explain why a security fix should not be interpreted as permission to retain every existing execution setting. Blocking one route to arbitrary host code does not change the warning about functions that fail to terminate. The patch introduces explicit consent for the unsafe mode while retaining comments describing the operational consequences of selecting it.

There is a useful distinction between code creation permissions and execution constraints here. Limiting who can create a function reduces who can introduce executable code through that mechanism. It does not make the code incapable of running for too long once accepted. Execution controls address behavior after a function exists. The advisory's permission requirement and the configuration's timeout warning describe different parts of the same administrative responsibility.

Mitigation remains specific to the execution mode

The February 11 advisory identifies restoring enable_user_defined_functions_threads: true as a mitigation and says that true is the default. It also directs users of the supported branches to 3.0.26, 3.11.12, or 4.0.2 respectively. Those are the versions named in this disclosure. The stated advice does not require moving every affected installation to a different release line.

The distinction between the release update and the configuration matters during that change. With the new validator, carrying forward disabled threads requires an explicit unsafe override. With the normal threading setting restored, that particular requirement does not arise. A successful update consequently includes understanding the execution settings that the updated process will accept, not merely replacing its binaries.

The disclosure explicitly says that the affected configuration was documented as unsafe and remains unsafe after the CVE. The patch implements that position through default-deny configuration checks and narrower runtime restrictions. A deployment that needs scripted UDFs can retain the default threaded execution, while a deployment selecting synchronous execution must now declare the associated exception in its configuration.