import CS git postgresql-13.23-3.el8

This commit is contained in:
AlmaLinux RelEng Bot 2026-06-23 09:44:58 -04:00
parent 13162f68d5
commit 9db80d4efd
6 changed files with 2628 additions and 1 deletions

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,155 @@
From 498829dca45ad207a23954fe2e5a30ed2ef4b363 Mon Sep 17 00:00:00 2001
From: Michael Paquier <michael@paquier.xyz>
Date: Mon, 11 May 2026 05:13:51 -0700
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 a807086..faaa5ce 100644
--- a/src/bin/pg_rewind/file_ops.c
+++ b/src/bin/pg_rewind/file_ops.c
@@ -48,6 +48,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;
@@ -188,6 +191,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;
@@ -208,6 +214,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;
@@ -230,6 +239,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;
@@ -244,6 +257,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;
@@ -258,6 +275,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;
@@ -272,6 +292,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 db53dec..a257d8d 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -55,6 +55,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 d37a484..117f919 100644
--- a/src/port/path.c
+++ b/src/port/path.c
@@ -505,6 +505,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.39.5 (Apple Git-154)

View File

@ -0,0 +1,219 @@
From 2b450648240c6c56f367e74bbf0f785b91f93396 Mon Sep 17 00:00:00 2001
From: Petr Khartskhaev <pkhartsk@redhat.com>
Date: Wed, 3 Jun 2026 16:34:32 +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 +-
13 files changed, 17098 insertions(+), 15 deletions(-)
diff --git a/doc/src/sgml/libpq.sgml b/doc/src/sgml/libpq.sgml
index 1a84241..e1f688c 100644
--- a/doc/src/sgml/libpq.sgml
+++ b/doc/src/sgml/libpq.sgml
@@ -5086,15 +5086,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 id="libpq-PQfn">PQfn</function><indexterm><primary>PQfn</primary></indexterm>
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index b30b565..076a9d0 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -2665,6 +2665,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;
@@ -2684,12 +2698,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 a94ce06..01e67a3 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 9360c54..0c2b629 100644
--- a/src/interfaces/libpq/fe-protocol2.c
+++ b/src/interfaces/libpq/fe-protocol2.c
@@ -1428,7 +1428,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)
{
@@ -1510,6 +1510,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 3da1ace..a092010 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -1903,7 +1903,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)
{
@@ -2034,6 +2034,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 814884a..1b0cf91 100644
--- a/src/interfaces/libpq/libpq-int.h
+++ b/src/interfaces/libpq/libpq-int.h
@@ -622,6 +622,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 === */
@@ -635,7 +638,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);
@@ -652,7 +656,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);

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 91df389..4b85063 100755
--- a/configure
+++ b/configure
@@ -16305,6 +16305,16 @@ fi
cat >>confdefs.h <<_ACEOF
#define HAVE_DECL_STRNLEN $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
# We can't use AC_REPLACE_FUNCS to replace these functions, because it
@@ -16639,6 +16649,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
+
if test "$PORTNAME" = "win32" -o "$PORTNAME" = "cygwin"; then
diff --git a/configure.in b/configure.in
index 318851d..5461fc6 100644
--- a/configure.in
+++ b/configure.in
@@ -1793,7 +1793,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])
# We can't use AC_REPLACE_FUNCS to replace these functions, because it
# won't handle deployment target restrictions on macOS
@@ -1843,6 +1843,7 @@ AC_REPLACE_FUNCS(m4_normalize([
strlcpy
strnlen
strtof
+ timingsafe_bcmp
]))
if test "$PORTNAME" = "win32" -o "$PORTNAME" = "cygwin"; then
diff --git a/src/backend/libpq/auth-scram.c b/src/backend/libpq/auth-scram.c
index 5214d32..75c97ca 100644
--- a/src/backend/libpq/auth-scram.c
+++ b/src/backend/libpq/auth-scram.c
@@ -537,7 +537,7 @@ scram_verify_plain_password(const char *username, const char *password,
* Compare the secret'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;
}
@@ -1074,9 +1074,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;
@@ -1117,7 +1117,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 8b63217..e56eab1 100644
--- a/src/backend/libpq/auth.c
+++ b/src/backend/libpq/auth.c
@@ -3375,7 +3375,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 17b91ac..7f3aea5 100644
--- a/src/backend/libpq/crypt.c
+++ b/src/backend/libpq/crypt.c
@@ -196,7 +196,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
{
@@ -261,7 +262,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 b351a0b..d86ad1d 100644
--- a/src/include/pg_config.h.in
+++ b/src/include/pg_config.h.in
@@ -175,6 +175,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
@@ -634,6 +638,9 @@
/* Define to 1 if you have the <termios.h> header file. */
#undef HAVE_TERMIOS_H
+/* 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 0e86dfb..d3ca64e 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -514,6 +514,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 3e705f8..065244c 100644
--- a/src/interfaces/libpq/fe-auth-scram.c
+++ b/src/interfaces/libpq/fe-auth-scram.c
@@ -612,7 +612,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"));
@@ -814,7 +814,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..288865f
--- /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,203 @@
From 2b026df29c1e1caafb259a2021528a28ec484018 Mon Sep 17 00:00:00 2001
From: Nathan Bossart <nathan@postgresql.org>
Date: Mon, 11 May 2026 05:13:52 -0700
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 6fbfef2..cbef463 100644
--- a/contrib/spi/refint.c
+++ b/contrib/spi/refint.c
@@ -166,21 +166,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));
@@ -196,6 +199,8 @@ check_primary_key(PG_FUNCTION_ARGS)
sizeof(SPIPlanPtr));
*(plan->splan) = pplan;
plan->nplans = 1;
+
+ pfree(sql.data);
}
/*
@@ -416,7 +421,6 @@ check_foreign_key(PG_FUNCTION_ARGS)
if (plan->nplans <= 0)
{
SPIPlanPtr pplan;
- char sql[8192];
char **args2 = args;
plan->splan = (SPIPlanPtr *) MemoryContextAlloc(TopMemoryContext,
@@ -424,6 +428,10 @@ check_foreign_key(PG_FUNCTION_ARGS)
for (r = 0; r < nrefs; r++)
{
+ StringInfoData sql;
+
+ initStringInfo(&sql);
+
relname = args2[0];
/*---------
@@ -437,8 +445,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
@@ -465,43 +472,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);
}
@@ -513,25 +501,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));
@@ -547,11 +536,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.39.5 (Apple Git-154)

View File

@ -60,7 +60,7 @@ Summary: PostgreSQL client programs
Name: postgresql
%global majorversion 13
Version: %{majorversion}.23
Release: 2%{?dist}
Release: 3%{?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.
@ -108,10 +108,17 @@ Patch6: postgresql-man.patch
Patch8: postgresql-external-libpq.patch
Patch9: postgresql-server-pg_config.patch
Patch10: 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
@ -119,6 +126,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
BuildRequires: m4 elinks docbook-utils help2man
@ -369,6 +377,11 @@ benchmarks.
%patch8 -p1
%patch9 -p1
%patch10 -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.
@ -1226,6 +1239,11 @@ make -C postgresql-setup-%{setup_version} check
%changelog
* Wed Jun 3 2026 Petr Khartskhaev <pkhartsk@redhat.com> - 13.23-3
- 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-179806
* Wed Feb 25 2026 Filip Janus <fjanus@redhat.com> - 13.23-2
- fix CVE-2026-2004 CVE-2026-2005 CVE-2026-2006