Fix pg_tle sizeof/INTERNALLENGTH mismatch, exact leaf recheck

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)
This commit is contained in:
Ryan Malloy 2026-02-18 11:48:49 -07:00
parent 347acf0906
commit de742fc3aa
2 changed files with 24 additions and 21 deletions

View File

@ -44,13 +44,7 @@ PG_FUNCTION_INFO_V1(gist_tle_distance);
/* Floating-point comparison tolerance (km and radians) */ /* Floating-point comparison tolerance (km and radians) */
#define KEY_EPSILON 1.0e-9 #define KEY_EPSILON 1.0e-9
/* /* sizeof(pg_tle) == 112, matching INTERNALLENGTH in CREATE TYPE. */
* 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
/* /*
* 2-D orbital key extracted from a TLE's mean elements. * 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. * Leaf entries carry the full pg_tle; we compress to tle_orbital_key.
* Internal entries are already tle_orbital_key from union operations. * Internal entries are already tle_orbital_key from union operations.
* *
* The allocation must be TLE_TYPLEN bytes (matching INTERNALLENGTH), * The allocation must be sizeof(pg_tle) bytes which matches
* not sizeof(tle_orbital_key) or sizeof(pg_tle). GiST's * INTERNALLENGTH not sizeof(tle_orbital_key). GiST's
* index_form_tuple() copies typlen bytes from the datum pointer; * index_form_tuple() copies typlen bytes from the datum pointer.
* under-allocating causes a heap buffer overread.
*/ */
Datum Datum
gist_tle_compress(PG_FUNCTION_ARGS) gist_tle_compress(PG_FUNCTION_ARGS)
@ -254,7 +247,7 @@ gist_tle_compress(PG_FUNCTION_ARGS)
if (entry->leafkey) if (entry->leafkey)
{ {
pg_tle *tle = (pg_tle *) DatumGetPointer(entry->key); 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); 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? * gist_tle_consistent -- can this subtree contain matches for the query?
* *
* Checks overlap in both altitude AND inclination dimensions. * 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 Datum
gist_tle_consistent(PG_FUNCTION_ARGS) gist_tle_consistent(PG_FUNCTION_ARGS)
@ -303,7 +298,12 @@ gist_tle_consistent(PG_FUNCTION_ARGS)
tle_to_orbital_key(query, &query_key); 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) switch (strategy)
{ {
@ -353,7 +353,7 @@ gist_tle_union(PG_FUNCTION_ARGS)
tle_orbital_key *result; tle_orbital_key *result;
tle_orbital_key *cur; 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); cur = (tle_orbital_key *) DatumGetPointer(entryvec->vector[0].key);
*result = *cur; *result = *cur;
@ -363,7 +363,7 @@ gist_tle_union(PG_FUNCTION_ARGS)
key_merge(result, cur); key_merge(result, cur);
} }
*sizep = TLE_TYPLEN; *sizep = sizeof(pg_tle);
PG_RETURN_POINTER(result); PG_RETURN_POINTER(result);
} }
@ -508,8 +508,8 @@ gist_tle_picksplit(PG_FUNCTION_ARGS)
splitvec->spl_nright = 0; splitvec->spl_nright = 0;
/* Compute union keys and assign entries */ /* Compute union keys and assign entries */
left_union = (tle_orbital_key *) palloc0(TLE_TYPLEN); left_union = (tle_orbital_key *) palloc0(sizeof(pg_tle));
right_union = (tle_orbital_key *) palloc0(TLE_TYPLEN); right_union = (tle_orbital_key *) palloc0(sizeof(pg_tle));
/* Seed the unions from the first entry in each half */ /* Seed the unions from the first entry in each half */
cur = (tle_orbital_key *) DatumGetPointer( cur = (tle_orbital_key *) DatumGetPointer(

View File

@ -75,10 +75,13 @@ typedef struct pg_tle
char classification; /* U = unclassified */ char classification; /* U = unclassified */
char ephemeris_type; /* 0 = SGP4/SDP4 default */ char ephemeris_type; /* 0 = SGP4/SDP4 default */
char intl_desig[9]; /* international designator, null-terminated */ 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; } 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. */
/* /*