Skip to content

Null pointer dereference in _sqlite execute via re-entrant sequence length #143380

Closed as duplicate

Description

@jackfromeast

What happened?

cursor.execute treats non-dict parameters as sequences and calls PySequence_Size, so a crafted object can close the connection inside __len__ and clear self->connection->db. The cursor continues using that null handle and eventually calls sqlite3_last_insert_rowid, which dereferences the null connection pointer and crashes.

Proof of Concept:

import sqlite3

class Seq:
    def __init__(self, conn):
        self.conn = conn

    def __len__(self):
        self.conn.close()
        return 1

    def __getitem__(self, i):
        if i:
            raise IndexError
        return 42


conn = sqlite3.connect(":memory:")
conn.cursor().execute("SELECT ?", Seq(conn))

Vulnerable Code Snippet:

Click to expand
/* Buggy Re-entrant Path */
static PyObject *
pysqlite_cursor_execute_impl(pysqlite_Cursor *self, PyObject *sql,
                             PyObject *parameters)
{
    return _pysqlite_query_execute(self, 0, sql, parameters);
}

static void
bind_parameters(pysqlite_state *state, pysqlite_Statement *self,
                PyObject *parameters)
{
    Py_ssize_t num_params;

    /* ... */

    if (PyTuple_CheckExact(parameters) || PyList_CheckExact(parameters) ||
        (!PyDict_Check(parameters) && PySequence_Check(parameters))) {
        /* parameters passed as sequence */
        if (PyTuple_CheckExact(parameters)) {
            num_params = PyTuple_GET_SIZE(parameters);
        } else if (PyList_CheckExact(parameters)) {
            num_params = PyList_GET_SIZE(parameters);
        } else {
            num_params = PySequence_Size(parameters);  /* Reentrant call site */
            if (num_params == -1) {
                return;
            }
        }
        /* ... */
    }
    /* ... */
}

PyObject *
_pysqlite_query_execute(pysqlite_Cursor* self, int multiple,
                        PyObject* operation, PyObject* second_argument)
{
    PyObject* parameters_iter = NULL;
    PyObject* parameters = NULL;

    /* ... */

    while (1) {
        parameters = PyIter_Next(parameters_iter);
        if (!parameters) {
            break;
        }

        bind_parameters(state, self->statement, parameters);

        /* ... */
    }

    if (!multiple) {
        sqlite_int64 lastrowid;

        Py_BEGIN_ALLOW_THREADS
        /* crashing pointer derived: self->connection->db */
        lastrowid = sqlite3_last_insert_rowid(self->connection->db);  /* Crash site */
        Py_END_ALLOW_THREADS

        Py_SETREF(self->lastrowid, PyLong_FromLongLong(lastrowid));
    }
    /* ... */
}

/* Clobbering Path */
static PyObject *
pysqlite_connection_close_impl(pysqlite_Connection *self)
{
    /* ... */
    Py_CLEAR(self->statement_cache);  /* state mutate site */
    if (connection_close(self) < 0) {
        return NULL;
    }

    Py_RETURN_NONE;
}

static int
connection_close(pysqlite_Connection *self)
{
    if (self->db == NULL) {
        return 0;
    }

    sqlite3 *db = self->db;
    self->db = NULL;  /* state mutate site */

    /* ... */
    return 0;
}

Sanitizer Output:

Click to expand
=================================================================
==1703088==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000038 (pc 0x7bb50c2331a4 bp 0x7ffd8274d420 sp 0x7ffd8274d248 T0)
==1703088==The signal is caused by a READ memory access.
==1703088==Hint: address points to the zero page.
    #0 0x7bb50c2331a4 in sqlite3_last_insert_rowid (/lib/x86_64-linux-gnu/libsqlite3.so.0+0xa41a4) (BuildId: ac2bec9c45eec6feab0928b3b1373b467aa51339)
    #1 0x7bb50db33e6d in _pysqlite_query_execute Modules/_sqlite/cursor.c:1000
    #2 0x63584fe84bc7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #3 0x63584fe84bc7 in PyObject_Vectorcall Objects/call.c:327
    #4 0x63584fd26b82 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #5 0x635850205256 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #6 0x635850205256 in _PyEval_Vector Python/ceval.c:2005
    #7 0x635850205256 in PyEval_EvalCode Python/ceval.c:888
    #8 0x63585034af8e in run_eval_code_obj Python/pythonrun.c:1365
    #9 0x63585034af8e in run_mod Python/pythonrun.c:1459
    #10 0x63585034fc37 in pyrun_file Python/pythonrun.c:1293
    #11 0x63585034fc37 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #12 0x63585035075c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #13 0x6358503c2ecc in pymain_run_file_obj Modules/main.c:410
    #14 0x6358503c2ecc in pymain_run_file Modules/main.c:429
    #15 0x6358503c2ecc in pymain_run_python Modules/main.c:691
    #16 0x6358503c47ae in Py_RunMain Modules/main.c:772
    #17 0x6358503c47ae in pymain_main Modules/main.c:802
    #18 0x6358503c47ae in Py_BytesMain Modules/main.c:826
    #19 0x7bb50e42a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #20 0x7bb50e42a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #21 0x63584fd5c694 in _start (/home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/python+0x20e694) (BuildId: 9b8528ade1939b4eac85ea0eb0dad4843ee8ca4b)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib/x86_64-linux-gnu/libsqlite3.so.0+0xa41a4) (BuildId: ac2bec9c45eec6feab0928b3b1373b467aa51339) in sqlite3_last_insert_rowid
==1703088==ABORTING

CPython versions tested on:

3.11, 3.12, 3.13, 3.14, 3.15

Details
Python Version Status Exit Code
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) Exception 1
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] Exception 1
Python 3.11.14+ (heads/3.11:88f3f5b5f11, Oct 28 2025, 16:53:08) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.12.12+ (heads/3.12:8cb2092bd8c, Oct 28 2025, 16:54:14) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.13.9+ (heads/3.13:9c8eade20c6, Oct 28 2025, 16:55:18) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.14.0+ (heads/3.14:2e216728038, Oct 28 2025, 16:56:16) [Clang 18.1.3 (1ubuntu1)] ASAN 1
Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0] ASAN 1

Operating systems tested on:

Linux

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

Python 3.15.0a1+ (heads/main:f5394c257ce, Oct 28 2025, 19:29:54) [GCC 13.3.0]

Activity

  1. picnixz commented on Jan 3, 2026

    @picnixz
    Member

    I think I fixed this by addressing #143198 as well. Can you check if the corresponding PR (#143210) fixes the crash please?

  2. self-assigned this
    on Jan 3, 2026
  3. picnixz commented on Jan 4, 2026

    @picnixz
    Member

    Confirmed that this would be fixed by my change.

  4. yet-another-agent commented on Oct 10, 2026

    @yet-another-agent

    This still reproduces on current main at 0ec3aee262b03276a18aeb23cb9957e1b57c9d08. The proposed fix in #143210 remains open, and a release-style AddressSanitizer build still reaches sqlite3_last_insert_rowid() with a null database pointer.

    import sqlite3
    
    class Seq:
        def __init__(self, conn):
            self.conn = conn
    
        def __len__(self):
            self.conn.close()
            return 1
    
        def __getitem__(self, i):
            if i:
                raise IndexError
            return 42
    
    
    conn = sqlite3.connect(":memory:")
    conn.cursor().execute("SELECT ?", Seq(conn))
    Complete current-main AddressSanitizer report
    AddressSanitizer:DEADLYSIGNAL
    =================================================================
    ==1==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000038 (pc 0xffff8ad0efec bp 0xffffc8924560 sp 0xffffc8924560 T0)
    ==1==The signal is caused by a READ memory access.
    ==1==Hint: address points to the zero page.
        #0 0xffff8ad0efec in sqlite3_last_insert_rowid (/lib/aarch64-linux-gnu/libsqlite3.so.0+0x9efec) (BuildId: 19397f681b17326ebfddc017eb944f4159d67f84)
        #1 0xffff8cf4bff4 in _pysqlite_query_execute Modules/_sqlite/cursor.c:976
        #2 0xaaaad6818fec in _PyObject_VectorcallTstate Include/internal/pycore_call.h:143
        #3 0xaaaad6818fec in PyObject_Vectorcall Objects/call.c:327
        #4 0xaaaad6b01e94 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:770
        #5 0xaaaad66dbcbc in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1906
        #6 0xaaaad6b0ef0c in _PyEval_EvalFrame Include/internal/pycore_ceval.h:122
        #7 0xaaaad6b0ef0c in _PyEval_Vector Python/ceval.c:2176
        #8 0xaaaad6b0ef0c in PyEval_EvalCode Python/ceval.c:681
        #9 0xaaaad6c3064c in run_mod Python/pythonrun.c:1509
        #10 0xaaaad6c32aac in _PyRun_File Python/pythonrun.c:1332
        #11 0xaaaad6c32aac in _PyRun_SimpleFile Python/pythonrun.c:544
        #12 0xaaaad6c34d30 in _PyRun_AnyFile Python/pythonrun.c:92
        #13 0xaaaad6ca23e0 in pymain_run_file_obj Modules/main.c:478
        #14 0xaaaad6ca23e0 in pymain_run_file Modules/main.c:494
        #15 0xaaaad6ca23e0 in pymain_run_python Modules/main.c:812
        #16 0xaaaad6ca23e0 in Py_RunMain Modules/main.c:900
        #17 0xaaaad6ca3284 in pymain_main Modules/main.c:927
        #18 0xaaaad6ca3284 in Py_BytesMain Modules/main.c:951
        #19 0xffff8d8684c0  (/lib/aarch64-linux-gnu/libc.so.6+0x284c0) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
        #20 0xffff8d868594 in __libc_start_main (/lib/aarch64-linux-gnu/libc.so.6+0x28594) (BuildId: 27027b96e5b8c475fc327aa445bea1c71d37b4e2)
        #21 0xaaaad66fd5ac in _start (/src/cpython/python+0x20d5ac) (BuildId: c7b91d0d6fc70a572b6ef078849822c6fb3085ae)
    
    AddressSanitizer can not provide additional info.
    SUMMARY: AddressSanitizer: SEGV (/lib/aarch64-linux-gnu/libsqlite3.so.0+0x9efec) (BuildId: 19397f681b17326ebfddc017eb944f4159d67f84) in sqlite3_last_insert_rowid
    ==1==ABORTING
    

    Python 3.16.0a0 (main, Oct 10 2026, 01:00:06) [GCC 13.3.0] on Ubuntu 24.04.5 LTS (aarch64).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

extension-modulesC modules in the Modules dirtopic-sqlite3type-crashA hard crash of the interpreter, possibly with a core dump

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions