Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1waCk5-001bWT-21 for pgsql-bugs@arkaria.postgresql.org; Thu, 18 Jun 2026 13:25:13 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1waCk4-00BUpP-1q for pgsql-bugs@arkaria.postgresql.org; Thu, 18 Jun 2026 13:25:12 +0000 Received: from magus.postgresql.org ([2a02:c0:301:0:ffff::29]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wa7af-00AQAz-2h for pgsql-bugs@lists.postgresql.org; Thu, 18 Jun 2026 07:55:09 +0000 Received: from mahout.postgresql.org ([2001:4800:3e1:1::227]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wa7ab-000000013eH-455j for pgsql-bugs@lists.postgresql.org; Thu, 18 Jun 2026 07:55:09 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=postgresql.org; s=20171124; h=Message-ID:Date:Reply-To:Cc:From:To:Subject: Content-Transfer-Encoding:MIME-Version:Content-Type:Sender:Content-ID: Content-Description:In-Reply-To:References; bh=hVdEyVMTvwMlkJDgZWp+Mf4fXqw5asDACXTKJrepCRk=; b=JpKRFEFRtO80K6kESMkqRWHxyS NDeI+WTQfecDXZmUHtWhbrLi8RyvoNpETbzOMrbEa0qz28EF3snMEt2N74vpe2Sp899YhOivY3d8a Egxwvh73MSjnTQGcD11a1RqOTAqB3rE4it5jyJzeEmHrPO9GQa8NoZCfuh9+Tua8MwOrwEtGjbcv5 c/0JkCbZuHlvqhbgplXJWCWzrdydFXOV2QEMi4/q2ZzYdrLPG2SSfcUkAXInk3WHfvQ9/RO4St0cb Us6w4e/xVYIJdGrlnGEYeivqz4Xw4Z+27aQruOINWYJ3kDwJure23kA/I3BjUIZ54TAgUPm2HI+eF 31X3w3ug==; Received: from wrigleys.postgresql.org ([2a02:16a8:dc51::60]) by mahout.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wa7aZ-002rBd-39 for pgsql-bugs@lists.postgresql.org; Thu, 18 Jun 2026 07:55:04 +0000 Received: from localhost ([127.0.0.1] helo=wrigleys.postgresql.org) by wrigleys.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wa7aZ-006UUW-19 for pgsql-bugs@lists.postgresql.org; Thu, 18 Jun 2026 07:55:03 +0000 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: BUG #19525: In `contrib/dict_int`, handling a token whose first byte is a null byte causes `pnstrdup()` . To: pgsql-bugs@lists.postgresql.org From: PG Bug reporting form Cc: 3020001251@tju.edu.cn Reply-To: 3020001251@tju.edu.cn, pgsql-bugs@lists.postgresql.org Date: Thu, 18 Jun 2026 07:54:52 +0000 Message-ID: <19525-b0be8e4eb7dbaf07@postgresql.org> X-Auto-Response-Suppress: All Auto-Submitted: auto-generated List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk The following bug has been logged on the website: Bug reference: 19525 Logged by: Yuelin Wang Email address: 3020001251@tju.edu.cn PostgreSQL version: 19beta1 Operating system: Linux (Ubuntu 24.04, x86_64) Description: =20 **Component**: `contrib/dict_int/dict_int.c`, function `dintdict_lexize()` (line 109) Requires a `SQL_ASCII`-encoded database (to bypass null-byte encoding checks) and superuser to install the extension and create a helper function that passes a `bytea` token directly to the lexize callback. Once the dictionary is created, any role granted `EXECUTE` on the helper can trigger the crash. ```sql -- 1. Create SQL_ASCII database (null bytes are not rejected) CREATE DATABASE vuln_ascii ENCODING 'SQL_ASCII' TEMPLATE template0; \c vuln_ascii -- 2. Install extension and create an intdict dictionary with REJECTLONG=3Dfalse CREATE EXTENSION dict_int; CREATE TEXT SEARCH DICTIONARY intdict_test ( TEMPLATE =3D intdict_template, MAXLEN =3D 8192, REJECTLONG =3D false ); -- 3. Create a C helper (raw_lexize.so) that invokes the lexize callback with -- a raw bytea token, bypassing the text encoding layer. CREATE FUNCTION raw_lexize(dict regdictionary, token bytea) RETURNS text[] AS 'raw_lexize', 'raw_lexize' LANGUAGE C STRICT; -- 4. Trigger: null byte at position 0 causes pnstrdup to allocate 1 byte, -- but txt[8192] =3D '\0' writes 8191 bytes past the end of the allocati= on. SELECT raw_lexize('intdict_test', decode('00' || repeat('78', 10000), 'hex')); -- Server closes connection; ASan reports heap-buffer-overflow WRITE of size 1 -- at dict_int.c:109 in dintdict_lexize. ``` ASan confirmation (server killed the backend; connection dropped): ``` =3D=3DERROR: AddressSanitizer: heap-buffer-overflow on address 0x5250000528= 80 WRITE of size 1 at 0x525000052880 thread T0 #0 in dintdict_lexize /data/ylwang/Projects/postgres/contrib/dict_int/dict_int.c:109 #1 in FunctionCall4Coll .../src/backend/utils/fmgr/fmgr.c:1215 #2 in raw_lexize /tmp/raw_lexize.c:37 SUMMARY: AddressSanitizer: heap-buffer-overflow .../contrib/dict_int/dict_int.c:109 in dintdict_lexize ``` `pnstrdup(ptr, len)` uses `strnlen(ptr, len)` internally, so when the token begins with a null byte it allocates only 1 byte. The variable `len` is not updated to reflect this and retains the original token length, so the guard at line 98 (`if (len > d->maxlen)`) passes, and line 109 writes `'\0'` at offset `d->maxlen` (e.g., 8192) into a 1-byte allocation. The fix is to recompute the effective length from the allocated buffer after the `pnstrdup` call, for example by replacing the `if (len > d->maxlen)` check with `if (strlen(txt) > d->maxlen)`. This ensures the truncation offset is always within the bounds of what `pnstrdup` actually allocated.