python / python/cpython

Null pointer dereference in `_sqlite` cursor cache via re-entrant parameter iterator

Open
#143,198 2 comments 0 reactions 1 assignee View on GitHub

@picnixz is already working on this.

Since Dec 27, 2025.

extension-modules topic-sqlite3 type-crash
Dominant language
Python
Stars
77.2k
Forks
35.9k
PR merge metrics
PR metrics pending

Description

What happened?

cursor.executemany fetches parameters by calling PyObject_GetIter, so a crafted iterable can close the connection during iteration and clear self->connection->db. The cursor then continues into get_statement_from_cache with a NULL connection handle, leading _Py_TYPE to dereference a null pointer when trying to reuse the cached statement.

Proof of Concept:

import sqlite3

class Params:
    def __iter__(self):
        conn.close()
        return iter([(1,)])

conn = sqlite3.connect(':memory:')
cur = conn.cursor()
cur.executemany('SELECT ?', Params())
Affected Versions
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
Vulnerable Code
PyObject *
_pysqlite_query_execute(pysqlite_Cursor* self, int multiple, PyObject* operation, PyObject* second_argument)
{
    PyObject* parameters_list = NULL;
    PyObject* parameters_iter = NULL;
    PyObject* parameters = NULL;
    int i;
    int rc;
    int numcols;
    PyObject* column_name;

    if (!check_cursor(self)) {
        goto error;
    }

    self->locked = 1;

    if (multiple) {
        if (PyIter_Check(second_argument)) {
            /* iterator */
            parameters_iter = Py_NewRef(second_argument);
        } else {
            /* sequence */
            // Bug: Trigger __iter__ method and set self->connection->db to null
            parameters_iter = PyObject_GetIter(second_argument);
            if (!parameters_iter) {
                goto error;
            }
        }
    } else {
        parameters_list = PyList_New(0);
        if (!parameters_list) {
            goto error;
        }

        if (second_argument == NULL) {
            second_argument = PyTuple_New(0);
            if (!second_argument) {
                goto error;
            }
        } else {
            Py_INCREF(second_argument);
        }
        if (PyList_Append(parameters_list, second_argument) != 0) {
            Py_DECREF(second_argument);
            goto error;
        }
        Py_DECREF(second_argument);

        parameters_iter = PyObject_GetIter(parameters_list);
        if (!parameters_iter) {
            goto error;
        }
    }

    /* reset description */
    Py_INCREF(Py_None);
    Py_SETREF(self->description, Py_None);

    if (self->statement) {
        // Reset pending statements on this cursor.
        if (stmt_reset(self->statement) != SQLITE_OK) {
            goto reset_failure;
        }
    }

    // Crash: Use the null pointer without anycheck
    PyObject *stmt = get_statement_from_cache(self, operation);
    Py_XSETREF(self->statement, (pysqlite_Statement *)stmt);
    if (!self->statement) {
        goto error;
    }

    /* ... */
Sanitizer Output
=================================================================
==1701025==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000008 (pc 0x5d9f40309b11 bp 0x7fff345709e0 sp 0x7fff345709d0 T0)
==1701025==The signal is caused by a READ memory access.
==1701025==Hint: address points to the zero page.
    #0 0x5d9f40309b11 in _Py_TYPE Include/object.h:277
    #1 0x5d9f40309b11 in _PyVectorcall_FunctionInline Include/internal/pycore_call.h:120
    #2 0x5d9f40309b11 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:164
    #3 0x5d9f40309b11 in PyObject_Vectorcall Objects/call.c:327
    #4 0x76a658353bb4 in get_statement_from_cache Modules/_sqlite/cursor.c:562
    #5 0x76a658353bb4 in _pysqlite_query_execute Modules/_sqlite/cursor.c:884
    #6 0x5d9f40309bc7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
    #7 0x5d9f40309bc7 in PyObject_Vectorcall Objects/call.c:327
    #8 0x5d9f401abb82 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
    #9 0x5d9f4068a256 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
    #10 0x5d9f4068a256 in _PyEval_Vector Python/ceval.c:2005
    #11 0x5d9f4068a256 in PyEval_EvalCode Python/ceval.c:888
    #12 0x5d9f407cff8e in run_eval_code_obj Python/pythonrun.c:1365
    #13 0x5d9f407cff8e in run_mod Python/pythonrun.c:1459
    #14 0x5d9f407d4c37 in pyrun_file Python/pythonrun.c:1293
    #15 0x5d9f407d4c37 in _PyRun_SimpleFileObject Python/pythonrun.c:521
    #16 0x5d9f407d575c in _PyRun_AnyFileObject Python/pythonrun.c:81
    #17 0x5d9f40847ecc in pymain_run_file_obj Modules/main.c:410
    #18 0x5d9f40847ecc in pymain_run_file Modules/main.c:429
    #19 0x5d9f40847ecc in pymain_run_python Modules/main.c:691
    #20 0x5d9f408497ae in Py_RunMain Modules/main.c:772
    #21 0x5d9f408497ae in pymain_main Modules/main.c:802
    #22 0x5d9f408497ae in Py_BytesMain Modules/main.c:826
    #23 0x76a658a2a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #24 0x76a658a2a28a in __libc_start_main_impl ../csu/libc-start.c:360
    #25 0x5d9f401e1694 in _start (/home/jackfromeast/Desktop/entropy/tasks/reproducexx/targets/cpython-main/python+0x20e694) (BuildId: 9b8528ade1939b4eac85ea0eb0dad4843ee8ca4b)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV Include/object.h:277 in _Py_TYPE
==1701025==ABORTING
Linked PRs
  • gh-143210

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.