[Git][java-team/libpostgresql-jdbc-java][master] 3 commits: New upstream version 42.7.14
Christoph Berg (@myon)
gitlab at salsa.debian.org
Fri Oct 9 10:29:11 BST 2026
Christoph Berg pushed to branch master at Debian Java Maintainers / libpostgresql-jdbc-java
Commits:
a87f3166 by Christoph Berg at 2026-10-09T11:14:23+02:00
New upstream version 42.7.14
- - - - -
4610d1a0 by Christoph Berg at 2026-10-09T11:14:28+02:00
Update upstream source from tag 'upstream/42.7.14'
Update to upstream version '42.7.14'
with Debian dir 22c9c5d34aeb359897a900922cbdac535afecdb1
- - - - -
2a969c51 by Christoph Berg at 2026-10-09T09:29:00+00:00
New upstream version 42.7.14. (Closes: #1150330)
+ CVE-2026-107315: versions 42.7.4 through 42.7.13 pad a value that is
shorter than its declared length with bytes left in its send buffer
instead of zeros, and the server stores those bytes as part of the
value. The bytes are messages the driver sent earlier on the same
connection: SQL text and parameter values of recent statements, which on
a pooled connection can come from other requests. Each padded value can
carry up to 8192 bytes of this traffic, or 16320 bytes on a connection
with GSS encryption. The padding happens when an application declares a
length larger than the data it supplies, through
PreparedStatement.setObject with a ByteStreamWriter, CopyIn.writeToCopy,
PGCopyOutputStream.write, LargeObject.write, or Blob.setBytes. The
driver accepts these calls without an error. An attacker who can make
the application store such a value and read it back can collect earlier
traffic. Applications whose declared lengths always match their data are
not affected. Versions 42.7.3 and earlier pad with zeros.
+ CVE-2026-107314: versions 42.7.11 through 42.7.13 enforce no restriction
when the requireAuth connection property excludes all six authentication
methods the driver knows, for example
requireAuth=!password,!md5,!gss,!sspi,!scram-sha-256,!none. The driver
then accepts any method the server asks for, including cleartext
password authentication. A value without a method in it, such as
requireAuth=, (a single comma), is affected the same way. An attacker
positioned between the application and its server can ask for cleartext
password authentication and receive the database password. A positive
list such as requireAuth=scram-sha-256, and a partial exclusion such as
requireAuth=!password,!md5, are enforced correctly. The property has no
default value, so a deployment that does not set it is not affected.
42.7.14 fixes the problem: such a connection is refused with SQLState
08004, and a value without a method in it is rejected as invalid.
- - - - -
12 changed files:
- debian/changelog
- pom.xml
- src/main/java/org/postgresql/core/AuthMethod.java
- src/main/java/org/postgresql/util/DriverInfo.java
- src/main/java/org/postgresql/util/internal/PgBufferedOutputStream.java
- src/main/resources/META-INF/MANIFEST.MF
- + src/test/java/org/postgresql/core/AuthMethodTest.java
- + src/test/java/org/postgresql/core/RequireAuthConnectTest.java
- + src/test/java/org/postgresql/core/RequireAuthValues.java
- src/test/java/org/postgresql/test/gss/GSSStreamTest.java
- src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java
- src/test/java/org/postgresql/util/internal/PgBufferedOutputStreamTest.java
Changes:
=====================================
debian/changelog
=====================================
@@ -1,3 +1,38 @@
+libpgjava (42.7.14-1) unstable; urgency=medium
+
+ * New upstream version 42.7.14. (Closes: #1150330)
+ + CVE-2026-107315: versions 42.7.4 through 42.7.13 pad a value that is
+ shorter than its declared length with bytes left in its send buffer
+ instead of zeros, and the server stores those bytes as part of the
+ value. The bytes are messages the driver sent earlier on the same
+ connection: SQL text and parameter values of recent statements, which on
+ a pooled connection can come from other requests. Each padded value can
+ carry up to 8192 bytes of this traffic, or 16320 bytes on a connection
+ with GSS encryption. The padding happens when an application declares a
+ length larger than the data it supplies, through
+ PreparedStatement.setObject with a ByteStreamWriter, CopyIn.writeToCopy,
+ PGCopyOutputStream.write, LargeObject.write, or Blob.setBytes. The
+ driver accepts these calls without an error. An attacker who can make
+ the application store such a value and read it back can collect earlier
+ traffic. Applications whose declared lengths always match their data are
+ not affected. Versions 42.7.3 and earlier pad with zeros.
+ + CVE-2026-107314: versions 42.7.11 through 42.7.13 enforce no restriction
+ when the requireAuth connection property excludes all six authentication
+ methods the driver knows, for example
+ requireAuth=!password,!md5,!gss,!sspi,!scram-sha-256,!none. The driver
+ then accepts any method the server asks for, including cleartext
+ password authentication. A value without a method in it, such as
+ requireAuth=, (a single comma), is affected the same way. An attacker
+ positioned between the application and its server can ask for cleartext
+ password authentication and receive the database password. A positive
+ list such as requireAuth=scram-sha-256, and a partial exclusion such as
+ requireAuth=!password,!md5, are enforced correctly. The property has no
+ default value, so a deployment that does not set it is not affected.
+ 42.7.14 fixes the problem: such a connection is refused with SQLState
+ 08004, and a value without a method in it is rejected as invalid.
+
+ -- Christoph Berg <myon at debian.org> Fri, 09 Oct 2026 11:14:28 +0200
+
libpgjava (42.7.13-1) unstable; urgency=medium
* New upstream version 42.7.13.
=====================================
pom.xml
=====================================
@@ -8,7 +8,7 @@
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
- <version>42.7.13</version>
+ <version>42.7.14</version>
<packaging>jar</packaging>
<name>PostgreSQL JDBC Driver - JDBC 4.2</name>
<description>Java JDBC 4.2 (JRE 8+) driver for PostgreSQL database</description>
=====================================
src/main/java/org/postgresql/core/AuthMethod.java
=====================================
@@ -31,17 +31,40 @@ public enum AuthMethod {
}
}
+ /**
+ * Parses a {@code requireAuth} value into the authentication methods the
+ * server may request.
+ *
+ * @param requireAuth comma-separated method names, either all plain or all
+ * prefixed with {@code !}
+ * @return {@code null} when {@code requireAuth} is {@code null}, and
+ * {@link #checkAuth} allows every method for {@code null}; otherwise the
+ * allowed methods, empty when the value excludes every method
+ * @throws PSQLException with {@link PSQLState#INVALID_PARAMETER_VALUE} if
+ * the value mixes the two forms, names a method twice, names an unknown
+ * method, or is written without a method
+ */
public static /* @Nullable */ EnumSet<AuthMethod> parseRequireAuth(/* @Nullable */ String requireAuth) throws PSQLException {
if (requireAuth == null) {
return null;
}
- EnumSet<AuthMethod> allowedMethods;
EnumSet<AuthMethod> seenMethods = EnumSet.noneOf(AuthMethod.class);
String[] methods = requireAuth.split(",");
- boolean isDisallowMode = methods.length > 0 && methods[0].trim().startsWith("!");
+ // split drops empty strings at the end, so "," and ",," give an empty
+ // array. A value without a method in it names neither methods to allow
+ // nor methods to exclude. It is a mistake, not a request to refuse every
+ // method, so it fails as an invalid value.
+ if (methods.length == 0) {
+ throw new PSQLException(
+ GT.tr("Invalid authentication method: {0}", requireAuth),
+ PSQLState.INVALID_PARAMETER_VALUE);
+ }
+ boolean isDisallowMode = methods[0].trim().startsWith("!");
- allowedMethods = isDisallowMode ? EnumSet.allOf(AuthMethod.class) : EnumSet.noneOf(AuthMethod.class);
+ EnumSet<AuthMethod> allowedMethods = isDisallowMode
+ ? EnumSet.allOf(AuthMethod.class)
+ : EnumSet.noneOf(AuthMethod.class);
for (String method : methods) {
method = method.trim();
@@ -62,9 +85,20 @@ public enum AuthMethod {
allowedMethods.add(authMethod);
}
}
- return allowedMethods.isEmpty() ? null : allowedMethods;
+ // Return an empty set, not null. checkAuth allows every method for null,
+ // so null here would let the server pick any method.
+ return allowedMethods;
}
+ /**
+ * Refuses an authentication method that {@code requireAuth} does not allow.
+ *
+ * @param allowedMethods the methods to allow, or {@code null} to allow
+ * every method; an empty set refuses every method
+ * @param authMethod the method the server asked for
+ * @throws PSQLException with {@link PSQLState#CONNECTION_REJECTED} if the
+ * method is not allowed
+ */
public static void checkAuth(/* @Nullable */ EnumSet<AuthMethod> allowedMethods, AuthMethod authMethod) throws PSQLException {
if (allowedMethods == null) {
return;
=====================================
src/main/java/org/postgresql/util/DriverInfo.java
=====================================
@@ -16,13 +16,13 @@ public final class DriverInfo {
// Driver name
public static final String DRIVER_NAME = "PostgreSQL JDBC Driver";
public static final String DRIVER_SHORT_NAME = "PgJDBC";
- public static final String DRIVER_VERSION = "42.7.13";
+ public static final String DRIVER_VERSION = "42.7.14";
public static final String DRIVER_FULL_NAME = DRIVER_NAME + " " + DRIVER_VERSION;
// Driver version
public static final int MAJOR_VERSION = 42;
public static final int MINOR_VERSION = 7;
- public static final int PATCH_VERSION = 13;
+ public static final int PATCH_VERSION = 14;
// JDBC specification
public static final String JDBC_VERSION = "4.2";
=====================================
src/main/java/org/postgresql/util/internal/PgBufferedOutputStream.java
=====================================
@@ -164,31 +164,18 @@ public class PgBufferedOutputStream extends FilterOutputStream {
* @throws IOException in case writing to the underlying stream fails
*/
public void writeZeros(int len) throws IOException {
- int startPos = count;
- if (count > 0) {
- int avail = buf.length - count;
- int prefixLength = Math.min(len, avail);
- Arrays.fill(buf, count, count + prefixLength, (byte) 0);
- count += prefixLength;
- len -= prefixLength;
+ if (len < 0) {
+ throw new IllegalArgumentException("len must be non-negative, got " + len);
+ }
+ // If the buffer is full on entry, the first pass fills nothing and just flushes.
+ do {
+ int chunk = Math.min(len, buf.length - count);
+ Arrays.fill(buf, count, count + chunk, (byte) 0);
+ count += chunk;
+ len -= chunk;
if (count == buf.length) {
flushBuffer();
}
- if (len == 0) {
- return;
- }
- }
- // The buffer is empty at this point, and startPos..buf.length is filled with zeros
- // So fill the beginning with zeros as well.
- Arrays.fill(buf, 0, Math.min(startPos, len), (byte) 0);
-
- while (len >= buf.length) {
- // Pretend we have the full buffer
- count = buf.length;
- flushBuffer();
- len -= buf.length;
- }
- // Pretend we have the remaining zeros in the buffer.
- count = len;
+ } while (len > 0);
}
}
=====================================
src/main/resources/META-INF/MANIFEST.MF
=====================================
@@ -1,7 +1,7 @@
Manifest-Version: 1.0
Bundle-License: BSD-2-Clause
Implementation-Title: PostgreSQL JDBC Driver
-Implementation-Version: 42.7.13
+Implementation-Version: 42.7.14
Specification-Vendor: Oracle Corporation
Specification-Version: 4.2
Specification-Title: JDBC
=====================================
src/test/java/org/postgresql/core/AuthMethodTest.java
=====================================
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.postgresql.util.PSQLException;
+import org.postgresql.util.PSQLState;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.util.EnumSet;
+
+/**
+ * An unset {@code requireAuth} allows every authentication method, and a
+ * value that excludes every method refuses all of them. A value cannot mix
+ * plain and {@code !} names, name a method twice, name an unknown method, or
+ * be written without a method. Commas at the very end of a value are
+ * ignored.
+ */
+class AuthMethodTest {
+
+ @Test
+ void anUnsetRequireAuthParsesToNull() throws PSQLException {
+ assertNull(AuthMethod.parseRequireAuth(null), "parseRequireAuth(null)");
+ }
+
+ @Test
+ void aPositiveListAllowsOnlyTheMethodsItNames() throws PSQLException {
+ assertEquals(EnumSet.of(AuthMethod.SCRAM_SHA_256, AuthMethod.GSS),
+ AuthMethod.parseRequireAuth("scram-sha-256,gss"));
+ }
+
+ @Test
+ void anExclusionListAllowsEveryMethodItDoesNotName() throws PSQLException {
+ EnumSet<AuthMethod> expected = EnumSet.allOf(AuthMethod.class);
+ expected.remove(AuthMethod.PASSWORD);
+ expected.remove(AuthMethod.MD5);
+
+ assertEquals(expected, AuthMethod.parseRequireAuth("!password,!md5"));
+ }
+
+ /**
+ * Excluding every method but one leaves exactly that one. Over all runs,
+ * each method is checked both when it is the only one allowed and when it
+ * is excluded. A method added to the enum later is checked too.
+ */
+ @ParameterizedTest(name = "every method but {0} excluded")
+ @EnumSource(AuthMethod.class)
+ void anExclusionListCanLeaveASingleMethod(AuthMethod kept)
+ throws PSQLException {
+ String allButKept =
+ RequireAuthValues.excluding(EnumSet.complementOf(EnumSet.of(kept)));
+ EnumSet<AuthMethod> allowed = AuthMethod.parseRequireAuth(allButKept);
+
+ assertEquals(EnumSet.of(kept), allowed,
+ "parseRequireAuth(\"" + allButKept + "\")");
+ assertDoesNotThrow(() -> AuthMethod.checkAuth(allowed, kept));
+ for (AuthMethod other : EnumSet.complementOf(EnumSet.of(kept))) {
+ PSQLException e = assertThrows(PSQLException.class,
+ () -> AuthMethod.checkAuth(allowed, other), "checkAuth for " + other);
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(),
+ "SQLState of the refusal of " + other);
+ }
+ }
+
+ @Test
+ void surroundingSpaceIsIgnoredInAPositiveList() throws PSQLException {
+ assertEquals(EnumSet.of(AuthMethod.PASSWORD, AuthMethod.MD5),
+ AuthMethod.parseRequireAuth(" password , md5 "));
+ }
+
+ @Test
+ void surroundingSpaceIsIgnoredInAnExclusionList() throws PSQLException {
+ assertEquals(
+ EnumSet.complementOf(EnumSet.of(AuthMethod.PASSWORD, AuthMethod.MD5)),
+ AuthMethod.parseRequireAuth(" !password , !md5 "));
+ }
+
+ /**
+ * Excluding every method must parse to an empty set, not to null.
+ * checkAuth allows every method for null, so with null the driver sends a
+ * cleartext password to a server that asks for one.
+ */
+ @Test
+ void excludingEveryMethodParsesToAnEmptySet() throws PSQLException {
+ EnumSet<AuthMethod> allowed =
+ AuthMethod.parseRequireAuth(RequireAuthValues.EVERY_METHOD_EXCLUDED);
+
+ assertEquals(EnumSet.noneOf(AuthMethod.class), allowed,
+ "parseRequireAuth(\"" + RequireAuthValues.EVERY_METHOD_EXCLUDED
+ + "\")");
+ }
+
+ @ParameterizedTest(name = "{0} against an empty allowed set")
+ @EnumSource(AuthMethod.class)
+ void anEmptyAllowedSetRefusesEveryMethod(AuthMethod method) {
+ PSQLException e = assertThrows(PSQLException.class,
+ () -> AuthMethod.checkAuth(EnumSet.noneOf(AuthMethod.class), method));
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(),
+ "SQLState of the refusal");
+ }
+
+ @ParameterizedTest(name = "{0} against an unset requireAuth")
+ @EnumSource(AuthMethod.class)
+ void anUnsetRequireAuthAllowsEveryMethod(AuthMethod method) {
+ assertDoesNotThrow(() -> AuthMethod.checkAuth(null, method));
+ }
+
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"password,!md5", "!password,md5"})
+ void positiveAndNegativeFormsCannotBeMixed(String requireAuth) {
+ assertParseError(requireAuth);
+ }
+
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"password,password", "!md5,!md5"})
+ void aMethodCannotBeNamedTwice(String requireAuth) {
+ assertParseError(requireAuth);
+ }
+
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"sha-256", "trust"})
+ void anUnknownMethodIsRefused(String requireAuth) {
+ assertParseError(requireAuth);
+ }
+
+ /**
+ * Commas at the very end of the value are ignored, and the methods before
+ * them are enforced. Users may already have such a value in their
+ * connection settings, and refusing it would break their connections.
+ */
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"password,", "password,,"})
+ void commasAtTheEndAreIgnored(String requireAuth) throws PSQLException {
+ assertEquals(EnumSet.of(AuthMethod.PASSWORD),
+ AuthMethod.parseRequireAuth(requireAuth),
+ "parseRequireAuth(\"" + requireAuth + "\")");
+ }
+
+ /**
+ * A value without a method in it is refused as an invalid value. It names
+ * neither methods to allow nor methods to exclude, so it is a mistake, not
+ * a request to refuse every method.
+ */
+ @ParameterizedTest(name = "requireAuth={0}")
+ @ValueSource(strings = {"!", ",", ",,"})
+ void aValueWithoutAMethodIsRefused(String requireAuth) {
+ assertParseError(requireAuth);
+ }
+
+ private static void assertParseError(String requireAuth) {
+ PSQLException e = assertThrows(PSQLException.class,
+ () -> AuthMethod.parseRequireAuth(requireAuth));
+ assertEquals(PSQLState.INVALID_PARAMETER_VALUE.getState(), e.getSQLState(),
+ "SQLState of the parse error");
+ }
+}
=====================================
src/test/java/org/postgresql/core/RequireAuthConnectTest.java
=====================================
@@ -0,0 +1,206 @@
+/*
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core;
+
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+import org.postgresql.PGProperty;
+import org.postgresql.util.PSQLState;
+
+import org.junit.jupiter.api.Test;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Properties;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.net.SocketFactory;
+
+/**
+ * A requireAuth value that excludes every method must refuse the connection
+ * with {@link PSQLState#CONNECTION_REJECTED}, whatever the server asks for,
+ * and the driver must not send the password.
+ */
+public class RequireAuthConnectTest {
+
+ /** AuthenticationCleartextPassword: the server asks for the password. */
+ private static final byte[] CLEARTEXT_PASSWORD_REQUEST =
+ {'R', 0, 0, 0, 8, 0, 0, 0, 3};
+
+ /** AuthenticationOk sent before any request: trust authentication. */
+ private static final byte[] AUTHENTICATION_OK =
+ {'R', 0, 0, 0, 8, 0, 0, 0, 0};
+
+ private static final String PASSWORD = "secret";
+
+ /** The PasswordMessage that carries {@link #PASSWORD}. */
+ private static final byte[] PASSWORD_MESSAGE =
+ {'p', 0, 0, 0, 11, 's', 'e', 'c', 'r', 'e', 't', 0};
+
+ @Test
+ void refusesACleartextPasswordRequest() {
+ FakeServer server = new FakeServer(CLEARTEXT_PASSWORD_REQUEST);
+ SQLException e = assertThrows(SQLException.class,
+ () -> server.connect(RequireAuthValues.EVERY_METHOD_EXCLUDED));
+ // The bytes are checked before the SQLState, so if the driver sends the
+ // password, the test fails on the password and not on the error that
+ // follows it.
+ assertArrayEquals(new byte[0], server.writtenAfterStartup(),
+ "bytes the driver wrote after the startup message");
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(),
+ "SQLState of the refusal");
+ }
+
+ /**
+ * A server that sends the same request gets a PasswordMessage when
+ * requireAuth allows the password. Without this test, the empty result in
+ * {@link #refusesACleartextPasswordRequest} could come from a socket that
+ * stopped recording.
+ */
+ @Test
+ void sendsThePasswordWhenRequireAuthAllowsIt() {
+ FakeServer server = new FakeServer(CLEARTEXT_PASSWORD_REQUEST);
+ // The reply ends after the request, so the driver fails at end of
+ // stream once it has sent the password. This test does not check that
+ // failure.
+ assertThrows(SQLException.class, () -> server.connect("password"));
+ assertArrayEquals(PASSWORD_MESSAGE, server.writtenAfterStartup(),
+ "bytes the driver wrote after the startup message");
+ }
+
+ /**
+ * requireAuth calls trust authentication "none", and
+ * {@link RequireAuthValues#EVERY_METHOD_EXCLUDED} excludes "none" too.
+ */
+ @Test
+ void refusesTrust() {
+ FakeServer server = new FakeServer(AUTHENTICATION_OK);
+ SQLException e = assertThrows(SQLException.class,
+ () -> server.connect(RequireAuthValues.EVERY_METHOD_EXCLUDED));
+ assertEquals(PSQLState.CONNECTION_REJECTED.getState(), e.getSQLState(),
+ "SQLState of the refusal");
+ }
+
+ /** Replies with a fixed message and records what the driver writes. */
+ private static final class FakeServer {
+ /**
+ * The driver creates the socket factory from its class name and passes
+ * it only string properties. So the factory finds the server by the key
+ * that {@link #connect} passes as socketFactoryArg.
+ */
+ static final Map<String, FakeServer> BY_KEY = new ConcurrentHashMap<>();
+
+ final byte[] reply;
+ final ByteArrayOutputStream written = new ByteArrayOutputStream();
+
+ FakeServer(byte[] reply) {
+ this.reply = reply;
+ }
+
+ void connect(String requireAuth) throws SQLException {
+ String key = UUID.randomUUID().toString();
+ Properties props = new Properties();
+ PGProperty.USER.set(props, "test");
+ PGProperty.PASSWORD.set(props, PASSWORD);
+ PGProperty.SSL_MODE.set(props, "disable");
+ PGProperty.GSS_ENC_MODE.set(props, "disable");
+ PGProperty.REQUIRE_AUTH.set(props, requireAuth);
+ PGProperty.SOCKET_FACTORY.set(props,
+ FakeServerSocketFactory.class.getName());
+ PGProperty.SOCKET_FACTORY_ARG.set(props, key);
+ BY_KEY.put(key, this);
+ try {
+ // The factory ignores the host. The driver records the failure for the
+ // host in the JVM-wide GlobalHostStatusTracker, which multi-host URLs
+ // read, so the host is a name that cannot exist.
+ DriverManager.getConnection(
+ "jdbc:postgresql://requireauth.invalid/test", props).close();
+ } finally {
+ BY_KEY.remove(key);
+ }
+ }
+
+ /**
+ * Returns what the driver wrote after its startup message, which starts
+ * with its length.
+ */
+ byte[] writtenAfterStartup() {
+ byte[] bytes = written.toByteArray();
+ int startupLength = ByteBuffer.wrap(bytes).getInt();
+ return Arrays.copyOfRange(bytes, startupLength, bytes.length);
+ }
+ }
+
+ /**
+ * Creates sockets that read {@link FakeServer#reply} and write to
+ * {@link FakeServer#written}. The driver loads this class through the
+ * socketFactory property and calls only {@link #createSocket()}.
+ */
+ public static class FakeServerSocketFactory extends SocketFactory {
+ private final FakeServer server;
+
+ public FakeServerSocketFactory(String key) {
+ this.server = FakeServer.BY_KEY.get(key);
+ }
+
+ @Override
+ public Socket createSocket() {
+ return new Socket() {
+ private final InputStream in = new ByteArrayInputStream(server.reply);
+
+ /** Reports connected, so the driver does not connect it. */
+ @Override
+ public boolean isConnected() {
+ return true;
+ }
+
+ @Override
+ public InputStream getInputStream() {
+ return in;
+ }
+
+ @Override
+ public OutputStream getOutputStream() {
+ return server.written;
+ }
+ };
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost,
+ int localPort) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port,
+ InetAddress localAddress, int localPort) {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
=====================================
src/test/java/org/postgresql/core/RequireAuthValues.java
=====================================
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2026, PostgreSQL Global Development Group
+ * See the LICENSE file in the project root for more information.
+ */
+
+package org.postgresql.core;
+
+import java.util.EnumSet;
+import java.util.Locale;
+import java.util.stream.Collectors;
+
+/**
+ * Builds {@code requireAuth} values from the {@link AuthMethod} enum, so the
+ * tests pick up a new method without anyone editing a list. If a name built
+ * here does not match what {@link AuthMethod#fromString} accepts, fromString
+ * throws and the tests fail.
+ */
+final class RequireAuthValues {
+
+ /** Excludes every method, so a connection that uses it must be refused. */
+ static final String EVERY_METHOD_EXCLUDED =
+ excluding(EnumSet.allOf(AuthMethod.class));
+
+ private RequireAuthValues() {
+ }
+
+ /**
+ * The requireAuth value that excludes each of {@code methods}, as in
+ * {@code !password,!md5}.
+ */
+ static String excluding(EnumSet<AuthMethod> methods) {
+ return methods.stream()
+ .map(method ->
+ "!" + method.name().toLowerCase(Locale.ROOT).replace('_', '-'))
+ .collect(Collectors.joining(","));
+ }
+}
=====================================
src/test/java/org/postgresql/test/gss/GSSStreamTest.java
=====================================
@@ -18,6 +18,8 @@ import org.junit.jupiter.api.Test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Arrays;
import java.util.Random;
public class GSSStreamTest {
@@ -74,4 +76,44 @@ public class GSSStreamTest {
assertArrayEquals(testMessage, unwrapResult,
"the message should be intact after wrap and unwrap");
}
+
+ /**
+ * {@link GSSOutputStream} inherits {@code writeZeros}. {@code write} sends 100 bytes or more
+ * written into an empty buffer straight from the caller's array, so the data goes in as 3 bytes
+ * and then 97: the 97-byte write fills the buffer exactly, {@code write} sends it and leaves the
+ * 0xff bytes in the array, and {@code writeZeros} starts on an empty buffer that is not zeroed.
+ * It used to send those bytes in place of the zeros.
+ */
+ @Test
+ void writeZerosAfterDataEndingAtBufferBoundarySendsZeros() throws Exception {
+ ByteArrayOutputStream wrappedContents = new ByteArrayOutputStream();
+ MockGSSContext gssContext = new MockGSSContext(0, messageProp);
+ // MockGSSContext.getWrapSizeLimit returns 100, so the GSSOutputStream buffer holds 100 bytes
+ GSSOutputStream out = new GSSOutputStream(
+ new PgBufferedOutputStream(wrappedContents, 20), gssContext, messageProp, 20);
+ byte[] data = new byte[100];
+ Arrays.fill(data, (byte) 0xff);
+ out.write(data, 0, 3);
+ out.write(data, 3, 97);
+
+ out.writeZeros(103);
+ out.flush();
+
+ byte[] expected = new byte[203];
+ Arrays.fill(expected, 0, 100, (byte) 0xff);
+ assertArrayEquals(expected, unwrap(wrappedContents.toByteArray(), gssContext),
+ "100 bytes of 0xff written as 3 + 97, then writeZeros(103)");
+ }
+
+ private byte[] unwrap(byte[] wrapped, MockGSSContext gssContext) throws IOException {
+ GSSInputStream in =
+ new GSSInputStream(new ByteArrayInputStream(wrapped), gssContext, messageProp);
+ ByteArrayOutputStream unwrapped = new ByteArrayOutputStream();
+ byte[] tmp = new byte[256];
+ int n;
+ while ((n = in.read(tmp)) != -1) {
+ unwrapped.write(tmp, 0, n);
+ }
+ return unwrapped.toByteArray();
+ }
}
=====================================
src/test/java/org/postgresql/test/util/ByteStreamWriterTest.java
=====================================
@@ -176,6 +176,22 @@ public class ByteStreamWriterTest extends BaseTest4 {
validateContent(expectedData);
}
+ /**
+ * The content is more than twice the 8192-byte send buffer, so the driver sends it to the socket
+ * without copying it into the buffer, and the buffer array still holds the bytes of the messages
+ * sent before the value when the padding starts. Those bytes used to be sent in place of the
+ * zeros, and the server stored them in the column.
+ */
+ @Test
+ public void contentOverTwiceTheSendBufferIsPaddedWithZeros() throws Exception {
+ ByteBuffer testData = testData(20 * 1024);
+ insertStream(testData, 30 * 1024);
+ byte[] expectedData = new byte[30 * 1024];
+ testData.rewind();
+ testData.get(expectedData, 0, 20 * 1024);
+ validateContent(expectedData);
+ }
+
@Test
public void testLengthLessThanContent() throws Exception {
ByteBuffer testData = testData(8);
=====================================
src/test/java/org/postgresql/util/internal/PgBufferedOutputStreamTest.java
=====================================
@@ -6,6 +6,7 @@
package org.postgresql.util.internal;
import static org.junit.jupiter.api.Assertions.assertAll;
+import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.params.provider.Arguments.arguments;
@@ -290,6 +291,32 @@ public class PgBufferedOutputStreamTest {
}
);
}
+
+ /**
+ * {@code flush()} sends the buffer and resets its count, but leaves the 0xff bytes in the
+ * array, so {@code writeZeros} starts on an empty buffer that is not zeroed. It used to send
+ * those bytes in place of the zeros.
+ */
+ @ParameterizedTest
+ @ValueSource(ints = {1, ZEROS_BUFFER_SIZE - 1, ZEROS_BUFFER_SIZE, ZEROS_BUFFER_SIZE + 1,
+ ZEROS_BUFFER_SIZE * 2 + 1})
+ void writesZerosWhenTheFlushedBufferStillHoldsEarlierBytes(int numZeros) throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ int bufferSize = ZEROS_BUFFER_SIZE;
+ PgBufferedOutputStream out = new PgBufferedOutputStream(baos, bufferSize);
+ for (int i = 0; i < bufferSize; i++) {
+ out.write(0xff);
+ }
+ out.flush();
+
+ out.writeZeros(numZeros);
+ out.flush();
+
+ byte[] expected = new byte[bufferSize + numZeros];
+ Arrays.fill(expected, 0, bufferSize, (byte) 0xff);
+ assertArrayEquals(expected, baos.toByteArray(),
+ () -> bufferSize + " bytes of 0xff, flush(), writeZeros(" + numZeros + ")");
+ }
}
@Test
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/6173746e56043abd48a341a000f56227fb198204...2a969c513914b30805ec2fe82f78d769250f0f89
--
View it on GitLab: https://salsa.debian.org/java-team/libpostgresql-jdbc-java/-/compare/6173746e56043abd48a341a000f56227fb198204...2a969c513914b30805ec2fe82f78d769250f0f89
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-java-commits/attachments/20261009/fad40264/attachment.htm>
More information about the pkg-java-commits
mailing list