python / python/cpython

`_sqlite`: NULL dereference when connection is closed from within a callback

Open
#145,040 11 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Bug report

Bug description:

A segmentation fault occurs in the _sqlite module when a connection is closed inside an aggregate step() callback. After sqlite3_step() returns, _pysqlite_query_execute() calls sqlite3_last_insert_rowid(self->connection->db) without verifying that self->connection->db is still valid.

If the connection was closed during the callback, self->connection->db becomes NULL, leading to a NULL pointer dereference inside sqlite3_last_insert_rowid().

Affected Code

Modules/_sqlite/cursor.c:974

lastrowid = sqlite3_last_insert_rowid(self->connection->db);

Reproducer

import sqlite3

conn = sqlite3.connect(":memory:", autocommit=True)
conn.execute("CREATE TABLE t(x INTEGER)")

for i in range(50):
    conn.execute("INSERT INTO t VALUES (?)", (i,))

class MyAgg:
    def __init__(self):
        self.total = 0

    def step(self, value):
        self.total += value
        conn.close()

    def finalize(self):
        return self.total


conn.create_aggregate("agg_sum", 1, MyAgg)
conn.execute("SELECT agg_sum(x) FROM t").fetchone()

Result

AddressSanitizer:DEADLYSIGNAL
=================================================================
==19418==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000038 (pc 0x742d9562d034 bp 0x7fffdf6c6360 sp 0x7fffdf6c62f8 T0)
==19418==The signal is caused by a READ memory access.
==19418==Hint: address points to the zero page.
    #0 0x742d9562d034 in sqlite3_last_insert_rowid (/lib/x86_64-linux-gnu/libsqlite3.so.0+0xaa034) (BuildId: f5959b839b5d4de9879dc3630ecde4d76d4be713)
    #1 0x742d96bd55c5 in _pysqlite_query_execute Modules/_sqlite/cursor.c:974
    #2 0x742d96bcdbc5 in pysqlite_connection_execute_impl Modules/_sqlite/connection.c:1848
    #3 0x742d96bcdd60 in pysqlite_connection_execute Modules/_sqlite/clinic/connection.c.h:967
    #4 0x5f5ad4a35cd6 in method_vectorcall_FASTCALL Objects/descrobject.c:402
    #5 0x5f5ad4a15ccc in _PyObject_VectorcallTstate Include/internal/pycore_call.h:136
    #6 0x5f5ad4a15dbf in PyObject_Vectorcall Objects/call.c:327
    #7 0x5f5ad4c948b7 in _Py_VectorCallInstrumentation_StackRefSteal Python/ceval.c:769
    #8 0x5f5ad4ca487b in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1817
    #9 0x5f5ad4cdbab0 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:118
    #10 0x5f5ad4cdbe16 in _PyEval_Vector Python/ceval.c:2132
    #11 0x5f5ad4cdc0cc in PyEval_EvalCode Python/ceval.c:680
    #12 0x5f5ad4ddf771 in run_eval_code_obj Python/pythonrun.c:1366
    #13 0x5f5ad4ddfab7 in run_mod Python/pythonrun.c:1469
    #14 0x5f5ad4de09ec in pyrun_file Python/pythonrun.c:1294
    #15 0x5f5ad4de3822 in _PyRun_SimpleFileObject Python/pythonrun.c:518
    #16 0x5f5ad4de3ace in _PyRun_AnyFileObject Python/pythonrun.c:81
    #17 0x5f5ad4e38d76 in pymain_run_file_obj Modules/main.c:410
    #18 0x5f5ad4e38fe3 in pymain_run_file Modules/main.c:429
    #19 0x5f5ad4e3a7e1 in pymain_run_python Modules/main.c:691
    #20 0x5f5ad4e3ae77 in Py_RunMain Modules/main.c:772
    #21 0x5f5ad4e3b063 in pymain_main Modules/main.c:802
    #22 0x5f5ad4e3b3e8 in Py_BytesMain Modules/main.c:826
    #23 0x5f5ad48a0675 in main Programs/python.c:15
    #24 0x742d9782a3b7 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #25 0x742d9782a47a in __libc_start_main_impl ../csu/libc-start.c:360
    #26 0x5f5ad48a05a4 in _start (/home/raminfp/Projects/cpython/python+0x2ee5a4) (BuildId: fde840bca26b2112d86f1a7490e122abaa6caef7)

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

Suggested Fix

Add a NULL check before calling sqlite3_last_insert_rowid():

if (self->connection->db != NULL) {
    lastrowid = sqlite3_last_insert_rowid(self->connection->db);
}
CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs
  • gh-145041
  • gh-151031

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.

Research direction

Start at Modules/_sqlite/cursor.c:974 and reproduce the aggregate callback scenario under AddressSanitizer. Check the connection state after the callback and verify the query no longer crashes when conn.close() is called from step(); add or update regression coverage if the surrounding test layout identifies an appropriate location.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, python, sqlite
Domain
databases
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Stale
Clarity
Clearly specified
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.