]> git.ipfire.org Git - thirdparty/postgresql.git/commitdiff
Make palloc_array() and friends safe against integer overflow.
authorTom Lane <tgl@sss.pgh.pa.us>
Mon, 11 May 2026 12:13:50 +0000 (05:13 -0700)
committerNoah Misch <noah@leadboat.com>
Mon, 11 May 2026 12:13:50 +0000 (05:13 -0700)
Sufficiently large "count" arguments could result in undetected
overflow, causing the allocated memory chunk to be much smaller
than what the caller will subsequently write into it.  This is
unlikely to be a hazard with 64-bit size_t but can sometimes
happen on 32-bit builds, primarily where a function allocates
workspace that's significantly larger than its input data.
Rather than trying to patch the at-risk callers piecemeal,
let's just redefine these macros so that they always check.

To do that, move the longstanding add_size() and mul_size() functions
into palloc.h and mcxt.c, and adjust them to not be specific to
shared-memory allocation.  Then invent palloc_mul(), palloc0_mul(),
palloc_mul_extended() to use these functions.  Actually, the latter
use inlined copies to save one function call.  repalloc_array() gets
similar treatment.  I didn't bother trying to inline the calls for
repalloc0_array() though.

In v14 and v15, this also adds repalloc_extended(), which previously
was only available in v16 and up.

We need copies of all this in fe_memutils.[hc] as well, since that
module also provides palloc_array() etc.

Reported-by: Xint Code
Author: Tom Lane <tgl@sss.pgh.pa.us>
Reviewed-by: Masahiko Sawada <sawada.mshk@gmail.com>
Backpatch-through: 14
Security: CVE-2026-6473

src/backend/storage/ipc/shmem.c
src/backend/utils/mmgr/mcxt.c
src/common/fe_memutils.c
src/include/common/fe_memutils.h
src/include/storage/shmem.h
src/include/utils/memutils.h
src/include/utils/palloc.h

index 10be765fb7b173947f5875cdf1d7b6e84b6b63de..f35cbaf02b2cf1847d63eaed3e8753ab735d0f79 100644 (file)
@@ -495,42 +495,6 @@ ShmemInitStruct(const char *name, Size size, bool *foundPtr)
 }
 
 
-/*
- * Add two Size values, checking for overflow
- */
-Size
-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)
-               ereport(ERROR,
-                               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                                errmsg("requested shared memory size overflows size_t")));
-       return result;
-}
-
-/*
- * Multiply two Size values, checking for overflow
- */
-Size
-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)
-               ereport(ERROR,
-                               (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
-                                errmsg("requested shared memory size overflows size_t")));
-       return result;
-}
-
 /* SQL SRF showing allocated shared memory */
 Datum
 pg_get_shmem_allocations(PG_FUNCTION_ARGS)
index 339a52891ce9c6d352e0628674ede467b2f12f76..f2d9cb2cb8b256e555101c0dad4858fab2ed9777 100644 (file)
@@ -21,6 +21,7 @@
 
 #include "postgres.h"
 
+#include "common/int.h"
 #include "mb/pg_wchar.h"
 #include "miscadmin.h"
 #include "storage/proc.h"
@@ -67,6 +68,8 @@ static void MemoryContextStatsInternal(MemoryContext context, int level,
 static void MemoryContextStatsPrint(MemoryContext context, void *passthru,
                                                                        const char *stats_string,
                                                                        bool print_to_stderr);
+static pg_noinline void add_size_error(Size s1, Size s2) pg_attribute_noreturn();
+static pg_noinline void mul_size_error(Size s1, Size s2) pg_attribute_noreturn();
 
 /*
  * You should not do memory allocations within a critical section, because
@@ -1237,6 +1240,172 @@ repalloc(void *pointer, Size size)
        return ret;
 }
 
+/*
+ * repalloc_extended
+ *             Adjust the size of a previously allocated chunk,
+ *             with HUGE and NO_OOM options.
+ */
+void *
+repalloc_extended(void *pointer, Size size, int flags)
+{
+       MemoryContext context = GetMemoryChunkContext(pointer);
+       void       *ret;
+
+       if (!((flags & MCXT_ALLOC_HUGE) != 0 ? AllocHugeSizeIsValid(size) :
+                 AllocSizeIsValid(size)))
+               elog(ERROR, "invalid memory alloc request size %zu", size);
+
+       AssertNotInCriticalSection(context);
+
+       /* isReset must be false already */
+       Assert(!context->isReset);
+
+       ret = context->methods->realloc(context, pointer, size);
+       if (unlikely(ret == NULL))
+       {
+               if ((flags & MCXT_ALLOC_NO_OOM) == 0)
+               {
+                       MemoryContextStats(TopMemoryContext);
+                       ereport(ERROR,
+                                       (errcode(ERRCODE_OUT_OF_MEMORY),
+                                        errmsg("out of memory"),
+                                        errdetail("Failed on request of size %zu in memory context \"%s\".",
+                                                          size, context->name)));
+               }
+               return NULL;
+       }
+
+       VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
+
+       return ret;
+}
+
+/*
+ * Support for safe calculation of memory request sizes
+ *
+ * These functions perform the requested calculation, but throw error if the
+ * result overflows.
+ *
+ * An important property of these functions is that if an argument was a
+ * negative signed int before promotion (implying overflow in calculating it)
+ * we will detect that as an error.  That happens because we reject results
+ * larger than SIZE_MAX / 2 later on, in the actual allocation step.
+ */
+Size
+add_size(Size s1, Size s2)
+{
+       Size            result;
+
+       if (unlikely(pg_add_size_overflow(s1, s2, &result)))
+               add_size_error(s1, s2);
+       return result;
+}
+
+static pg_noinline void
+add_size_error(Size s1, Size s2)
+{
+       ereport(ERROR,
+                       (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                        errmsg("invalid memory allocation request size %zu + %zu",
+                                       s1, s2)));
+}
+
+Size
+mul_size(Size s1, Size s2)
+{
+       Size            result;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &result)))
+               mul_size_error(s1, s2);
+       return result;
+}
+
+static pg_noinline void
+mul_size_error(Size s1, Size s2)
+{
+       ereport(ERROR,
+                       (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+                        errmsg("invalid memory allocation request size %zu * %zu",
+                                       s1, s2)));
+}
+
+/*
+ * palloc_mul
+ *             Equivalent to palloc(mul_size(s1, s2)).
+ */
+void *
+palloc_mul(Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+               mul_size_error(s1, s2);
+       return palloc(req);
+}
+
+/*
+ * palloc0_mul
+ *             Equivalent to palloc0(mul_size(s1, s2)).
+ *
+ * This is comparable to standard calloc's behavior.
+ */
+void *
+palloc0_mul(Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+               mul_size_error(s1, s2);
+       return palloc0(req);
+}
+
+/*
+ * palloc_mul_extended
+ *             Equivalent to palloc_extended(mul_size(s1, s2), flags).
+ */
+void *
+palloc_mul_extended(Size s1, Size s2, int flags)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+               mul_size_error(s1, s2);
+       return palloc_extended(req, flags);
+}
+
+/*
+ * repalloc_mul
+ *             Equivalent to repalloc(p, mul_size(s1, s2)).
+ */
+void *
+repalloc_mul(void *p, Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+               mul_size_error(s1, s2);
+       return repalloc(p, req);
+}
+
+/*
+ * repalloc_mul_extended
+ *             Equivalent to repalloc_extended(p, mul_size(s1, s2), flags).
+ */
+void *
+repalloc_mul_extended(void *p, Size s1, Size s2, int flags)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req)))
+               mul_size_error(s1, s2);
+       return repalloc_extended(p, req, flags);
+}
+
 /*
  * MemoryContextAllocHuge
  *             Allocate (possibly-expansive) space within the specified context.
@@ -1280,31 +1449,8 @@ MemoryContextAllocHuge(MemoryContext context, Size size)
 void *
 repalloc_huge(void *pointer, Size size)
 {
-       MemoryContext context = GetMemoryChunkContext(pointer);
-       void       *ret;
-
-       if (!AllocHugeSizeIsValid(size))
-               elog(ERROR, "invalid memory alloc request size %zu", size);
-
-       AssertNotInCriticalSection(context);
-
-       /* isReset must be false already */
-       Assert(!context->isReset);
-
-       ret = context->methods->realloc(context, pointer, size);
-       if (unlikely(ret == NULL))
-       {
-               MemoryContextStats(TopMemoryContext);
-               ereport(ERROR,
-                               (errcode(ERRCODE_OUT_OF_MEMORY),
-                                errmsg("out of memory"),
-                                errdetail("Failed on request of size %zu in memory context \"%s\".",
-                                                  size, context->name)));
-       }
-
-       VALGRIND_MEMPOOL_CHANGE(context, pointer, ret, size);
-
-       return ret;
+       /* this one seems not worth its own implementation */
+       return repalloc_extended(pointer, size, MCXT_ALLOC_HUGE);
 }
 
 /*
index 21fcce5430931610fab115b2e8cf101a9faddf75..519fc18857ecceb9d2062e8c1582422d0dd9ad2d 100644 (file)
 
 #include "postgres_fe.h"
 
+#include "common/int.h"
+
+static pg_noinline void add_size_error(Size s1, Size s2) pg_attribute_noreturn();
+static pg_noinline void mul_size_error(Size s1, Size s2) pg_attribute_noreturn();
+
+
 static inline void *
 pg_malloc_internal(size_t size, int flags)
 {
@@ -174,3 +180,185 @@ repalloc(void *pointer, Size size)
 {
        return pg_realloc(pointer, size);
 }
+
+/*
+ * Support for safe calculation of memory request sizes
+ *
+ * These functions perform the requested calculation, but throw error if the
+ * result overflows.
+ *
+ * An important property of these functions is that if an argument was a
+ * negative signed int before promotion (implying overflow in calculating it)
+ * we will detect that as an error.  That happens because we reject results
+ * larger than SIZE_MAX / 2.  In the backend we rely on later checks to do
+ * that, but in frontend we must do it here.
+ */
+Size
+add_size(Size s1, Size s2)
+{
+       Size            result;
+
+       if (unlikely(pg_add_size_overflow(s1, s2, &result) ||
+                                result > (SIZE_MAX / 2)))
+               add_size_error(s1, s2);
+       return result;
+}
+
+static pg_noinline void
+add_size_error(Size s1, Size s2)
+{
+       fprintf(stderr, _("invalid memory allocation request size %zu + %zu\n"),
+                       s1, s2);
+       exit(EXIT_FAILURE);
+}
+
+Size
+mul_size(Size s1, Size s2)
+{
+       Size            result;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &result) ||
+                                result > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return result;
+}
+
+static pg_noinline void
+mul_size_error(Size s1, Size s2)
+{
+       fprintf(stderr, _("invalid memory allocation request size %zu * %zu\n"),
+                       s1, s2);
+       exit(EXIT_FAILURE);
+}
+
+/*
+ * pg_malloc_mul
+ *             Equivalent to pg_malloc(mul_size(s1, s2)).
+ */
+void *
+pg_malloc_mul(Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+                                req > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return pg_malloc(req);
+}
+
+/*
+ * pg_malloc0_mul
+ *             Equivalent to pg_malloc0(mul_size(s1, s2)).
+ *
+ * This is comparable to standard calloc's behavior.
+ */
+void *
+pg_malloc0_mul(Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+                                req > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return pg_malloc0(req);
+}
+
+/*
+ * pg_malloc_mul_extended
+ *             Equivalent to pg_malloc_extended(mul_size(s1, s2), flags).
+ */
+void *
+pg_malloc_mul_extended(Size s1, Size s2, int flags)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+                                req > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return pg_malloc_extended(req, flags);
+}
+
+/*
+ * pg_realloc_mul
+ *             Equivalent to pg_realloc(p, mul_size(s1, s2)).
+ */
+void *
+pg_realloc_mul(void *p, Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+                                req > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return pg_realloc(p, req);
+}
+
+/*
+ * palloc_mul
+ *             Equivalent to palloc(mul_size(s1, s2)).
+ */
+void *
+palloc_mul(Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+                                req > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return palloc(req);
+}
+
+/*
+ * palloc0_mul
+ *             Equivalent to palloc0(mul_size(s1, s2)).
+ *
+ * This is comparable to standard calloc's behavior.
+ */
+void *
+palloc0_mul(Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+                                req > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return palloc0(req);
+}
+
+/*
+ * palloc_mul_extended
+ *             Equivalent to palloc_extended(mul_size(s1, s2), flags).
+ */
+void *
+palloc_mul_extended(Size s1, Size s2, int flags)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+                                req > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return palloc_extended(req, flags);
+}
+
+/*
+ * repalloc_mul
+ *             Equivalent to repalloc(p, mul_size(s1, s2)).
+ */
+void *
+repalloc_mul(void *p, Size s1, Size s2)
+{
+       /* inline mul_size() for efficiency */
+       Size            req;
+
+       if (unlikely(pg_mul_size_overflow(s1, s2, &req) ||
+                                req > (SIZE_MAX / 2)))
+               mul_size_error(s1, s2);
+       return repalloc(p, req);
+}
index 63f2b6a802d0a35d27bfb3ef3f04efebd5cc932a..e915ed19d9fa7a1abbb07a86f21908092c3a580f 100644 (file)
@@ -29,6 +29,16 @@ extern void *pg_malloc_extended(size_t size, int flags);
 extern void *pg_realloc(void *pointer, size_t size);
 extern void pg_free(void *pointer);
 
+/*
+ * Support for safe calculation of memory request sizes
+ */
+extern Size add_size(Size s1, Size s2);
+extern Size mul_size(Size s1, Size s2);
+extern void *pg_malloc_mul(Size s1, Size s2);
+extern void *pg_malloc0_mul(Size s1, Size s2);
+extern void *pg_malloc_mul_extended(Size s1, Size s2, int flags);
+extern void *pg_realloc_mul(void *p, Size s1, Size s2);
+
 /*
  * Variants with easier notation and more type safety
  */
@@ -42,14 +52,15 @@ extern void pg_free(void *pointer);
 /*
  * Allocate space for "count" objects of type "type"
  */
-#define pg_malloc_array(type, count) ((type *) pg_malloc(sizeof(type) * (count)))
-#define pg_malloc0_array(type, count) ((type *) pg_malloc0(sizeof(type) * (count)))
+#define pg_malloc_array(type, count) ((type *) pg_malloc_mul(sizeof(type), count))
+#define pg_malloc0_array(type, count) ((type *) pg_malloc0_mul(sizeof(type), count))
+#define pg_malloc_array_extended(type, count, flags) ((type *) pg_malloc_mul_extended(sizeof(type), count, flags))
 
 /*
  * Change size of allocation pointed to by "pointer" to have space for "count"
  * objects of type "type"
  */
-#define pg_realloc_array(pointer, type, count) ((type *) pg_realloc(pointer, sizeof(type) * (count)))
+#define pg_realloc_array(pointer, type, count) ((type *) pg_realloc_mul(pointer, sizeof(type), count))
 
 /* Equivalent functions, deliberately named the same as backend functions */
 extern char *pstrdup(const char *in);
@@ -59,12 +70,17 @@ extern void *palloc0(Size size);
 extern void *palloc_extended(Size size, int flags);
 extern void *repalloc(void *pointer, Size size);
 extern void pfree(void *pointer);
+extern void *palloc_mul(Size s1, Size s2);
+extern void *palloc0_mul(Size s1, Size s2);
+extern void *palloc_mul_extended(Size s1, Size s2, int flags);
+extern void *repalloc_mul(void *p, Size s1, Size s2);
 
 #define palloc_object(type) ((type *) palloc(sizeof(type)))
 #define palloc0_object(type) ((type *) palloc0(sizeof(type)))
-#define palloc_array(type, count) ((type *) palloc(sizeof(type) * (count)))
-#define palloc0_array(type, count) ((type *) palloc0(sizeof(type) * (count)))
-#define repalloc_array(pointer, type, count) ((type *) repalloc(pointer, sizeof(type) * (count)))
+#define palloc_array(type, count) ((type *) palloc_mul(sizeof(type), count))
+#define palloc0_array(type, count) ((type *) palloc0_mul(sizeof(type), count))
+#define palloc_array_extended(type, count, flags) ((type *) palloc_mul_extended(sizeof(type), count, flags))
+#define repalloc_array(pointer, type, count) ((type *) repalloc_mul(pointer, sizeof(type), count))
 
 /* sprintf into a palloc'd buffer --- these are in psprintf.c */
 extern char *psprintf(const char *fmt,...) pg_attribute_printf(1, 2);
index de9e7c6e73fc66bb5368be16adf2f137cb2d94b3..f63a8caaf4708a4fef4cb6aa2766ef2800d67797 100644 (file)
@@ -42,8 +42,6 @@ extern void InitShmemIndex(void);
 extern HTAB *ShmemInitHash(const char *name, long init_size, long max_size,
                                                   HASHCTL *infoP, int hash_flags);
 extern void *ShmemInitStruct(const char *name, Size size, bool *foundPtr);
-extern Size add_size(Size s1, Size s2);
-extern Size mul_size(Size s1, Size s2);
 
 /* ipci.c */
 extern void RequestAddinShmemSpace(Size size);
index 495d1af2010d1572aff7f0683d52a2cbf74b33d8..cbc7bbf00cf00eec570b41476e07e6571cb35f26 100644 (file)
@@ -41,6 +41,7 @@
 
 #define AllocSizeIsValid(size) ((Size) (size) <= MaxAllocSize)
 
+/* Do not make this any bigger; see add_size() and mul_size() */
 #define MaxAllocHugeSize       (SIZE_MAX / 2)
 
 #define AllocHugeSizeIsValid(size)     ((Size) (size) <= MaxAllocHugeSize)
index a0b62aa7b0c7179d7188c0cbd9fd7f33e626032d..b619760a192fd0d6d658a26ea202332f8143b331 100644 (file)
@@ -78,8 +78,22 @@ extern void *palloc(Size size);
 extern void *palloc0(Size size);
 extern void *palloc_extended(Size size, int flags);
 extern pg_nodiscard void *repalloc(void *pointer, Size size);
+extern pg_nodiscard void *repalloc_extended(void *pointer,
+                                                                                       Size size, int flags);
 extern void pfree(void *pointer);
 
+/*
+ * Support for safe calculation of memory request sizes
+ */
+extern Size add_size(Size s1, Size s2);
+extern Size mul_size(Size s1, Size s2);
+extern void *palloc_mul(Size s1, Size s2);
+extern void *palloc0_mul(Size s1, Size s2);
+extern void *palloc_mul_extended(Size s1, Size s2, int flags);
+pg_nodiscard extern void *repalloc_mul(void *p, Size s1, Size s2);
+pg_nodiscard extern void *repalloc_mul_extended(void *p, Size s1, Size s2,
+                                                                                               int flags);
+
 /*
  * Variants with easier notation and more type safety
  */
@@ -93,14 +107,16 @@ extern void pfree(void *pointer);
 /*
  * Allocate space for "count" objects of type "type"
  */
-#define palloc_array(type, count) ((type *) palloc(sizeof(type) * (count)))
-#define palloc0_array(type, count) ((type *) palloc0(sizeof(type) * (count)))
+#define palloc_array(type, count) ((type *) palloc_mul(sizeof(type), count))
+#define palloc0_array(type, count) ((type *) palloc0_mul(sizeof(type), count))
+#define palloc_array_extended(type, count, flags) ((type *) palloc_mul_extended(sizeof(type), count, flags))
 
 /*
  * Change size of allocation pointed to by "pointer" to have space for "count"
  * objects of type "type"
  */
-#define repalloc_array(pointer, type, count) ((type *) repalloc(pointer, sizeof(type) * (count)))
+#define repalloc_array(pointer, type, count) ((type *) repalloc_mul(pointer, sizeof(type), count))
+#define repalloc_array_extended(pointer, type, count, flags) ((type *) repalloc_mul_extended(pointer, sizeof(type), count, flags))
 
 /*
  * The result of palloc() is always word-aligned, so we can skip testing