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 1wXIpm-003BQQ-36 for pgsql-bugs@arkaria.postgresql.org; Wed, 10 Jun 2026 13:19:07 +0000 Received: from localhost ([127.0.0.1] helo=malur.postgresql.org) by malur.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wXIpk-00BaQw-2m for pgsql-bugs@arkaria.postgresql.org; Wed, 10 Jun 2026 13:19:04 +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 1wXEEL-00A3O2-1l for pgsql-bugs@lists.postgresql.org; Wed, 10 Jun 2026 08:24:09 +0000 Received: from mahout.postgresql.org ([2001:4800:3e1:1::227]) by makus.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.98.2) (envelope-from ) id 1wXEEJ-00000001yjo-2m2N for pgsql-bugs@lists.postgresql.org; Wed, 10 Jun 2026 08:24:08 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=postgresql.org; s=20171124; h=Message-ID:Date:Reply-To:Cc:From:To:Subject: Content-Transfer-Encoding:MIME-Version:Content-Type:Sender:Content-ID: Content-Description:In-Reply-To:References; bh=y2FBkCcSEQtaTIg1bN3Zl9te66gJKYxosjv9sA/RGyA=; b=qNdMVxebtATGPyks4t8XCRtd9g PTvZfwwikl5msnHa3mdDVXpTC3DsrqChDi+z0c5tahaRO2e7yWxOv55x+briWsmNhcLF+s4Y+mXSR GkrveHNr0GPt/8mLXYrzbwU/zeYpXTRbm5eP77f2LPsy9wVz2G2Mtn7zsdbKBIv+OyqORYC1C/OIK 1N0pTFbKC4UfkkjG48TWjx8nhHZvy8p86KMW0aMRRiueiACmeUbVTtJEKuRPQ8LmCvIQRc1J2XEK2 Mrn+T2H+3+KFwr02Fw3T9jEemf4lAkmqGE1QuGbSUVCaLxrr+52jHeeXwUFQrAfyahd8+oxvn7TrV P3PrUGxw==; Received: from wrigleys.postgresql.org ([2a02:16a8:dc51::60]) by mahout.postgresql.org with esmtps (TLS1.3) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.96) (envelope-from ) id 1wXEEG-006PdE-38 for pgsql-bugs@lists.postgresql.org; Wed, 10 Jun 2026 08:24:07 +0000 Received: from localhost ([127.0.0.1] helo=wrigleys.postgresql.org) by wrigleys.postgresql.org with esmtp (Exim 4.96) (envelope-from ) id 1wXEEF-00ETxR-1j for pgsql-bugs@lists.postgresql.org; Wed, 10 Jun 2026 08:24:03 +0000 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Subject: BUG #19515: Creating a faulty BRIN operator class can cause the server to crash when used. To: pgsql-bugs@lists.postgresql.org From: PG Bug reporting form Cc: 1165125080@qq.com Reply-To: 1165125080@qq.com, pgsql-bugs@lists.postgresql.org Date: Wed, 10 Jun 2026 08:23:27 +0000 Message-ID: <19515-d3c30c3a8bf99ee9@postgresql.org> X-Auto-Response-Suppress: All Auto-Submitted: auto-generated List-Id: List-Help: List-Subscribe: List-Post: List-Owner: List-Archive: Archived-At: Precedence: bulk The following bug has been logged on the website: Bug reference: 19515 Logged by: ji xiaohang Email address: 1165125080@qq.com PostgreSQL version: 18.4 Operating system: centos8 Description: =20 Creating a BRIN operator class with a support function that has a wrong signature (e.g. accepting text instead of internal) succeeds at DDL time, but crashes the server when a BRIN index using that opclass is created. The root cause is that assignProcTypes() in opclasscmds.c validates support function signatures for btree (amcanorder) and hash (amcanhash) at CREATE OPERATOR CLASS / ALTER OPERATOR FAMILY time, but has no validation branch for BRIN (amsummarizing). This allows registering functions with incorrect signatures that then cause memory corruption when BRIN calls them via fmgr, passing arguments that don't match what the function expects. Steps to reproduce: 1. Create a table with a text column: CREATE TABLE t_sva (sva text); 2. Create a function with the wrong signature (text/text instead of internal): CREATE FUNCTION si_same(text, text) RETURNS boolean AS $$ SELECT $1 =3D $2 $$ LANGUAGE sql; 3. Create a BRIN operator class using this wrong-signature function as support function 1 (OPCINFO): CREATE OPERATOR CLASS sva_special_ops FOR TYPE text USING brin AS OPERATOR 1 =3D, FUNCTION 1 si_same(text, text); This succeeds =E2=80=94 it should have been rejected. 4. Create a BRIN index using the opclass: CREATE INDEX idx_special1 ON t_sva USING brin(sva sva_special_ops); The server crashes at this point. Expected behavior: Step 3 should fail with an error like: ERROR: BRIN support function 1 must accept type "internal" The server should never crash due to a DDL operation with user-defined functions. Analysis: assignProcTypes() has validation for btree and hash support functions but skips BRIN entirely, falling through to the default type-assignment logic. BRIN support functions use "internal" argument types, so the default logic cannot infer correct types and allows the wrong-signature function through. At runtime, brin_build_desc() calls the OPCINFO function via fmgr, passing an Oid as "internal". The wrong-signature SQL function then interprets the argument as "text", causing memory corruption. The same gap exists for GiST, GIN, and SP-GiST =E2=80=94 they likewise la= ck validation branches in assignProcTypes(), so wrong-signature support functions can be registered for those AMs as well. If the BRIN fix approach is accepted, similar validation can be added for those access methods in a follow-up patch. Proposed fix: Add an "else if (amroutine->amsummarizing)" validation branch in assignProcTypes(), after the existing amcanhash block. This branch would validate the signatures of the 4 mandatory BRIN support functions (OPCINFO, ADDVALUE, CONSISTENT, UNION) and reject invalid proc numbers. Optional procs (11-15) would be accepted without signature checking, consistent with brinvalidate(). Fix the patch as follows: diff --git a/src/backend/commands/opclasscmds.c b/src/backend/commands/opclasscmds.c index a6dd8ea..e5078eb 100644 --- a/src/backend/commands/opclasscmds.c +++ b/src/backend/commands/opclasscmds.c @@ -17,6 +17,7 @@ #include +#include "access/brin_internal.h" #include "access/genam.h" #include "access/hash.h" #include "access/htup_details.h" @@ -1205,6 +1206,7 @@ assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, { HeapTuple proctup; Form_pg_proc procform; + IndexAmRoutine *amroutine; /* Fetch the procedure definition */ proctup =3D SearchSysCache1(PROCOID, ObjectIdGetDatum(member->object)); @@ -1212,6 +1214,9 @@ assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, elog(ERROR, "cache lookup failed for function %u", member->object); procform =3D (Form_pg_proc) GETSTRUCT(proctup); + /* Fetch the AM routine once, for use in validation below */ + amroutine =3D GetIndexAmRoutineByAmId(amoid, false); + /* Check the signature of the opclass options parsing function */ if (member->number =3D=3D opclassOptsProcNum) { @@ -1249,7 +1254,7 @@ assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, * a 1-arg proc returning int4, while proc 2 must be a 2-arg proc * returning int8. Otherwise we don't know. */ - else if (GetIndexAmRoutineByAmId(amoid, false)->amcanorder) + else if (amroutine->amcanorder) { if (member->number =3D=3D BTORDER_PROC) { @@ -1357,7 +1362,7 @@ assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, errmsg("btree skip support functions must not be cross-type"))); } } - else if (GetIndexAmRoutineByAmId(amoid, false)->amcanhash) + else if (amroutine->amcanhash) { if (member->number =3D=3D HASHSTANDARD_PROC) { @@ -1390,6 +1395,92 @@ assignProcTypes(OpFamilyMember *member, Oid amoid, Oid typeoid, if (!OidIsValid(member->righttype)) member->righttype =3D procform->proargtypes.values[= 0]; } + else if (amroutine->amsummarizing) + { + /* + * For BRIN, support functions use "internal" argument types, so + * we can't infer lefttype/righttype from them. Validate the + * function signatures per BRIN requirements. + */ + if (member->number =3D=3D BRIN_PROCNUM_OPCINFO) + { + if (procform->pronargs !=3D 1 || + procform->proargtypes.values[0] !=3D INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 1 must accept type \"internal\""))); + if (procform->prorettype !=3D INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 1 must return type \"internal\""))); + } + else if (member->number =3D=3D BRIN_PROCNUM_ADDVALUE) + { + if (procform->pronargs !=3D 4) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 2 must have four arguments"))); + if (procform->proargtypes.values[0] !=3D INTERNALOID || + procform->proargtypes.values[1] !=3D INTERNALOID || + procform->proargtypes.values[2] !=3D INTERNALOID || + procform->proargtypes.values[3] !=3D INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 2 must accept type \"internal\""))); + if (procform->prorettype !=3D BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 2 must return boolean"))); + } + else if (member->number =3D=3D BRIN_PROCNUM_CONSISTENT) + { + if (procform->pronargs !=3D 3 && procform->pronargs= !=3D 4) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 3 must have three or four arguments"))); + if (procform->proargtypes.values[0] !=3D INTERNALOID || + procform->proargtypes.values[1] !=3D INTERNALOID || + procform->proargtypes.values[2] !=3D INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 3 must accept type \"internal\""))); + if (procform->pronargs =3D=3D 4 && + procform->proargtypes.values[3] !=3D INT4OI= D) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 3 optional fourth argument must be type \"integer\""))); + if (procform->prorettype !=3D BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 3 must return boolean"))); + } + else if (member->number =3D=3D BRIN_PROCNUM_UNION) + { + if (procform->pronargs !=3D 3) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 4 must have three arguments"))); + if (procform->proargtypes.values[0] !=3D INTERNALOID || + procform->proargtypes.values[1] !=3D INTERNALOID || + procform->proargtypes.values[2] !=3D INTERNALOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 4 must accept type \"internal\""))); + if (procform->prorettype !=3D BOOLOID) + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("BRIN support function 4 must return boolean"))); + } + else if (member->number < BRIN_FIRST_OPTIONAL_PROCNUM || + member->number > BRIN_LAST_OPTIONAL_PROCNUM) + { + ereport(ERROR, + (errcode(ERRCODE_INVALID_OBJECT_DEFINITION), + errmsg("invalid support function number %d for BRIN", + member->number))); + } + /* Optional procs (11-15): accept without signature checking */ + } /* * The default in CREATE OPERATOR CLASS is to use the class' opcintype as