From de742fc3aad9a675876489755ea6e203a256fd69 Mon Sep 17 00:00:00 2001 From: Ryan Malloy Date: Wed, 18 Feb 2026 11:48:49 -0700 Subject: [PATCH] Fix pg_tle sizeof/INTERNALLENGTH mismatch, exact leaf recheck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pg_tle struct has been 104 bytes since v0.1.0, but INTERNALLENGTH is 112. The size comment claimed "11 doubles (88 bytes)" — there are 10 (80 bytes). Every palloc(sizeof(pg_tle)) across the codebase allocated 104 bytes while PostgreSQL's datumCopy/heap_form_tuple copied 112, causing an 8-byte overread. Fix: add _reserved[8] to pg_tle, making sizeof(pg_tle) == 112. This is backward compatible — existing on-disk tuples already have 112 bytes allocated (from typlen), with zeros in the trailing 8. Also in gist_tle.c: - Remove TLE_TYPLEN band-aid, use sizeof(pg_tle) everywhere - Set recheck = false for leaf entries in consistent: the orbital key is computed identically to the SQL operator, so the GiST leaf check is exact (eliminates unnecessary heap fetches) --- src/gist_tle.c | 38 +++++++++++++++++++------------------- src/types.h | 7 +++++-- 2 files changed, 24 insertions(+), 21 deletions(-) diff --git a/src/gist_tle.c b/src/gist_tle.c index a3d1437..ef4f603 100644 --- a/src/gist_tle.c +++ b/src/gist_tle.c @@ -44,13 +44,7 @@ PG_FUNCTION_INFO_V1(gist_tle_distance); /* Floating-point comparison tolerance (km and radians) */ #define KEY_EPSILON 1.0e-9 -/* - * The SQL type's INTERNALLENGTH. sizeof(pg_tle) is 104 due to struct - * packing, but the SQL definition declares 112. All allocations that - * become index datums must use TLE_TYPLEN so that PostgreSQL's - * index_form_tuple() never reads past the allocation. - */ -#define TLE_TYPLEN 112 +/* sizeof(pg_tle) == 112, matching INTERNALLENGTH in CREATE TYPE. */ /* * 2-D orbital key extracted from a TLE's mean elements. @@ -240,10 +234,9 @@ tle_alt_distance(PG_FUNCTION_ARGS) * Leaf entries carry the full pg_tle; we compress to tle_orbital_key. * Internal entries are already tle_orbital_key from union operations. * - * The allocation must be TLE_TYPLEN bytes (matching INTERNALLENGTH), - * not sizeof(tle_orbital_key) or sizeof(pg_tle). GiST's - * index_form_tuple() copies typlen bytes from the datum pointer; - * under-allocating causes a heap buffer overread. + * The allocation must be sizeof(pg_tle) bytes — which matches + * INTERNALLENGTH — not sizeof(tle_orbital_key). GiST's + * index_form_tuple() copies typlen bytes from the datum pointer. */ Datum gist_tle_compress(PG_FUNCTION_ARGS) @@ -254,7 +247,7 @@ gist_tle_compress(PG_FUNCTION_ARGS) if (entry->leafkey) { pg_tle *tle = (pg_tle *) DatumGetPointer(entry->key); - tle_orbital_key *key = (tle_orbital_key *) palloc0(TLE_TYPLEN); + tle_orbital_key *key = (tle_orbital_key *) palloc0(sizeof(pg_tle)); tle_to_orbital_key(tle, key); @@ -286,8 +279,10 @@ gist_tle_decompress(PG_FUNCTION_ARGS) * gist_tle_consistent -- can this subtree contain matches for the query? * * Checks overlap in both altitude AND inclination dimensions. - * Always sets recheck = true because 2-D overlap is only a necessary - * condition -- the real conjunction test requires propagation. + * + * For leaf entries, recheck = false: the orbital key is computed + * identically to the SQL operator, so the GiST check is exact. + * For internal nodes, recheck is irrelevant (GiST ignores it). */ Datum gist_tle_consistent(PG_FUNCTION_ARGS) @@ -303,7 +298,12 @@ gist_tle_consistent(PG_FUNCTION_ARGS) tle_to_orbital_key(query, &query_key); - *recheck = true; + /* + * Leaf keys are exact (same tle_to_orbital_key as the operator), + * so no recheck needed. For internal nodes PostgreSQL ignores + * the flag, but we set true by convention. + */ + *recheck = !GIST_LEAF(entry); switch (strategy) { @@ -353,7 +353,7 @@ gist_tle_union(PG_FUNCTION_ARGS) tle_orbital_key *result; tle_orbital_key *cur; - result = (tle_orbital_key *) palloc0(TLE_TYPLEN); + result = (tle_orbital_key *) palloc0(sizeof(pg_tle)); cur = (tle_orbital_key *) DatumGetPointer(entryvec->vector[0].key); *result = *cur; @@ -363,7 +363,7 @@ gist_tle_union(PG_FUNCTION_ARGS) key_merge(result, cur); } - *sizep = TLE_TYPLEN; + *sizep = sizeof(pg_tle); PG_RETURN_POINTER(result); } @@ -508,8 +508,8 @@ gist_tle_picksplit(PG_FUNCTION_ARGS) splitvec->spl_nright = 0; /* Compute union keys and assign entries */ - left_union = (tle_orbital_key *) palloc0(TLE_TYPLEN); - right_union = (tle_orbital_key *) palloc0(TLE_TYPLEN); + left_union = (tle_orbital_key *) palloc0(sizeof(pg_tle)); + right_union = (tle_orbital_key *) palloc0(sizeof(pg_tle)); /* Seed the unions from the first entry in each half */ cur = (tle_orbital_key *) DatumGetPointer( diff --git a/src/types.h b/src/types.h index 15b9d20..df9cd5e 100644 --- a/src/types.h +++ b/src/types.h @@ -75,10 +75,13 @@ typedef struct pg_tle char classification; /* U = unclassified */ char ephemeris_type; /* 0 = SGP4/SDP4 default */ char intl_desig[9]; /* international designator, null-terminated */ - char _pad; /* alignment */ + char _pad; /* alignment to int32 boundary */ + char _reserved[8]; /* match INTERNALLENGTH = 112 */ } pg_tle; -/* Size: 11 doubles (88 bytes) + 3 int32 (12 bytes) + 12 chars = 112 bytes */ +/* Size: 10 doubles (80) + 3 int32 (12) + 12 chars + 8 reserved = 112 bytes + * Must match INTERNALLENGTH in CREATE TYPE. PostgreSQL's datumCopy() and + * heap_form_tuple() copy exactly typlen bytes from any pg_tle pointer. */ /*