summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTom Lane2026-08-10 13:38:18 +0000
committerNoah Misch2026-08-10 13:38:18 +0000
commit83d0a083f178f22c60814b93d17ecacdcce76eac (patch)
tree3546d35c6da57e67ecd608bac02e16639f84b74a
parenteb9e5529745c129c25deb04098612898998fb123 (diff)
Return nulls honestly in aggregate "combine" functions.
numeric_combine() and several other state-combining functions for aggregates cheated for the case of both inputs being NULL: they returned a null pointer without bothering to mark it as a SQL NULL. This was harmless in the expected usage where the result would be passed to the same combine function or a related aggregate final function. But it's bad news from a security standpoint, because now that value can be passed to an internal-accepting function even if said function is strict. While a previous patch prevented such queries from being issued, it seems like good defense-in-depth to expend the few additional lines of code needed to do this properly. Comparable functions such as array_agg_combine() already do so. Reported-by: Amy Burnett (OpenAI Codex Security) Author: Tom Lane <tgl@sss.pgh.pa.us> Backpatch-through: 14 Security: CVE-2026-14680
-rw-r--r--src/backend/utils/adt/numeric.c32
-rw-r--r--src/backend/utils/adt/timestamp.c8
2 files changed, 40 insertions, 0 deletions
diff --git a/src/backend/utils/adt/numeric.c b/src/backend/utils/adt/numeric.c
index d3d33aae935..14e8895ec70 100644
--- a/src/backend/utils/adt/numeric.c
+++ b/src/backend/utils/adt/numeric.c
@@ -5073,7 +5073,15 @@ numeric_combine(PG_FUNCTION_ARGS)
state2 = PG_ARGISNULL(1) ? NULL : (NumericAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
+ {
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
+ if (state1 == NULL)
+ PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
+ }
/* manually copy all fields from state2 to state1 */
if (state1 == NULL)
@@ -5165,7 +5173,15 @@ numeric_avg_combine(PG_FUNCTION_ARGS)
state2 = PG_ARGISNULL(1) ? NULL : (NumericAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
+ {
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
+ if (state1 == NULL)
+ PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
+ }
/* manually copy all fields from state2 to state1 */
if (state1 == NULL)
@@ -5649,7 +5665,15 @@ numeric_poly_combine(PG_FUNCTION_ARGS)
state2 = PG_ARGISNULL(1) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
+ {
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
+ if (state1 == NULL)
+ PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
+ }
/* manually copy all fields from state2 to state1 */
if (state1 == NULL)
@@ -5852,7 +5876,15 @@ int8_avg_combine(PG_FUNCTION_ARGS)
state2 = PG_ARGISNULL(1) ? NULL : (PolyNumAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
+ {
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
+ if (state1 == NULL)
+ PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
+ }
/* manually copy all fields from state2 to state1 */
if (state1 == NULL)
diff --git a/src/backend/utils/adt/timestamp.c b/src/backend/utils/adt/timestamp.c
index afd5d7f7502..b57739b7e83 100644
--- a/src/backend/utils/adt/timestamp.c
+++ b/src/backend/utils/adt/timestamp.c
@@ -4035,7 +4035,15 @@ interval_avg_combine(PG_FUNCTION_ARGS)
state2 = PG_ARGISNULL(1) ? NULL : (IntervalAggState *) PG_GETARG_POINTER(1);
if (state2 == NULL)
+ {
+ /*
+ * NULL state2 is easy, just return state1, which we know is already
+ * in the agg_context
+ */
+ if (state1 == NULL)
+ PG_RETURN_NULL();
PG_RETURN_POINTER(state1);
+ }
if (state1 == NULL)
{