Skip to content

Fix a possible use-after-free in EDS transaction rollback#8991

Open
MochalovAlexey wants to merge 1 commit intoFirebirdSQL:masterfrom
MochalovAlexey:eds_transaction_rollback_crash
Open

Fix a possible use-after-free in EDS transaction rollback#8991
MochalovAlexey wants to merge 1 commit intoFirebirdSQL:masterfrom
MochalovAlexey:eds_transaction_rollback_crash

Conversation

@MochalovAlexey
Copy link
Copy Markdown
Contributor

EDS::Transaction::rollback() used to call m_connection.deleteTransaction() before reporting rollback errors. This cleanup may release or delete the owning EDS::Connection.

If rollback returned an error, the code then called conn.raise(), which uses getDataSourceName() and reads m_dbName from the potentially deleted connection. This could lead to a crash while building the error message.

The fix raises the rollback error while the connection is still alive and uses a catch/rethrow block to guarantee external transaction cleanup before propagating the original exception.

Raise EDS rollback exception before the connection can be released by
deleteTransaction(). Cleanup is still performed by catching the error and rethrow the original exception.
@aafemt
Copy link
Copy Markdown
Contributor

aafemt commented Apr 16, 2026

I would suggest to use Cleanup class instead:

Cleanup cleanup = [&]()
{
	if (!retain)
	{
		detachFromJrdTran();
		m_connection.deleteTransaction(tdbb, this);
	}
};

Comment thread src/jrd/extds/ExtDS.cpp
detachFromJrdTran();
m_connection.deleteTransaction(tdbb, this);
}
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not use class Cleanup here and avoid re-throw and explicit cleanup ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Answered below #8991 (comment)

@MochalovAlexey
Copy link
Copy Markdown
Contributor Author

I would suggest to use Cleanup class instead:

Cleanup cleanup = [&]()
{
	if (!retain)
	{
		detachFromJrdTran();
		m_connection.deleteTransaction(tdbb, this);
	}
};

That would be risky here because the cleanup destructor may throw from deleteTransaction().
If that happens while another exception is already being unwound, C++ will call std::terminate().

@aafemt
Copy link
Copy Markdown
Contributor

aafemt commented Apr 16, 2026

That would be risky here because the cleanup destructor may throw from deleteTransaction().

Because it is a cleanup routine, I would make sure that it cannot throw. Throwing from rollback() can create an unrecoverable state of code.

@dyemanov
Copy link
Copy Markdown
Member

Strictly speaking Alexey is correct. I'd say all usages of class Cleanup should be revisited, because CCH_release, TRA_rollback, whatever -- all they can throw (even if unlikely). Moreover, our code in /common/classes often throws from RAII destructors (usually this is system_call_failed). The question is whether we're going to solve this problem globally before v6 is out.

@aafemt
Copy link
Copy Markdown
Contributor

aafemt commented Apr 16, 2026

I'd say all usages of class Cleanup should be revisited,

They say clang-tidy can track cases when noexcept functions call non-noexcept functions. May be it can handle destructors and lambdas. In this case it could be enough to mark Cleanup parameter as noexcept.

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.

5 participants