public inbox for [email protected]  
help / color / mirror / Atom feed
From: Richard Guo <[email protected]>
To: Tender Wang <[email protected]>
Cc: [email protected]
Cc: [email protected]
Subject: Re: BUG #19405: Assertion in eval_windowaggregates() fails due to integer overflow
Date: Wed, 8 Apr 2026 18:42:12 +0900
Message-ID: <CAMbWs48QRWz7y52kGTwdSDYcerb_9FdCczJG3-_=RAQz0cNMOw@mail.gmail.com> (raw)
In-Reply-To: <CAMbWs4-stRKsWK6JXYn1eaR1JJzxAeF7QULQ-=0mJFX9k4RFzA@mail.gmail.com>
References: <[email protected]>
	<CAMbWs4_GnG0NYnsBZJpHG-BLo28euD6VUx0WhFd4Ur6RaLr5WQ@mail.gmail.com>
	<CAHewXNnpM95Zg8ARhZwO87_6+4+ag5iBYhoaOJ0TAD_-ygm9tg@mail.gmail.com>
	<CAMbWs4-stRKsWK6JXYn1eaR1JJzxAeF7QULQ-=0mJFX9k4RFzA@mail.gmail.com>

On Tue, Feb 17, 2026 at 10:55 AM Richard Guo <[email protected]> wrote:
> I've included test cases covering the overflow scenarios for ROWS mode
> in v3.  (I failed to come up with queries for GROUPS mode that
> demonstrate the bug, but I suspect I just haven't found the right test
> case yet.)  I have also included a commit message.

Here is the updated patch.  I've added test cases for GROUPS mode with
an overflow-inducing offset.  These don't seem to produce visibly
wrong results without the patch.  I believe that is because the
incremental nature of group pointer advancement happens to mask the
misbehavior.  But I think they are still worth having since signed
integer overflow is undefined behavior in C.

Regarding performance, I don't think this should be a concern.
pg_add_s64_overflow compiles down to a native add+jo sequence on
platforms with __builtin_add_overflow, and falls back to a couple of
inlined comparisons on others.  Either way, the cost seems negligible
next to the per-row tuple store and aggregation work these functions
already do.

- Richard


Attachments:

  [application/octet-stream] v4-0001-Fix-integer-overflow-in-nodeWindowAgg.c.patch (10.6K, 2-v4-0001-Fix-integer-overflow-in-nodeWindowAgg.c.patch)
  download | inline diff:
From b4e74c3ad01def514ab0b42f9dd84a1ee683e8c9 Mon Sep 17 00:00:00 2001
From: Richard Guo <[email protected]>
Date: Sat, 14 Feb 2026 18:16:27 +0900
Subject: [PATCH v4] Fix integer overflow in nodeWindowAgg.c

In nodeWindowAgg.c, the calculations for frame start and end positions
in ROWS and GROUPS modes were performed using simple integer addition.
If a user-supplied offset was sufficiently large (close to INT64_MAX),
adding it to the current row or group index could cause a signed
integer overflow, wrapping the result to a negative number.

This led to incorrect behavior where frame boundaries that should have
extended indefinitely (or beyond the partition end) were treated as
falling at the first row, or where valid rows were incorrectly marked
as out-of-frame.  Depending on the specific query and data, these
overflows can result in incorrect query results, execution errors, or
assertion failures.

To fix, use overflow-aware integer addition (ie, pg_add_s64_overflow)
to check for overflows during these additions.  If an overflow is
detected, the boundary is now clamped to INT64_MAX.  This ensures the
logic correctly treats the boundary as extending to the end of the
partition.
---
 src/backend/executor/nodeWindowAgg.c | 62 ++++++++++++++++---
 src/test/regress/expected/window.out | 91 ++++++++++++++++++++++++++++
 src/test/regress/sql/window.sql      | 26 ++++++++
 3 files changed, 172 insertions(+), 7 deletions(-)

diff --git a/src/backend/executor/nodeWindowAgg.c b/src/backend/executor/nodeWindowAgg.c
index 784ceeb8246..f52a7aae843 100644
--- a/src/backend/executor/nodeWindowAgg.c
+++ b/src/backend/executor/nodeWindowAgg.c
@@ -37,6 +37,7 @@
 #include "catalog/objectaccess.h"
 #include "catalog/pg_aggregate.h"
 #include "catalog/pg_proc.h"
+#include "common/int.h"
 #include "executor/executor.h"
 #include "executor/instrument.h"
 #include "executor/nodeWindowAgg.h"
@@ -1534,12 +1535,21 @@ row_is_in_frame(WindowObject winobj, int64 pos, TupleTableSlot *slot,
 		if (frameOptions & FRAMEOPTION_ROWS)
 		{
 			int64		offset = DatumGetInt64(winstate->endOffsetValue);
+			int64		frameendpos = 0;
 
 			/* rows after current row + offset are out of frame */
 			if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
 				offset = -offset;
 
-			if (pos > winstate->currentpos + offset)
+			/*
+			 * If we have an overflow, it means the frame end is beyond the
+			 * range of int64.  Since currentpos >= 0, this can only be a
+			 * positive overflow.  We treat this as meaning that the frame
+			 * extends to end of partition.
+			 */
+			if (!pg_add_s64_overflow(winstate->currentpos, offset,
+									 &frameendpos) &&
+				pos > frameendpos)
 				return -1;
 		}
 		else if (frameOptions & (FRAMEOPTION_RANGE | FRAMEOPTION_GROUPS))
@@ -1674,7 +1684,16 @@ update_frameheadpos(WindowAggState *winstate)
 			if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
 				offset = -offset;
 
-			winstate->frameheadpos = winstate->currentpos + offset;
+			/*
+			 * If we have an overflow, it means the frame head is beyond the
+			 * range of int64.  Since currentpos >= 0, this can only be a
+			 * positive overflow.  We treat this as being beyond end of
+			 * partition.
+			 */
+			if (pg_add_s64_overflow(winstate->currentpos, offset,
+									&winstate->frameheadpos))
+				winstate->frameheadpos = PG_INT64_MAX;
+
 			/* frame head can't go before first row */
 			if (winstate->frameheadpos < 0)
 				winstate->frameheadpos = 0;
@@ -1786,12 +1805,21 @@ update_frameheadpos(WindowAggState *winstate)
 			 * framehead_slot empty.
 			 */
 			int64		offset = DatumGetInt64(winstate->startOffsetValue);
-			int64		minheadgroup;
+			int64		minheadgroup = 0;
 
 			if (frameOptions & FRAMEOPTION_START_OFFSET_PRECEDING)
 				minheadgroup = winstate->currentgroup - offset;
 			else
-				minheadgroup = winstate->currentgroup + offset;
+			{
+				/*
+				 * If we have an overflow, it means the target group is beyond
+				 * the range of int64.  We treat this as "infinity", which
+				 * ensures the loop below advances to end of partition.
+				 */
+				if (pg_add_s64_overflow(winstate->currentgroup, offset,
+										&minheadgroup))
+					minheadgroup = PG_INT64_MAX;
+			}
 
 			tuplestore_select_read_pointer(winstate->buffer,
 										   winstate->framehead_ptr);
@@ -1928,7 +1956,18 @@ update_frametailpos(WindowAggState *winstate)
 			if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
 				offset = -offset;
 
-			winstate->frametailpos = winstate->currentpos + offset + 1;
+			/*
+			 * If we have an overflow, it means the frame tail is beyond the
+			 * range of int64.  Since currentpos >= 0, this can only be a
+			 * positive overflow.  We treat this as being beyond end of
+			 * partition.
+			 */
+			if (pg_add_s64_overflow(winstate->currentpos, offset,
+									&winstate->frametailpos) ||
+				pg_add_s64_overflow(winstate->frametailpos, 1,
+									&winstate->frametailpos))
+				winstate->frametailpos = PG_INT64_MAX;
+
 			/* smallest allowable value of frametailpos is 0 */
 			if (winstate->frametailpos < 0)
 				winstate->frametailpos = 0;
@@ -2040,12 +2079,21 @@ update_frametailpos(WindowAggState *winstate)
 			 * leave frametailpos = end+1 and frametail_slot empty.
 			 */
 			int64		offset = DatumGetInt64(winstate->endOffsetValue);
-			int64		maxtailgroup;
+			int64		maxtailgroup = 0;
 
 			if (frameOptions & FRAMEOPTION_END_OFFSET_PRECEDING)
 				maxtailgroup = winstate->currentgroup - offset;
 			else
-				maxtailgroup = winstate->currentgroup + offset;
+			{
+				/*
+				 * If we have an overflow, it means the target group is beyond
+				 * the range of int64.  We treat this as "infinity", which
+				 * ensures the loop below advances to end of partition.
+				 */
+				if (pg_add_s64_overflow(winstate->currentgroup, offset,
+										&maxtailgroup))
+					maxtailgroup = PG_INT64_MAX;
+			}
 
 			tuplestore_select_read_pointer(winstate->buffer,
 										   winstate->frametail_ptr);
diff --git a/src/test/regress/expected/window.out b/src/test/regress/expected/window.out
index 7a04d3a7a9f..e6aac27a2a9 100644
--- a/src/test/regress/expected/window.out
+++ b/src/test/regress/expected/window.out
@@ -1361,6 +1361,97 @@ SELECT pg_get_viewdef('v_window');
     FROM generate_series(now(), (now() + '@ 100 days'::interval), '@ 1 hour'::interval) i(i);
 (1 row)
 
+-- test overflow frame specifications
+SELECT sum(unique1) over (rows between current row and 9223372036854775807 following exclude current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  41 |       4 |    0
+  39 |       2 |    2
+  38 |       1 |    1
+  32 |       6 |    2
+  23 |       9 |    1
+  15 |       8 |    0
+  10 |       5 |    1
+   7 |       3 |    3
+   0 |       7 |    3
+     |       0 |    0
+(10 rows)
+
+SELECT sum(unique1) over (rows between 9223372036854775807 following and 1 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       4 |    0
+     |       2 |    2
+     |       1 |    1
+     |       6 |    2
+     |       9 |    1
+     |       8 |    0
+     |       5 |    1
+     |       3 |    3
+     |       7 |    3
+     |       0 |    0
+(10 rows)
+
+SELECT last_value(unique1) over (ORDER BY four rows between current row and 9223372036854775807 following exclude current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ last_value | unique1 | four 
+------------+---------+------
+          7 |       0 |    0
+          7 |       8 |    0
+          7 |       4 |    0
+          7 |       5 |    1
+          7 |       9 |    1
+          7 |       1 |    1
+          7 |       6 |    2
+          7 |       2 |    2
+          7 |       3 |    3
+            |       7 |    3
+(10 rows)
+
+-- These test GROUPS mode with an offset large enough to cause overflow when
+-- added to currentgroup.  Although the overflow doesn't produce visibly wrong
+-- results (due to the incremental nature of group pointer advancement), we
+-- still need to protect against it as signed integer overflow is undefined
+-- behavior in C.
+SELECT sum(unique1) over (ORDER BY four groups between current row and 9223372036854775807 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+  45 |       0 |    0
+  45 |       8 |    0
+  45 |       4 |    0
+  33 |       5 |    1
+  33 |       9 |    1
+  33 |       1 |    1
+  18 |       6 |    2
+  18 |       2 |    2
+  10 |       3 |    3
+  10 |       7 |    3
+(10 rows)
+
+SELECT sum(unique1) over (ORDER BY four groups between 9223372036854775807 following and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+ sum | unique1 | four 
+-----+---------+------
+     |       0 |    0
+     |       8 |    0
+     |       4 |    0
+     |       5 |    1
+     |       9 |    1
+     |       1 |    1
+     |       6 |    2
+     |       2 |    2
+     |       3 |    3
+     |       7 |    3
+(10 rows)
+
 -- RANGE offset PRECEDING/FOLLOWING tests
 SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
 	unique1, four
diff --git a/src/test/regress/sql/window.sql b/src/test/regress/sql/window.sql
index 37d837a2f66..305549b104d 100644
--- a/src/test/regress/sql/window.sql
+++ b/src/test/regress/sql/window.sql
@@ -330,6 +330,32 @@ CREATE TEMP VIEW v_window AS
 
 SELECT pg_get_viewdef('v_window');
 
+-- test overflow frame specifications
+SELECT sum(unique1) over (rows between current row and 9223372036854775807 following exclude current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (rows between 9223372036854775807 following and 1 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT last_value(unique1) over (ORDER BY four rows between current row and 9223372036854775807 following exclude current row),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+-- These test GROUPS mode with an offset large enough to cause overflow when
+-- added to currentgroup.  Although the overflow doesn't produce visibly wrong
+-- results (due to the incremental nature of group pointer advancement), we
+-- still need to protect against it as signed integer overflow is undefined
+-- behavior in C.
+SELECT sum(unique1) over (ORDER BY four groups between current row and 9223372036854775807 following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
+SELECT sum(unique1) over (ORDER BY four groups between 9223372036854775807 following and unbounded following),
+	unique1, four
+FROM tenk1 WHERE unique1 < 10;
+
 -- RANGE offset PRECEDING/FOLLOWING tests
 
 SELECT sum(unique1) over (order by four range between 2::int8 preceding and 1::int2 preceding),
-- 
2.39.5 (Apple Git-154)



reply

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Reply to all the recipients using the --to and --cc options:
  reply via email

  To: [email protected]
  Cc: [email protected], [email protected], [email protected], [email protected]
  Subject: Re: BUG #19405: Assertion in eval_windowaggregates() fails due to integer overflow
  In-Reply-To: <CAMbWs48QRWz7y52kGTwdSDYcerb_9FdCczJG3-_=RAQz0cNMOw@mail.gmail.com>

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox