0001-Fix-out-of-bounds-access-on-embedded-null-tokens-in-.patch

application/x-patch

Filename: 0001-Fix-out-of-bounds-access-on-embedded-null-tokens-in-.patch
Type: application/x-patch
Part: 0
Message: Re: BUG #19525: In `contrib/dict_int`, handling a token whose first byte is a null byte causes `pnstrdup()` .

Patch

Format: format-patch
Series: patch 0001
Subject: Fix out-of-bounds access on embedded null tokens in dict_int and dict_xsyn
File+
contrib/dict_int/dict_int.c 6 0
contrib/dict_xsyn/dict_xsyn.c 7 8
From 382c736b599292470b050e112b66945c46a5a7e7 Mon Sep 17 00:00:00 2001
From: Ayush Tiwari <ayushtiwari.slg01@gmail.com>
Date: Thu, 18 Jun 2026 19:54:54 +0530
Subject: [PATCH] Fix out-of-bounds access on embedded null tokens in dict_int
 and dict_xsyn

pnstrdup() stops at the first null byte, so a token beginning with a null
is copied as a 1-byte string.  Both dictionaries then reused the original
token length against that shorter copy.

dintdict_lexize() (dict_int) used it for the "len > maxlen" check and then
wrote txt[maxlen] = '\0' past the end of the 1-byte allocation; recompute
len from the copy.  dxsyn_lexize() (dict_xsyn) passed the original length
to str_tolower(), which then read past the copy; pass the token directly
instead, dropping the needless copy.

Reaching either path needs a token with a null byte, which the normal
text-search input path does not produce (it requires SQL_ASCII and a
caller passing raw bytes to the lexize method).

Reported-by: Yuelin Wang <3020001251@tju.edu.cn>
Bug: #19525
---
 contrib/dict_int/dict_int.c   |  6 ++++++
 contrib/dict_xsyn/dict_xsyn.c | 15 +++++++--------
 2 files changed, 13 insertions(+), 8 deletions(-)

diff --git a/contrib/dict_int/dict_int.c b/contrib/dict_int/dict_int.c
index aafce894977..b4413cfde99 100644
--- a/contrib/dict_int/dict_int.c
+++ b/contrib/dict_int/dict_int.c
@@ -95,6 +95,12 @@ dintdict_lexize(PG_FUNCTION_ARGS)
 	else
 		txt = pnstrdup(in, len);
 
+	/*
+	 * pnstrdup() stops at the first null byte, so recompute len from the copy;
+	 * otherwise the truncation below could write past the allocation.
+	 */
+	len = strlen(txt);
+
 	if (len > d->maxlen)
 	{
 		if (d->rejectlong)
diff --git a/contrib/dict_xsyn/dict_xsyn.c b/contrib/dict_xsyn/dict_xsyn.c
index 9e3784e0f47..b375e8f7387 100644
--- a/contrib/dict_xsyn/dict_xsyn.c
+++ b/contrib/dict_xsyn/dict_xsyn.c
@@ -211,14 +211,13 @@ dxsyn_lexize(PG_FUNCTION_ARGS)
 	if (!length || d->len == 0)
 		PG_RETURN_POINTER(NULL);
 
-	/* Create search pattern */
-	{
-		char	   *temp = pnstrdup(in, length);
-
-		word.key = str_tolower(temp, length, DEFAULT_COLLATION_OID);
-		pfree(temp);
-		word.value = NULL;
-	}
+	/*
+	 * Pass the token directly to str_tolower(); copying it first with
+	 * pnstrdup() and reusing the original length would read past the copy if
+	 * the token contains a null byte.
+	 */
+	word.key = str_tolower(in, length, DEFAULT_COLLATION_OID);
+	word.value = NULL;
 
 	/* Look for matching syn */
 	found = (Syn *) bsearch(&word, d->syn, d->len, sizeof(Syn), compare_syn);
-- 
2.34.1