Subject: [PATCH] Fix NULL dereference in pg_get_database_ddl()

From: Ayush Tiwari <[email-masked]>
Date: Fri, 10 Apr 2026 19:27:58 +0530

Hi,

pg_get_database_ddl_internal() can dereference a NULL pointer when
pg_database.dattablespace points to a tablespace OID that no longer
exists.

The immediate issue is that get_tablespace_name() may return NULL, but
the result is passed directly to pg_strcasecmp():

spcname = get_tablespace_name(dbform->dattablespace);
if (pg_strcasecmp(spcname, "pg_default") != 0)
...

That leads to a backend crash.  I reproduced it on current master as a
SIGSEGV with crash recovery.

This function was introduced by commit a4f774cf1c7.

Deterministic reproduction:

CREATE DATABASE regression_testdb;
SET allow_system_table_mods = on;
UPDATE pg_database
 SET dattablespace = 99999
 WHERE datname = 'regression_testdb';
RESET allow_system_table_mods;

SELECT * FROM pg_get_database_ddl('regression_testdb');

The attached patch fixes this by checking for NULL before calling
pg_strcasecmp().  In that case, pg_get_database_ddl() simply omits the
TABLESPACE clause.

I also added a regression test in database_ddl.sql that exercises this
case by setting dattablespace to a non-existent OID and verifying that
the function returns successfully.

Patch attached. Please review and let me know if it needs any edits. Thanks!

Regards,
Ayush Tiwari

---

From: Ayush Tiwari <[email-masked]>
Date: Fri, 10 Apr 2026 19:43:10 +0530

> Patch attached. Please review and let me know if it needs any edits.

Re-attaching patch without trailing white-space

Regards,
Ayush

---

From: David Rowley <[email-masked]>
Date: Mon, 13 Apr 2026 21:32:32 +1200

> pg_strcasecmp().  In that case, pg_get_database_ddl() simply omits the

Can you explain why this method of self-inflicted catalogue corruption
is any more important than any of the just-about-infinite other ways
there are of causing issues by manually updating the catalogue tables?

The typical response to this sort of thing can be seen in the thread
in [1], in particular, the response in [2].

David

[1] https://www.postgresql.org/message-id/[email-masked]
[2] https://www.postgresql.org/message-id/1538113.1768921841%40sss.pgh.pa.us

---

From: Ayush Tiwari <[email-masked]>
Date: Mon, 13 Apr 2026 18:23:35 +0530

Hi,

On Mon, 13 Apr 2026 at 15:02, David Rowley <[email-masked]> wrote:

> [1]
> https://www.postgresql.org/message-id/[email-masked]
> [2]
> https://www.postgresql.org/message-id/1538113.1768921841%40sss.pgh.pa.us

Thanks for the review.

I dug into this further and I don't think I can support the patch as a real
bug fix.

My original thought was that there might be a supported concurrent-DDL path
where
pg_get_database_ddl_internal() reads pg_database.dattablespace, and then a
concurrent change makes get_tablespace_name() return NULL before the second
lookup. I tried to reproduce that by widening the window and testing
ALTER DATABASE ... SET TABLESPACE together with DROP TABLESPACE, but I
could not
make it happen.

The reason seems to be that both catalog reads are governed by the same
statement-level CatalogSnapshot, so the function continues to see a
consistent
catalog view for the duration of the call. On the other side, DROP
TABLESPACE
also doesn't appear able to commit a state where pg_database still points
at a
vanished tablespace row through supported SQL, because the drop fails while
the
database directory is still present.

So at this point the remaining crash case seems to require a broken catalog
state,
which puts this in the same bucket as the precedent you cited rather than a
supported-path bug.

Given that, I don't think this patch is worth pursuing further in its
current
form. I'll drop it here unless I find a supported reproducer or a different,
stronger issue around the missing database/tablespace bookkeeping.

Thanks again for the push to investigate it more carefully.

Regards,
Ayush