postgresql-jdbc/CVE-2026-42198-tests.patch
2026-06-04 12:44:52 +02:00

137 lines
6.3 KiB
Diff

From e0cf9bfeac832b8f99d52a4030980be8e379b765 Mon Sep 17 00:00:00 2001
From: Marian Koncek <mkoncek@redhat.com>
Date: Wed, 20 May 2026 13:24:31 +0200
Subject: [PATCH] Add tests for CVE-2026-42198
---
.../java/org/postgresql/jdbc/ScramTest.java | 90 +++++++++++++++++++
1 file changed, 90 insertions(+)
diff --git a/src/test/java/org/postgresql/jdbc/ScramTest.java b/src/test/java/org/postgresql/jdbc/ScramTest.java
index c67b778..77880af 100644
--- a/src/test/java/org/postgresql/jdbc/ScramTest.java
+++ b/src/test/java/org/postgresql/jdbc/ScramTest.java
@@ -15,10 +15,12 @@ import static org.junit.jupiter.api.Assumptions.assumeTrue;
import org.postgresql.PGProperty;
import org.postgresql.core.ServerVersion;
import org.postgresql.test.TestUtil;
+import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
@@ -29,6 +31,7 @@ import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.text.NumberFormat;
import java.util.Properties;
import java.util.stream.Stream;
@@ -125,6 +128,79 @@ class ScramTest {
}
}
+ private PSQLException scramAuthExpectingFailure(String scramMaxIterations, int serverScramIterations, String password) throws SQLException {
+ createRoleWithCustomScramIters(serverScramIterations);
+ Properties props = new Properties();
+ PGProperty.USER.set(props, ROLE_NAME);
+ PGProperty.PASSWORD.set(props, password);
+ if (scramMaxIterations != null) {
+ PGProperty.SCRAM_MAX_ITERATIONS.set(props, scramMaxIterations);
+ }
+ return assertThrows(PSQLException.class, () -> TestUtil.openDB(props));
+ }
+
+ @Test
+ void rejectIterationCountAboveDefaultCap() throws SQLException {
+ int serverScramIterations = 789_123_456;
+ PSQLException ex = scramAuthExpectingFailure(null, serverScramIterations, "does-not-matter");
+ assertTrue(ex.getMessage().contains("exceeds"),
+ "expected iteration-cap error, got: " + ex.getMessage());
+ assertTrue(ex.getMessage().contains("scramMaxIterations"),
+ "error should reference the connection property name, got: " + ex.getMessage());
+ // The message is formatted through MessageFormat, which applies locale-aware grouping
+ // to integer arguments; format the expected numbers the same way.
+ NumberFormat nf = NumberFormat.getNumberInstance();
+ assertTrue(ex.getMessage().contains(nf.format(serverScramIterations)),
+ "error should include the configured cap, got: " + ex.getMessage());
+ }
+
+ @Test
+ void rejectIterationCountAboveCustomCap() throws SQLException {
+ int scramMaxIterations = 123_456;
+ int serverScramIterations = 789_123_456;
+ PSQLException ex = scramAuthExpectingFailure(Integer.toString(scramMaxIterations), serverScramIterations, "does-not-matter");
+ // The message is formatted through MessageFormat, which applies locale-aware grouping
+ // to integer arguments; format the expected numbers the same way.
+ NumberFormat nf = NumberFormat.getNumberInstance();
+ assertTrue(ex.getMessage().contains(nf.format(scramMaxIterations)),
+ "error should include the configured cap, got: " + ex.getMessage());
+ assertTrue(ex.getMessage().contains(nf.format(serverScramIterations)),
+ "error should include the server-supplied iteration count, got: " + ex.getMessage());
+ }
+
+ @Test
+ void rejectValidCredentialsAboveCustomCap() throws SQLException {
+ String password = "t0pSecret";
+ createRole(password);
+ Properties props = new Properties();
+ PGProperty.USER.set(props, ROLE_NAME);
+ PGProperty.PASSWORD.set(props, password);
+ PGProperty.SCRAM_MAX_ITERATIONS.set(props, "1234");
+ PSQLException ex = assertThrows(PSQLException.class, () -> TestUtil.openDB(props));
+ // The message is formatted through MessageFormat, which applies locale-aware grouping
+ // to integer arguments; format the expected numbers the same way.
+ NumberFormat nf = NumberFormat.getNumberInstance();
+ assertTrue(ex.getMessage().contains(nf.format(1234)),
+ "error should include the configured cap, got: " + ex.getMessage());
+ }
+
+ @Test
+ void acceptsValidCredentialsBelowCustomCap() throws SQLException {
+ assumeTrue(TestUtil.haveMinimumServerVersion(con, ServerVersion.v16),
+ "scram_iterations configuration requires PostgreSQL 16+");
+ int serverScramIterations = Integer.parseInt(TestUtil.queryForString(con, "SHOW scram_iterations"));
+ String password = "t0pSecret";
+ createRole(password);
+ Properties props = new Properties();
+ PGProperty.USER.set(props, ROLE_NAME);
+ PGProperty.PASSWORD.set(props, password);
+ PGProperty.SCRAM_MAX_ITERATIONS.set(props, Integer.toString(serverScramIterations));
+ try (Connection conn = TestUtil.openDB(props)) {
+ String username = TestUtil.queryForString(conn, "SELECT USER");
+ assertEquals(ROLE_NAME, username);
+ }
+ }
+
private void createRole(String passwd) throws SQLException {
try (Statement stmt = con.createStatement()) {
stmt.execute("SET password_encryption='scram-sha-256'");
@@ -133,4 +209,18 @@ class ScramTest {
}
}
+ private static void createRoleWithCustomScramIters(int iters) throws SQLException {
+ TestUtil.execute(con, "DROP ROLE IF EXISTS " + ROLE_NAME);
+ TestUtil.execute(con, "CREATE ROLE " + ROLE_NAME + " WITH LOGIN");
+ // SCRAM-SHA-256$<iter>:<salt-base64>$<StoredKey-base64>:<ServerKey-base64>
+ // salt: 16 zero bytes, StoredKey and ServerKey: 32 zero bytes each.
+ String encodedPassword = "SCRAM-SHA-256$" + iters
+ + ":AAAAAAAAAAAAAAAAAAAAAA=="
+ + "$AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
+ + ":AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
+ // NOTE: We must directly update the system catalog to prevent the server from trying to
+ // verify the password at creation time. Otherwise it will try to hash empty string with
+ // our huge number of iterations to ensure the password is not an empty string.
+ TestUtil.execute(con, "UPDATE pg_authid SET rolpassword = '" + encodedPassword + "' WHERE rolname = '" + ROLE_NAME + "'");
+ }
}
--
2.54.0