public inbox for [email protected]
help / color / mirror / Atom feedFrom: surya poondla <[email protected]>
To: Andrey Borodin <[email protected]>
Cc: songjinzhou <[email protected]>
Cc: dllggyx <[email protected]>
Cc: pgsql-bugs <[email protected]>
Subject: Re: BUG #19382: Server crash at __nss_database_lookup
Date: Wed, 8 Apr 2026 21:24:06 -0700
Message-ID: <CAOVWO5o9YOpCTgg6FfNepCoH_6pFSa7TJ3SEWfJAoBvNOb0OdQ@mail.gmail.com> (raw)
In-Reply-To: <[email protected]>
References: <[email protected]>
<CAOVWO5rVBKsjG4YwO_PJQu2OBGp8qUdF1jineYY6Lm3zc6-KWQ@mail.gmail.com>
<CAOVWO5rbwKgHWLYJMvKuvGxW9eFSk7LADk=ZxDEvwA1uTefvAg@mail.gmail.com>
<CAOVWO5qN8UXdwMnDa+a7aVq=irGSfm2aeYvEc-uV6j4QHZiyrA@mail.gmail.com>
<CAOVWO5py4zLPYsnGK1EEzHnC4feTSmbsEZn-BiDgY_=J1P6Wmw@mail.gmail.com>
<CAOVWO5pbgCVx0zgTr1mxZug2hoGwxZOk+-Owvwg0jaQv9JE3Fw@mail.gmail.com>
<CAOVWO5r19cctAFKbW24jfMKsD-pkyV21w+z7L3pCPxM1CArtjQ@mail.gmail.com>
<CAOVWO5oSeBouPv0ueVByh+_6EgRCjWh0spSmnF6Cv-TF1twqKg@mail.gmail.com>
<[email protected]>
<CAOVWO5oH37CETZuxxXw3dhCMOHPMA0xFoJBWTpfJ06OV7sGzTQ@mail.gmail.com>
<[email protected]>
<CAOVWO5oRGPd7mA3d85jNYmjLNfeBAca5oDcHTfRFxbAwPLxs5g@mail.gmail.com>
<[email protected]>
Hi Andrey,
Thank you for the comments.
> The server still crashes when only an inner type is altered:
>
> CREATE TYPE inner_t AS (x INT, y INT);
> CREATE TYPE outer_t AS (a INT, b inner_t);
>
> CREATE OR REPLACE FUNCTION test_nested() RETURNS record LANGUAGE plpgsql
> AS $$
> DECLARE r1 outer_t; r2 outer_t;
> BEGIN
> r1 := ROW(1, ROW(10, power(2,30)::int4)::inner_t)::outer_t;
> ALTER TYPE inner_t ALTER ATTRIBUTE y TYPE TEXT;
> r2 := r1;
> RETURN r2;
> END; $$;
>
> SELECT test_nested(); -- server crash
>
> Thank you for the nested composite testcase and the fix in cursor
code, the changes look good.. I fixed the PL/pgsql to fix the nested
components.
Here is the patch for the pl/pgsql fix
Regards,
Surya Poondla
Attachments:
[application/octet-stream] 0005-Fix-bug-19382-server-crash-when-ALTER-TYPE-is-used-m.patch (14.2K, 3-0005-Fix-bug-19382-server-crash-when-ALTER-TYPE-is-used-m.patch)
download | inline diff:
From 2d83dde32a3a94b7298399c3f47c38f702cc96e7 Mon Sep 17 00:00:00 2001
From: spoondla <[email protected]>
Date: Fri, 23 Jan 2026 17:28:54 -0800
Subject: [PATCH v5] Fix (bug #19382) server crash when ALTER TYPE is used
mid-transaction in PL/pgSQL
When ALTER TYPE changes a composite type's column types within a
transaction, PL/pgSQL record variables that were populated before
the ALTER still hold data in the old format. Returning such records
causes a crash because the output functions expect data matching the
new type definition, not the old one.
The crash manifested as a segmentation fault in record_out() when it
attempted to interpret integer data as a text pointer, due to the
mismatch between the stored data and the current type definition.
The fix snapshots tupDesc_identifier values for all composite types
reachable from a record variable's type (including nested composite
types) at assignment time. At RETURN/RETURN NEXT time, these
identifiers are compared against current values from the type cache.
If any have changed, an error is raised instead of risking a crash.
---
.../plpgsql/src/expected/plpgsql_record.out | 67 ++++++
src/pl/plpgsql/src/pl_exec.c | 209 +++++++++++++++++-
src/pl/plpgsql/src/plpgsql.h | 9 +
src/pl/plpgsql/src/sql/plpgsql_record.sql | 58 +++++
4 files changed, 342 insertions(+), 1 deletion(-)
diff --git a/src/pl/plpgsql/src/expected/plpgsql_record.out b/src/pl/plpgsql/src/expected/plpgsql_record.out
index 511f9e03c85..ad21f8bbf3f 100644
--- a/src/pl/plpgsql/src/expected/plpgsql_record.out
+++ b/src/pl/plpgsql/src/expected/plpgsql_record.out
@@ -885,3 +885,70 @@ table two_int8s_tab;
(42,42)
(1 row)
+-- Tests for bug #19382: server crash when ALTER TYPE is used mid-transaction
+-- in PL/pgSQL. Record variables populated before ALTER TYPE must not be
+-- returned, as the stored data no longer matches the current type definition.
+-- Case 1: Direct composite type change (INT -> TEXT)
+create type bug19382_foo as (a int, b int);
+create function bug19382_test_direct() returns record as $$
+declare r bug19382_foo := row(123, power(2, 30));
+begin
+ alter type bug19382_foo alter attribute b type text;
+ return r;
+end;
+$$ language plpgsql;
+select bug19382_test_direct();
+ERROR: cannot return record variable "r" after composite type "bug19382_foo" was altered
+HINT: Reassign the record variable after ALTER TYPE.
+CONTEXT: PL/pgSQL function bug19382_test_direct() line 5 at RETURN
+drop function bug19382_test_direct();
+drop type bug19382_foo cascade;
+-- Case 2: Nested composite type change
+create type bug19382_inner as (x int, y int);
+create type bug19382_outer as (a int, b bug19382_inner);
+create function bug19382_test_nested() returns record as $$
+declare r bug19382_outer;
+begin
+ r := row(1, row(10, power(2, 30)::int4)::bug19382_inner)::bug19382_outer;
+ alter type bug19382_inner alter attribute y type text;
+ return r;
+end;
+$$ language plpgsql;
+select bug19382_test_nested();
+ERROR: cannot return record variable "r" after composite type "bug19382_inner" was altered
+HINT: Reassign the record variable after ALTER TYPE.
+CONTEXT: PL/pgSQL function bug19382_test_nested() line 6 at RETURN
+drop function bug19382_test_nested();
+drop type bug19382_outer cascade;
+drop type bug19382_inner cascade;
+-- Case 3: OUT parameter
+create type bug19382_foo1 as (a int, b int);
+create function bug19382_test_out(out r1 bug19382_foo1) as $$
+begin
+ r1 := row(1, 2);
+ alter type bug19382_foo1 alter attribute b type text;
+ return;
+end;
+$$ language plpgsql;
+select bug19382_test_out();
+ERROR: cannot return record variable "r1" after composite type "bug19382_foo1" was altered
+HINT: Reassign the record variable after ALTER TYPE.
+CONTEXT: PL/pgSQL function bug19382_test_out() line 5 at RETURN
+drop function bug19382_test_out();
+drop type bug19382_foo1 cascade;
+-- Case 4: No ALTER TYPE (baseline — must not error)
+create type bug19382_foo2 as (a int, b int);
+create function bug19382_test_baseline() returns bug19382_foo2 as $$
+declare r bug19382_foo2 := row(1, 2);
+begin
+ return r;
+end;
+$$ language plpgsql;
+select bug19382_test_baseline();
+ bug19382_test_baseline
+------------------------
+ (1,2)
+(1 row)
+
+drop function bug19382_test_baseline();
+drop type bug19382_foo2;
diff --git a/src/pl/plpgsql/src/pl_exec.c b/src/pl/plpgsql/src/pl_exec.c
index 65b0fd0790f..be3529445f9 100644
--- a/src/pl/plpgsql/src/pl_exec.c
+++ b/src/pl/plpgsql/src/pl_exec.c
@@ -470,6 +470,12 @@ static char *format_preparedparamsdata(PLpgSQL_execstate *estate,
static PLpgSQL_variable *make_callstmt_target(PLpgSQL_execstate *estate,
PLpgSQL_expr *expr);
+static void check_record_type_not_altered(PLpgSQL_rec *rec);
+static void collect_composite_type_versions(Oid typid,
+ Oid **oids, uint64 **versions,
+ int *n, int *alloc);
+static void snapshot_record_composite_types(PLpgSQL_execstate *estate,
+ PLpgSQL_rec *rec);
/* ----------
* plpgsql_exec_function Called by the call handler for
@@ -3287,8 +3293,30 @@ exec_stmt_return(PLpgSQL_execstate *estate, PLpgSQL_stmt_return *stmt)
}
break;
- case PLPGSQL_DTYPE_ROW:
case PLPGSQL_DTYPE_REC:
+ {
+ PLpgSQL_rec *rec = (PLpgSQL_rec *) retvar;
+ int32 rettypmod;
+
+ /*
+ * Check if the record's composite type was altered since
+ * the record was populated. If so, raise an error to
+ * prevent crashes when outputting the record.
+ */
+ if (rec->rectypeid != RECORDOID && rec->erh != NULL &&
+ !ExpandedRecordIsEmpty(rec->erh))
+ check_record_type_not_altered(rec);
+
+ exec_eval_datum(estate,
+ retvar,
+ &estate->rettype,
+ &rettypmod,
+ &estate->retval,
+ &estate->retisnull);
+ }
+ break;
+
+ case PLPGSQL_DTYPE_ROW:
{
/* exec_eval_datum can handle these cases */
int32 rettypmod;
@@ -3434,6 +3462,14 @@ exec_stmt_return_next(PLpgSQL_execstate *estate,
TupleDesc rec_tupdesc;
TupleConversionMap *tupmap;
+ /*
+ * Check if the record's composite type was altered since
+ * the record was populated. If so, raise an error to
+ * prevent crashes when storing to the tuplestore.
+ */
+ if (rec->rectypeid != RECORDOID && rec->erh != NULL)
+ check_record_type_not_altered(rec);
+
/* If rec is null, try to convert it to a row of nulls */
if (rec->erh == NULL)
instantiate_empty_record_variable(estate, rec);
@@ -7042,6 +7078,10 @@ exec_move_row(PLpgSQL_execstate *estate,
if (rec->erh)
DeleteExpandedObject(ExpandedRecordGetDatum(rec->erh));
rec->erh = NULL;
+ /* Clear composite type snapshot */
+ rec->nCompTypes = 0;
+ rec->compTypeOids = NULL;
+ rec->compTypeVersions = NULL;
}
return;
}
@@ -8967,6 +9007,9 @@ assign_record_var(PLpgSQL_execstate *estate, PLpgSQL_rec *rec,
/* ... and install the new */
rec->erh = erh;
+
+ /* Snapshot composite type versions for ALTER TYPE detection */
+ snapshot_record_composite_types(estate, rec);
}
/*
@@ -9216,3 +9259,167 @@ format_preparedparamsdata(PLpgSQL_execstate *estate,
return paramstr.data;
}
+
+/*
+ * check_record_type_not_altered
+ *
+ * Check if any composite type reachable from this record's type has been
+ * altered since the record was populated. If so, raise an error to prevent
+ * crashes that would occur when outputting data that no longer matches the
+ * current type definition.
+ *
+ * Uses the composite type version snapshot taken at record assignment time
+ * to detect changes in both the outermost type and any nested composite types.
+ */
+static void
+check_record_type_not_altered(PLpgSQL_rec *rec)
+{
+ int i;
+
+ /* Nothing to do for anonymous RECORD type or no snapshot */
+ if (rec->rectypeid == RECORDOID || rec->nCompTypes <= 0)
+ return;
+
+ for (i = 0; i < rec->nCompTypes; i++)
+ {
+ TypeCacheEntry *typentry;
+
+ typentry = lookup_type_cache(rec->compTypeOids[i], TYPECACHE_TUPDESC);
+
+ if (typentry->tupDesc_identifier != rec->compTypeVersions[i])
+ ereport(ERROR,
+ (errcode(ERRCODE_DATATYPE_MISMATCH),
+ errmsg("cannot return record variable \"%s\" after composite type \"%s\" was altered",
+ rec->refname,
+ format_type_be(rec->compTypeOids[i])),
+ errhint("Reassign the record variable after ALTER TYPE.")));
+ }
+}
+
+/*
+ * collect_composite_type_versions
+ *
+ * Recursively collect tupDesc_identifier values for a composite type and
+ * all composite types reachable from its attributes. Skips anonymous
+ * RECORD types and types already recorded (to prevent infinite recursion).
+ *
+ * oids/versions arrays are repalloc'd as needed; n/alloc updated in place.
+ */
+static void
+collect_composite_type_versions(Oid typid,
+ Oid **oids, uint64 **versions,
+ int *n, int *alloc)
+{
+ TypeCacheEntry *typentry;
+ TupleDesc tupdesc;
+ int i;
+
+ /* Skip if already recorded */
+ for (i = 0; i < *n; i++)
+ {
+ if ((*oids)[i] == typid)
+ return;
+ }
+
+ typentry = lookup_type_cache(typid, TYPECACHE_TUPDESC);
+
+ /* Grow arrays if needed */
+ if (*n >= *alloc)
+ {
+ *alloc *= 2;
+ *oids = repalloc(*oids, *alloc * sizeof(Oid));
+ *versions = repalloc(*versions, *alloc * sizeof(uint64));
+ }
+
+ (*oids)[*n] = typid;
+ (*versions)[*n] = typentry->tupDesc_identifier;
+ (*n)++;
+
+ tupdesc = typentry->tupDesc;
+ if (tupdesc == NULL)
+ return;
+
+ /* Recurse into composite-type attributes */
+ for (i = 0; i < tupdesc->natts; i++)
+ {
+ Form_pg_attribute attr = TupleDescAttr(tupdesc, i);
+ Oid attrtypid;
+ char typtype;
+
+ if (attr->attisdropped)
+ continue;
+
+ attrtypid = attr->atttypid;
+ if (attrtypid == RECORDOID)
+ continue;
+
+ typtype = get_typtype(attrtypid);
+
+ /* Resolve domain types to their base type */
+ if (typtype == TYPTYPE_DOMAIN)
+ {
+ attrtypid = getBaseType(attrtypid);
+ typtype = get_typtype(attrtypid);
+ }
+
+ if (typtype == TYPTYPE_COMPOSITE)
+ collect_composite_type_versions(attrtypid,
+ oids, versions, n, alloc);
+ }
+}
+
+/*
+ * snapshot_record_composite_types
+ *
+ * Take a snapshot of tupDesc_identifier values for all composite types
+ * reachable from the record's declared type. Called when a record variable
+ * is assigned a new value, so that check_record_type_not_altered() can
+ * detect mid-transaction ALTER TYPE at RETURN time.
+ */
+static void
+snapshot_record_composite_types(PLpgSQL_execstate *estate,
+ PLpgSQL_rec *rec)
+{
+ MemoryContext oldcxt;
+ int alloc = 8;
+ int n = 0;
+ Oid *oids;
+ uint64 *versions;
+
+ /* Nothing to do for anonymous RECORD type */
+ if (rec->rectypeid == RECORDOID)
+ {
+ rec->nCompTypes = 0;
+ return;
+ }
+
+ oldcxt = MemoryContextSwitchTo(estate->datum_context);
+ oids = palloc(alloc * sizeof(Oid));
+ versions = palloc(alloc * sizeof(uint64));
+
+ collect_composite_type_versions(rec->rectypeid,
+ &oids, &versions, &n, &alloc);
+
+ MemoryContextSwitchTo(oldcxt);
+
+ if (n > 0)
+ {
+ /* Free previous snapshot if any */
+ if (rec->compTypeOids)
+ pfree(rec->compTypeOids);
+ if (rec->compTypeVersions)
+ pfree(rec->compTypeVersions);
+
+ rec->nCompTypes = n;
+ rec->compTypeOids = oids;
+ rec->compTypeVersions = versions;
+ }
+ else
+ {
+ pfree(oids);
+ pfree(versions);
+ rec->nCompTypes = 0;
+ rec->compTypeOids = NULL;
+ rec->compTypeVersions = NULL;
+ }
+}
diff --git a/src/pl/plpgsql/src/plpgsql.h b/src/pl/plpgsql/src/plpgsql.h
index addb14a9959..cf9a657613d 100644
--- a/src/pl/plpgsql/src/plpgsql.h
+++ b/src/pl/plpgsql/src/plpgsql.h
@@ -435,6 +435,15 @@ typedef struct PLpgSQL_rec
/* We always store record variables as "expanded" records */
ExpandedRecordHeader *erh;
+
+ /*
+ * Composite type version snapshot for ALTER TYPE detection.
+ * Populated when the record is assigned; checked at RETURN time.
+ * Includes the outermost type and all nested composite types.
+ */
+ int nCompTypes;
+ Oid *compTypeOids;
+ uint64 *compTypeVersions;
} PLpgSQL_rec;
/*
diff --git a/src/pl/plpgsql/src/sql/plpgsql_record.sql b/src/pl/plpgsql/src/sql/plpgsql_record.sql
index 4fbed38b8bb..95f40e15b2f 100644
--- a/src/pl/plpgsql/src/sql/plpgsql_record.sql
+++ b/src/pl/plpgsql/src/sql/plpgsql_record.sql
@@ -577,3 +577,61 @@ insert into two_int8s_tab values (compresult(42));
-- reconnect so we lose any local knowledge of anonymous record types
\c -
table two_int8s_tab;
+
+-- Tests for bug #19382: server crash when ALTER TYPE is used mid-transaction
+-- in PL/pgSQL. Record variables populated before ALTER TYPE must not be
+-- returned, as the stored data no longer matches the current type definition.
+
+-- Case 1: Direct composite type change (INT -> TEXT)
+create type bug19382_foo as (a int, b int);
+create function bug19382_test_direct() returns record as $$
+declare r bug19382_foo := row(123, power(2, 30));
+begin
+ alter type bug19382_foo alter attribute b type text;
+ return r;
+end;
+$$ language plpgsql;
+select bug19382_test_direct();
+drop function bug19382_test_direct();
+drop type bug19382_foo cascade;
+
+-- Case 2: Nested composite type change
+create type bug19382_inner as (x int, y int);
+create type bug19382_outer as (a int, b bug19382_inner);
+create function bug19382_test_nested() returns record as $$
+declare r bug19382_outer;
+begin
+ r := row(1, row(10, power(2, 30)::int4)::bug19382_inner)::bug19382_outer;
+ alter type bug19382_inner alter attribute y type text;
+ return r;
+end;
+$$ language plpgsql;
+select bug19382_test_nested();
+drop function bug19382_test_nested();
+drop type bug19382_outer cascade;
+drop type bug19382_inner cascade;
+
+-- Case 3: OUT parameter
+create type bug19382_foo1 as (a int, b int);
+create function bug19382_test_out(out r1 bug19382_foo1) as $$
+begin
+ r1 := row(1, 2);
+ alter type bug19382_foo1 alter attribute b type text;
+ return;
+end;
+$$ language plpgsql;
+select bug19382_test_out();
+drop function bug19382_test_out();
+drop type bug19382_foo1 cascade;
+
+-- Case 4: No ALTER TYPE (baseline — must not error)
+create type bug19382_foo2 as (a int, b int);
+create function bug19382_test_baseline() returns bug19382_foo2 as $$
+declare r bug19382_foo2 := row(1, 2);
+begin
+ return r;
+end;
+$$ language plpgsql;
+select bug19382_test_baseline();
+drop function bug19382_test_baseline();
+drop type bug19382_foo2;
--
2.39.5 (Apple Git-154)
reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Reply to all the recipients using the --to and --cc options:
reply via email
To: [email protected]
Cc: [email protected], [email protected], [email protected], [email protected], [email protected]
Subject: Re: BUG #19382: Server crash at __nss_database_lookup
In-Reply-To: <CAOVWO5o9YOpCTgg6FfNepCoH_6pFSa7TJ3SEWfJAoBvNOb0OdQ@mail.gmail.com>
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox