BUG: Accessing Temporary Tables of Other Sessions via Relation Extension Path
Core Problem
PostgreSQL's temporary table isolation has a gap in its enforcement mechanism. Two prior commits (40927d458fe1 and ce146621f786) added checks to prevent sessions from accessing temporary tables belonging to other sessions, but they placed these checks in ReadBufferExtended. This left an uncovered code path: when a table has zero pages (is empty), the first INSERT doesn't go through ReadBufferExtended at all.
The Architectural Root Cause
The insert path for an empty relation follows:
heap_insert → RelationGetBufferForTuple → RelationAddBlocks → ExtendBufferedRelLocal
When extending a relation (adding new blocks), PostgreSQL bypasses the buffer read path entirely — it extends the relation's storage via smgr and allocates a new buffer directly. This means:
- The
RELATION_IS_OTHER_TEMPcheck inReadBufferExtendedis never hit — the code path for extending a relation doesn't read existing blocks; it creates new ones. - A local temp buffer is allocated in the wrong session's buffer pool — the extending session creates a buffer in its own
temp_bufferspool for a relation whose physical files belong to another session's temporary tablespace. - Subsequent flush attempts fail catastrophically — when the buffer manager tries to write back this buffer, it attempts to open a file path (e.g.,
base/5/t3_16386) that doesn't exist in the current session's context, producingERROR: could not open file.
The second INSERT does fail with the correct error because by then the relation has a page (the one just created), so the code takes the ReadBufferExtended path to find free space on existing pages, hitting the existing check.
Why This Matters Architecturally
PostgreSQL's temp buffer system is fundamentally session-local by design. Each backend maintains its own pool of temporary buffers (temp_buffers) that are never shared via the shared buffer pool. The physical files for temporary relations use session-specific naming (the t prefix with a backend-specific path). Cross-session access to these structures is not just a permissions issue — it's architecturally impossible to do correctly without fundamental redesign. The existing checks are guardrails preventing data corruption and confusing errors, not policy enforcement.
Proposed Solution
The patch adds a RELATION_IS_OTHER_TEMP check inside ExtendBufferedRelLocal() (in src/backend/storage/buffer/localbuf.c). This is strategically chosen because:
- All temp-relation-extend paths flow through this function — it's the single bottleneck for local buffer extension.
- It catches the problem at the lowest common point — rather than patching individual callers (heap_insert, etc.), it ensures no future code path can accidentally extend another session's temp relation.
- It mirrors the existing check in
ReadBufferExtended— providing consistent enforcement for both read and extend operations.
The fix is minimal and surgical: a single RELATION_IS_OTHER_TEMP check with the standard error message, placed before any buffer allocation or smgr extension occurs.
Key Design Decisions and Tradeoffs
Why DROP of Other Session's Temp Tables Is Allowed
An important nuance emerges in the discussion: while data access (reading/writing pages) to other sessions' temp tables is prohibited, catalog operations like DROP are intentionally permitted. The rationale is:
- The prohibition is technical, not policy-based — the temp_buffers implementation cannot handle cross-session page access; DROP doesn't need to read data pages.
- Autovacuum needs this capability — orphaned temporary table cleanup requires the ability to drop temp tables whose owning session has crashed or disconnected.
- Administrative use cases exist — DBAs may need to clean up abandoned temp tables.
This distinction (catalog-level operations are fine; page-level access is not) is a deliberate architectural decision documented in the test suite (013_temp_obj_multisession.pl).
Check Placement Strategy
The choice to put the check in ExtendBufferedRelLocal rather than higher up (e.g., in heap_insert or RelationGetBufferForTuple) follows the principle of defense-in-depth at the storage layer boundary. This prevents not just INSERT but any operation that might extend a temp relation (COPY, bulk loading, index creation on temp tables, etc.) from bypassing the check.
Patch Versions
- v1: Initial fix with check in
ExtendBufferedRelLocal, comment says "covering any attempt to extend local relation" - v2: Identical logic, but comment updated to "covering any attempt to extend a temporary relation" to avoid confusion with the separate
RELATION_IS_LOCALmacro (which has different semantics related to relation locality in the catalog sense)