-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
Open
Assignees
@jackfromeast
Description
A user-provided text_factory runs while _pysqlite_fetch_one_row converts TEXT columns. If it calls Connection.close, connection->db is set to NULL, but pysqlite_cursor_iternext still calls sqlite3_changes(self->connection->db) once iteration advances. The SQLite helper dereferences the cleared pointer and segfaults.
Proof of Concept:
import sqlite3 conn = sqlite3.connect(":memory:") conn.execute("create table t(x)") cur = conn.cursor() cur.execute("insert into t values ('a') returning x") def text_factory(val, _conn=conn): if not getattr(text_factory, "armed", False): text_factory.armed = True _conn.close() return "x" conn.text_factory = text_factory cur.fetchone()
Vulnerable Code Snippet:
Click to expand
/* Buggy Re-entrant Path */ static PyObject * pysqlite_cursor_fetchone_impl(pysqlite_Cursor *self) { return pysqlite_cursor_iternext((PyObject *)self); } static PyObject * pysqlite_cursor_iternext(PyObject *op) { pysqlite_Cursor *self = _pysqlite_Cursor_CAST(op); /* ... */ PyObject *row = _pysqlite_fetch_one_row(self); /* ... */ if (rc == SQLITE_DONE) { if (self->statement->is_dml) { sqlite3 *db = self->connection->db; /* crashing pointer derived */ self->rowcount = (long)sqlite3_changes(db); /* Crash site */ } /* ... */ } return row; } static PyObject * _pysqlite_fetch_one_row(pysqlite_Cursor* self) { /* ... */ converted = PyObject_CallFunction(self->connection->text_factory, "y#", text, nbytes); /* Reentrant call site */ /* ... */ } /* Clobbering Path */ static int connection_close(pysqlite_Connection *self) { sqlite3 *db = self->db; self->db = NULL; /* state mutate site */ (void)sqlite3_close_v2(db); /* ... */ return rc; }
Sanitizer Output:
Click to expand
AddressSanitizer:DEADLYSIGNAL
=================================================================
==431550==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000078 (pc 0x7fbfd133c1b4 bp 0x7ffed0387610 sp 0x7ffed0387608 T0)
==431550==The signal is caused by a READ memory access.
==431550==Hint: address points to the zero page.
#0 0x7fbfd133c1b4 in sqlite3_changes64 (/lib/x86_64-linux-gnu/libsqlite3.so.0+0xa41b4) (BuildId: ac2bec9c45eec6feab0928b3b1373b467aa51339)
#1 0x7fbfd133c1cc in sqlite3_changes (/lib/x86_64-linux-gnu/libsqlite3.so.0+0xa41cc) (BuildId: ac2bec9c45eec6feab0928b3b1373b467aa51339)
#2 0x7fbfd3bdbb1c in pysqlite_cursor_iternext Modules/_sqlite/cursor.c:1121
#3 0x7fbfd3bdbb1c in pysqlite_cursor_fetchone_impl Modules/_sqlite/cursor.c:1154
#4 0x7fbfd3bdbb1c in pysqlite_cursor_fetchone Modules/_sqlite/clinic/cursor.c.h:169
#5 0x6390ee26e3e7 in _PyObject_VectorcallTstate Include/internal/pycore_call.h:169
#6 0x6390ee26e3e7 in PyObject_Vectorcall Objects/call.c:327
#7 0x6390ee1225a2 in _PyEval_EvalFrameDefault Python/generated_cases.c.h:1620
#8 0x6390ee5ecad6 in _PyEval_EvalFrame Include/internal/pycore_ceval.h:121
#9 0x6390ee5ecad6 in _PyEval_Vector Python/ceval.c:2001
#10 0x6390ee5ecad6 in PyEval_EvalCode Python/ceval.c:884
#11 0x6390ee73216e in run_eval_code_obj Python/pythonrun.c:1365
#12 0x6390ee73216e in run_mod Python/pythonrun.c:1459
#13 0x6390ee736e17 in pyrun_file Python/pythonrun.c:1293
#14 0x6390ee736e17 in _PyRun_SimpleFileObject Python/pythonrun.c:521
#15 0x6390ee73793c in _PyRun_AnyFileObject Python/pythonrun.c:81
#16 0x6390ee7aae3c in pymain_run_file_obj Modules/main.c:410
#17 0x6390ee7aae3c in pymain_run_file Modules/main.c:429
#18 0x6390ee7aae3c in pymain_run_python Modules/main.c:691
#19 0x6390ee7ac71e in Py_RunMain Modules/main.c:772
#20 0x6390ee7ac71e in pymain_main Modules/main.c:802
#21 0x6390ee7ac71e in Py_BytesMain Modules/main.c:826
#22 0x7fbfd442a1c9 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
#23 0x7fbfd442a28a in __libc_start_main_impl ../csu/libc-start.c:360
#24 0x6390ee146634 in _start (/home/jackfromeast/Desktop/entropy/targets/grammar-afl++-latest/targets/cpython/python+0x206634) (BuildId: 4d105290d0ad566a4d6f4f7b2f05fbc9e317b533)
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/lib/x86_64-linux-gnu/libsqlite3.so.0+0xa41b4) (BuildId: ac2bec9c45eec6feab0928b3b1373b467aa51339) in sqlite3_changes64
==431550==ABORTING
Affected Versions:
Details
| Python Version | Status | Exit Code |
|---|---|---|
Python 3.9.24+ (heads/3.9:111bbc15b26, Oct 28 2025, 16:51:20) |
OK | 0 |
Python 3.10.19+ (heads/3.10:014261980b1, Oct 28 2025, 16:52:08) [Clang 18.1.3 (1ubuntu1)] |
OK | 0 |
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 |
CPython versions tested on:
CPython main branch
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]
Metadata
Metadata
Assignees
Labels
Projects
Status
No status