Post-release followup: pg_add_size_overflow()

Started by Jacob Championabout 2 months ago6 messages
#1Jacob Champion
jacob.champion@enterprisedb.com
2 attachment(s)

Hi all,

The fix for CVE-2025-12818 introduced a few identical copies of size_t
addition, and now that we've released, I'd like to pull those back
into shape.

0001 replaces the bespoke code with a new size_t implementation of the
operators in common/int.h. 0002 additionally makes use of these in
shmem.c, because I couldn't think of a good reason not to.

Couple things to note:

1) The backend's add_size(), which I patterned the CVE fix on
originally, checks if the result is less than either operand. The
common/int.h implementations check only the *first* operand, which
also looks correct to me -- if (result < a), it must also be true that
(result < b), because otherwise (result - b) is nonnegative and we
couldn't have overflowed the addition in the first place. But my brain
is a little fried from looking at these problems, and I could use a +1
from someone with fresh eyes.

2) I have not implemented pg_neg_size_overflow(), because to me it
seems likely to be permanently dead code, and it would require
additional reasoning about the portability of SSIZE_MAX.
(pg_sub_size_overflow(), by contrast, is easy to do and feels like it
might be useful to someone eventually.)

I don't currently plan to backport this, because I don't think the
delta is likely to cause anyone additional pain in the future, but let
me know if you disagree.

Thanks!
--Jacob

Attachments:

0001-Add-pg_add_size_overflow-and-friends.patchapplication/octet-stream; name=0001-Add-pg_add_size_overflow-and-friends.patchDownload
From c6806ca8269e0066947a3b01669b918e1f8e2480 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champion@enterprisedb.com>
Date: Mon, 17 Nov 2025 13:47:45 -0800
Subject: [PATCH 1/2] Add pg_add_size_overflow() and friends

Commit 600086f47 added (several bespoke copies of) size_t addition with
overflow checks to libpq. Move this to common/int.h, along with
its subtraction and multiplication counterparts.

pg_neg_size_overflow() is intentionally omitted; I'm not sure we should
add SSIZE_MAX to win32_port.h for the sake of a function with no
callers.
---
 src/include/common/int.h            | 67 +++++++++++++++++++++++++++++
 src/interfaces/libpq/fe-exec.c      | 44 ++++++-------------
 src/interfaces/libpq/fe-print.c     | 36 ++++------------
 src/interfaces/libpq/fe-protocol3.c | 27 ++----------
 4 files changed, 91 insertions(+), 83 deletions(-)

diff --git a/src/include/common/int.h b/src/include/common/int.h
index 3973f13379d..a99f2b95c67 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -601,6 +601,73 @@ pg_neg_u64_overflow(uint64 a, int64 *result)
 #endif
 }
 
+/*
+ * size_t
+ */
+static inline bool
+pg_add_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+	return __builtin_add_overflow(a, b, result);
+#else
+	size_t		res = a + b;
+
+	if (res < a)
+	{
+		*result = 0x5EED;		/* to avoid spurious warnings */
+		return true;
+	}
+	*result = res;
+	return false;
+#endif
+}
+
+static inline bool
+pg_sub_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+	return __builtin_sub_overflow(a, b, result);
+#else
+	if (b > a)
+	{
+		*result = 0x5EED;		/* to avoid spurious warnings */
+		return true;
+	}
+	*result = a - b;
+	return false;
+#endif
+}
+
+static inline bool
+pg_mul_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+	return __builtin_mul_overflow(a, b, result);
+#else
+	size_t		res = a * b;
+
+	if (a != 0 && b != res / a)
+	{
+		*result = 0x5EED;		/* to avoid spurious warnings */
+		return true;
+	}
+	*result = res;
+	return false;
+#endif
+}
+
+/*
+ * pg_neg_size_overflow is currently omitted, to avoid having to reason about
+ * the portability of SSIZE_MIN/_MAX before a use case exists.
+ */
+#if 0
+static inline bool
+pg_neg_size_overflow(size_t a, ssize_t *result)
+{
+	...
+}
+#endif
+
 /*------------------------------------------------------------------------
  *
  * Comparison routines for integer types.
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index dcc8a447d66..7ab33930a39 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -24,6 +24,7 @@
 #include <unistd.h>
 #endif
 
+#include "common/int.h"
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
@@ -4220,27 +4221,6 @@ PQescapeString(char *to, const char *from, size_t length)
 }
 
 
-/*
- * Frontend version of the backend's add_size(), intended to be API-compatible
- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
- * Returns true instead if the addition overflows.
- *
- * TODO: move to common/int.h
- */
-static bool
-add_size_overflow(size_t s1, size_t s2, size_t *dst)
-{
-	size_t		result;
-
-	result = s1 + s2;
-	if (result < s1 || result < s2)
-		return true;
-
-	*dst = result;
-	return false;
-}
-
-
 /*
  * Escape arbitrary strings.  If as_ident is true, we escape the result
  * as an identifier; if false, as a literal.  The result is returned in
@@ -4324,14 +4304,14 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
 	 * Allocate output buffer. Protect against overflow, in case the caller
 	 * has allocated a large fraction of the available size_t.
 	 */
-	if (add_size_overflow(input_len, num_quotes, &result_size) ||
-		add_size_overflow(result_size, 3, &result_size))	/* two quotes plus a NUL */
+	if (pg_add_size_overflow(input_len, num_quotes, &result_size) ||
+		pg_add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */
 		goto overflow;
 
 	if (!as_ident && num_backslashes > 0)
 	{
-		if (add_size_overflow(result_size, num_backslashes, &result_size) ||
-			add_size_overflow(result_size, 2, &result_size))	/* for " E" prefix */
+		if (pg_add_size_overflow(result_size, num_backslashes, &result_size) ||
+			pg_add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */
 			goto overflow;
 	}
 
@@ -4493,9 +4473,9 @@ PQescapeByteaInternal(PGconn *conn,
 	if (use_hex)
 	{
 		/* We prepend "\x" and double each input character. */
-		if (add_size_overflow(len, bslash_len + 1, &len) ||
-			add_size_overflow(len, from_length, &len) ||
-			add_size_overflow(len, from_length, &len))
+		if (pg_add_size_overflow(len, bslash_len + 1, &len) ||
+			pg_add_size_overflow(len, from_length, &len) ||
+			pg_add_size_overflow(len, from_length, &len))
 			goto overflow;
 	}
 	else
@@ -4505,22 +4485,22 @@ PQescapeByteaInternal(PGconn *conn,
 		{
 			if (*vp < 0x20 || *vp > 0x7e)
 			{
-				if (add_size_overflow(len, bslash_len + 3, &len))	/* octal "\ooo" */
+				if (pg_add_size_overflow(len, bslash_len + 3, &len))	/* octal "\ooo" */
 					goto overflow;
 			}
 			else if (*vp == '\'')
 			{
-				if (add_size_overflow(len, 2, &len))	/* double each quote */
+				if (pg_add_size_overflow(len, 2, &len)) /* double each quote */
 					goto overflow;
 			}
 			else if (*vp == '\\')
 			{
-				if (add_size_overflow(len, bslash_len * 2, &len))	/* double each backslash */
+				if (pg_add_size_overflow(len, bslash_len * 2, &len))	/* double each backslash */
 					goto overflow;
 			}
 			else
 			{
-				if (add_size_overflow(len, 1, &len))
+				if (pg_add_size_overflow(len, 1, &len))
 					goto overflow;
 			}
 		}
diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c
index cc667c9bac9..fc153bee630 100644
--- a/src/interfaces/libpq/fe-print.c
+++ b/src/interfaces/libpq/fe-print.c
@@ -33,6 +33,7 @@
 #endif
 #endif
 
+#include "common/int.h"
 #include "libpq-fe.h"
 #include "libpq-int.h"
 
@@ -451,27 +452,6 @@ do_field(const PQprintOpt *po, const PGresult *res,
 }
 
 
-/*
- * Frontend version of the backend's add_size(), intended to be API-compatible
- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
- * Returns true instead if the addition overflows.
- *
- * TODO: move to common/int.h
- */
-static bool
-add_size_overflow(size_t s1, size_t s2, size_t *dst)
-{
-	size_t		result;
-
-	result = s1 + s2;
-	if (result < s1 || result < s2)
-		return true;
-
-	*dst = result;
-	return false;
-}
-
-
 static char *
 do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
 		  const char **fieldNames, unsigned char *fieldNotNum,
@@ -492,20 +472,20 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
 		for (; n < nFields; n++)
 		{
 			/* Field plus separator, plus 2 extra '-' in standard format. */
-			if (add_size_overflow(tot, fieldMax[n], &tot) ||
-				add_size_overflow(tot, fs_len, &tot) ||
-				(po->standard && add_size_overflow(tot, 2, &tot)))
+			if (pg_add_size_overflow(tot, fieldMax[n], &tot) ||
+				pg_add_size_overflow(tot, fs_len, &tot) ||
+				(po->standard && pg_add_size_overflow(tot, 2, &tot)))
 				goto overflow;
 		}
 		if (po->standard)
 		{
 			/* An extra separator at the front and back. */
-			if (add_size_overflow(tot, fs_len, &tot) ||
-				add_size_overflow(tot, fs_len, &tot) ||
-				add_size_overflow(tot, 2, &tot))
+			if (pg_add_size_overflow(tot, fs_len, &tot) ||
+				pg_add_size_overflow(tot, fs_len, &tot) ||
+				pg_add_size_overflow(tot, 2, &tot))
 				goto overflow;
 		}
-		if (add_size_overflow(tot, 1, &tot))	/* terminator */
+		if (pg_add_size_overflow(tot, 1, &tot)) /* terminator */
 			goto overflow;
 
 		border = malloc(tot);
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 838e42e661a..16f504a867d 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -25,6 +25,7 @@
 #include <netinet/tcp.h>
 #endif
 
+#include "common/int.h"
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
@@ -2404,26 +2405,6 @@ pqBuildStartupPacket3(PGconn *conn, int *packetlen,
 	return startpacket;
 }
 
-/*
- * Frontend version of the backend's add_size(), intended to be API-compatible
- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
- * Returns true instead if the addition overflows.
- *
- * TODO: move to common/int.h
- */
-static bool
-add_size_overflow(size_t s1, size_t s2, size_t *dst)
-{
-	size_t		result;
-
-	result = s1 + s2;
-	if (result < s1 || result < s2)
-		return true;
-
-	*dst = result;
-	return false;
-}
-
 /*
  * Build a startup packet given a filled-in PGconn structure.
  *
@@ -2456,11 +2437,11 @@ build_startup_packet(const PGconn *conn, char *packet,
 	do { \
 		if (packet) \
 			strcpy(packet + packet_len, optname); \
-		if (add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \
+		if (pg_add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \
 			return 0; \
 		if (packet) \
 			strcpy(packet + packet_len, optval); \
-		if (add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \
+		if (pg_add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \
 			return 0; \
 	} while(0)
 
@@ -2496,7 +2477,7 @@ build_startup_packet(const PGconn *conn, char *packet,
 	/* Add trailing terminator */
 	if (packet)
 		packet[packet_len] = '\0';
-	if (add_size_overflow(packet_len, 1, &packet_len))
+	if (pg_add_size_overflow(packet_len, 1, &packet_len))
 		return 0;
 
 	return packet_len;
-- 
2.34.1

0002-postgres-Use-pg_-add-mul-_size_overflow.patchapplication/octet-stream; name=0002-postgres-Use-pg_-add-mul-_size_overflow.patchDownload
From a828fcfbd7619601303839e4b9b3eb7644518b19 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champion@enterprisedb.com>
Date: Mon, 17 Nov 2025 14:02:38 -0800
Subject: [PATCH 2/2] postgres: Use pg_{add,mul}_size_overflow()

The backend implementations of add_size() and mul_size() can now make
use of the APIs provided in common/int.h.
---
 src/backend/storage/ipc/shmem.c | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 0f18beb6ad4..ee3408df301 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -65,6 +65,7 @@
 
 #include "postgres.h"
 
+#include "common/int.h"
 #include "fmgr.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -495,9 +496,7 @@ add_size(Size s1, Size s2)
 {
 	Size		result;
 
-	result = s1 + s2;
-	/* We are assuming Size is an unsigned type here... */
-	if (result < s1 || result < s2)
+	if (pg_add_size_overflow(s1, s2, &result))
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("requested shared memory size overflows size_t")));
@@ -512,11 +511,7 @@ mul_size(Size s1, Size s2)
 {
 	Size		result;
 
-	if (s1 == 0 || s2 == 0)
-		return 0;
-	result = s1 * s2;
-	/* We are assuming Size is an unsigned type here... */
-	if (result / s2 != s1)
+	if (pg_mul_size_overflow(s1, s2, &result))
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("requested shared memory size overflows size_t")));
-- 
2.34.1

#2Chao Li
li.evan.chao@gmail.com
In reply to: Jacob Champion (#1)
Re: Post-release followup: pg_add_size_overflow()

Hi, Jacob,

I just reviewed the patch. Overall looks solid to me. Just a small comments:

On Nov 19, 2025, at 04:09, Jacob Champion <jacob.champion@enterprisedb.com> wrote:

Hi all,

The fix for CVE-2025-12818 introduced a few identical copies of size_t
addition, and now that we've released, I'd like to pull those back
into shape.

0001 replaces the bespoke code with a new size_t implementation of the
operators in common/int.h. 0002 additionally makes use of these in
shmem.c, because I couldn't think of a good reason not to.

Couple things to note:

1) The backend's add_size(), which I patterned the CVE fix on
originally, checks if the result is less than either operand. The
common/int.h implementations check only the *first* operand, which
also looks correct to me -- if (result < a), it must also be true that
(result < b), because otherwise (result - b) is nonnegative and we
couldn't have overflowed the addition in the first place. But my brain
is a little fried from looking at these problems, and I could use a +1
from someone with fresh eyes.

2) I have not implemented pg_neg_size_overflow(), because to me it
seems likely to be permanently dead code, and it would require
additional reasoning about the portability of SSIZE_MAX.
(pg_sub_size_overflow(), by contrast, is easy to do and feels like it
might be useful to someone eventually.)

I don't currently plan to backport this, because I don't think the
delta is likely to cause anyone additional pain in the future, but let
me know if you disagree.

Thanks!
--Jacob
<0001-Add-pg_add_size_overflow-and-friends.patch><0002-postgres-Use-pg_-add-mul-_size_overflow.patch>

1 - 0001
```
+/*
+ * pg_neg_size_overflow is currently omitted, to avoid having to reason about
+ * the portability of SSIZE_MIN/_MAX before a use case exists.
+ */
+#if 0
+static inline bool
+pg_neg_size_overflow(size_t a, ssize_t *result)
+{
+	...
+}
+#endif
```

Putting “…” inside a function body looks quite uncommon. I searched over the source tree and couldn't find other occurrence. As the comment has explained why omitting pg_neg_size_overflow, maybe just remove the entry #if 0 block, or just leave an empty function body.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/

#3Michael Paquier
michael@paquier.xyz
In reply to: Jacob Champion (#1)
Re: Post-release followup: pg_add_size_overflow()

On Tue, Nov 18, 2025 at 12:09:17PM -0800, Jacob Champion wrote:

The fix for CVE-2025-12818 introduced a few identical copies of size_t
addition, and now that we've released, I'd like to pull those back
into shape.

Yes, I've noticed these TODOs in the final patch. Thanks for the
follow-up cleanup.

0001 replaces the bespoke code with a new size_t implementation of the
operators in common/int.h. 0002 additionally makes use of these in
shmem.c, because I couldn't think of a good reason not to.

Sounds good to me.

Couple things to note:

1) The backend's add_size(), which I patterned the CVE fix on
originally, checks if the result is less than either operand. The
common/int.h implementations check only the *first* operand, which
also looks correct to me -- if (result < a), it must also be true that
(result < b), because otherwise (result - b) is nonnegative and we
couldn't have overflowed the addition in the first place. But my brain
is a little fried from looking at these problems, and I could use a +1
from someone with fresh eyes.

This is following the same pattern as what has been introduced in
7dedfd22b798 for the other unsigned types in int.h. Anyway, looking
at that separately, the logic of 0001 seems right here.

2) I have not implemented pg_neg_size_overflow(), because to me it
seems likely to be permanently dead code, and it would require
additional reasoning about the portability of SSIZE_MAX.
(pg_sub_size_overflow(), by contrast, is easy to do and feels like it
might be useful to someone eventually.)

Documenting the portability issue is important, indeed. I'd suggest
to not use a ifdef 0, though, which may be confusing on grep if one
does not look at the surrounding lines. Leaving that in the shape of
a comment would be hard to miss. Anyway, are you worrying about
SIZE_MAX matching with something different than the compile-time value
at runtime? If we don't have use for a neg subroutine yet, leaving
that out of the picture for now is fine here.

I don't currently plan to backport this, because I don't think the
delta is likely to cause anyone additional pain in the future, but let
me know if you disagree.

Keeping this cleanup on HEAD sounds fine to me.
--
Michael

#4Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Chao Li (#2)
Re: Post-release followup: pg_add_size_overflow()

On Tue, Nov 18, 2025 at 5:17 PM Chao Li <li.evan.chao@gmail.com> wrote:

I just reviewed the patch. Overall looks solid to me.

Thanks for the review!

Putting “…” inside a function body looks quite uncommon. I searched over the source tree and couldn't find other occurrence. As the comment has explained why omitting pg_neg_size_overflow, maybe just remove the entry #if 0 block, or just leave an empty function body.

My intent is just to document what the signature would have been. But
with Michael adding that it could confuse a casual grepper, I think
I'll switch to a standard comment, at minimum.

Thanks,
--Jacob

#5Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Michael Paquier (#3)
2 attachment(s)
Re: Post-release followup: pg_add_size_overflow()

On Tue, Nov 18, 2025 at 9:18 PM Michael Paquier <michael@paquier.xyz> wrote:

This is following the same pattern as what has been introduced in
7dedfd22b798 for the other unsigned types in int.h. Anyway, looking
at that separately, the logic of 0001 seems right here.

Okay, good. Thanks for the review!

2) I have not implemented pg_neg_size_overflow(), because to me it
seems likely to be permanently dead code, and it would require
additional reasoning about the portability of SSIZE_MAX.
(pg_sub_size_overflow(), by contrast, is easy to do and feels like it
might be useful to someone eventually.)

Documenting the portability issue is important, indeed. I'd suggest
to not use a ifdef 0, though, which may be confusing on grep if one
does not look at the surrounding lines. Leaving that in the shape of
a comment would be hard to miss.

Done with a standard comment in v2, attached. Or were you also
suggesting that I should just get rid of the sample, and rely on the
comment above it?

Anyway, are you worrying about
SIZE_MAX matching with something different than the compile-time value
at runtime?

(To avoid any confusion: I'm referring to SSIZE_MAX in particular,
from POSIX, not SIZE_MAX which is C99.)

My concern is that I'll need to add code to win32_port.h for this,
alongside our existing ssize_t definition, and then use the buildfarm
to flush out collisions with any other third-party headers that might
have done the same on Windows. That seems like a lot of potential pain
for no benefit.

--Jacob

Attachments:

v2-0001-Add-pg_add_size_overflow-and-friends.patchapplication/octet-stream; name=v2-0001-Add-pg_add_size_overflow-and-friends.patchDownload
From 850dc8793f47d4cb1f60e916b45c7873301a8e93 Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champion@enterprisedb.com>
Date: Mon, 17 Nov 2025 13:47:45 -0800
Subject: [PATCH v2 1/2] Add pg_add_size_overflow() and friends

Commit 600086f47 added (several bespoke copies of) size_t addition with
overflow checks to libpq. Move this to common/int.h, along with
its subtraction and multiplication counterparts.

pg_neg_size_overflow() is intentionally omitted; I'm not sure we should
add SSIZE_MAX to win32_port.h for the sake of a function with no
callers.

Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/CAOYmi%2B%3D%2BpqUd2MUitvgW1pAJuXgG_TKCVc3_Ek7pe8z9nkf%2BAg%40mail.gmail.com
---
 src/include/common/int.h            | 67 +++++++++++++++++++++++++++++
 src/interfaces/libpq/fe-exec.c      | 44 ++++++-------------
 src/interfaces/libpq/fe-print.c     | 36 ++++------------
 src/interfaces/libpq/fe-protocol3.c | 27 ++----------
 4 files changed, 91 insertions(+), 83 deletions(-)

diff --git a/src/include/common/int.h b/src/include/common/int.h
index 3973f13379d..435716b7906 100644
--- a/src/include/common/int.h
+++ b/src/include/common/int.h
@@ -601,6 +601,73 @@ pg_neg_u64_overflow(uint64 a, int64 *result)
 #endif
 }
 
+/*
+ * size_t
+ */
+static inline bool
+pg_add_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+	return __builtin_add_overflow(a, b, result);
+#else
+	size_t		res = a + b;
+
+	if (res < a)
+	{
+		*result = 0x5EED;		/* to avoid spurious warnings */
+		return true;
+	}
+	*result = res;
+	return false;
+#endif
+}
+
+static inline bool
+pg_sub_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+	return __builtin_sub_overflow(a, b, result);
+#else
+	if (b > a)
+	{
+		*result = 0x5EED;		/* to avoid spurious warnings */
+		return true;
+	}
+	*result = a - b;
+	return false;
+#endif
+}
+
+static inline bool
+pg_mul_size_overflow(size_t a, size_t b, size_t *result)
+{
+#if defined(HAVE__BUILTIN_OP_OVERFLOW)
+	return __builtin_mul_overflow(a, b, result);
+#else
+	size_t		res = a * b;
+
+	if (a != 0 && b != res / a)
+	{
+		*result = 0x5EED;		/* to avoid spurious warnings */
+		return true;
+	}
+	*result = res;
+	return false;
+#endif
+}
+
+/*
+ * pg_neg_size_overflow is currently omitted, to avoid having to reason about
+ * the portability of SSIZE_MIN/_MAX before a use case exists.
+ */
+/*
+ * static inline bool
+ * pg_neg_size_overflow(size_t a, ssize_t *result)
+ * {
+ *     ...
+ * }
+ */
+
 /*------------------------------------------------------------------------
  *
  * Comparison routines for integer types.
diff --git a/src/interfaces/libpq/fe-exec.c b/src/interfaces/libpq/fe-exec.c
index dcc8a447d66..7ab33930a39 100644
--- a/src/interfaces/libpq/fe-exec.c
+++ b/src/interfaces/libpq/fe-exec.c
@@ -24,6 +24,7 @@
 #include <unistd.h>
 #endif
 
+#include "common/int.h"
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
@@ -4220,27 +4221,6 @@ PQescapeString(char *to, const char *from, size_t length)
 }
 
 
-/*
- * Frontend version of the backend's add_size(), intended to be API-compatible
- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
- * Returns true instead if the addition overflows.
- *
- * TODO: move to common/int.h
- */
-static bool
-add_size_overflow(size_t s1, size_t s2, size_t *dst)
-{
-	size_t		result;
-
-	result = s1 + s2;
-	if (result < s1 || result < s2)
-		return true;
-
-	*dst = result;
-	return false;
-}
-
-
 /*
  * Escape arbitrary strings.  If as_ident is true, we escape the result
  * as an identifier; if false, as a literal.  The result is returned in
@@ -4324,14 +4304,14 @@ PQescapeInternal(PGconn *conn, const char *str, size_t len, bool as_ident)
 	 * Allocate output buffer. Protect against overflow, in case the caller
 	 * has allocated a large fraction of the available size_t.
 	 */
-	if (add_size_overflow(input_len, num_quotes, &result_size) ||
-		add_size_overflow(result_size, 3, &result_size))	/* two quotes plus a NUL */
+	if (pg_add_size_overflow(input_len, num_quotes, &result_size) ||
+		pg_add_size_overflow(result_size, 3, &result_size)) /* two quotes plus a NUL */
 		goto overflow;
 
 	if (!as_ident && num_backslashes > 0)
 	{
-		if (add_size_overflow(result_size, num_backslashes, &result_size) ||
-			add_size_overflow(result_size, 2, &result_size))	/* for " E" prefix */
+		if (pg_add_size_overflow(result_size, num_backslashes, &result_size) ||
+			pg_add_size_overflow(result_size, 2, &result_size)) /* for " E" prefix */
 			goto overflow;
 	}
 
@@ -4493,9 +4473,9 @@ PQescapeByteaInternal(PGconn *conn,
 	if (use_hex)
 	{
 		/* We prepend "\x" and double each input character. */
-		if (add_size_overflow(len, bslash_len + 1, &len) ||
-			add_size_overflow(len, from_length, &len) ||
-			add_size_overflow(len, from_length, &len))
+		if (pg_add_size_overflow(len, bslash_len + 1, &len) ||
+			pg_add_size_overflow(len, from_length, &len) ||
+			pg_add_size_overflow(len, from_length, &len))
 			goto overflow;
 	}
 	else
@@ -4505,22 +4485,22 @@ PQescapeByteaInternal(PGconn *conn,
 		{
 			if (*vp < 0x20 || *vp > 0x7e)
 			{
-				if (add_size_overflow(len, bslash_len + 3, &len))	/* octal "\ooo" */
+				if (pg_add_size_overflow(len, bslash_len + 3, &len))	/* octal "\ooo" */
 					goto overflow;
 			}
 			else if (*vp == '\'')
 			{
-				if (add_size_overflow(len, 2, &len))	/* double each quote */
+				if (pg_add_size_overflow(len, 2, &len)) /* double each quote */
 					goto overflow;
 			}
 			else if (*vp == '\\')
 			{
-				if (add_size_overflow(len, bslash_len * 2, &len))	/* double each backslash */
+				if (pg_add_size_overflow(len, bslash_len * 2, &len))	/* double each backslash */
 					goto overflow;
 			}
 			else
 			{
-				if (add_size_overflow(len, 1, &len))
+				if (pg_add_size_overflow(len, 1, &len))
 					goto overflow;
 			}
 		}
diff --git a/src/interfaces/libpq/fe-print.c b/src/interfaces/libpq/fe-print.c
index cc667c9bac9..fc153bee630 100644
--- a/src/interfaces/libpq/fe-print.c
+++ b/src/interfaces/libpq/fe-print.c
@@ -33,6 +33,7 @@
 #endif
 #endif
 
+#include "common/int.h"
 #include "libpq-fe.h"
 #include "libpq-int.h"
 
@@ -451,27 +452,6 @@ do_field(const PQprintOpt *po, const PGresult *res,
 }
 
 
-/*
- * Frontend version of the backend's add_size(), intended to be API-compatible
- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
- * Returns true instead if the addition overflows.
- *
- * TODO: move to common/int.h
- */
-static bool
-add_size_overflow(size_t s1, size_t s2, size_t *dst)
-{
-	size_t		result;
-
-	result = s1 + s2;
-	if (result < s1 || result < s2)
-		return true;
-
-	*dst = result;
-	return false;
-}
-
-
 static char *
 do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
 		  const char **fieldNames, unsigned char *fieldNotNum,
@@ -492,20 +472,20 @@ do_header(FILE *fout, const PQprintOpt *po, const int nFields, int *fieldMax,
 		for (; n < nFields; n++)
 		{
 			/* Field plus separator, plus 2 extra '-' in standard format. */
-			if (add_size_overflow(tot, fieldMax[n], &tot) ||
-				add_size_overflow(tot, fs_len, &tot) ||
-				(po->standard && add_size_overflow(tot, 2, &tot)))
+			if (pg_add_size_overflow(tot, fieldMax[n], &tot) ||
+				pg_add_size_overflow(tot, fs_len, &tot) ||
+				(po->standard && pg_add_size_overflow(tot, 2, &tot)))
 				goto overflow;
 		}
 		if (po->standard)
 		{
 			/* An extra separator at the front and back. */
-			if (add_size_overflow(tot, fs_len, &tot) ||
-				add_size_overflow(tot, fs_len, &tot) ||
-				add_size_overflow(tot, 2, &tot))
+			if (pg_add_size_overflow(tot, fs_len, &tot) ||
+				pg_add_size_overflow(tot, fs_len, &tot) ||
+				pg_add_size_overflow(tot, 2, &tot))
 				goto overflow;
 		}
-		if (add_size_overflow(tot, 1, &tot))	/* terminator */
+		if (pg_add_size_overflow(tot, 1, &tot)) /* terminator */
 			goto overflow;
 
 		border = malloc(tot);
diff --git a/src/interfaces/libpq/fe-protocol3.c b/src/interfaces/libpq/fe-protocol3.c
index 838e42e661a..16f504a867d 100644
--- a/src/interfaces/libpq/fe-protocol3.c
+++ b/src/interfaces/libpq/fe-protocol3.c
@@ -25,6 +25,7 @@
 #include <netinet/tcp.h>
 #endif
 
+#include "common/int.h"
 #include "libpq-fe.h"
 #include "libpq-int.h"
 #include "mb/pg_wchar.h"
@@ -2404,26 +2405,6 @@ pqBuildStartupPacket3(PGconn *conn, int *packetlen,
 	return startpacket;
 }
 
-/*
- * Frontend version of the backend's add_size(), intended to be API-compatible
- * with the pg_add_*_overflow() helpers. Stores the result into *dst on success.
- * Returns true instead if the addition overflows.
- *
- * TODO: move to common/int.h
- */
-static bool
-add_size_overflow(size_t s1, size_t s2, size_t *dst)
-{
-	size_t		result;
-
-	result = s1 + s2;
-	if (result < s1 || result < s2)
-		return true;
-
-	*dst = result;
-	return false;
-}
-
 /*
  * Build a startup packet given a filled-in PGconn structure.
  *
@@ -2456,11 +2437,11 @@ build_startup_packet(const PGconn *conn, char *packet,
 	do { \
 		if (packet) \
 			strcpy(packet + packet_len, optname); \
-		if (add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \
+		if (pg_add_size_overflow(packet_len, strlen(optname) + 1, &packet_len)) \
 			return 0; \
 		if (packet) \
 			strcpy(packet + packet_len, optval); \
-		if (add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \
+		if (pg_add_size_overflow(packet_len, strlen(optval) + 1, &packet_len)) \
 			return 0; \
 	} while(0)
 
@@ -2496,7 +2477,7 @@ build_startup_packet(const PGconn *conn, char *packet,
 	/* Add trailing terminator */
 	if (packet)
 		packet[packet_len] = '\0';
-	if (add_size_overflow(packet_len, 1, &packet_len))
+	if (pg_add_size_overflow(packet_len, 1, &packet_len))
 		return 0;
 
 	return packet_len;
-- 
2.34.1

v2-0002-postgres-Use-pg_-add-mul-_size_overflow.patchapplication/octet-stream; name=v2-0002-postgres-Use-pg_-add-mul-_size_overflow.patchDownload
From 7b44866a307a31052c8882a24c6ad613899cc9ce Mon Sep 17 00:00:00 2001
From: Jacob Champion <jacob.champion@enterprisedb.com>
Date: Mon, 17 Nov 2025 14:02:38 -0800
Subject: [PATCH v2 2/2] postgres: Use pg_{add,mul}_size_overflow()

The backend implementations of add_size() and mul_size() can now make
use of the APIs provided in common/int.h.

Reviewed-by: Chao Li <li.evan.chao@gmail.com>
Reviewed-by: Michael Paquier <michael@paquier.xyz>
Discussion: https://postgr.es/m/CAOYmi%2B%3D%2BpqUd2MUitvgW1pAJuXgG_TKCVc3_Ek7pe8z9nkf%2BAg%40mail.gmail.com
---
 src/backend/storage/ipc/shmem.c | 11 +++--------
 1 file changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c
index 0f18beb6ad4..ee3408df301 100644
--- a/src/backend/storage/ipc/shmem.c
+++ b/src/backend/storage/ipc/shmem.c
@@ -65,6 +65,7 @@
 
 #include "postgres.h"
 
+#include "common/int.h"
 #include "fmgr.h"
 #include "funcapi.h"
 #include "miscadmin.h"
@@ -495,9 +496,7 @@ add_size(Size s1, Size s2)
 {
 	Size		result;
 
-	result = s1 + s2;
-	/* We are assuming Size is an unsigned type here... */
-	if (result < s1 || result < s2)
+	if (pg_add_size_overflow(s1, s2, &result))
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("requested shared memory size overflows size_t")));
@@ -512,11 +511,7 @@ mul_size(Size s1, Size s2)
 {
 	Size		result;
 
-	if (s1 == 0 || s2 == 0)
-		return 0;
-	result = s1 * s2;
-	/* We are assuming Size is an unsigned type here... */
-	if (result / s2 != s1)
+	if (pg_mul_size_overflow(s1, s2, &result))
 		ereport(ERROR,
 				(errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
 				 errmsg("requested shared memory size overflows size_t")));
-- 
2.34.1

#6Jacob Champion
jacob.champion@enterprisedb.com
In reply to: Jacob Champion (#5)
Re: Post-release followup: pg_add_size_overflow()

On Wed, Nov 19, 2025 at 9:46 AM Jacob Champion
<jacob.champion@enterprisedb.com> wrote:

Done with a standard comment in v2, attached. Or were you also
suggesting that I should just get rid of the sample, and rely on the
comment above it?

Pushed v2; let me know if some other documentation style would be more helpful.

Thank you both for the reviews!

--Jacob