Hi,
 
parse_oid() in pg_combinebackup assigns the result of strtoul() to an Oid
before range-checking it:
 
    Oid         oid;
    ...
    oid = strtoul(s, &ep, 10);
    if (errno != 0 || *ep != '\0' || oid < 1 || oid > PG_UINT32_MAX)
        return false;
 
Since Oid is 32 bits, the value has already been truncated by the time
"oid > PG_UINT32_MAX" is evaluated, so on platforms where unsigned long is
wider than 32 bits that test can never fire.  An out-of-range string is
then accepted as its truncated value rather than being rejected:
"4294967297" is accepted as OID 1, and "-1" is accepted as OID 4294967295.
 
parse_oid() is only fed directory names found under pg_tblspc, so the
practical consequence is limited: pg_combinebackup treats a bogus
directory name as a valid tablespace OID instead of ignoring it.  It still
seems worth fixing.
 
The attached patch keeps the parsed value in an unsigned long until it has
been checked and casts to Oid afterwards, matching what
parse_relfilenumber() in pg_upgrade already does.
 
The patch is against master.  The same code is present unchanged back to
v17 (dc212340058), and the patch applies cleanly to REL_17_STABLE,
REL_18_STABLE and REL_19_STABLE.
 
Regards,
Egor Ivkov
From 0b6f1d35290cb98a3263ee95c899aee0a55d90f9 Mon Sep 17 00:00:00 2001
From: Egor Ivkov <[email protected]>
Date: Tue, 22 Sep 2026 22:44:05 +0300
Subject: [PATCH v1] pg_combinebackup: make the OID range check in parse_oid()
 effective

parse_oid() assigned the result of strtoul() to an Oid variable before
range-checking it, so the value had already been truncated to 32 bits by
the time "oid > PG_UINT32_MAX" was evaluated.  On platforms where
unsigned long is wider than 32 bits that test is dead code, and an
out-of-range string is accepted as its truncated value instead of being
rejected: "4294967297" is accepted as OID 1, and "-1" is accepted as OID
4294967295.

Keep the parsed value in an unsigned long until it has been checked, and
cast to Oid only afterwards.  This is the same shape as
parse_relfilenumber() in pg_upgrade, which gets it right.

parse_oid() is only fed directory names found under pg_tblspc, so the
practical consequence is limited to pg_combinebackup treating a bogus
directory name as a valid tablespace OID instead of ignoring it.
---
 src/bin/pg_combinebackup/pg_combinebackup.c | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/bin/pg_combinebackup/pg_combinebackup.c b/src/bin/pg_combinebackup/pg_combinebackup.c
index 254a27b125b..600962f1aec 100644
--- a/src/bin/pg_combinebackup/pg_combinebackup.c
+++ b/src/bin/pg_combinebackup/pg_combinebackup.c
@@ -816,7 +816,7 @@ help(const char *progname)
 static bool
 parse_oid(char *s, Oid *result)
 {
-	Oid			oid;
+	unsigned long oid;
 	char	   *ep;
 
 	errno = 0;
@@ -824,7 +824,7 @@ parse_oid(char *s, Oid *result)
 	if (errno != 0 || *ep != '\0' || oid < 1 || oid > PG_UINT32_MAX)
 		return false;
 
-	*result = oid;
+	*result = (Oid) oid;
 	return true;
 }
 
-- 
2.43.0

Reply via email to