Received: from malur.postgresql.org ([217.196.149.56]) by arkaria.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wf85G-005HPt-1f for pgsql-hackers@arkaria.postgresql.org; Thu, 02 Jul 2026 03:27:26 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wf85F-00GqND-1e for pgsql-hackers@arkaria.postgresql.org; Thu, 02 Jul 2026 03:27:25 +0000 Received: from makus.postgresql.org ([2001:4800:3e1:1::229]) by malur.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wf85F-00GqN4-0H for pgsql-hackers@lists.postgresql.org; Thu, 02 Jul 2026 03:27:25 +0000 Received: from mail-wm1-x32f.google.com ([2a00:1450:4864:20::32f]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wf85D-00000001Apj-0zzu for pgsql-hackers@lists.postgresql.org; Thu, 02 Jul 2026 03:27:24 +0000 Received: by mail-wm1-x32f.google.com with SMTP id 5b1f17b1804b1-493c55d5c7aso2739605e9.1 for ; Wed, 01 Jul 2026 20:27:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782962840; x=1783567640; darn=lists.postgresql.org; h=in-reply-to:content-transfer-encoding:content-disposition :mime-version:references:message-id:subject:cc:to:from:date:from:to :cc:subject:date:message-id:reply-to; bh=pntG8xx55vXGzOHMumWUH5D2/3kymMZER8ofZ3RUkaE=; b=GvJNT6jOILlWXZcdK6dCErIkSRpK/9sG3KodVT4PKEnYElkKRPrCHSgTNtpAOHVwNn GbJswwhcJ0peD+dfiAEFKibn/qIK9sPC9N9kYZDgHqRxCkRcgYTgAU6tNOJJ84w+OHXq 2iBE9GzAbV4gdDhNtCn8fjBm32WiQlgjW/LyfESNTpvsFPFBtLutJjYg597uvuXtKZ8i km4JuRJl2UEqV/UmVcXTLFw2nd8GgNwGbnX31gMUA7CL5qUoJEEDjyKVj/pGHqoX81eP P8kVq06JvjlAvzz8UpJBHzF9U1Ze3cRKs6ookSv7LBU4yQI8fQfw1UvKOBf/G6E9tdyb WUKg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782962840; x=1783567640; h=in-reply-to:content-transfer-encoding:content-disposition :mime-version:references:message-id:subject:cc:to:from:date:x-gm-gg :x-gm-message-state:from:to:cc:subject:date:message-id:reply-to; bh=pntG8xx55vXGzOHMumWUH5D2/3kymMZER8ofZ3RUkaE=; b=o0379FXWHG0wUhaQYPM/C6KzBLSn82FyAiqLpOvPKoQNy+jzGbmqx2N9EyV+X1KmQv R+4uqTaicYHR/Ej7TQkQa5Ej093rvG0AAdmg3marMlqXPQJqqlYq5nvbQ1vy1PePKerF uOXgY1OR6NDEJp9bLIxjTeU7KVEB9cpLiJsVA0y+UL/w1/s8uQi4gAmOrHX5QOASSPTn xby0KQysBU9rq0ycNZJ2bOd8+ACZeFJMyZnaqas1JwbpEqZXb/fhAW9L2HPcWLNo2/9P UUx+IgLouZFivIW9wnPDkgnRoQda0xQSqSyj0nRJ7y3EX7s/cmP8gnU3deFrP3xlAVW4 beXg== X-Gm-Message-State: AOJu0YxfkUFc3gxYsmmMEy9rfT0iqtOINEURnY9+he1rv/nhxdd7w+kJ B++NDoR4F7JfgVVMExXUMqnpUa9yOfnb1Gjgbjnqyj9umY9gd9wA4W0nzWqNkg== X-Gm-Gg: AfdE7cn2ipuIS7ZyNPcTb18Pf+eBtQlkryBVDr+MY4kArod3k9f0C+qgPJmS3LTz1aQ 28jcR/NimjUqVTy8L3Vqv0Xpbg1AG3QYCDmHuo91/pzYSmy8mX51ylimarXjQCN6q0R3N2zCuCt igikcSOfmmu6EcQEyQVWZyuJ5RjX7swB9Wg5MWWxiYo2jIYeSMfgh7xXDBFUmyBkuNyUqQg3Nzc UT5uVFIOz7GzRzaoTs2jPi5ijCXVa7WXvjG5JhaKT81M7flm8ttFvnSUavkbuN5wlt4GvGfzOM5 CNAYgmQy7G6eokwIfvELs/XxOZck/8Mb1v0Ba9lHo4v8QWp8cr279iFpcmVcX3y6XzkK+bKZLKv DIYaPUWu/2QCHB8BFQJSYhLD686oQyOar0VSGg3SVf2R2XdVVLnj+QkpuURzdOv0jv/gn2j0i0G XMKtWXXj25Il1D8cmqwCij74p/8RwTeyPrkfK1z+eTNz4j7z4Ki/VwK1HoF5x5+JBXIewiBNT6 X-Received: by 2002:a05:600c:848d:b0:493:a7fd:15d6 with SMTP id 5b1f17b1804b1-493c2b49d54mr62646655e9.9.1782962839819; Wed, 01 Jul 2026 20:27:19 -0700 (PDT) Received: from bdtpg (ec2-15-237-197-144.eu-west-3.compute.amazonaws.com. [15.237.197.144]) by smtp.gmail.com with ESMTPSA id 5b1f17b1804b1-493c6381e4fsm13100225e9.8.2026.07.01.20.27.19 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Jul 2026 20:27:19 -0700 (PDT) Date: Thu, 2 Jul 2026 03:27:18 +0000 From: Bertrand Drouvot To: Ewan Young Cc: pgsql-hackers@lists.postgresql.org, Michael Paquier Subject: Re: Prevent crash when calling pgstat functions with unregistered stats kind Message-ID: References: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="cdkC2WBRrpXQO1hi" Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --cdkC2WBRrpXQO1hi Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit Hi Ewan, On Wed, Jul 01, 2026 at 04:20:39PM +0800, Ewan Young wrote: > Thanks for the patch — nice catch, and the diagnosis looks right. Thanks for looking at it! > One small thing: in pgstat_snapshot_fixed(), the existing > Assert(pgstat_is_kind_valid(kind)); becomes redundant after the new NULL > check. A non-NULL kind_info already implies the kind is valid (that's the > only way pgstat_get_kind_info() returns non-NULL), so the assert can never > fire. Might as well drop it and keep just the fixed_amount one. Yeah good catch, done in the attached. Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com --cdkC2WBRrpXQO1hi Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v2-0001-Prevent-crash-when-calling-pgstat-functions-with-.patch" From 8b6b7b478478f213376d9c925571eb7418241579 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot Date: Wed, 1 Jul 2026 05:43:07 +0000 Subject: [PATCH v2] Prevent crash when calling pgstat functions with unregistered stats kind If a custom statistics extension is loaded via CREATE EXTENSION without being listed in shared_preload_libraries, its _PG_init() skips the call to pgstat_register_kind(). The SQL functions are still created, and calling them invokes pgstat functions with a kind that was never registered. pgstat_get_kind_info() returns NULL in this case. The existing code only checked this via Assert() in some paths, so non-assert builds would dereference NULL and segfault. Add runtime checks in all public-facing pgstat functions that accept a PgStat_Kind and dereference the returned kind info: - pgstat_prep_pending_entry() - pgstat_fetch_entry() - pgstat_reset() - pgstat_reset_of_kind() - pgstat_have_entry() - pgstat_snapshot_fixed() - pgstat_init_entry() - pgstat_reset_entry() Each now raises ERROR with ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE when the kind is not known or registered. This affects any extension using the custom cumulative statistics API introduced in PG18. Author: Bertrand Drouvot Reviewed-by: Ewan Young Discussion: https://postgr.es/m/akS/ldidWeqG1FWk%40bdtpg --- src/backend/utils/activity/pgstat.c | 46 ++++++++++++++++++++--- src/backend/utils/activity/pgstat_shmem.c | 13 ++++++- 2 files changed, 52 insertions(+), 7 deletions(-) 100.0% src/backend/utils/activity/ diff --git a/src/backend/utils/activity/pgstat.c b/src/backend/utils/activity/pgstat.c index c4fa14f138f..540db1ef115 100644 --- a/src/backend/utils/activity/pgstat.c +++ b/src/backend/utils/activity/pgstat.c @@ -885,8 +885,13 @@ pgstat_reset(PgStat_Kind kind, Oid dboid, uint64 objid) const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); TimestampTz ts = GetCurrentTimestamp(); + if (unlikely(kind_info == NULL)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("statistics kind %u is not known or registered", kind))); + /* not needed atm, and doesn't make sense with the current signature */ - Assert(!pgstat_get_kind_info(kind)->fixed_amount); + Assert(!kind_info->fixed_amount); /* reset the "single counter" */ pgstat_reset_entry(kind, dboid, objid, ts); @@ -907,6 +912,11 @@ pgstat_reset_of_kind(PgStat_Kind kind) const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); TimestampTz ts = GetCurrentTimestamp(); + if (unlikely(kind_info == NULL)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("statistics kind %u is not known or registered", kind))); + if (kind_info->fixed_amount) kind_info->reset_all_cb(ts); else @@ -967,6 +977,11 @@ pgstat_fetch_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *may_free) void *stats_data; const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + if (unlikely(kind_info == NULL)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("statistics kind %u is not known or registered", kind))); + /* should be called from backends */ Assert(IsUnderPostmaster || !IsPostmasterEnvironment); Assert(!kind_info->fixed_amount); @@ -1088,8 +1103,15 @@ pgstat_get_stat_snapshot_timestamp(bool *have_snapshot) bool pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid) { + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (unlikely(kind_info == NULL)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("statistics kind %u is not known or registered", kind))); + /* fixed-numbered stats always exist */ - if (pgstat_get_kind_info(kind)->fixed_amount) + if (kind_info->fixed_amount) return true; return pgstat_get_entry_ref(kind, dboid, objid, false, NULL) != NULL; @@ -1104,8 +1126,14 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid) void pgstat_snapshot_fixed(PgStat_Kind kind) { - Assert(pgstat_is_kind_valid(kind)); - Assert(pgstat_get_kind_info(kind)->fixed_amount); + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (unlikely(kind_info == NULL)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("statistics kind %u is not known or registered", kind))); + + Assert(kind_info->fixed_amount); if (force_stats_snapshot_clear) pgstat_clear_snapshot(); @@ -1310,9 +1338,15 @@ PgStat_EntryRef * pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *created_entry) { PgStat_EntryRef *entry_ref; + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (unlikely(kind_info == NULL)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("statistics kind %u is not known or registered", kind))); /* need to be able to flush out */ - Assert(pgstat_get_kind_info(kind)->flush_pending_cb != NULL); + Assert(kind_info->flush_pending_cb != NULL); if (unlikely(!pgStatPendingContext)) { @@ -1327,7 +1361,7 @@ pgstat_prep_pending_entry(PgStat_Kind kind, Oid dboid, uint64 objid, bool *creat if (entry_ref->pending == NULL) { - size_t entrysize = pgstat_get_kind_info(kind)->pending_size; + size_t entrysize = kind_info->pending_size; Assert(entrysize != (size_t) -1); diff --git a/src/backend/utils/activity/pgstat_shmem.c b/src/backend/utils/activity/pgstat_shmem.c index 5ea3f1973f9..4e6a556af93 100644 --- a/src/backend/utils/activity/pgstat_shmem.c +++ b/src/backend/utils/activity/pgstat_shmem.c @@ -318,6 +318,11 @@ pgstat_init_entry(PgStat_Kind kind, PgStatShared_Common *shheader; const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + if (unlikely(kind_info == NULL)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("statistics kind %u is not known or registered", kind))); + /* * Initialize refcount to 1, marking it as valid / not dropped. The entry * can't be freed before the initialization because it can't be found as @@ -1127,8 +1132,14 @@ void pgstat_reset_entry(PgStat_Kind kind, Oid dboid, uint64 objid, TimestampTz ts) { PgStat_EntryRef *entry_ref; + const PgStat_KindInfo *kind_info = pgstat_get_kind_info(kind); + + if (unlikely(kind_info == NULL)) + ereport(ERROR, + (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("statistics kind %u is not known or registered", kind))); - Assert(!pgstat_get_kind_info(kind)->fixed_amount); + Assert(!kind_info->fixed_amount); entry_ref = pgstat_get_entry_ref(kind, dboid, objid, false, NULL); if (!entry_ref || entry_ref->shared_entry->dropped) -- 2.34.1 --cdkC2WBRrpXQO1hi--