155 lines
7.4 KiB
Diff
155 lines
7.4 KiB
Diff
From be412561fe988dc9396ec3e492af2e8a4e43c620 Mon Sep 17 00:00:00 2001
|
|
From: Sehrope Sarkuni <sehrope@jackdb.com>
|
|
Date: Sat, 18 Apr 2026 08:20:17 -0400
|
|
Subject: [PATCH] fix: Limit SCRAM PBKDF2 iterations accepted from the server
|
|
|
|
A malicious or compromised PostgreSQL server can advertise an
|
|
arbitrarily large PBKDF2 iteration count in its SCRAM
|
|
server-first-message, forcing the client to burn CPU inside
|
|
clientFinalMessage() before authentication can possibly fail. Combined
|
|
with an abandoned connect-thread on loginTimeout expiry, that CPU
|
|
continues spinning after the caller has given up.
|
|
|
|
We add a new `scramMaxIterations` connection property (default 100000)
|
|
and validate the iteration count from ServerFirstMessage against it
|
|
after parsing but before the PBKDF2-heavy clientFinalMessage() step.
|
|
Exceeding the cap throws a PSQLException with CONNECTION_REJECTED and
|
|
an error message naming the property so operators can raise it for
|
|
trusted servers that legitimately use a higher count.
|
|
|
|
Fixes CVE-2026-42198
|
|
---
|
|
.../src/main/java/org/postgresql/PGProperty.java | 14 ++++++++++++++
|
|
.../core/v3/ConnectionFactoryImpl.java | 9 ++++++++-
|
|
.../org/postgresql/ds/common/BaseDataSource.java | 16 ++++++++++++++++
|
|
.../postgresql/jre7/sasl/ScramAuthenticator.java | 15 ++++++++++++++-
|
|
4 files changed, 52 insertions(+), 2 deletions(-)
|
|
|
|
diff --git a/pgjdbc/src/main/java/org/postgresql/PGProperty.java b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
|
|
index 3f4cad67..2d6ec487 100644
|
|
--- a/pgjdbc/src/main/java/org/postgresql/PGProperty.java
|
|
+++ b/pgjdbc/src/main/java/org/postgresql/PGProperty.java
|
|
@@ -480,6 +480,20 @@ public enum PGProperty {
|
|
"false",
|
|
"Enable optimization to rewrite and collapse compatible INSERT statements that are batched."),
|
|
|
|
+ /**
|
|
+ * Maximum number of PBKDF2 iterations the client will accept from the server during SCRAM
|
|
+ * authentication. If the server advertises more iterations than this value, authentication
|
|
+ * is rejected before the expensive PBKDF2 computation runs. This mitigates a denial-of-service
|
|
+ * vector where a malicious or compromised server forces the client to burn CPU on an
|
|
+ * attacker-controlled iteration count. Must be a non-negative integer. Defaults to 100000. Raise
|
|
+ * only if you know you are connecting to a trusted server that legitimately uses a higher
|
|
+ * iteration count. A value of zero disables this check.
|
|
+ */
|
|
+ SCRAM_MAX_ITERATIONS(
|
|
+ "scramMaxIterations",
|
|
+ "100000",
|
|
+ "Maximum PBKDF2 iteration count accepted from the server during SCRAM authentication. A value of zero disables this check."),
|
|
+
|
|
/**
|
|
* Socket write buffer size (SO_SNDBUF). A value of {@code -1}, which is the default, means system
|
|
* default.
|
|
diff --git a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
|
|
index 0ffc9e56..7732bdcb 100644
|
|
--- a/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
|
|
+++ b/pgjdbc/src/main/java/org/postgresql/core/v3/ConnectionFactoryImpl.java
|
|
@@ -754,9 +754,16 @@ public class ConnectionFactoryImpl extends ConnectionFactory {
|
|
|
|
case AUTH_REQ_SASL:
|
|
LOGGER.log(Level.FINEST, " <=BE AuthenticationSASL");
|
|
+ int scramMaxIterations = PGProperty.SCRAM_MAX_ITERATIONS.getInt(info);
|
|
+ if (scramMaxIterations < 0) {
|
|
+ throw new PSQLException(
|
|
+ GT.tr("{0} must be a non-negative integer, but was: {1}",
|
|
+ PGProperty.SCRAM_MAX_ITERATIONS.getName(), scramMaxIterations),
|
|
+ PSQLState.INVALID_PARAMETER_VALUE);
|
|
+ }
|
|
|
|
//#if mvn.project.property.postgresql.jdbc.spec >= "JDBC4.1"
|
|
- scramAuthenticator = new org.postgresql.jre7.sasl.ScramAuthenticator(user, castNonNull(password), pgStream);
|
|
+ scramAuthenticator = new org.postgresql.jre7.sasl.ScramAuthenticator(user, castNonNull(password), pgStream, scramMaxIterations);
|
|
scramAuthenticator.processServerMechanismsAndInit();
|
|
scramAuthenticator.sendScramClientFirstMessage();
|
|
// This works as follows:
|
|
diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
|
|
index 1dfa42c3..f1c5ec78 100644
|
|
--- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
|
|
+++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java
|
|
@@ -1208,6 +1208,22 @@ public abstract class BaseDataSource implements CommonDataSource, Referenceable
|
|
PGProperty.LOGGER_FILE.set(properties, loggerFile);
|
|
}
|
|
|
|
+ /**
|
|
+ * @return maximum PBKDF2 iteration count accepted during SCRAM authentication
|
|
+ * @see PGProperty#SCRAM_MAX_ITERATIONS
|
|
+ */
|
|
+ public int getScramMaxIterations() {
|
|
+ return PGProperty.SCRAM_MAX_ITERATIONS.getIntNoCheck(properties);
|
|
+ }
|
|
+
|
|
+ /**
|
|
+ * @param scramMaxIterations maximum PBKDF2 iteration count accepted during SCRAM authentication
|
|
+ * @see PGProperty#SCRAM_MAX_ITERATIONS
|
|
+ */
|
|
+ public void setScramMaxIterations(int scramMaxIterations) {
|
|
+ PGProperty.SCRAM_MAX_ITERATIONS.set(properties, scramMaxIterations);
|
|
+ }
|
|
+
|
|
/**
|
|
* Generates a {@link DriverManager} URL from the other properties supplied.
|
|
*
|
|
diff --git a/pgjdbc/src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java b/pgjdbc/src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java
|
|
index 59f272a0..f14b3c07 100644
|
|
--- a/pgjdbc/src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java
|
|
+++ b/pgjdbc/src/main/java/org/postgresql/jre7/sasl/ScramAuthenticator.java
|
|
@@ -7,6 +7,7 @@ package org.postgresql.jre7.sasl;
|
|
|
|
import static org.postgresql.util.internal.Nullness.castNonNull;
|
|
|
|
+import org.postgresql.PGProperty;
|
|
import org.postgresql.core.PGStream;
|
|
import org.postgresql.util.GT;
|
|
import org.postgresql.util.PSQLException;
|
|
@@ -34,6 +35,7 @@ public class ScramAuthenticator {
|
|
private final String user;
|
|
private final String password;
|
|
private final PGStream pgStream;
|
|
+ private final int maxIterations;
|
|
private /* @Nullable */ ScramClient scramClient;
|
|
private /* @Nullable */ ScramSession scramSession;
|
|
private /* @Nullable */ ScramSession.ClientFinalProcessor clientFinalProcessor;
|
|
@@ -50,10 +52,11 @@ public class ScramAuthenticator {
|
|
pgStream.flush();
|
|
}
|
|
|
|
- public ScramAuthenticator(String user, String password, PGStream pgStream) {
|
|
+ public ScramAuthenticator(String user, String password, PGStream pgStream, int maxIterations) {
|
|
this.user = user;
|
|
this.password = password;
|
|
this.pgStream = pgStream;
|
|
+ this.maxIterations = maxIterations;
|
|
}
|
|
|
|
public void processServerMechanismsAndInit() throws IOException, PSQLException {
|
|
@@ -144,6 +147,16 @@ public class ScramAuthenticator {
|
|
);
|
|
}
|
|
|
|
+ int iterations = serverFirstProcessor.getIteration();
|
|
+ if (maxIterations > 0 && iterations > maxIterations) {
|
|
+ throw new PSQLException(
|
|
+ GT.tr("Server requested {0} SCRAM PBKDF2 iterations, which exceeds the "
|
|
+ + "client-side limit of {1}. If you trust this server, raise the "
|
|
+ + "{2} connection property.",
|
|
+ iterations, maxIterations, PGProperty.SCRAM_MAX_ITERATIONS.getName()),
|
|
+ PSQLState.CONNECTION_REJECTED);
|
|
+ }
|
|
+
|
|
clientFinalProcessor = serverFirstProcessor.clientFinalProcessor(password);
|
|
|
|
String clientFinalMessage = clientFinalProcessor.clientFinalMessage();
|
|
--
|
|
2.52.0
|
|
|