Skip to content

Fix memory pool corruption after allocation failure in FreeObjects::newBlock() - #9250

Open
gabrieleciech wants to merge 2 commits into
FirebirdSQL:masterfrom
gabrieleciech:fix/newblock-exception-safety
Open

gabrieleciech wants to merge 2 commits into
FirebirdSQL:masterfrom
gabrieleciech:fix/newblock-exception-safety

Conversation

@gabrieleciech

Copy link
Copy Markdown

FreeObjects::newBlock() retires the current extent (decrUsage()) before calling
pool->newExtent(). If newExtent() throws, currentExtent still points to the retired extent
and the next call retires it again, so every allocation failure drops one more usage reference.
The medium hunk's useCount goes below the number of live blocks; when it reaches zero the hunk
is reclaimed while still in use, and unlinkBlocks() treats live blocks as free list elements
(SIGSEGV in SemiDoubleLink::remove(), writes into unrelated memory, or use-after-free).

Change

  • Allocate the new extent first, then retire the old one. If newExtent() throws, the old extent
    keeps its usage reference and is retired by the next successful call.
  • !currentExtent is now handled in the same branch, so the separate "no current extent" block
    is gone; behaviour on success is unchanged.

Test

New Boost test CommonSuite/AllocSuite/AllocTests/MediumExtentAllocationFailure
(src/common/classes/tests/AllocTest.cpp, Linux only): it makes two allocations of the next
medium extent fail by lowering RLIMIT_AS, then allocates and frees normally.

  • without the fix: memory access violation at address 0x0 (Release); fb_assert(e->prev) in Debug
    on 3.0.14;
  • with the fix: passes; the full common_test passes.

Observed in production

Firebird 3.0.14.33856, Linux x64, Classic Server. After clients received "unable to allocate
memory from operating system", many Classic processes crashed within a few seconds of each other
during attachment cleanup (purge_attachment → MET_clear_cache → MemoryPool::deletePool),
mostly at SemiDoubleLink::remove() with a write to NULL. In the core dumps the reclaimed medium
hunk of the attachment pool has useCount == 0 while still holding a live block; in another case
a statement pool faults in ~FreeObjects() because its current extent lies in a medium hunk of
the (still alive) attachment pool that had already been unmapped.

Notes

The same code is in v4.0-release and v5.0-release (and 3.0)

gabrieleciech and others added 2 commits October 29, 2026 15:29
…allocation failure

When newExtent() throws (e.g. std::bad_alloc), the current extent has already
been retired with decrUsage() but currentExtent still points to it, so the next
call retires it again. Its usage count drops below the number of live blocks
and the extent (and the medium hunk holding it) can be reclaimed while still
in use, leading to crashes in unlinkBlocks()/SemiDoubleLink::remove().

Allocate the new extent first and retire the old one only after success.
Add a unit test that forces the allocation failure via RLIMIT_AS.
@AlexPeshkoff AlexPeshkoff self-assigned this Oct 29, 2026
@AlexPeshkoff

Copy link
Copy Markdown
Member

I see no problems with you fix - just wait for started now CI.
Will you provide backports to versions 3,4,5 ?

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