Skip to content

sqlite3 crashes if a callback closes the connection during sqlite3_step #151030

@KowalskiThomas

Description

@KowalskiThomas

Crash report

What happened?

If a Python callback is invoked by SQLite during query preparation or execution -- an authorizer (set_authorizer), progress handler (set_progress_handler), trace callback (set_trace_callback), or user-defined function (create_function) -- and that callback calls con.close while SQLite is still on the C stack, the process hard-crashes.

The problem is that connection_close sets self->db to null (then calls sqlite3_close_v2 -- which defers the actual close until all statements are finalised).

connection_close(pysqlite_Connection *self)
{
if (self->db == NULL) {
return 0;
}
int rc = 0;
if (self->autocommit == AUTOCOMMIT_DISABLED &&
!sqlite3_get_autocommit(self->db))
{
if (connection_exec_stmt(self, "ROLLBACK") < 0) {
rc = -1;
}
}
sqlite3 *db = self->db;
self->db = NULL;
Py_BEGIN_ALLOW_THREADS
/* The v2 close call always returns SQLITE_OK if given a valid database
* pointer (which we do), so we can safely ignore the return value */
(void)sqlite3_close_v2(db);
Py_END_ALLOW_THREADS
free_callback_contexts(self);

There are two different crash causes from what I can tell.


The first is a null pointer dereference on db.
After the callback returns, self->connection->db is null but the cursor code reads it unconditionally in several places:

All call sites either call set_error_from_db directly or pass db to a SQLite API. set_error_from_db also lacks a null guard:

set_error_from_db(pysqlite_state *state, sqlite3 *db)
{
int errorcode = sqlite3_errcode(db);
PyObject *exc_class = get_exception_class(state, errorcode);
if (exc_class == NULL) {
// No new exception need be raised.
return SQLITE_OK;
}
/* Create and set the exception. */
int extended_errcode = sqlite3_extended_errcode(db);
// sqlite3_errmsg() always returns an UTF-8 encoded message
const char *errmsg = sqlite3_errmsg(db);
raise_exception(exc_class, extended_errcode, errmsg);
return errorcode;
}


The second crash path is a use-after-free of callback_context (authorizer / progress / trace)

sqlite3_prepare_v2 can call the authorizer more than once per statement (e.g. once for SQLITE_SELECT, then once per SQLITE_READ). If the authorizer closes the connection (through connection_close), free_callback_contexts is called, which calls decref_callback_context on the in-flight context. Combined with the matching decref at the end of the C wrapper, the context's refcount reaches zero and the struct is freed. The next authorizer invocation then calls incref_callback_context on a freed callback_context.
The same problem applies to progress_callback and trace_callback when those fire more than once per step.

Additionally, because pysqlite_connection_close_impl calls Py_CLEAR(self->statement_cache) before calling into connection_close, and because get_statement_from_cache holds only a borrowed reference to the cache object, closing the connection from within a callback frees that object while the call into it is still ongoing.

Reproducer

This is a reproducer for just one example, the set_authorizer one.

import sqlite3

con = sqlite3.connect(":memory:")
con.execute("CREATE TABLE t (v INTEGER)")

def auth(action, arg1, arg2, db, trigger):
    con.close()
    return sqlite3.SQLITE_OK

con.set_authorizer(auth)
con.execute("SELECT v FROM t")

On CPython main, this results in

Assertion failed: (ctx->refcount > 0), function incref_callback_context, file connection.c, line 1131.
zsh: abort      ./python.exe repro.py

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Output from running 'python -VV' on the command line:

No response

Linked PRs

Metadata

Metadata

Assignees

No one assigned

    Labels

    type-crashA hard crash of the interpreter, possibly with a core dump
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions