Skip to content

Give each compiled function its own memory context#22

Merged
jdatcmd merged 1 commit into
masterfrom
memory-hardening
Jul 6, 2026
Merged

Give each compiled function its own memory context#22
jdatcmd merged 1 commit into
masterfrom
memory-hardening

Conversation

@jdatcmd

@jdatcmd jdatcmd commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Implements the FIXME that has been in plphp.c since the beginning: each compiled function's descriptor and all its subsidiary data (name, fmgr info records, and whatever those allocate) now live in a private memory context, child of TopMemoryContext, deleted wholesale when the function is redefined.

What this fixes

  • A use-after-free window. The old code freed the stale descriptor while the compiled-function cache still pointed at it, re-adding the cache entry only later in recompilation. Any error in between — realistically a statement cancel or OOM landing inside a syscache lookup — left the cache dangling for the next call to dereference. The cache entry is now removed before the old context is deleted; a failed compilation just leaves it absent.
  • fmgr allocations pinned forever. fmgr_info_cxt targets the function's context instead of TopMemoryContext, so non-builtin I/O function state no longer accumulates across redefinitions.
  • Error-path cleanup: PG_TRY reclaims the context on any compilation error before the descriptor is published; the scattered free() pairs are gone; a long-standing -Wshadow warning with them.

Honest measurement

A 5000× CREATE OR REPLACE churn shows descriptor-side memory flat; the residual ~1.8 kB/redefinition is Zend's per-eval compilation arena — identical before and after, bounded by redefinition frequency, and not addressable from plphp.

Tests

The recompilation path had zero coverage before; new cases in coverage: 50 consecutive redefine-and-call cycles, ALTER FUNCTION invalidation, and recovery after replacing a function with an uncompilable body. Full suite green on PG 11–18 + jsonb_plphp in the container sweep.

🤖 Generated with Claude Code

Implements the FIXME that has been in this file since the beginning:
a compiled function's descriptor and everything hanging off it (the
name, fmgr info records and whatever they allocate) now live in a
private memory context, child of TopMemoryContext, deleted wholesale
when the function is redefined.  fmgr info records consequently no
longer accumulate in TopMemoryContext across redefinitions.

This also closes a use-after-free window: the old code freed the
stale descriptor while the compiled-function cache still pointed at
it, and only re-added the cache entry later in recompilation.  Any
error raised in between -- realistically an unluckily timed statement
cancel or an out-of-memory inside a syscache lookup -- left the cache
holding a dangling pointer that the next call would dereference.  The
cache entry is now removed before the old context is deleted, and a
compilation failure just leaves the entry absent.

Compilation errors before the new descriptor is published reclaim its
context via PG_TRY; a PHP-level eval failure keeps the established
poisoned-xmin retry scheme.  The scattered free() pairs in error
paths are gone, and the inner "pointer" variable is renamed, silencing
a long-standing -Wshadow warning.

New coverage cases exercise the previously untested recompilation
path: 50 consecutive CREATE OR REPLACE cycles with a call between
each, ALTER FUNCTION invalidation, and recovery after replacing a
function with an uncompilable body.

Measured with a 5000-redefinition churn: descriptor-side memory is
flat; the remaining ~1.8kB/redefinition of backend growth is Zend's
per-eval compilation arena, identical before and after this change,
and bounded by how often functions are redefined in a session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jdatcmd
jdatcmd merged commit 5269386 into master Jul 6, 2026
11 checks passed
@jdatcmd
jdatcmd deleted the memory-hardening branch July 6, 2026 02:18
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.

2 participants