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 1wepEj-00531p-19 for pgsql-hackers@arkaria.postgresql.org; Wed, 01 Jul 2026 07:19:57 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wepEh-00CGEG-2s for pgsql-hackers@arkaria.postgresql.org; Wed, 01 Jul 2026 07:19:55 +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 1wepEh-00CGE8-1W for pgsql-hackers@lists.postgresql.org; Wed, 01 Jul 2026 07:19:55 +0000 Received: from mail-wm1-x335.google.com ([2a00:1450:4864:20::335]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (Exim 4.98.2) (envelope-from ) id 1wepEf-000000012fl-1RjE for pgsql-hackers@lists.postgresql.org; Wed, 01 Jul 2026 07:19:54 +0000 Received: by mail-wm1-x335.google.com with SMTP id 5b1f17b1804b1-493bc8fda98so2487325e9.0 for ; Wed, 01 Jul 2026 00:19:53 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1782890392; x=1783495192; darn=lists.postgresql.org; h=content-disposition:mime-version:message-id:subject:cc:to:from:date :from:to:cc:subject:date:message-id:reply-to; bh=goPL3BvZ/4Ed18TnWbdEVvXl9M7yptClchlvXf9AXQY=; b=CNCRFwr+sWT6xVXa/gSPEW8G3iz84QdFedF4pLXIRPSBM8If7R9Wvbtk9o0rJyF0yY nhmFtMUD85+HjaXzBtdkPkYWiRJ2X8F40J4DSshhw8jx6+PHDq4ImVvyfoe1DmVmRWlG Y59vgmtmw4UzonEFjGrsihYFh7Cbp/IyTRnBtFw7UIwItaklhuHqLoTiq+E3FRwady8j ZbimoIFIOAlBV7b/ocSUcFeBA/dQZFcoQG+YkvcBd2BAxV6LOyxdTChEcPYmrPpbZhhN qXSuWfCSou0z7Ln6GQ+mG74ESti5+E80qVnaF1KP4YuaoFqUcOuXWRQS9ooMGm0iDtEb eJHQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1782890392; x=1783495192; h=content-disposition:mime-version:message-id:subject:cc:to:from:date :x-gm-gg:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=goPL3BvZ/4Ed18TnWbdEVvXl9M7yptClchlvXf9AXQY=; b=BPgntb1Yfiql+vCjEDbGlj4l/1LigZSoYBCJFoWktVFRqd0Lv+ql6HAqJF6RCyNL+m tRvEfRoJmbIslPAD52v5og3wsSzhTOfRS/kqHbUi0wZQz7HgPEpsY3pd8o+tvsDkCjfN V+cbiCh087FTM/6YhXRF/QXzmIUM0bm6QBf4z7t4Ro+BqsB4JMwIb2A3a8HyXzVyS2MI jCWYOFy/w2C2XDwzTpCeP7Idq+9/JUa7rX+45DXCJFQsvbOrY1MfKIgwT3ek7XmDB3RZ NHX8KxxLlUrB+Eegqi4+EcPkejZ1HYyF7wi9E2woUDURFiMjtly9ojcxBgl9JqGi69Oc cLTg== X-Gm-Message-State: AOJu0YyW6ESzkH1qbCo8R3Kc15Z3T4qPyPIQL4n63qxx/8NthhtokeOI mS8tScLp/n5GqKvb105dekix3goGqNbGIoSKFeCfl1pdkT92pqOTlE0dnldcCg== X-Gm-Gg: AfdE7cmBS5UnB/msSwwwbtFfynXg6ZU0+PYBS8xwIN8AQ/UyWvch2Yz3rbgmCbDu2BL S5f266/yKZ/8cTr+yjP8js+QJBThHdwJQ67z/jMNO7r6bORMMm9DWZcVVm1oS/q/zLVfQfJ4qDm YF4QDfYQ84U9WIe1tjVK1rkHXApDIasnQ0BL0UsS0FDy739tniBRKEt8GJEWbO33r7sw8swbFWy 3QUFeyq9Ih82S2of7QWAePu/kIpYHl1D8B7DfNksVWRwEt5YCWzK1tit3otIfgXGuzLcgDos3jb BV67DLhnNBhGRG7txxTa+mYvTX8LHlgcUCdh/rdnjNzmnGdN9MLf0yYkpysKY9q6gc9f2nbbWdQ Pe5RC2HhC4rKRDa5wUd1bORwLpe+bc4B5jBPlEavFmRsEiylhcNf7THUpSkczRBRRc+rUvJuc9Y lk5DNgJRmWdb2Ue+PWiV0RbLAcxgysiShELCR571UFIHPmEEpQAAKcmOqzHZ3GTDmLzai6znKR X-Received: by 2002:a05:600d:6450:10b0:493:b8dd:9d68 with SMTP id 5b1f17b1804b1-493c2b54576mr5185645e9.10.1782890391602; Wed, 01 Jul 2026 00:19:51 -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-493be4d15ddsm52629405e9.6.2026.07.01.00.19.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Wed, 01 Jul 2026 00:19:51 -0700 (PDT) Date: Wed, 1 Jul 2026 07:19:49 +0000 From: Bertrand Drouvot To: pgsql-hackers@lists.postgresql.org Cc: Michael Paquier Subject: Prevent crash when calling pgstat functions with unregistered stats kind Message-ID: MIME-Version: 1.0 Content-Type: multipart/mixed; boundary="jlSGGOUQ0kGWBHE+" Content-Disposition: inline List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk --jlSGGOUQ0kGWBHE+ Content-Type: text/plain; charset=us-ascii Content-Disposition: inline Hi hackers, While reviewing [1], I got segfault(s) because I created a custom statistics extension that I forgot to add to shared_preload_libraries. Then using one of its function produced: " Core was generated by `postgres: postgres postgres [local] SELECT '. Program terminated with signal SIGSEGV, Segmentation fault. #0 pgstat_init_entry (kind=kind@entry=24, shhashent=shhashent@entry=0x73f6c341a740) at pgstat_shmem.c:335 335 chunk = dsa_allocate_extended(pgStatLocal.dsa, " Indeed, 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. The attached patch adds 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. [1]: https://postgr.es/m/akSi2txzLZWQL31Q%40bdtpg Regards, -- Bertrand Drouvot PostgreSQL Contributors Team RDS Open Source Databases Amazon Web Services: https://aws.amazon.com --jlSGGOUQ0kGWBHE+ Content-Type: text/x-diff; charset=us-ascii Content-Disposition: attachment; filename="v1-0001-Prevent-crash-when-calling-pgstat-functions-with-.patch" From 15080a6725a940ea3ccb481898b99d53be388fc3 Mon Sep 17 00:00:00 2001 From: Bertrand Drouvot Date: Wed, 1 Jul 2026 05:43:07 +0000 Subject: [PATCH v1] 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: Discussion: --- src/backend/utils/activity/pgstat.c | 45 ++++++++++++++++++++--- src/backend/utils/activity/pgstat_shmem.c | 13 ++++++- 2 files changed, 52 insertions(+), 6 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..5180201c4e8 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,15 @@ pgstat_have_entry(PgStat_Kind kind, Oid dboid, uint64 objid) void pgstat_snapshot_fixed(PgStat_Kind kind) { + 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_is_kind_valid(kind)); - Assert(pgstat_get_kind_info(kind)->fixed_amount); + Assert(kind_info->fixed_amount); if (force_stats_snapshot_clear) pgstat_clear_snapshot(); @@ -1310,9 +1339,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 +1362,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 --jlSGGOUQ0kGWBHE+--