From 3cdef8541488d46027cc92f6f688f3bddc73e722 Mon Sep 17 00:00:00 2001 From: Ewan Young 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