From 22e3bd831552aa648f85176918d3a0e1de802d20 Mon Sep 17 00:00:00 2001 From: Ewan Young Date: Mon, 7 Sep 2026 23:22:24 +0800 Subject: [PATCH v5] Fix parallel GIN index build with keys larger than 65535 bytes During a parallel GIN build, each key is serialized into a GinTuple, a transient representation used only while sorting. _gin_build_tuple() lays out the whole tuple -- the palloc size, the key memcpy, and the offset of the posting list -- from a local variable holding the real key length, but then stored that length in GinTuple.keylen, which was uint16. For a key longer than 65535 bytes the stored length was thus silently truncated. On read-back, GinTupleGetFirst() and _gin_parse_tuple_items() recompute the posting-list offset from the truncated keylen and land inside the key data, so ginPostingListDecodeAllSegments() decodes garbage: an assertion failure with assertions enabled, and a read past the end of the allocation without. Only parallel builds are affected, because only they materialize a GinTuple; a serial build of the same data succeeds, as index_form_tuple() compresses large keys before the GinMaxItemSize check. Widen GinTuple.keylen to Size, which is what VARSIZE_ANY() returns and what GinBuffer.keylen already uses, and use Size for the local in _gin_build_tuple() too, which was an int that truncated VARSIZE_ANY() the same way. Widening keylen moves GinTuple.data. The key value is accessed in place in data, so data must be MAXALIGN'ed; before, that was only true by accident of the field layout. Make that explicit with alignas(MAXIMUM_ALIGNOF) on data, so that the layout stays correct on 32-bit platforms, where Size is 4 bytes and would otherwise leave data under-aligned when MAXIMUM_ALIGNOF is 8. Bug: #19545 Reported-by: Yuelin Wang <1217816127@qq.com> Discussion: https://postgr.es/m/19545-0f25b7e47351e8fc@postgresql.org --- src/backend/access/gin/gininsert.c | 2 +- src/include/access/gin_tuple.h | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/backend/access/gin/gininsert.c b/src/backend/access/gin/gininsert.c index 37f689a2cac..aaef7020981 100644 --- a/src/backend/access/gin/gininsert.c +++ b/src/backend/access/gin/gininsert.c @@ -2250,7 +2250,7 @@ _gin_build_tuple(OffsetNumber attrnum, unsigned char category, char *ptr; Size tuplen; - int keylen; + Size keylen; dlist_mutable_iter iter; dlist_head segments; diff --git a/src/include/access/gin_tuple.h b/src/include/access/gin_tuple.h index 7bde05e2de4..d2c7b50d1de 100644 --- a/src/include/access/gin_tuple.h +++ b/src/include/access/gin_tuple.h @@ -23,12 +23,17 @@ typedef struct GinTuple { 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? */ int nitems; /* number of TIDs in the data */ - char data[FLEXIBLE_ARRAY_MEMBER]; + + /* + * The key value is accessed in place, so it must be aligned well enough + * for any key type. + */ + char alignas(MAXIMUM_ALIGNOF) data[FLEXIBLE_ARRAY_MEMBER]; } GinTuple; static inline ItemPointer -- 2.47.3