Fix use-after-free when ending a transaction with registered users - #7
Open
mariuz wants to merge 1 commit into
Open
Fix use-after-free when ending a transaction with registered users#7mariuz wants to merge 1 commit into
mariuz wants to merge 1 commit into
Conversation
Reading a WITH TIME ZONE column and then disconnecting raises EObjectCheck: Object reference is Nil from inside TFBTransaction.DoDefaultTransactionEnd, reached via TFBAttachment.EndAllTransactions during Disconnect. The sequence: reading a time zone value creates TFB30TimeZoneServices, which starts an internal transaction for the rdb$time_zone_util lookups and registers itself on it with AddObject. At disconnect, EndAllTransactions calls DoDefaultTransactionEnd on that transaction, whose loop calls TransactionEnding on each registered user. The time zone services then clears its own FTransaction, and since the interface list holds raw object pointers rather than counted references, that was the last reference - so the transaction is destroyed while DoDefaultTransactionEnd is still running, and the Commit at the end of that method operates on a freed object. Holding an ITransaction reference to self for the duration of the method keeps it alive until the work is finished. Reproduced against Firebird 6.0.0 with a twenty-line program: an integer column and a plain TIMESTAMP disconnect cleanly, TIMESTAMP WITH TIME ZONE and TIME WITH TIME ZONE do not. With this change all four disconnect cleanly.
mariuz
force-pushed
the
fix-use-after-free-in-transaction-end
branch
from
July 26, 2026 18:32
fbb29ba to
16e49b4
Compare
mariuz
added a commit
to mariuz/gmarathon-freepascal
that referenced
this pull request
Jul 29, 2026
The WITH TIME ZONE disconnect fault is a use-after-free in fbintf, not something this codebase can fix from outside. Root cause: reading a time zone value creates TFB30TimeZoneServices, which starts an internal transaction for the rdb$time_zone_util lookups and registers itself on it. At disconnect, DoDefaultTransactionEnd calls TransactionEnding on each registered user; the time zone services clears its own reference, and since the interface list holds raw object pointers rather than counted references, that is the last one. The transaction is destroyed while DoDefaultTransactionEnd is still running and its closing Commit operates on freed memory. Fix proposed upstream as MWASoftware/fbintf#7: hold a reference to self for the duration of the method. Verified against Firebird 6.0.0 - before it, TIMESTAMP WITH TIME ZONE and TIME WITH TIME ZONE fault while an integer and a plain TIMESTAMP do not; after it, all four disconnect cleanly. Kept as a patch file rather than by pointing the submodule at a fork, so that git submodule update keeps working against MWASoftware for everyone. Marathon does not depend on it being applied: its own queries cast such columns to text and SafeDisconnect contains the fault otherwise, and the smoke test reports which of the two states it is in. patches/README.md also records what was deliberately not patched - parallel workers and inline ODS upgrade would mean proposing untested API upstream, TIBQuery parameter types would change binding behaviour with no regression suite to justify it, and the fb_info_crypt_state classification did not take effect in testing so its cause is not yet understood. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mariuz
added a commit
to mariuz/gmarathon-freepascal
that referenced
this pull request
Jul 29, 2026
Until MWASoftware/fbintf#7 is merged, lib/fbintf points at https://github.com/mariuz/fbintf at the commit carrying the use-after-free fix, so that a fresh git submodule update --init --recursive builds with it instead of needing the patch applied by hand. The fork tracks upstream and carries nothing else. patches/README.md records the single step that undoes this once the pull request is merged - repoint the URL, drop the branch pin, move the submodule to an upstream commit containing the fix, and delete the patch file. The patch is kept alongside the fork so the change stays readable here and can be applied to any other checkout. The workarounds this makes unnecessary in principle - casting WITH TIME ZONE columns to text, and SafeDisconnect - deliberately stay. They serve anyone building against a stock fbintf, and the casts are also how the IANA zone name reaches the grid at all rather than being only a workaround. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
mariuz
added a commit
to mariuz/gmarathon-freepascal
that referenced
this pull request
Jul 29, 2026
Unblocked by patching the submodules rather than waiting on upstream. Two things were missing. consts_pub.inc predates Firebird 5 and defines no isc_spb_bkp_parallel_workers - value 21 in Firebird's own shipped header, where isc_spb_res_parallel_workers is the same code - and IBXServices built its service parameter block without one. The fbintf side now defines the constants and TIBXBackupRestoreService gained a published ParallelWorkers property, sent only when greater than one and the server is Firebird 5 or later, so nothing changes for existing callers or for older servers, which reject the parameter. The Maintenance dialog offers a worker count on the Backup tab, defaulting to 1. Verified against a live Firebird 6.0.0 server: with one worker the parameter is not sent, with four it is, and the resulting file restores to a working database. The "with four it is" half was checked with a temporary trace at the point the parameter is added, because the server clamps an absurd worker count rather than rejecting it - so a successful backup on its own proves nothing about whether the parameter was ever sent. Both submodules now point at forks pinned to the changes, so a fresh clone builds with them. Three pull requests are open upstream: MWASoftware/fbintf#7 (transaction use-after-free), fbintf#8 (the constants) and ibx4lazarus#23 (the property). The fbintf fork keeps the two changes on separate branches so those pull requests stay independent, with a marathon-integration branch carrying both for the submodule to pin. patches/README.md records the one-step revert for each once merged. Sweep is not covered: it takes the parameter through a different service and that was not exercised here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reading a
WITH TIME ZONEcolumn and then disconnecting raisesEObjectCheck: Object reference is Nilfrom insideTFBTransaction.DoDefaultTransactionEnd, reached viaTFBAttachment.EndAllTransactionsduringDisconnect.Cause
Reading a time zone value creates
TFB30TimeZoneServices, which starts aninternal transaction for the
rdb$time_zone_utillookups and registers itselfon it:
FTransaction := FAttachment.StartTransaction([...], taCommit); (FTransaction as TFBTransaction).AddObject(self);At disconnect,
EndAllTransactionscallsDoDefaultTransactionEndon thattransaction. Its loop calls
TransactionEndingon each registered user, andthe time zone services clears its own reference:
TInterfaceOwnerstores rawTInterfacedObjectpointers rather than countedreferences, and
EndAllTransactionsiterates them without taking one, so thatwas the last reference to the transaction. The object is destroyed while
DoDefaultTransactionEndis still executing, and theCommit(Force)at theend of that same method then runs on freed memory.
Fix
Hold an
ITransactionreference toselffor the duration of the method, so auser's
TransactionEndingcannot destroy the transaction out from under theCommit/Rollbackthat follows.Reproduction
Against Firebird 6.0.0, with a table holding an integer, a plain
TIMESTAMP,a
TIMESTAMP WITH TIME ZONEand aTIME WITH TIME ZONE; each case opens oneTIBQuery, closes it, commits, then disconnects:integerTIMESTAMPTIMESTAMP WITH TIME ZONEEObjectCheckTIME WITH TIME ZONEEObjectCheckIt is not schema qualification, not the prepared statement (unpreparing first
does not help), and not global temporary tables; freeing the query and the
transaction before disconnecting does not help either, which is what pointed at
the attachment rather than the query.
Found while adding Firebird 4/5/6 support to Marathon, where it also surfaced
through the profiler:
PLG$PROF_SESSIONShasTIMESTAMP WITH TIME ZONEcolumns, so reading it made the following disconnect fault.