gh-145040: Fix crash in sqlite3 when connection is closed from within a callback#145041
gh-145040: Fix crash in sqlite3 when connection is closed from within a callback#145041raminfp wants to merge 8 commits intopython:mainfrom
Conversation
…g aggregate callback Fix a segmentation fault in the _sqlite module that occurs when Connection.close() is called inside an aggregate step() callback. After stmt_step() returns, _pysqlite_query_execute() calls sqlite3_last_insert_rowid(self->connection->db) without checking if self->connection->db is still valid. If the connection was closed during the callback, self->connection->db is NULL, causing a NULL pointer dereference. The fix adds a NULL check for self->connection->db after stmt_step() returns, raising ProgrammingError instead of crashing.
Misc/NEWS.d/next/Library/2026-02-20-00-00-00.gh-issue-145040.sqlite3-close-crash.rst
Outdated
Show resolved
Hide resolved
…qlite3-close-crash.rst Co-authored-by: Benedikt Johannes <benedikt.johannes.hofer@gmail.com>
|
Please update the PR title to more accurately reflect the problem it addresses. Ditto for the NEWS item. |
…within a callback
4305a82 to
05086f0
Compare
|
Sorry for force push!!! |
|
This PR adds a sanity check after the damage has been done. The SQLite C API clearly tells us what's legal and illegal operations on the |
…lback Instead of detecting a closed connection after the damage has been done, prevent Connection.close() from succeeding while a SQLite callback is executing. This aligns with the SQLite C API docs, which state that applications must not close the database connection from within a callback. Add an in_callback counter to the connection object, incremented before stmt_step() and decremented after. If close() is called while the counter is positive, ProgrammingError is raised and the database connection remains open. A counter (rather than a boolean flag) is used to correctly handle nested callbacks. Also convert test docstrings to comments per reviewer feedback, and add a test for the nested callback scenario.
|
I agree, preventing the misuse upfront is the better approach. I've pushed a new commit that does exactly that:
The complexity turned out to be quite manageable, just a few lines in close(), and |
…ted callbacks Only check and consume the close_attempted_in_callback flag when in_callback reaches zero (the outermost level). Previously, a nested stmt_step() inside a callback could consume the flag, causing the outermost caller to miss the error.
|
We should be able to exploit the already present |
I did a quick PoC, and this indeed works. It's a small change, and it works for both the iternext and the query execute scenarios. However, it requires we roll back parts of 74c1f41. If we choose this path, we should do the rolling back as a separate PR first. |
|
@erlend-aasland I implemented your suggested approach. Here's what I did: Removed in_callback counter, now using
All 509 test_sqlite3 tests pass. |
…tion in callback Replace the in_callback counter with cursor->locked checks. Restore the cursor weakref list (partially rolling back 74c1f41) so that close() can iterate cursors and detect locked ones.
Summary
Fix a segmentation fault (NULL pointer dereference) in the
_sqlitemodule that occurs whenConnection.close()is called inside an aggregatestep()callback.Fix
stmt_step(), check ifself->connection->db == NULL. If so, raiseProgrammingError("Cannot operate on a closed database.")and jump to the error path.sqlite3_last_insert_rowid()call withself->connection->db != NULLas an extra safety net.Test
Added
test_aggr_close_conn_in_stepinLib/test/test_sqlite3/test_userfunctions.pythat reproduces the exact crash scenario and verifies thatProgrammingErroris raised instead of a segfault.ASAN output (before fix)
After fix
_sqlite: NULL dereference when connection is closed from within a callback #145040