| From: | Ewan Young <kdbase(dot)hack(at)gmail(dot)com> |
|---|---|
| To: | PostgreSQL Hackers <pgsql-hackers(at)lists(dot)postgresql(dot)org> |
| Cc: | Michael Paquier <michael(at)paquier(dot)xyz>, Corey Huinker <corey(dot)huinker(at)gmail(dot)com> |
| Subject: | Reject ill-formed range bounds histograms in pg_restore_attribute_stats() |
| Date: | 2026-07-07 08:13:30 |
| Message-ID: | CAON2xHNM809WLR_g0ymKgU-tWxtbyH1Xvh4fqzRqy9YP2A59pg@mail.gmail.com |
| Views: | Whole Thread | Raw Message | Download mbox | Resend email |
| Thread: | |
| Lists: | pgsql-hackers |
Hi,
pg_restore_attribute_stats() (and pg_set_attribute_stats()) stores a
STATISTIC_KIND_BOUNDS_HISTOGRAM verbatim from user input, checking only
that the argument is a one-dimensional, NULL-free array of the right
type. It does not verify the invariant that ANALYZE's
compute_range_stats() always establishes: the bounds histogram must
contain no empty ranges, and its lower and upper bounds must each be
sorted in ascending order.
The range and multirange selectivity estimators rely on that invariant.
They split the histogram into separate lower- and upper-bound histograms
and assume each is ordered. So an imported histogram that violates it
makes the planner misbehave when it later reads the statistics:
- a non-monotonic histogram makes calc_length_hist_frac() fail its
Assert(length2 >= length1) (rangetypes_selfuncs.c, and the identical
multirangetypes_selfuncs.c), i.e. a backend crash on an assert-enabled
build, or a nonsensical, possibly negative, selectivity otherwise;
- an empty range trips the "shouldn't happen" elog(ERROR, "bounds
histogram contains an empty range").
Reproducer on HEAD (assert build):
CREATE TABLE t (r int4range);
INSERT INTO t SELECT int4range(g, g+10) FROM generate_series(1,500) g;
ANALYZE t;
SELECT pg_restore_attribute_stats(
'schemaname', 'public', 'relname', 't',
'attname', 'r', 'inherited', false,
'range_bounds_histogram',
'{"[50,60)","[1,2)","[90,100)","[5,6)"}'::text,
'range_length_histogram', '{1,5,10,20}'::text,
'range_empty_frac', 0.0::real); -- accepted, returns true
EXPLAIN SELECT * FROM t WHERE r <@ int4range(1,100);
-- TRAP: failed Assert("length2 >= length1"), rangetypes_selfuncs.c
The same happens for a multirange column (whose bounds histogram is also
an array of ranges). Setting statistics requires table ownership /
MAINTAIN, so this is not a security issue, but it is another way for a
stats-import call to feed the planner data it cannot cope with, and the
functions already validate other properties and reject bad input with a
WARNING rather than crashing.
This is the same class of problem as commit f6e4ec0a705, which rejected
oversized MCV lists in pg_restore_extended_stats().
The attached patch validates the bounds histogram at import time and
rejects an ill-formed one with a WARNING, matching the treatment of the
other inconsistent inputs. Because the histogram element type is a range
for both range- and multirange-typed columns, the single check covers
both.
--
Regards,
Ewan Young
| Attachment | Content-Type | Size |
|---|---|---|
| v1-0001-Reject-ill-formed-range-bounds-histograms-in-pg_rest.patch | application/octet-stream | 8.2 KB |
| From | Date | Subject | |
|---|---|---|---|
| Next Message | Amit Kapila | 2026-07-07 08:24:44 | Re: Proposal: Conflict log history table for Logical Replication |
| Previous Message | Jelte Fennema-Nio | 2026-07-07 08:02:44 | Re: Bump soft open file limit (RLIMIT_NOFILE) to hard limit on startup |