Skip to content

fix: prevent catalog scan use-after-free (1.5)#506

Merged
staticlibs merged 1 commit into
duckdb:v1.5-variegatafrom
staticlibs:catalog_uaf_15
Jul 8, 2026
Merged

fix: prevent catalog scan use-after-free (1.5)#506
staticlibs merged 1 commit into
duckdb:v1.5-variegatafrom
staticlibs:catalog_uaf_15

Conversation

@staticlibs

Copy link
Copy Markdown
Member

This is a backport of the PR #501 to v1.5-variegata stable branch.

Resolves #502

PostgresCatalogSet::Scan hands its callback a bare CatalogEntry& without pinning it into the calling transaction first. GetEntry and CreateEntry both keep the underlying shared_ptr<CatalogEntry> alive in PostgresTransaction::referenced_entries for the rest of the transaction. Scan does not.

If pg_clear_cache runs after a Scan but before every reference handed out by that Scan is done being used, ClearEntries drops the shared_ptr for those entries and destroys them. Anything still holding a reference from the earlier Scan is then reading freed memory; i.e., funcs from Duckdb core like duckdb_tables() or duckdb_vies()

Pinning every entry into the calling transaction inside Scan's existing lock scope, the same way GetEntry and CreateEntry already do:

for (auto &entry : entries) {
        transaction.ReferenceEntry(entry.second);
        callback(*entry.second);
}

ReferenceEntry/referenced_entries is already the established solution for this exact problem. Scan just wasn't using it yet

This is a backport of the PR duckdb#501 to v1.5-variegata stable branch.

Resolves duckdb#502

`PostgresCatalogSet::Scan` hands its callback a bare `CatalogEntry&`
without pinning it into the calling transaction first. `GetEntry` and
`CreateEntry` both keep the underlying `shared_ptr<CatalogEntry>`
alive in `PostgresTransaction::referenced_entries` for the rest of the
transaction. `Scan` does not.

If `pg_clear_cache` runs after a `Scan` but before every reference
handed out by that `Scan` is done being used, `ClearEntries` drops
the `shared_ptr` for those entries and destroys them. Anything still
holding a reference from the earlier `Scan` is then reading freed
memory; i.e., funcs from Duckdb core like `duckdb_tables()` or
`duckdb_vies()`

Pinning every entry into the calling transaction inside `Scan`'s
existing lock scope, the same way `GetEntry` and `CreateEntry` already
do:

```cpp
for (auto &entry : entries) {
        transaction.ReferenceEntry(entry.second);
        callback(*entry.second);
}
```

`ReferenceEntry`/`referenced_entries` is already the established
solution for this exact problem. `Scan` just wasn't using it yet
@staticlibs staticlibs merged commit 2bb2683 into duckdb:v1.5-variegata Jul 8, 2026
7 checks passed
@staticlibs staticlibs deleted the catalog_uaf_15 branch July 8, 2026 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant