Skip to content

[jdbc-v2] Bind DatabaseMetaData patterns instead of concatenating them into SQL - #3171

Merged
chernser merged 2 commits into
ClickHouse:mainfrom
piyush15102003:fix/metadata-pattern-sql-injection
Sep 30, 2026
Merged

chernser merged 2 commits into
ClickHouse:mainfrom
piyush15102003:fix/metadata-pattern-sql-injection

Conversation

@piyush15102003

Copy link
Copy Markdown
Contributor

Closes #3169.

What was wrong

Three DatabaseMetaData methods in jdbc-v2 built their WHERE clause by concatenating the
caller's pattern into the SQL text as ... LIKE '" + pattern + "':

  • getSchemas(String catalog, String schemaPattern)
  • getPrimaryKeys(String catalog, String schema, String table)
  • getFunctions(String catalog, String schemaPattern, String functionNamePattern)

A single quote in the argument closes the literal early, which has two consequences:

  1. A name that ClickHouse legally accepts fails. CREATE DATABASE `a'b` is valid, but
    getSchemas(null, "a'b") throws Code: 62 ... Single quoted string is not closed (SYNTAX_ERROR).
  2. The rest of the argument is parsed as SQL, so the pattern changes the result set instead of
    filtering it.

The sibling methods already get this right: getTables binds the pattern as a PreparedStatement
parameter, and getColumns uses SQLUtils.enquoteLiteral.

Change

Bind each pattern as a PreparedStatement parameter, the way getTables does. The LIKE / ILIKE
operators, the null → % default and the JDBC search escape (getSearchStringEscape() returns
\) are all unchanged.

I chose binding over SQLUtils.enquoteLiteral for the reason given in the issue.
PreparedStatementImpl.encodeObject encodes String parameters with SQLUtils.escapeSingleQuotes,
which escapes the backslash before the quote, so this path is already correct for backslashes.
This makes the fix independent of #3063 / #3146 — it does not need the enquoteLiteral
backslash fix to land first, and it will not need revisiting once that one does.

Because the PreparedStatement is now closed by try-with-resources, the rows are returned through
DetachedResultSet, matching getTables and getTableTypes. As a side effect this stops the three
methods leaking the Statement they previously created and never closed.

Testing

Added testMetadataPatternIsUsedAsValueNotSql to DatabaseMetaDataTest: a @DataProvider over the
three methods, covering a database mdquotea'b and table t'1 whose names contain a quote (must be
found) and the pattern nomatch' OR '1'='1 (must return no rows). The database name deliberately
contains no _, since LIKE treats _ as a single-character wildcard and the row-count assertion
would otherwise be non-deterministic.

Run against ClickHouse 25.8 via Testcontainers:

Run Result
New test, with this fix 6/6 pass
New test, with DatabaseMetaDataImpl reverted to main 6/6 fail
Whole DatabaseMetaDataTest class, with this fix 47/47 pass

The reverted run reproduces the issue exactly, so the new cases genuinely pin the bug:

getSchemas(null, "nomatch' OR '1'='1")     -> 6 rows (all databases), expected 0
getPrimaryKeys(null, "nomatch' OR '1'='1") -> 149 rows, expected 0
getFunctions(null, null, "nomatch' OR '1'='1") -> 1719 rows, expected 0
getSchemas(null, "mdquotea'b")             -> Code: 62 SYNTAX_ERROR at position 98
getPrimaryKeys(null, "mdquotea'b", "t'1")  -> Code: 62 SYNTAX_ERROR at position 399
getFunctions(null, null, "to'Int")         -> Code: 62 SYNTAX_ERROR at position 307

I pinned -DclickhouseVersion=25.8 (the version nightly.yml uses) because on latest the run
fails with Invalid LZ4 magic byte for every test, patched or not — that is #3105 and unrelated to
this change. I did not add a compress=false workaround to the test, since it would mask real
failures.

Note on overlap

Open PR #3168 makes getSchemas use SHOW DATABASES by default, but keeps this system.databases
query for jdbc_metadata_use_show_statements=false, and does not touch getPrimaryKeys or
getFunctions. Happy to rebase on it if it lands first.

Closes ClickHouse#3169.

getSchemas(String, String), getPrimaryKeys and getFunctions in jdbc-v2
DatabaseMetaDataImpl built their WHERE clause by concatenating the
caller's pattern into the SQL text as "... LIKE '" + pattern + "'". A
single quote in the argument closed the literal early, so a name that
ClickHouse accepts (for example a database created as `a'b`) failed with
Code 62 SYNTAX_ERROR, and the remainder of the argument was parsed as
SQL - getSchemas(null, "nomatch' OR '1'='1") returned every database
instead of none.

Bind each pattern as a PreparedStatement parameter, the way the sibling
getTables already does. PreparedStatementImpl encodes String parameters
with SQLUtils.escapeSingleQuotes, which escapes the backslash before the
quote, so this path is correct for backslashes too and does not depend
on the enquoteLiteral fix in ClickHouse#3063. The LIKE / ILIKE operators, the null
to "%" default and the JDBC search escape are unchanged.

Because the PreparedStatement is now closed with the try-with-resources
block, the rows are returned through DetachedResultSet, matching
getTables and getTableTypes. This also stops the three methods leaking
the Statement they used to create and never close.

Add a regression test to DatabaseMetaDataTest with a @dataProvider over
the three methods, covering a database and table whose names contain a
single quote (must be found) and the pattern "nomatch' OR '1'='1" (must
return no rows).
@CLAassistant

CLAassistant commented Sep 30, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@chernser

Copy link
Copy Markdown
Contributor

@piyush15102003

Thank you! LGTM
please merge main so I can run tests.

@piyush15102003

Copy link
Copy Markdown
Contributor Author

@chernser Merged main in with no conflicts. DatabaseMetaDataTest is 47/47 locally
against CH 25.8. Ready for your test run whenever you are.

@chernser
chernser merged commit 16dcde4 into ClickHouse:main Sep 30, 2026
17 of 20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[jdbc-v2] DatabaseMetaData getSchemas / getPrimaryKeys / getFunctions put the pattern into SQL without escaping

3 participants