nocfbot-0050-Fix-signed-integer-overflow-in-row-pattern-recogn.txt
text/plain
Filename: nocfbot-0050-Fix-signed-integer-overflow-in-row-pattern-recogn.txt
Type: text/plain
Part: 49
Message:
Re: Row pattern recognition
From 8e806e30fd8a7dcbeb18e13faccd50cdeff0f911 Mon Sep 17 00:00:00 2001
From: Henson Choi <assam258@gmail.com>
Date: Fri, 5 Jun 2026 13:31:20 +0900
Subject: [PATCH 50/68] Fix signed-integer overflow in row pattern recognition
frame-end clamp
ExecRPRProcessRow() computed "frameOffset + 1" before passing it to
pg_add_s64_overflow(), so a FOLLOWING offset near PG_INT64_MAX made
that add overflow on its own -- undefined behavior that crashed
cassert builds and gave wrong results otherwise.
Add the offset and the +1 in two separately checked steps, clamping to
PG_INT64_MAX on overflow, as core WindowAgg already does. The clamp is
safe because the partition row count is an int64, so no row ever sits
at that position; a huge FOLLOWING offset thus behaves like UNBOUNDED
FOLLOWING. Also add a regression test.
---
src/backend/executor/execRPR.c | 31 +++++++++++++++++++++-----
src/test/regress/expected/rpr_base.out | 25 +++++++++++++++++++++
src/test/regress/sql/rpr_base.sql | 16 +++++++++++++
3 files changed, 66 insertions(+), 6 deletions(-)
diff --git a/src/backend/executor/execRPR.c b/src/backend/executor/execRPR.c
index 56399c0c7fd..69e3603adef 100644
--- a/src/backend/executor/execRPR.c
+++ b/src/backend/executor/execRPR.c
@@ -1784,9 +1784,15 @@ ExecRPRProcessRow(WindowAggState *winstate, int64 currentPos,
{
int64 ctxFrameEnd;
- /* Clamp to INT64_MAX on overflow */
- if (pg_add_s64_overflow(ctx->matchStartRow, frameOffset + 1,
- &ctxFrameEnd))
+ /*
+ * Clamp to INT64_MAX on overflow. frameOffset can be as large as
+ * PG_INT64_MAX (e.g. "ROWS <huge> FOLLOWING"), so add the offset
+ * and the trailing +1 in two separately checked steps to avoid
+ * signed-integer overflow in the "frameOffset + 1" subexpression.
+ */
+ if (pg_add_s64_overflow(ctx->matchStartRow, frameOffset,
+ &ctxFrameEnd) ||
+ pg_add_s64_overflow(ctxFrameEnd, 1, &ctxFrameEnd))
ctxFrameEnd = PG_INT64_MAX;
/*
@@ -1844,10 +1850,23 @@ ExecRPRProcessRow(WindowAggState *winstate, int64 currentPos,
* mismatch (nfa_match with NULL), which removes all states (all
* states are at VAR positions after advance). So any surviving
* context here must be within its frame boundary.
+ *
+ * Compute the (clamped) frame end the same way as Phase 1, using two
+ * separately checked adds so that "frameOffset + 1" cannot overflow
+ * when frameOffset is near PG_INT64_MAX.
*/
- Assert(!hasLimitedFrame ||
- ctx->matchStartRow > PG_INT64_MAX - frameOffset - 1 ||
- currentPos < ctx->matchStartRow + frameOffset + 1);
+#ifdef USE_ASSERT_CHECKING
+ if (hasLimitedFrame)
+ {
+ int64 ctxFrameEnd;
+
+ if (pg_add_s64_overflow(ctx->matchStartRow, frameOffset,
+ &ctxFrameEnd) ||
+ pg_add_s64_overflow(ctxFrameEnd, 1, &ctxFrameEnd))
+ ctxFrameEnd = PG_INT64_MAX;
+ Assert(currentPos < ctxFrameEnd);
+ }
+#endif
nfa_advance(winstate, ctx, currentPos);
}
diff --git a/src/test/regress/expected/rpr_base.out b/src/test/regress/expected/rpr_base.out
index 1410ba75395..4fe7360114f 100644
--- a/src/test/regress/expected/rpr_base.out
+++ b/src/test/regress/expected/rpr_base.out
@@ -643,6 +643,31 @@ ORDER BY id;
6 | 30 | 1
(6 rows)
+-- int64 frame-end overflow: a huge FOLLOWING offset must clamp to the
+-- partition end (matchStartRow + offset + 1 overflows int64; the clamp makes
+-- it behave like UNBOUNDED FOLLOWING). Guards against signed-integer overflow
+-- in the "frameOffset + 1" subexpression (undefined behavior). The cnt values
+-- must match the UNBOUNDED FOLLOWING result for the same data.
+SELECT id, val, COUNT(*) OVER w as cnt
+FROM rpr_frame
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND 9223372036854775806 FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+)
+ DEFINE A AS val > 0
+)
+ORDER BY id;
+ id | val | cnt
+----+-----+-----
+ 1 | 10 | 6
+ 2 | 10 | 5
+ 3 | 10 | 4
+ 4 | 20 | 3
+ 5 | 20 | 2
+ 6 | 30 | 1
+(6 rows)
+
-- RANGE frame with RPR (not permitted)
SELECT id, val, COUNT(*) OVER w as cnt
FROM rpr_frame
diff --git a/src/test/regress/sql/rpr_base.sql b/src/test/regress/sql/rpr_base.sql
index 53bf090b903..c6fcfa3e9ff 100644
--- a/src/test/regress/sql/rpr_base.sql
+++ b/src/test/regress/sql/rpr_base.sql
@@ -515,6 +515,22 @@ WINDOW w AS (
)
ORDER BY id;
+-- int64 frame-end overflow: a huge FOLLOWING offset must clamp to the
+-- partition end (matchStartRow + offset + 1 overflows int64; the clamp makes
+-- it behave like UNBOUNDED FOLLOWING). Guards against signed-integer overflow
+-- in the "frameOffset + 1" subexpression (undefined behavior). The cnt values
+-- must match the UNBOUNDED FOLLOWING result for the same data.
+SELECT id, val, COUNT(*) OVER w as cnt
+FROM rpr_frame
+WINDOW w AS (
+ ORDER BY id
+ ROWS BETWEEN CURRENT ROW AND 9223372036854775806 FOLLOWING
+ AFTER MATCH SKIP TO NEXT ROW
+ PATTERN (A+)
+ DEFINE A AS val > 0
+)
+ORDER BY id;
+
-- RANGE frame with RPR (not permitted)
SELECT id, val, COUNT(*) OVER w as cnt
FROM rpr_frame
--
2.50.1 (Apple Git-155)