pg.ddx.io  pgsql-hackers@postgresql.org mailing list archive  
help / color / mirror / Atom feed
From: Florin Irion <irionr@gmail.com>
To: pgsql-hackers@lists.postgresql.org
Cc: Álvaro Herrera <alvherre@kurilemu.de>
Subject: UBSAN crash in EventTriggerCollectAlterTSConfig (memcpy with NULL src)
Date: Tue, 3 Mar 2026 18:33:40 +0100
Message-ID: <c6fff161-9aee-4290-9ada-71e21e4d84de@gmail.com> (raw)

Hi,

While working on the pg_get_domain_ddl() patch [1], I installed an
event trigger on ddl_command_end in test_setup.sql to automatically
round-trip DDL.  This triggered a UBSAN crash on CI [2] that turns out
to be a pre-existing bug since b488c580aef(?).


The DROP MAPPING code path in tsearchcmds.c unconditionally calls:

```
EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0);
```

Inside EventTriggerCollectAlterTSConfig(), this reaches:

```
command->d.atscfg.dictIds = palloc_array(Oid, ndicts);   /* ndicts=0 */
memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
                                    ^^^^^^^ NULL
```

Under -fsanitize=undefined this triggers SIGABRT and crashes the server.

The bug has been latent for 11 years because without an active event
trigger, currentEventTriggerState is NULL and the function returns early
at the top, never reaching the memcpy.  The standard regression suite
never had an event trigger installed during the tsdicts test — until now.

Reproducer (crashes only under UBSAN) and patch attached:


This affects all branches back to 9.5 where b488c580aef landed.

[1] https://www.postgresql.org/message-id/CAPgqM1V4LW2qiDLPsusb7s0kYbSDJjH5Tt%2B-ZzVmPU7xV0TJNQ%40mail.g...
[2] https://cirrus-ci.com/task/6170470552174592

--

Cheers,
Florin

EDB -- www.enterprisedb.com
From bc38f7016c868ff1be68214b4a134773cda214cb Mon Sep 17 00:00:00 2001
From: Florin Irion <florin.irion@enterprisedb.com>
Date: Tue, 3 Mar 2026 17:41:23 +0100
Subject: [PATCH v1 1/2] Add regression test for
 EventTriggerCollectAlterTSConfig with NULL dictIds

The DROP MAPPING code path in tsearchcmds.c calls
EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0).  When an event
trigger on ddl_command_end is active, this reaches memcpy(dest, NULL, 0)
which is undefined behavior.  Under -fsanitize=undefined this crashes
the server.

Add a test that exercises this exact code path: a ddl_command_end event
trigger combined with ALTER TEXT SEARCH CONFIGURATION ... DROP MAPPING.

Bug latent since b488c580aef (2015).
---
 src/test/regress/expected/event_trigger.out | 15 +++++++++++++++
 src/test/regress/sql/event_trigger.sql      | 18 ++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 16e4530708c..1f37f4c3d5a 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -817,3 +817,18 @@ NOTICE:  DROP POLICY dropped policy
 CREATE POLICY pguc ON event_trigger_test USING (FALSE);
 SET event_triggers = 'off';
 DROP POLICY pguc ON event_trigger_test;
+-- Test that EventTriggerCollectAlterTSConfig handles DROP MAPPING
+-- with ndicts=0, dictIds=NULL (the DROP MAPPING code path always
+-- passes NULL,0).  Previously crashed under UBSAN due to
+-- memcpy(dest, NULL, 0) being undefined behavior.
+CREATE FUNCTION noop_event_trigger() RETURNS event_trigger
+LANGUAGE plpgsql AS $$ BEGIN END; $$;
+CREATE EVENT TRIGGER noop_event_trigger ON ddl_command_end
+    EXECUTE FUNCTION noop_event_trigger();
+SET event_triggers = 'on';
+CREATE TEXT SEARCH CONFIGURATION evttrig_tscfg (COPY = pg_catalog.simple);
+ALTER TEXT SEARCH CONFIGURATION evttrig_tscfg
+    DROP MAPPING FOR word;
+DROP TEXT SEARCH CONFIGURATION evttrig_tscfg;
+DROP EVENT TRIGGER noop_event_trigger;
+DROP FUNCTION noop_event_trigger;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index c613c0cfd43..d955b3d0abe 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -638,3 +638,21 @@ DROP POLICY pguc ON event_trigger_test;
 CREATE POLICY pguc ON event_trigger_test USING (FALSE);
 SET event_triggers = 'off';
 DROP POLICY pguc ON event_trigger_test;
+
+-- Test that EventTriggerCollectAlterTSConfig handles DROP MAPPING
+-- with ndicts=0, dictIds=NULL (the DROP MAPPING code path always
+-- passes NULL,0).  Previously crashed under UBSAN due to
+-- memcpy(dest, NULL, 0) being undefined behavior.
+CREATE FUNCTION noop_event_trigger() RETURNS event_trigger
+LANGUAGE plpgsql AS $$ BEGIN END; $$;
+CREATE EVENT TRIGGER noop_event_trigger ON ddl_command_end
+    EXECUTE FUNCTION noop_event_trigger();
+SET event_triggers = 'on';
+
+CREATE TEXT SEARCH CONFIGURATION evttrig_tscfg (COPY = pg_catalog.simple);
+ALTER TEXT SEARCH CONFIGURATION evttrig_tscfg
+    DROP MAPPING FOR word;
+
+DROP TEXT SEARCH CONFIGURATION evttrig_tscfg;
+DROP EVENT TRIGGER noop_event_trigger;
+DROP FUNCTION noop_event_trigger;
-- 
2.45.1


From 04344b57c717271a372d1e6335309e0fe53458b6 Mon Sep 17 00:00:00 2001
From: Florin Irion <florin.irion@enterprisedb.com>
Date: Tue, 3 Mar 2026 17:44:18 +0100
Subject: [PATCH v1 2/2] Fix UBSAN crash in EventTriggerCollectAlterTSConfig

Guard the palloc_array + memcpy with an ndicts > 0 check.  The command
struct is already palloc0'd so dictIds defaults to NULL.
---
 src/backend/commands/event_trigger.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 028f9e2de90..2898967fa67 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2004,8 +2004,11 @@ EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
 	command->in_extension = creating_extension;
 	ObjectAddressSet(command->d.atscfg.address,
 					 TSConfigRelationId, cfgId);
-	command->d.atscfg.dictIds = palloc_array(Oid, ndicts);
-	memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	if (ndicts > 0)
+	{
+		command->d.atscfg.dictIds = palloc_array(Oid, ndicts);
+		memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	}
 	command->d.atscfg.ndicts = ndicts;
 	command->parsetree = (Node *) copyObject(stmt);
 
-- 
2.45.1



Attachments:

  [text/plain] v1-0001-Add-regression-test-for-EventTriggerCollectAlterT.patch (3.2K, ../c6fff161-9aee-4290-9ada-71e21e4d84de@gmail.com/2-v1-0001-Add-regression-test-for-EventTriggerCollectAlterT.patch)
  download | inline diff:
From bc38f7016c868ff1be68214b4a134773cda214cb Mon Sep 17 00:00:00 2001
From: Florin Irion <florin.irion@enterprisedb.com>
Date: Tue, 3 Mar 2026 17:41:23 +0100
Subject: [PATCH v1 1/2] Add regression test for
 EventTriggerCollectAlterTSConfig with NULL dictIds

The DROP MAPPING code path in tsearchcmds.c calls
EventTriggerCollectAlterTSConfig(stmt, cfgId, NULL, 0).  When an event
trigger on ddl_command_end is active, this reaches memcpy(dest, NULL, 0)
which is undefined behavior.  Under -fsanitize=undefined this crashes
the server.

Add a test that exercises this exact code path: a ddl_command_end event
trigger combined with ALTER TEXT SEARCH CONFIGURATION ... DROP MAPPING.

Bug latent since b488c580aef (2015).
---
 src/test/regress/expected/event_trigger.out | 15 +++++++++++++++
 src/test/regress/sql/event_trigger.sql      | 18 ++++++++++++++++++
 2 files changed, 33 insertions(+)

diff --git a/src/test/regress/expected/event_trigger.out b/src/test/regress/expected/event_trigger.out
index 16e4530708c..1f37f4c3d5a 100644
--- a/src/test/regress/expected/event_trigger.out
+++ b/src/test/regress/expected/event_trigger.out
@@ -817,3 +817,18 @@ NOTICE:  DROP POLICY dropped policy
 CREATE POLICY pguc ON event_trigger_test USING (FALSE);
 SET event_triggers = 'off';
 DROP POLICY pguc ON event_trigger_test;
+-- Test that EventTriggerCollectAlterTSConfig handles DROP MAPPING
+-- with ndicts=0, dictIds=NULL (the DROP MAPPING code path always
+-- passes NULL,0).  Previously crashed under UBSAN due to
+-- memcpy(dest, NULL, 0) being undefined behavior.
+CREATE FUNCTION noop_event_trigger() RETURNS event_trigger
+LANGUAGE plpgsql AS $$ BEGIN END; $$;
+CREATE EVENT TRIGGER noop_event_trigger ON ddl_command_end
+    EXECUTE FUNCTION noop_event_trigger();
+SET event_triggers = 'on';
+CREATE TEXT SEARCH CONFIGURATION evttrig_tscfg (COPY = pg_catalog.simple);
+ALTER TEXT SEARCH CONFIGURATION evttrig_tscfg
+    DROP MAPPING FOR word;
+DROP TEXT SEARCH CONFIGURATION evttrig_tscfg;
+DROP EVENT TRIGGER noop_event_trigger;
+DROP FUNCTION noop_event_trigger;
diff --git a/src/test/regress/sql/event_trigger.sql b/src/test/regress/sql/event_trigger.sql
index c613c0cfd43..d955b3d0abe 100644
--- a/src/test/regress/sql/event_trigger.sql
+++ b/src/test/regress/sql/event_trigger.sql
@@ -638,3 +638,21 @@ DROP POLICY pguc ON event_trigger_test;
 CREATE POLICY pguc ON event_trigger_test USING (FALSE);
 SET event_triggers = 'off';
 DROP POLICY pguc ON event_trigger_test;
+
+-- Test that EventTriggerCollectAlterTSConfig handles DROP MAPPING
+-- with ndicts=0, dictIds=NULL (the DROP MAPPING code path always
+-- passes NULL,0).  Previously crashed under UBSAN due to
+-- memcpy(dest, NULL, 0) being undefined behavior.
+CREATE FUNCTION noop_event_trigger() RETURNS event_trigger
+LANGUAGE plpgsql AS $$ BEGIN END; $$;
+CREATE EVENT TRIGGER noop_event_trigger ON ddl_command_end
+    EXECUTE FUNCTION noop_event_trigger();
+SET event_triggers = 'on';
+
+CREATE TEXT SEARCH CONFIGURATION evttrig_tscfg (COPY = pg_catalog.simple);
+ALTER TEXT SEARCH CONFIGURATION evttrig_tscfg
+    DROP MAPPING FOR word;
+
+DROP TEXT SEARCH CONFIGURATION evttrig_tscfg;
+DROP EVENT TRIGGER noop_event_trigger;
+DROP FUNCTION noop_event_trigger;
-- 
2.45.1



  [text/plain] v1-0002-Fix-UBSAN-crash-in-EventTriggerCollectAlterTSConf.patch (1.3K, ../c6fff161-9aee-4290-9ada-71e21e4d84de@gmail.com/3-v1-0002-Fix-UBSAN-crash-in-EventTriggerCollectAlterTSConf.patch)
  download | inline diff:
From 04344b57c717271a372d1e6335309e0fe53458b6 Mon Sep 17 00:00:00 2001
From: Florin Irion <florin.irion@enterprisedb.com>
Date: Tue, 3 Mar 2026 17:44:18 +0100
Subject: [PATCH v1 2/2] Fix UBSAN crash in EventTriggerCollectAlterTSConfig

Guard the palloc_array + memcpy with an ndicts > 0 check.  The command
struct is already palloc0'd so dictIds defaults to NULL.
---
 src/backend/commands/event_trigger.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/src/backend/commands/event_trigger.c b/src/backend/commands/event_trigger.c
index 028f9e2de90..2898967fa67 100644
--- a/src/backend/commands/event_trigger.c
+++ b/src/backend/commands/event_trigger.c
@@ -2004,8 +2004,11 @@ EventTriggerCollectAlterTSConfig(AlterTSConfigurationStmt *stmt, Oid cfgId,
 	command->in_extension = creating_extension;
 	ObjectAddressSet(command->d.atscfg.address,
 					 TSConfigRelationId, cfgId);
-	command->d.atscfg.dictIds = palloc_array(Oid, ndicts);
-	memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	if (ndicts > 0)
+	{
+		command->d.atscfg.dictIds = palloc_array(Oid, ndicts);
+		memcpy(command->d.atscfg.dictIds, dictIds, sizeof(Oid) * ndicts);
+	}
 	command->d.atscfg.ndicts = ndicts;
 	command->parsetree = (Node *) copyObject(stmt);
 
-- 
2.45.1



view thread (5+ messages)  latest in thread

Message-ID: <c6fff161-9aee-4290-9ada-71e21e4d84de@gmail.com>
Permalink:  ../c6fff161-9aee-4290-9ada-71e21e4d84de@gmail.com/
Also on:    postgresql.org/message-id/c6fff161-9aee-4290-9ada-71e21e4d84de@gmail.com

 ·  · 

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: pgsql-hackers@postgresql.org
  Cc: irionr@gmail.com, pgsql-hackers@lists.postgresql.org, alvherre@kurilemu.de
  Subject: Re: UBSAN crash in EventTriggerCollectAlterTSConfig (memcpy with NULL src)
  In-Reply-To: <c6fff161-9aee-4290-9ada-71e21e4d84de@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 DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox