import CS git postgresql-12.22-7.el8

This commit is contained in:
AlmaLinux RelEng Bot 2026-06-25 19:21:38 -04:00
parent 6ee4778461
commit 70d1a0d58d
7 changed files with 2782 additions and 14 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,155 @@
From 9923bac72843cd02938868b59284cae16ae24183 Mon Sep 17 00:00:00 2001
From: Petr Khartskhaev <pkhartsk@redhat.com>
Date: Thu, 4 Jun 2026 16:01:59 +0200
Subject: [PATCH] Prevent path traversal in pg_basebackup and pg_rewind
pg_rewind and pg_basebackup could be fed paths from rogue endpoints that
could overwrite the contents of the client when received, achieving path
traversal.
There were two areas in the tree that were sensitive to this problem:
- pg_basebackup, through the astreamer code, where no validation was
performed before building an output path when streaming tar data. This
is an issue in v15 and newer versions.
- pg_rewind file operations for paths received through libpq, for all
the stable branches supported.
In order to address this problem, this commit adds a helper function in
path.c, that reuses path_is_relative_and_below_cwd() after applying
canonicalize_path(). This can be used to validate the paths received
from a connection point. A path is considered invalid if any of the two
following conditions is satisfied:
- The path is absolute.
- The path includes a direct parent-directory reference.
Reported-by: XlabAI Team of Tencent Xuanwu Lab
Reported-by: Valery Gubanov <valerygubanov95@gmail.com>
Author: Michael Paquier <michael@paquier.xyz>
Reviewed-by: Amit Kapila <amit.kapila16@gmail.com>
Backpatch-through: 14
Security: CVE-2026-6475
---
src/bin/pg_rewind/file_ops.c | 23 +++++++++++++++++++++++
src/include/port.h | 1 +
src/port/path.c | 17 +++++++++++++++++
3 files changed, 41 insertions(+)
diff --git a/src/bin/pg_rewind/file_ops.c b/src/bin/pg_rewind/file_ops.c
index f9e41b1..9b218a4 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -43,6 +43,9 @@ open_target_file(const char *path, bool trunc)
{
int mode;
+ if (!path_is_safe_for_extraction(path))
+ pg_fatal("target file path is unsafe for open: \"%s\"", path);
+
if (dry_run)
return;
@@ -173,6 +176,9 @@ remove_target_file(const char *path, bool missing_ok)
{
char dstpath[MAXPGPATH];
+ if (!path_is_safe_for_extraction(path))
+ pg_fatal("target file path is unsafe for removal: \"%s\"", path);
+
if (dry_run)
return;
@@ -193,6 +199,9 @@ truncate_target_file(const char *path, off_t newsize)
char dstpath[MAXPGPATH];
int fd;
+ if (!path_is_safe_for_extraction(path))
+ pg_fatal("target file path is unsafe for truncation: \"%s\"", path);
+
if (dry_run)
return;
@@ -215,6 +224,10 @@ create_target_dir(const char *path)
{
char dstpath[MAXPGPATH];
+ if (!path_is_safe_for_extraction(path))
+ pg_fatal("target directory path is unsafe for directory creation: \"%s\"",
+ path);
+
if (dry_run)
return;
@@ -229,6 +242,10 @@ remove_target_dir(const char *path)
{
char dstpath[MAXPGPATH];
+ if (!path_is_safe_for_extraction(path))
+ pg_fatal("target directory path is unsafe for directory removal: \"%s\"",
+ path);
+
if (dry_run)
return;
@@ -243,6 +260,9 @@ create_target_symlink(const char *path, const char *link)
{
char dstpath[MAXPGPATH];
+ if (!path_is_safe_for_extraction(path))
+ pg_fatal("target symlink path is unsafe for creation: \"%s\"", path);
+
if (dry_run)
return;
@@ -257,6 +277,9 @@ remove_target_symlink(const char *path)
{
char dstpath[MAXPGPATH];
+ if (!path_is_safe_for_extraction(path))
+ pg_fatal("target symlink path is unsafe for removal: \"%s\"", path);
+
if (dry_run)
return;
diff --git a/src/include/port.h b/src/include/port.h
index 32925ad..21a8f8a 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -54,6 +54,7 @@ extern void make_native_path(char *path);
extern void cleanup_path(char *path);
extern bool path_contains_parent_reference(const char *path);
extern bool path_is_relative_and_below_cwd(const char *path);
+extern bool path_is_safe_for_extraction(const char *path);
extern bool path_is_prefix_of_path(const char *path1, const char *path2);
extern char *make_absolute_path(const char *path);
extern const char *get_progname(const char *argv0);
diff --git a/src/port/path.c b/src/port/path.c
index 710988b..db30842 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -428,6 +428,23 @@ path_is_relative_and_below_cwd(const char *path)
return true;
}
+/*
+ * Detect whether a path is safe for use during archive extraction.
+ *
+ * This applies canonicalize_path(), then it checks that the path does
+ * not contain any parent directory references.
+ */
+bool
+path_is_safe_for_extraction(const char *path)
+{
+ char buf[MAXPGPATH];
+
+ strlcpy(buf, path, sizeof(buf));
+ canonicalize_path(buf);
+
+ return path_is_relative_and_below_cwd(buf);
+}
+
/*
* Detect whether path1 is a prefix of path2 (including equality).
*
--
2.54.0

View File

@ -0,0 +1,222 @@
From 7174222c87a07fbc0423f2b9bde3cea2cada311c Mon Sep 17 00:00:00 2001
From: Petr Khartskhaev <pkhartsk@redhat.com>
Date: Thu, 4 Jun 2026 16:01:38 +0200
Subject: [PATCH] fix CVE-2026-6477
---
doc/src/sgml/libpq.sgml | 11 ++++++++---
src/interfaces/libpq/fe-exec.c | 18 ++++++++++++++++--
src/interfaces/libpq/fe-lobj.c | 12 ++++++------
src/interfaces/libpq/fe-protocol2.c | 16 +++++++++++++++-
src/interfaces/libpq/fe-protocol3.c | 14 +++++++++++++-
src/interfaces/libpq/libpq-int.h | 9 +++++++--
6 files changed, 65 insertions(+), 15 deletions(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index cda4e8d..3e87183 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -5272,15 +5272,20 @@ int PQrequestCancel(PGconn *conn);
to send simple function calls to the server.
</para>
- <tip>
+ <warning>
<para>
- This interface is somewhat obsolete, as one can achieve similar
+ This interface is unsafe and should not be used. When
+ <parameter>result_is_int</parameter> is set to <literal>0</literal>,
+ <function>PQfn</function> may write data beyond the end of
+ <parameter>result_buf</parameter>, regardless of whether the buffer has
+ enough space for the requested number of bytes. Furthermore, it is
+ obsolete, as one can achieve similar
performance and greater functionality by setting up a prepared
statement to define the function call. Then, executing the statement
with binary transmission of parameters and results substitutes for a
fast-path function call.
</para>
- </tip>
+ </warning>
<para>
The function <function>PQfn</function><indexterm><primary>PQfn</primary></indexterm>
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index ff101c4..5dea7e7 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2661,6 +2661,20 @@ PQfn(PGconn *conn,
int result_is_int,
const PQArgBlock *args,
int nargs)
+{
+ return PQnfn(conn, fnid, result_buf, -1, result_len,
+ result_is_int, args, nargs);
+}
+
+/*
+ * PQnfn
+ * Private version of PQfn() with verification that returned data fits in
+ * result_buf when result_is_int == 0. Setting buf_size to -1 disables
+ * this verification.
+ */
+PGresult *
+PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size, int *result_len,
+ int result_is_int, const PQArgBlock *args, int nargs)
{
*result_len = 0;
@@ -2680,12 +2694,12 @@ PQfn(PGconn *conn,
if (PG_PROTOCOL_MAJOR(conn->pversion) >= 3)
return pqFunctionCall3(conn, fnid,
- result_buf, result_len,
+ result_buf, buf_size, result_len,
result_is_int,
args, nargs);
else
return pqFunctionCall2(conn, fnid,
- result_buf, result_len,
+ result_buf, buf_size, result_len,
result_is_int,
args, nargs);
}
diff --git a/src/interfaces/libpq/fe-lobj.c b/src/interfaces/libpq/fe-lobj.c
index 6866d64..b019b9e 100644
--- a/src/interfaces/libpq/fe-lobj.c
+++ b/src/interfaces/libpq/fe-lobj.c
@@ -288,8 +288,8 @@ lo_read(PGconn *conn, int fd, char *buf, size_t len)
argv[1].len = 4;
argv[1].u.integer = (int) len;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_read,
- (void *) buf, &result_len, 0, argv, 2);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_read,
+ (void *) buf, len, &result_len, 0, argv, 2);
if (PQresultStatus(res) == PGRES_COMMAND_OK)
{
PQclear(res);
@@ -439,8 +439,8 @@ lo_lseek64(PGconn *conn, int fd, pg_int64 offset, int whence)
argv[2].len = 4;
argv[2].u.integer = whence;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_lseek64,
- (void *) &retval, &result_len, 0, argv, 3);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_lseek64,
+ (void *) &retval, sizeof(retval), &result_len, 0, argv, 3);
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
@@ -605,8 +605,8 @@ lo_tell64(PGconn *conn, int fd)
argv[0].len = 4;
argv[0].u.integer = fd;
- res = PQfn(conn, conn->lobjfuncs->fn_lo_tell64,
- (void *) &retval, &result_len, 0, argv, 1);
+ res = PQnfn(conn, conn->lobjfuncs->fn_lo_tell64,
+ (void *) &retval, sizeof(retval), &result_len, 0, argv, 1);
if (PQresultStatus(res) == PGRES_COMMAND_OK && result_len == 8)
{
PQclear(res);
diff --git a/src/interfaces/libpq/fe-protocol2.c b/src/interfaces/libpq/fe-protocol2.c
index 0e36974..b4f0c10 100644
--- a/src/interfaces/libpq/fe-protocol2.c
+++ b/src/interfaces/libpq/fe-protocol2.c
@@ -1434,7 +1434,7 @@ pqEndcopy2(PGconn *conn)
*/
PGresult *
pqFunctionCall2(PGconn *conn, Oid fnid,
- int *result_buf, int *actual_result_len,
+ int *result_buf, int buf_size, int *actual_result_len,
int result_is_int,
const PQArgBlock *args, int nargs)
{
@@ -1516,6 +1516,20 @@ pqFunctionCall2(PGconn *conn, Oid fnid,
}
else
{
+ /*
+ * If the server returned too much data for the
+ * buffer, something fishy is going on. Abandon ship.
+ */
+ if (buf_size != -1 && *actual_result_len > buf_size)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server returned too much data\n"));
+ conn->asyncStatus = PGASYNC_READY;
+ pqDropConnection(conn, true);
+ conn->status = CONNECTION_BAD;
+ return pqPrepareAsyncResult(conn);
+ }
+
if (pqGetnchar((char *) result_buf,
*actual_result_len,
conn))
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index c9f88ba..2931453 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -1893,7 +1893,7 @@ pqEndcopy3(PGconn *conn)
*/
PGresult *
pqFunctionCall3(PGconn *conn, Oid fnid,
- int *result_buf, int *actual_result_len,
+ int *result_buf, int buf_size, int *actual_result_len,
int result_is_int,
const PQArgBlock *args, int nargs)
{
@@ -2024,6 +2024,18 @@ pqFunctionCall3(PGconn *conn, Oid fnid,
}
else
{
+ /*
+ * If the server returned too much data for the
+ * buffer, something fishy is going on. Abandon ship.
+ */
+ if (buf_size != -1 && *actual_result_len > buf_size)
+ {
+ appendPQExpBufferStr(&conn->errorMessage,
+ libpq_gettext("server returned too much data\n"));
+ handleSyncLoss(conn, id, *actual_result_len);
+ return pqPrepareAsyncResult(conn);
+ }
+
if (pqGetnchar((char *) result_buf,
*actual_result_len,
conn))
diff --git a/src/interfaces/libpq/libpq-int.h b/src/interfaces/libpq/libpq-int.h
index 7ecbd55..1a073ff 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -606,6 +606,9 @@ extern void pqSaveMessageField(PGresult *res, char code,
extern void pqSaveParameterStatus(PGconn *conn, const char *name,
const char *value);
extern int pqRowProcessor(PGconn *conn, const char **errmsgp);
+extern PGresult *PQnfn(PGconn *conn, int fnid, int *result_buf, int buf_size,
+ int *result_len, int result_is_int,
+ const PQArgBlock *args, int nargs);
/* === in fe-protocol2.c === */
@@ -619,7 +622,8 @@ extern int pqGetline2(PGconn *conn, char *s, int maxlen);
extern int pqGetlineAsync2(PGconn *conn, char *buffer, int bufsize);
extern int pqEndcopy2(PGconn *conn);
extern PGresult *pqFunctionCall2(PGconn *conn, Oid fnid,
- int *result_buf, int *actual_result_len,
+ int *result_buf, int buf_size,
+ int *actual_result_len,
int result_is_int,
const PQArgBlock *args, int nargs);
@@ -636,7 +640,8 @@ extern int pqGetline3(PGconn *conn, char *s, int maxlen);
extern int pqGetlineAsync3(PGconn *conn, char *buffer, int bufsize);
extern int pqEndcopy3(PGconn *conn);
extern PGresult *pqFunctionCall3(PGconn *conn, Oid fnid,
- int *result_buf, int *actual_result_len,
+ int *result_buf, int buf_size,
+ int *actual_result_len,
int result_is_int,
const PQArgBlock *args, int nargs);
--
2.54.0

View File

@ -0,0 +1,250 @@
Created by combining upstream commit 4608619a1cf578f16e799510eaa0a21c0f1f08e3
that fixes the CVE
with upstream commit b282280e9b69cae988c0c69cce3eda4d4bd38fff
that provides the function the fix uses, `timingsafe_bcmp`
diff --git a/configure b/configure
index 350d426..7537c19 100755
--- a/configure
+++ b/configure
@@ -16305,6 +16305,16 @@ fi
cat >>confdefs.h <<_ACEOF
#define HAVE_DECL_RTLD_NOW $ac_have_decl
_ACEOF
+ac_fn_c_check_decl "$LINENO" "timingsafe_bcmp" "ac_cv_have_decl_timingsafe_bcmp" "$ac_includes_default"
+if test "x$ac_cv_have_decl_timingsafe_bcmp" = xyes; then :
+ ac_have_decl=1
+else
+ ac_have_decl=0
+fi
+
+cat >>confdefs.h <<_ACEOF
+#define HAVE_DECL_TIMINGSAFE_BCMP $ac_have_decl
+_ACEOF
ac_fn_c_check_type "$LINENO" "struct sockaddr_in6" "ac_cv_type_struct_sockaddr_in6" "$ac_includes_default
@@ -16617,6 +16627,19 @@ esac
fi
+ac_fn_c_check_func "$LINENO" "timingsafe_bcmp" "ac_cv_func_timingsafe_bcmp"
+if test "x$ac_cv_func_timingsafe_bcmp" = xyes; then :
+ $as_echo "#define HAVE_TIMINGSAFE_BCMP 1" >>confdefs.h
+
+else
+ case " $LIBOBJS " in
+ *" timingsafe_bcmp.$ac_objext "* ) ;;
+ *) LIBOBJS="$LIBOBJS timingsafe_bcmp.$ac_objext"
+ ;;
+esac
+
+fi
+
case $host_os in
diff --git a/configure.in b/configure.in
index ffce35e..a29366d 100644
--- a/configure.in
+++ b/configure.in
@@ -1780,7 +1780,7 @@ AC_CHECK_DECLS(posix_fadvise, [], [], [#include <fcntl.h>])
]) # fi
AC_CHECK_DECLS(fdatasync, [], [], [#include <unistd.h>])
-AC_CHECK_DECLS([strlcat, strlcpy, strnlen])
+AC_CHECK_DECLS([strlcat, strlcpy, strnlen, timingsafe_bcmp])
# This is probably only present on macOS, but may as well check always
AC_CHECK_DECLS(F_FULLFSYNC, [], [], [#include <fcntl.h>])
@@ -1841,6 +1841,7 @@ AC_REPLACE_FUNCS(m4_normalize([
strlcpy
strnlen
strtof
+ timingsafe_bcmp
]))
case $host_os in
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 6b60abe..def1fce 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -535,7 +535,7 @@ scram_verify_plain_password(const char *username, const char *password,
* Compare the verifier's Server Key with the one computed from the
* user-supplied password.
*/
- return memcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0;
+ return timingsafe_bcmp(computed_key, server_key, SCRAM_KEY_LEN) == 0;
}
@@ -1050,9 +1050,9 @@ verify_final_nonce(scram_state *state)
if (final_nonce_len != client_nonce_len + server_nonce_len)
return false;
- if (memcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0)
+ if (timingsafe_bcmp(state->client_final_nonce, state->client_nonce, client_nonce_len) != 0)
return false;
- if (memcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0)
+ if (timingsafe_bcmp(state->client_final_nonce + client_nonce_len, state->server_nonce, server_nonce_len) != 0)
return false;
return true;
@@ -1093,7 +1093,7 @@ verify_client_proof(scram_state *state)
/* Hash it one more time, and compare with StoredKey */
scram_H(ClientKey, SCRAM_KEY_LEN, client_StoredKey);
- if (memcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
+ if (timingsafe_bcmp(client_StoredKey, state->StoredKey, SCRAM_KEY_LEN) != 0)
return false;
return true;
diff --git a/src/backend/libpq/auth.c b/src/backend/libpq/auth.c
index 98b4225..c61615f 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -3345,7 +3345,7 @@ PerformRadiusTransaction(const char *server, const char *secret, const char *por
}
pfree(cryptvector);
- if (memcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
+ if (timingsafe_bcmp(receivepacket->vector, encryptedpassword, RADIUS_VECTOR_LENGTH) != 0)
{
ereport(LOG,
(errmsg("RADIUS response from %s has incorrect MD5 signature",
diff --git a/src/backend/libpq/crypt.c b/src/backend/libpq/crypt.c
index 6e273dc..95cd78d 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -199,7 +199,8 @@ md5_crypt_verify(const char *role, const char *shadow_pass,
return STATUS_ERROR;
}
- if (strcmp(client_pass, crypt_pwd) == 0)
+ if (strlen(client_pass) == strlen(crypt_pwd) &&
+ timingsafe_bcmp(client_pass, crypt_pwd, strlen(crypt_pwd)) == 0)
retval = STATUS_OK;
else
{
@@ -264,7 +265,8 @@ plain_crypt_verify(const char *role, const char *shadow_pass,
*/
return STATUS_ERROR;
}
- if (strcmp(crypt_client_pass, shadow_pass) == 0)
+ if (strlen(crypt_client_pass) == strlen(shadow_pass) &&
+ timingsafe_bcmp(crypt_client_pass, shadow_pass, strlen(shadow_pass)) == 0)
return STATUS_OK;
else
{
diff --git a/src/include/pg_config.h.in b/src/include/pg_config.h.in
index c876033..d2f3477 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -189,6 +189,10 @@
don't. */
#undef HAVE_DECL_STRTOULL
+/* Define to 1 if you have the declaration of `timingsafe_bcmp', and to 0 if
+ you don't. */
+#undef HAVE_DECL_TIMINGSAFE_BCMP
+
/* Define to 1 if you have the `dlopen' function. */
#undef HAVE_DLOPEN
@@ -661,6 +665,9 @@
`HAVE_STRUCT_TM_TM_ZONE' instead. */
#undef HAVE_TM_ZONE
+/* Define to 1 if you have the `timingsafe_bcmp' function. */
+#undef HAVE_TIMINGSAFE_BCMP
+
/* Define to 1 if your compiler understands `typeof' or something similar. */
#undef HAVE_TYPEOF
diff --git a/src/include/port.h b/src/include/port.h
index 2229ec7..32925ad 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -497,6 +497,10 @@ extern int pqGethostbyname(const char *name,
struct hostent **result,
int *herrno);
+#if !HAVE_DECL_TIMINGSAFE_BCMP
+extern int timingsafe_bcmp(const void *b1, const void *b2, size_t len);
+#endif
+
extern void pg_qsort(void *base, size_t nel, size_t elsize,
int (*cmp) (const void *, const void *));
extern int pg_qsort_strcmp(const void *a, const void *b);
diff --git a/src/interfaces/libpq/fe-auth-scram.c b/src/interfaces/libpq/fe-auth-scram.c
index 6b55bff..f87e6f7 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -549,7 +549,7 @@ read_server_first_message(fe_scram_state *state, char *input)
/* Verify immediately that the server used our part of the nonce */
if (strlen(nonce) < strlen(state->client_nonce) ||
- memcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0)
+ timingsafe_bcmp(nonce, state->client_nonce, strlen(state->client_nonce)) != 0)
{
printfPQExpBuffer(&conn->errorMessage,
libpq_gettext("invalid SCRAM response (nonce mismatch)\n"));
@@ -748,7 +748,8 @@ verify_server_signature(fe_scram_state *state)
strlen(state->client_final_message_without_proof));
scram_HMAC_final(expected_ServerSignature, &ctx);
- if (memcmp(expected_ServerSignature, state->ServerSignature, SCRAM_KEY_LEN) != 0)
+ if (timingsafe_bcmp(expected_ServerSignature, state->ServerSignature,
+ SCRAM_KEY_LEN) != 0)
return false;
return true;
diff --git a/src/port/timingsafe_bcmp.c b/src/port/timingsafe_bcmp.c
new file mode 100644
index 0000000..fbad8a7
--- /dev/null
+++ b/src/port/timingsafe_bcmp.c
@@ -0,0 +1,43 @@
+/*
+ * src/port/timingsafe_bcmp.c
+ *
+ * $OpenBSD: timingsafe_bcmp.c,v 1.3 2015/08/31 02:53:57 guenther Exp $
+ */
+
+/*
+ * Copyright (c) 2010 Damien Miller. All rights reserved.
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include "c.h"
+
+#ifdef USE_SSL
+#include <openssl/crypto.h>
+#endif
+
+int
+timingsafe_bcmp(const void *b1, const void *b2, size_t n)
+{
+#ifdef USE_SSL
+ return CRYPTO_memcmp(b1, b2, n);
+#else
+ const unsigned char *p1 = b1,
+ *p2 = b2;
+ int ret = 0;
+
+ for (; n > 0; n--)
+ ret |= *p1++ ^ *p2++;
+ return (ret != 0);
+#endif
+}

View File

@ -0,0 +1,202 @@
From 44abf5b7e2b9892edce3ec78bf26e1b5ebf796f9 Mon Sep 17 00:00:00 2001
From: Petr Khartskhaev <pkhartsk@redhat.com>
Date: Thu, 4 Jun 2026 16:01:19 +0200
Subject: [PATCH] refint: Fix SQL injection and buffer overruns.
Maliciously crafted key value updates could achieve SQL injection
within check_foreign_key(). To fix, ensure new key values are
properly quoted and escaped in the internally generated SQL
statements. While at it, avoid potential buffer overruns by
replacing the stack buffers for internally generated SQL statements
with StringInfo.
Reported-by: Nikolay Samokhvalov <nik@postgres.ai>
Author: Nathan Bossart <nathandbossart@gmail.com>
Reviewed-by: Noah Misch <noah@leadboat.com>
Reviewed-by: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Fujii Masao <masao.fujii@gmail.com>
Security: CVE-2026-6637
Backpatch-through: 14
---
contrib/spi/refint.c | 84 ++++++++++++++++++++------------------------
1 file changed, 38 insertions(+), 46 deletions(-)
diff --git a/contrib/spi/refint.c b/contrib/spi/refint.c
index adf0490..266f65e 100644
--- a/contrib/spi/refint.c
+++ b/contrib/spi/refint.c
@@ -165,21 +165,24 @@ check_primary_key(PG_FUNCTION_ARGS)
if (plan->nplans <= 0)
{
SPIPlanPtr pplan;
- char sql[8192];
+ StringInfoData sql;
+
+ initStringInfo(&sql);
/*
* Construct query: SELECT 1 FROM _referenced_relation_ WHERE Pkey1 =
* $1 [AND Pkey2 = $2 [...]]
*/
- snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
- for (i = 0; i < nkeys; i++)
+ appendStringInfo(&sql, "select 1 from %s where ", relname);
+ for (i = 1; i <= nkeys; i++)
{
- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
- args[i + nkeys + 1], i + 1, (i < nkeys - 1) ? "and " : "");
+ appendStringInfo(&sql, "%s = $%d ", args[i + nkeys], i);
+ if (i < nkeys)
+ appendStringInfoString(&sql, "and ");
}
/* Prepare plan for query */
- pplan = SPI_prepare(sql, nkeys, argtypes);
+ pplan = SPI_prepare(sql.data, nkeys, argtypes);
if (pplan == NULL)
/* internal error */
elog(ERROR, "check_primary_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
@@ -194,6 +197,8 @@ check_primary_key(PG_FUNCTION_ARGS)
plan->splan = (SPIPlanPtr *) malloc(sizeof(SPIPlanPtr));
*(plan->splan) = pplan;
plan->nplans = 1;
+
+ pfree(sql.data);
}
/*
@@ -414,13 +419,16 @@ check_foreign_key(PG_FUNCTION_ARGS)
if (plan->nplans <= 0)
{
SPIPlanPtr pplan;
- char sql[8192];
char **args2 = args;
plan->splan = (SPIPlanPtr *) malloc(nrefs * sizeof(SPIPlanPtr));
for (r = 0; r < nrefs; r++)
{
+ StringInfoData sql;
+
+ initStringInfo(&sql);
+
relname = args2[0];
/*---------
@@ -434,8 +442,7 @@ check_foreign_key(PG_FUNCTION_ARGS)
*---------
*/
if (action == 'r')
-
- snprintf(sql, sizeof(sql), "select 1 from %s where ", relname);
+ appendStringInfo(&sql, "select 1 from %s where ", relname);
/*---------
* For 'C'ascade action we construct DELETE query
@@ -462,43 +469,24 @@ check_foreign_key(PG_FUNCTION_ARGS)
char *nv;
int k;
- snprintf(sql, sizeof(sql), "update %s set ", relname);
+ appendStringInfo(&sql, "update %s set ", relname);
for (k = 1; k <= nkeys; k++)
{
- int is_char_type = 0;
- char *type;
-
fn = SPI_fnumber(tupdesc, args_temp[k - 1]);
Assert(fn > 0); /* already checked above */
nv = SPI_getvalue(newtuple, tupdesc, fn);
- type = SPI_gettype(tupdesc, fn);
-
- if (strcmp(type, "text") == 0 ||
- strcmp(type, "varchar") == 0 ||
- strcmp(type, "char") == 0 ||
- strcmp(type, "bpchar") == 0 ||
- strcmp(type, "date") == 0 ||
- strcmp(type, "timestamp") == 0)
- is_char_type = 1;
-#ifdef DEBUG_QUERY
- elog(DEBUG4, "check_foreign_key Debug value %s type %s %d",
- nv, type, is_char_type);
-#endif
- /*
- * is_char_type =1 i set ' ' for define a new value
- */
- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
- " %s = %s%s%s %s ",
- args2[k], (is_char_type > 0) ? "'" : "",
- nv, (is_char_type > 0) ? "'" : "", (k < nkeys) ? ", " : "");
+ appendStringInfo(&sql, " %s = %s ",
+ args2[k], quote_literal_cstr(nv));
+ if (k < nkeys)
+ appendStringInfoString(&sql, ", ");
}
- strcat(sql, " where ");
+ appendStringInfoString(&sql, " where ");
}
else
/* DELETE */
- snprintf(sql, sizeof(sql), "delete from %s where ", relname);
+ appendStringInfo(&sql, "delete from %s where ", relname);
}
@@ -510,25 +498,26 @@ check_foreign_key(PG_FUNCTION_ARGS)
*/
else if (action == 's')
{
- snprintf(sql, sizeof(sql), "update %s set ", relname);
+ appendStringInfo(&sql, "update %s set ", relname);
for (i = 1; i <= nkeys; i++)
{
- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql),
- "%s = null%s",
- args2[i], (i < nkeys) ? ", " : "");
+ appendStringInfo(&sql, "%s = null", args2[i]);
+ if (i < nkeys)
+ appendStringInfoString(&sql, ", ");
}
- strcat(sql, " where ");
+ appendStringInfoString(&sql, " where ");
}
/* Construct WHERE qual */
for (i = 1; i <= nkeys; i++)
{
- snprintf(sql + strlen(sql), sizeof(sql) - strlen(sql), "%s = $%d %s",
- args2[i], i, (i < nkeys) ? "and " : "");
+ appendStringInfo(&sql, "%s = $%d ", args2[i], i);
+ if (i < nkeys)
+ appendStringInfoString(&sql, "and ");
}
/* Prepare plan for query */
- pplan = SPI_prepare(sql, nkeys, argtypes);
+ pplan = SPI_prepare(sql.data, nkeys, argtypes);
if (pplan == NULL)
/* internal error */
elog(ERROR, "check_foreign_key: SPI_prepare returned %s", SPI_result_code_string(SPI_result));
@@ -544,11 +533,14 @@ check_foreign_key(PG_FUNCTION_ARGS)
plan->splan[r] = pplan;
args2 += nkeys + 1; /* to the next relation */
+
+#ifdef DEBUG_QUERY
+ elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql.data);
+#endif
+
+ pfree(sql.data);
}
plan->nplans = nrefs;
-#ifdef DEBUG_QUERY
- elog(DEBUG4, "check_foreign_key Debug Query is : %s ", sql);
-#endif
}
/*
--
2.54.0

View File

@ -1,25 +1,84 @@
From 9d18b30ac7a17d70ee789b710865bd20b206023d Mon Sep 17 00:00:00 2001
From: Filip Janus <fjanus@redhat.com>
Date: Tue, 18 Mar 2025 10:11:09 +0100
Subject: [PATCH] Fix failing test regardless the CVE-2025-1094 fix
From dee1e86d0e6d20c6326073db1736ae2088aac191 Mon Sep 17 00:00:00 2001
From: Tom Lane <tgl@sss.pgh.pa.us>
Date: Mon, 20 Jan 2025 15:47:53 -0500
Subject: [PATCH] Avoid using timezone Asia/Manila in regression tests.
The freshly-released 2025a version of tzdata has a refined estimate
for the longitude of Manila, changing their value for LMT in
pre-standardized-timezone days. This changes the output of one of
our test cases. Since we need to be able to run with system tzdata
files that may or may not contain this update, we'd better stop
making that specific test.
I switched it to use Asia/Singapore, which has a roughly similar UTC
offset. That LMT value hasn't changed in tzdb since 2003, so we can
hope that it's well established.
I also noticed that this set of make_timestamptz tests only exercises
zones east of Greenwich, which seems rather sad, and was not the
original intent AFAICS. (We've already changed these tests once
to stabilize their results across tzdata updates, cf 66b737cd9;
it looks like I failed to consider the UTC-offset-sign aspect then.)
To improve that, add a test with Pacific/Honolulu. That LMT offset
is also quite old in tzdb, so we'll cross our fingers that it doesn't
get improved.
Reported-by: Christoph Berg <cb@df7cb.de>
Discussion: https://postgr.es/m/Z46inkznCxesvDEb@msg.df7cb.de
Backpatch-through: 13
---
src/test/regress/expected/timestamptz.out | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
src/include/datatype/timestamp.h | 2 +-
src/test/regress/expected/timestamptz.out | 10 ++++++++--
src/test/regress/sql/timestamptz.sql | 3 ++-
3 files changed, 11 insertions(+), 4 deletions(-)
diff --git a/src/include/datatype/timestamp.h b/src/include/datatype/timestamp.h
index 6be6d35d1e2c2..92b1cca9901be 100644
--- a/src/include/datatype/timestamp.h
+++ b/src/include/datatype/timestamp.h
@@ -96,7 +96,7 @@ typedef struct
/*
* We allow numeric timezone offsets up to 15:59:59 either way from Greenwich.
* Currently, the record holders for wackiest offsets in actual use are zones
- * Asia/Manila, at -15:56:00 until 1844, and America/Metlakatla, at +15:13:42
+ * Asia/Manila, at -15:56:08 until 1844, and America/Metlakatla, at +15:13:42
* until 1867. If we were to reject such values we would fail to dump and
* restore old timestamptz values with these zone settings.
*/
diff --git a/src/test/regress/expected/timestamptz.out b/src/test/regress/expected/timestamptz.out
index 55efd183868..5964b65bc6b 100644
index 0ce33862019b2..77abfab1f6663 100644
--- a/src/test/regress/expected/timestamptz.out
+++ b/src/test/regress/expected/timestamptz.out
@@ -2060,7 +2060,7 @@ SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') AT TIME ZONE 'UT
SELECT make_timestamptz(1846, 12, 10, 0, 0, 0, 'Asia/Manila') AT TIME ZONE 'UTC';
@@ -2072,10 +2072,16 @@ SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') AT TIME ZONE 'UT
Tue Dec 09 23:00:00 2014
(1 row)
-SELECT make_timestamptz(1846, 12, 10, 0, 0, 0, 'Asia/Manila') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1881, 12, 10, 0, 0, 0, 'Asia/Singapore') AT TIME ZONE 'UTC';
timezone
--------------------------
- Wed Dec 09 15:56:00 1846
+ Wed Dec 09 15:56:08 1846
+ Fri Dec 09 17:04:35 1881
+(1 row)
+
+SELECT make_timestamptz(1881, 12, 10, 0, 0, 0, 'Pacific/Honolulu') AT TIME ZONE 'UTC';
+ timezone
+--------------------------
+ Sat Dec 10 10:31:26 1881
(1 row)
SELECT make_timestamptz(1881, 12, 10, 0, 0, 0, 'Europe/Paris') AT TIME ZONE 'UTC';
--
2.39.5 (Apple Git-154)
diff --git a/src/test/regress/sql/timestamptz.sql b/src/test/regress/sql/timestamptz.sql
index f0a8d3e5aa6dc..ac0bc14854956 100644
--- a/src/test/regress/sql/timestamptz.sql
+++ b/src/test/regress/sql/timestamptz.sql
@@ -331,7 +331,8 @@ SELECT make_timestamptz(1973, 07, 15, 08, 15, 55.33, '+2') = '1973-07-15 08:15:5
-- full timezone names
SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') = timestamptz '2014-12-10 00:00:00 Europe/Prague';
SELECT make_timestamptz(2014, 12, 10, 0, 0, 0, 'Europe/Prague') AT TIME ZONE 'UTC';
-SELECT make_timestamptz(1846, 12, 10, 0, 0, 0, 'Asia/Manila') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1881, 12, 10, 0, 0, 0, 'Asia/Singapore') AT TIME ZONE 'UTC';
+SELECT make_timestamptz(1881, 12, 10, 0, 0, 0, 'Pacific/Honolulu') AT TIME ZONE 'UTC';
SELECT make_timestamptz(1881, 12, 10, 0, 0, 0, 'Europe/Paris') AT TIME ZONE 'UTC';
SELECT make_timestamptz(1910, 12, 24, 0, 0, 0, 'Nehwon/Lankhmar');

View File

@ -60,7 +60,7 @@ Summary: PostgreSQL client programs
Name: postgresql
%global majorversion 12
Version: %{majorversion}.22
Release: 6%{?dist}
Release: 7%{?dist}
# The PostgreSQL license is very similar to other MIT licenses, but the OSI
# recognizes it as an independent license, so we do as well.
@ -112,10 +112,17 @@ Patch11: backport-cve-2025-1094.patch
Patch12: timezone-test-fix.patch
Patch13: CVE-2025-8715.patch
Patch14: CVE-2026-2004--CVE-2026-2005--CVE-2026-2006.patch
Patch15: postgresql-CVE-2026-6478.patch
Patch16: postgresql-CVE-2026-6637.patch
Patch17: postgresql-CVE-2026-6477.patch
Patch18: postgresql-CVE-2026-6475.patch
Patch19: postgresql-CVE-2026-6473.patch
BuildRequires: gcc
BuildRequires: perl(ExtUtils::MakeMaker) glibc-devel bison flex gawk
BuildRequires: perl(ExtUtils::Embed), perl-devel
BuildRequires: perl(Opcode)
BuildRequires: perl(FindBin)
%if 0%{?fedora} || 0%{?rhel} > 7
BuildRequires: perl-generators
%endif
@ -123,6 +130,7 @@ BuildRequires: readline-devel zlib-devel
BuildRequires: systemd systemd-devel util-linux
BuildRequires: multilib-rpm-config
BuildRequires: libpq-devel
BuildRequires: docbook-style-xsl
# postgresql-setup build requires
@ -378,6 +386,11 @@ benchmarks.
%patch12 -p1
%patch13 -p1
%patch14 -p1
%patch15 -p1
%patch16 -p1
%patch17 -p1
%patch18 -p1
%patch19 -p1
# We used to run autoconf here, but there's no longer any real need to,
# since Postgres ships with a reasonably modern configure script.
@ -1233,6 +1246,11 @@ make -C postgresql-setup-%{setup_version} check
%changelog
* Thu Jun 4 2026 Petr Khartskhaev <pkhartsk@redhat.com> - 12.22-7
- Backport fix for CVE-2026-6478 from PostgreSQL 14.23
- Backport fixes for CVE-2026-6637, CVE-2026-6477, CVE-2026-6475, CVE-2026-6473
- Resolves: RHEL-179790
* Fri Feb 27 2026 Filip Janus <fjanus@redhat.com> - 12.22-6
- Fix CVE-2026-2004 CVE-2026-2005 CVE-2026-2006