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 1whkBs-000OX6-2x for pgsql-bugs@arkaria.postgresql.org; Thu, 09 Jul 2026 08:33:05 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1whkBr-00Bxus-1A for pgsql-bugs@arkaria.postgresql.org; Thu, 09 Jul 2026 08:33:04 +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 1whkBr-00Bxuj-05 for pgsql-bugs@lists.postgresql.org; Thu, 09 Jul 2026 08:33:03 +0000 Received: from mail-lf1-x131.google.com ([2a00:1450:4864:20::131]) by magus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1whkBo-00000000R9y-48O7 for pgsql-bugs@lists.postgresql.org; Thu, 09 Jul 2026 08:33:03 +0000 Received: by mail-lf1-x131.google.com with SMTP id 2adb3069b0e04-5b0148201fbso759653e87.3 for ; Thu, 09 Jul 2026 01:33:01 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1783585978; cv=none; d=google.com; s=arc-20260327; b=BghGp7ur5JxFxNQoEVTwsi1XOOVJ02C8fzLbMhJDX/xjfBTLnHQ9jnC+eVBkutMJq8 lpfGMAMGRrWV4ldDFzo80gci7la9n8u2/3BOWEk3ABBQUbTbM4UEpWLPcUnUReJ919FO 9s47O4TTF9brOS4+X55+7V/LcQ6jNRj+Ee1gLSMgwSw4wFt0x0+5Ei0Se6EyGDe3b2cx /xAkbfu7tUZ0Ux4yikcxXfPJXbkkF2A3K0Pz+AJXbrXj4KLfy+ChUACxA7WY+5E1S/xo gu914hv/Zhp9IRB6F86D4V2EbLX5N2kspYRVo5oaYxsJHOSmjzhv9NWzdc6K0UOe6WyS 0c+Q== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20260327; h=content-transfer-encoding:cc:to:subject:message-id:date:from :in-reply-to:references:mime-version:dkim-signature; bh=CCVeAaTup443sOKvHgEqwGUoLiBRbWm2uU4D+mjZXLo=; fh=mAvH+XChw1TKFAFUqMw/cYrxwiCXRh9ndtI7XpAP+Bc=; b=dYFJbhVbGSrVp4Vo146Xw1uLQFNaFy9e1w5EfnhWDvqW8xKlw+akeOIiuWsF5Ru1wJ qji4z0Eq3RCQ/N1MbRnfs3NGUKkIcjeuM7kH/ksn3nDB3PhzqKoO0qhoe7uz1PiAstn4 jRqHnAZJUHrdZfZFoFexb22bLYcKQefJK46vou+WFTQvukvxLA1tSML6ofEKd+1lHaSg ZaSMjo5/roKz1O82Dse/rHjCOPYaWS36EhpAC3KNI1q+g7RujG6ofMwFJTfUv5Myjct4 I+M2+unNQByMilBt3Gp9IWGz0XuRuipj2E8bSiA1fHQmlJj2E8TT3RrWZTb+FfF4ZoTm O5Tw==; darn=lists.postgresql.org ARC-Authentication-Results: i=1; mx.google.com; arc=none DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1783585978; x=1784190778; darn=lists.postgresql.org; h=content-transfer-encoding:content-type:cc:to:subject:message-id :date:from:in-reply-to:references:mime-version:from:to:cc:subject :date:message-id:reply-to:content-type; bh=CCVeAaTup443sOKvHgEqwGUoLiBRbWm2uU4D+mjZXLo=; b=M5KcDe/L2Fi9gVrvo7oIRcoRZtuwvOuJgHqfupihOUzcRnF71HCxdVZFAH+qms/d8C 9QEaJUn09sFmTRAmBDPhzykkZ2G1gL+iTvSucfch6gj/XmBVJFZODNflUsY7nQfxDUDn AGWZbcW8VS5q5PwF1b+CZF5bBxdhhsBtoqAPAphISWsUzV5pZ5zJKrz0ywIE3GxCKa2m hME2s4PLzgOLOvoH1h0VuIajnMnTxbHkT4MTzJ3rMl7uLCCRoTrK2nuN+oH46d56bJ9w EdRHfxNG5rA9JYqGiakmgLKveaaWHNM2JshJXUejdCEC/xzPwvYmDRg+ZYoVCZ0KID8I ezYw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1783585978; x=1784190778; h=content-transfer-encoding:content-type:cc:to:subject:message-id :date:from:in-reply-to:references:mime-version:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to :content-type; bh=CCVeAaTup443sOKvHgEqwGUoLiBRbWm2uU4D+mjZXLo=; b=cuAGIqrGKJOle1Xd/opLVpAikMqCh+T2u3IeJe4jrFAzX+kzc0LmKr7umPVKIWYg3B H99/psazA6FJHnphVeqnmUTIaKAWVMl0pmDUCwcyuG1PvFPH2l+SizgNNpKzzVVXTfKI jfmfcGjRzwnvAJ7LOHsObiFUYzTzVR4xpN9tn13G3NueG8SCEWzmP9UdZiMkAaVVE8UA EIWyK0Ub+6coALV1c4+mpyQdrys+d/PnvzO4Igs2FNHr0nZcsr5EsPahjuImtTk7KWRc GHPT4sHaHQLtLBhexjtm2QdPZt0CyGMDGvR2JjWZ1zmRAZG0mEhUkBRSocIyytcNj1jM y6tA== X-Forwarded-Encrypted: i=1; AHgh+Rpdz4MRRsXXRg3vUvaiAlPOTB/Cb+v4o0kZpwtTxEksxkoBdCcDWZ6yeTiOsbXSZlyTqHlMzLQ+XJv0@lists.postgresql.org X-Gm-Message-State: AOJu0YzLJJlDOkwrJLBg0p6fo4SMji3o/jcVGM5O+FiNYAj78WEgI93u bRR6cN+OB5rs28+m9uBIXzxvD16U2NFFNo7V3KGm0KrAb37LDLjKa0G04/pL0DSwRL024+ckgZ0 Dm+JGTCZbqA5OYkiwjUNfPeerfrAmKtY= X-Gm-Gg: AfdE7ck3natqX1Ks0+f6TrmPO0SeakgPfuTuk7IyGoeKKOG2OGnRB6rCjL3NlGd/5Fn Nc9W30Ko3HxqhxfdnuiWb81889DZ+xoX5gons6vnkC9tKJjUuyxDsuA8fC8pFljC7hDZewvW4LO nmjGIiOfcBTX+L2NxxjbsWFCjaDoLW/xG1ut3LehFwKR9zsCpY36cT3495VqynddzQHRr5/fyQ0 yzk08hH34GLYxROmcnXdCFsB2oQZF9pUuhQhyXynzeP4PbjT4iFIIf72R4sfVXsZW3I2pMNe++X wIhplI0F9Z1AAxpzJLlURbJpFd8xxFunkcMI3f2qQn9eYNRzpfe8uLIUZfbW1GFZRuHz1fSImmR myNViCOlLlVzUwNRIa9SHNCA= X-Received: by 2002:a05:6512:a351:b0:5ae:ba7a:1790 with SMTP id 2adb3069b0e04-5b011463d96mr1007337e87.11.1783585978302; Thu, 09 Jul 2026 01:32:58 -0700 (PDT) MIME-Version: 1.0 References: <19545-0f25b7e47351e8fc@postgresql.org> <2411429.1783574907@sss.pgh.pa.us> In-Reply-To: <2411429.1783574907@sss.pgh.pa.us> From: Ewan Young Date: Thu, 9 Jul 2026 16:32:45 +0800 X-Gm-Features: AUfX_mzje0g0bAZnLDa7eB-BwV0F2gsBkmzgtdn441shHAmNzKeSopVwHvoKk34 Message-ID: Subject: Re: BUG #19545: Integer truncation of `GinTuple.keylen` causes out-of-bounds read in parallel GIN index build To: Tom Lane Cc: Heikki Linnakangas , Peter Eisentraut , 1217816127@qq.com, pgsql-bugs@lists.postgresql.org Content-Type: text/plain; charset="UTF-8" Content-Transfer-Encoding: quoted-printable List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk Hi Tom, Thanks for taking a look at this, and for pushing back on the field widths. It made me reconsider the whole approach. On Thu, Jul 9, 2026 at 1:28=E2=80=AFPM Tom Lane wrote: > > Ewan Young writes: > > Agreed, that's clearly better. v3 (attached) uses Size for > > GinTuple.keylen (GinBuffer.keylen already was Size), and also for the > > local in _gin_build_tuple(), which was the int that truncated > > VARSIZE_ANY() in the first place. > > Am I reading this correctly that you propose using Size for the > length of the key value (keylen) along with int for the length of the > whole tuple (tuplen)? > > { > int tuplen; /* length of the whole tuple */ > OffsetNumber attrnum; /* attnum of index key */ > - uint16 keylen; /* bytes in data for key value */ > + Size keylen; /* bytes in data for key value */ > int16 typlen; /* typlen for key */ > bool typbyval; /* typbyval for key */ > signed char category; /* category: normal or NULL? */ > > Please explain how that's sane. It isn't -- you're right, and let me back up and lay out the root cause, because I think it also settles which way to fix it. In a parallel build each key is serialized into a transient GinTuple for the tuplesort. _gin_build_tuple() computes the key length into a full-width local, and lays out the whole tuple from it -- the allocation, the key copy, and the offset to the posting list that follows the key. But it then stores that length in GinTuple.keylen, which is uint16, so for a key longer than 65535 bytes the stored length is truncated. On read-back (during the merge in _gin_process_worker_data() / _gin_parallel_merge()), GinTupleGetFirst() and _gin_parse_tuple_items() recompute the posting-list offset as SHORTALIGN(offsetof(data) + keylen) using the *truncated* keylen. That lands ~64kB too early, inside the key bytes, and ginPostingListDecodeAllSegments() then decodes those bytes as a posting list -- tripping the OffsetNumberIsValid() assert with assertions on, or reading past the allocation without. The key point is that this only happens in a parallel build, because only the parallel path materializes a GinTuple; a serial build inserts the key straight into the index via GinFormTuple(). So for a >65535-byte key the two builds already diverge today: serial: indexes it if it compresses, else clean "index row size ... exceeds maximum" parallel: crashes A parallel build is supposed to be equivalent to a serial one -- it's an optimization that should be transparent to the user, not something that changes whether CREATE INDEX succeeds. > > I kind of agree with the upthread comment that we should just reject > key lengths exceeding BLCKSZ or so up-front, rather than fooling I looked into that, and it runs into the equivalence point above, plus two others: * _gin_build_tuple() only runs in the parallel path, so a reject there makes a parallel build refuse keys a serial build indexes fine -- i.e. it replaces the crash with a *different* serial/parallel divergence, where the same CREATE INDEX on the same data succeeds or fails depending on max_parallel_maintenance_workers. * It would reject keys that serial builds accept today. The real limit is enforced by GinFormTuple() on the *compressed* tuple, so a large-but-compressible key is valid and indexes fine: CREATE TABLE t (a text[]); INSERT INTO t SELECT ARRAY[repeat('x',100000)] FROM generate_series(1,50); SET max_parallel_maintenance_workers =3D 0; -- force a serial build CREATE INDEX ON t USING gin (a); -- succeeds, 100000-byte= key Heikki reached the same conclusion earlier in the thread. * The threshold would be arbitrary: at _gin_build_tuple() time the key is necessarily uncompressed (it has to stay uncompressed so the tuplesort comparator can compare key values), so an uncompressed-length cap doesn't correspond to the GinMaxItemSize limit, which only applies after compression. To avoid the divergence we'd have to apply the cap in the serial path too, which changes long-standing behavior (rejecting keys that index fine today). > around with these field widths. This patch widens GinTuple > noticeably, and will do so more if we also widen tuplen. Is that > free? Not free, but small: with keylen as int it's +4 bytes per GinTuple (offsetof(data) 16 -> 20), tuplen stays int so nothing widens beyond that, and there's no per-tuple runtime cost. > > regards, tom lane --=20 Regards, Ewan Young