v1-0001-Reject-oversized-MCV-lists-in-extended-statistics.patch
application/octet-stream
Filename: v1-0001-Reject-oversized-MCV-lists-in-extended-statistics.patch
Type: application/octet-stream
Part: 0
Patch
Format: format-patch
Series: patch v1-0001
Subject: Reject oversized MCV lists in extended statistics import
| File | + | − |
|---|---|---|
| src/backend/statistics/extended_stats_funcs.c | 17 | 0 |
| src/test/regress/expected/stats_import.out | 20 | 0 |
| src/test/regress/sql/stats_import.sql | 15 | 0 |
From 3cdef8541488d46027cc92f6f688f3bddc73e722 Mon Sep 17 00:00:00 2001
From: Ewan Young <kdbase.hack@gmail.com>
Date: Tue, 16 Jun 2026 18:16:26 +0800
Subject: [PATCH v1] Reject oversized MCV lists in extended statistics import
import_mcv(), reached from pg_restore_extended_stats(), stored a
most-common-values list of any length, but the planner and
statext_mcv_deserialize() reject a list with more than
STATS_MCVLIST_MAX_ITEMS items. An oversized list could therefore be
imported successfully and then make every attempt to read the statistics
object back fail with "invalid length (%u) item array in MCVList",
leaving the affected columns unusable for planning until the bad
statistics are removed.
Reject such a list at import time with a WARNING, matching how the other
most_common_vals array checks report invalid input, so the oversized
list is never stored.
---
src/backend/statistics/extended_stats_funcs.c | 17 ++++++++++++++++
src/test/regress/expected/stats_import.out | 20 +++++++++++++++++++
src/test/regress/sql/stats_import.sql | 15 ++++++++++++++
3 files changed, 52 insertions(+)
diff --git a/src/backend/statistics/extended_stats_funcs.c b/src/backend/statistics/extended_stats_funcs.c
index 4a65a46df41..db88a0adeb7 100644
--- a/src/backend/statistics/extended_stats_funcs.c
+++ b/src/backend/statistics/extended_stats_funcs.c
@@ -851,6 +851,23 @@ import_mcv(const ArrayType *mcv_arr, const ArrayType *freqs_arr,
* the reference array for determining their length.
*/
nitems = ARR_DIMS(mcv_arr)[0];
+
+ /*
+ * Reject a list larger than the maximum the planner and the MCV
+ * deserialization code accept (STATS_MCVLIST_MAX_ITEMS). Without this
+ * check an oversized list would be stored successfully but then make every
+ * attempt to read it back fail in statext_mcv_deserialize().
+ */
+ if (nitems > STATS_MCVLIST_MAX_ITEMS)
+ {
+ ereport(WARNING,
+ errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+ errmsg("could not parse array \"%s\": number of items (%d) exceeds maximum (%d)",
+ extarginfo[MOST_COMMON_VALS_ARG].argname,
+ nitems, STATS_MCVLIST_MAX_ITEMS));
+ goto mcv_error;
+ }
+
if (!check_mcvlist_array(freqs_arr, MOST_COMMON_FREQS_ARG, 1, nitems) ||
!check_mcvlist_array(base_freqs_arr, MOST_COMMON_BASE_FREQS_ARG, 1, nitems))
{
diff --git a/src/test/regress/expected/stats_import.out b/src/test/regress/expected/stats_import.out
index 4520f0b664e..f65ca0fc59f 100644
--- a/src/test/regress/expected/stats_import.out
+++ b/src/test/regress/expected/stats_import.out
@@ -2231,6 +2231,26 @@ WARNING: could not parse array "most_common_vals": found 4 attributes but expec
f
(1 row)
+-- warn: more MCV items than the planner/deserialization code can read back
+-- (must be rejected, not stored)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcv',
+ 'inherited', false,
+ 'most_common_vals', (SELECT array_agg(ARRAY[g::text, g::text])
+ FROM generate_series(1, 10001) g),
+ 'most_common_freqs', (SELECT array_agg((1.0 / 10001)::double precision)
+ FROM generate_series(1, 10001) g),
+ 'most_common_base_freqs', (SELECT array_agg((1.0 / 10001)::double precision)
+ FROM generate_series(1, 10001) g));
+WARNING: could not parse array "most_common_vals": number of items (10001) exceeds maximum (10000)
+ pg_restore_extended_stats
+---------------------------
+ f
+(1 row)
+
-- ok: mcv
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
diff --git a/src/test/regress/sql/stats_import.sql b/src/test/regress/sql/stats_import.sql
index 6064b7722da..a3a2795efc6 100644
--- a/src/test/regress/sql/stats_import.sql
+++ b/src/test/regress/sql/stats_import.sql
@@ -1612,6 +1612,21 @@ SELECT pg_catalog.pg_restore_extended_stats(
'most_common_freqs', '{0.25,0.25,0.25,0.25}'::double precision[],
'most_common_base_freqs', '{0.00390625,0.015625,0.00390625,0.015625}'::double precision[]);
+-- warn: more MCV items than the planner/deserialization code can read back
+-- (must be rejected, not stored)
+SELECT pg_catalog.pg_restore_extended_stats(
+ 'schemaname', 'stats_import',
+ 'relname', 'test',
+ 'statistics_schemaname', 'stats_import',
+ 'statistics_name', 'test_stat_mcv',
+ 'inherited', false,
+ 'most_common_vals', (SELECT array_agg(ARRAY[g::text, g::text])
+ FROM generate_series(1, 10001) g),
+ 'most_common_freqs', (SELECT array_agg((1.0 / 10001)::double precision)
+ FROM generate_series(1, 10001) g),
+ 'most_common_base_freqs', (SELECT array_agg((1.0 / 10001)::double precision)
+ FROM generate_series(1, 10001) g));
+
-- ok: mcv
SELECT pg_catalog.pg_restore_extended_stats(
'schemaname', 'stats_import',
--
2.47.3