From 60c2405ea935ed58b1ed1f2031400a374333748a Mon Sep 17 00:00:00 2001 From: Zsolt Parragi Date: Sat, 5 Sep 2026 21:42:26 +0000 Subject: [PATCH 2/2] test_bitmapset: Decode arguments with readBitmapset() The SQL wrappers of this module decoded their text arguments with the generic stringToNode(), casting whatever came out to a Bitmapset. stringToNode() assumes its input to be valid: the read routines of readfuncs.c fetch tokens with pg_strtok() and feed them to atoi() without checking for NULL, so any other node type name, like '{QUERY}', crashes the backend on the first missing field. Strings for other node types that do read fine, like '42' or '(i 1 2)', were then misinterpreted as a Bitmapset. The functions are executable by any user, so this was reachable without any privileges. To fix, decode with readBitmapset(), the reader used where a Bitmapset is known to be expected, which checks every token it reads and rejects anything else. "<>", the representation of an empty set written by nodeToString(), is still accepted. Oversight in commit 00c3d87a5ca. --- .../expected/test_bitmapset.out | 29 ++++++++++++++++++ .../test_bitmapset/sql/test_bitmapset.sql | 13 ++++++++ .../modules/test_bitmapset/test_bitmapset.c | 30 +++++++++++++++++-- 3 files changed, 69 insertions(+), 3 deletions(-) diff --git a/src/test/modules/test_bitmapset/expected/test_bitmapset.out b/src/test/modules/test_bitmapset/expected/test_bitmapset.out index f8f5cf5e7f0..ab98cbd448b 100644 --- a/src/test/modules/test_bitmapset/expected/test_bitmapset.out +++ b/src/test/modules/test_bitmapset/expected/test_bitmapset.out @@ -1656,4 +1656,33 @@ SELECT test_random_offset_operations(NULL, 1000, 0, 1024) AS result; 1000 (1 row) +-- malformed inputs, rejected before reaching the read routines of other +-- node types +SELECT test_bms_num_members('{QUERY}'); -- error +ERROR: unrecognized token: "{" +SELECT test_bms_copy('{VAR}'); -- error +ERROR: unrecognized token: "{" +SELECT test_bms_num_members('42'); -- error +ERROR: unrecognized token: "42" +SELECT test_bms_num_members('(i 1 2)'); -- error +ERROR: unrecognized token: "i" +SELECT test_bms_num_members('(b 1'); -- error +ERROR: unterminated Bitmapset structure +SELECT test_bms_num_members('(b x)'); -- error +ERROR: unrecognized integer: "x" +SELECT test_bms_num_members(''); -- error +ERROR: incomplete Bitmapset structure +-- empty set, as written by nodeToString() +SELECT test_bms_num_members('<>') AS result; + result +-------- + 0 +(1 row) + +SELECT test_bms_copy('<>') AS result; + result +-------- + <> +(1 row) + DROP EXTENSION test_bitmapset; diff --git a/src/test/modules/test_bitmapset/sql/test_bitmapset.sql b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql index d44cda114a4..4f3cb7bfc6d 100644 --- a/src/test/modules/test_bitmapset/sql/test_bitmapset.sql +++ b/src/test/modules/test_bitmapset/sql/test_bitmapset.sql @@ -426,4 +426,17 @@ SELECT test_random_operations(NULL, 10000, 0, 81920) > 0 AS result; -- perform some random tests on bms_offset_members() SELECT test_random_offset_operations(NULL, 1000, 0, 1024) AS result; +-- malformed inputs, rejected before reaching the read routines of other +-- node types +SELECT test_bms_num_members('{QUERY}'); -- error +SELECT test_bms_copy('{VAR}'); -- error +SELECT test_bms_num_members('42'); -- error +SELECT test_bms_num_members('(i 1 2)'); -- error +SELECT test_bms_num_members('(b 1'); -- error +SELECT test_bms_num_members('(b x)'); -- error +SELECT test_bms_num_members(''); -- error +-- empty set, as written by nodeToString() +SELECT test_bms_num_members('<>') AS result; +SELECT test_bms_copy('<>') AS result; + DROP EXTENSION test_bitmapset; diff --git a/src/test/modules/test_bitmapset/test_bitmapset.c b/src/test/modules/test_bitmapset/test_bitmapset.c index af8c664949e..9123c5efd7a 100644 --- a/src/test/modules/test_bitmapset/test_bitmapset.c +++ b/src/test/modules/test_bitmapset/test_bitmapset.c @@ -24,6 +24,7 @@ #include "nodes/bitmapset.h" #include "nodes/nodes.h" #include "nodes/pg_list.h" +#include "nodes/readfuncs.h" #include "utils/array.h" #include "utils/builtins.h" #include "utils/timestamp.h" @@ -86,16 +87,39 @@ PG_FUNCTION_INFO_V1(test_random_offset_operations); #expr, __FILE__, __LINE__); \ } while (0) -/* Encode/Decode to/from TEXT and Bitmapset */ +/* Encode a Bitmapset into its serialized representation */ #define BITMAPSET_TO_TEXT(bms) cstring_to_text(nodeToString(bms)) -#define TEXT_TO_BITMAPSET(str) ((Bitmapset *) stringToNode(text_to_cstring(str))) + +/* + * Decode a Bitmapset from its serialized representation, either "<>" for an + * empty set or "(b member ...)". + * + * This goes through readBitmapset(), the reader for contexts where a + * Bitmapset is known to be expected, rather than through the generic + * stringToNode(). The latter assumes its input to be valid, and the read + * routines of readfuncs.c it dispatches to for other node types do not check + * for missing tokens, so a string naming any of those could crash the + * backend. readBitmapset() checks everything it reads. + */ +static Bitmapset * +text_to_bitmapset(text *txt) +{ + char *str = text_to_cstring(txt); + ReadNodeContext ctx = {.str = str}; + + /* nodeToString() writes an empty set as "<>" */ + if (strcmp(str, "<>") == 0) + return NULL; + + return readBitmapset(&ctx); +} /* * Helper macro to fetch text parameters as Bitmapsets. SQL-NULL means empty * set. */ #define PG_ARG_GETBITMAPSET(n) \ - (PG_ARGISNULL(n) ? NULL : TEXT_TO_BITMAPSET(PG_GETARG_TEXT_PP(n))) + (PG_ARGISNULL(n) ? NULL : text_to_bitmapset(PG_GETARG_TEXT_PP(n))) /* * Helper macro to handle converting sets back to text, returning the -- 2.55.0