python / python/cpython

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

Ouverte
#143,198 2 commentaires 0 réactions 1 personne assignée Voir sur GitHub

@picnixz y travaille déjà.

Depuis le 27/12/2025.

extension-modules topic-sqlite3 type-crash
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

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

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.