python / python/cpython

sqlite3 Connection and Cursor segfault if __init__ is bypassed (by subclass or __new__ call)

Open
#152,954 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

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

Description

Crash report

What happened?
import sqlite3
con = sqlite3.Connection.__new__(sqlite3.Connection)
print(con.row_factory)

or, more likely:

import sqlite3
class MyCon(sqlite3.Connection):
  def __init__(self, *a, **kw):
    if self.row_factory:
      pass
    super().__init__()
c = MyCon(':memory:')

Both give:

fish: Job 1, './python.exe repro.py' terminated by signal SIGSEGV (Address boundary error)

LLDB:

Process 76976 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
       frame #0: 0x0000000100a93df0 _sqlite3.cpython-316d-darwin.so`Py_INCREF(op=0x0000000000000000) at refcount.h:286:31 [opt] [inlined]
   283 	        _Py_atomic_add_ssize(&op->ob_ref_shared, (1 << _Py_REF_SHARED_SHIFT));
   284 	    }
   285 	#elif SIZEOF_VOID_P > 4
-> 286 	    uint32_t cur_refcnt = op->ob_refcnt;
   287 	    if (cur_refcnt >= _Py_IMMORTAL_INITIAL_REFCNT) {
   288 	        // the object is immortal
   289 	        _Py_INCREF_IMMORTAL_STAT_INC();
Target 0: (python.exe) stopped.
warning: _sqlite3.cpython-316d-darwin.so was compiled with optimization - stepping may behave oddly; variables may not be available.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
   * frame #0: 0x0000000100a93df0 _sqlite3.cpython-316d-darwin.so`Py_INCREF(op=0x0000000000000000) at refcount.h:286:31 [opt] [inlined]
     frame #1: 0x0000000100a93df0 _sqlite3.cpython-316d-darwin.so`_Py_NewRef(obj=0x0000000000000000) at refcount.h:536:5 [opt] [inlined]
     frame #2: 0x0000000100a93df0 _sqlite3.cpython-316d-darwin.so`connection_get_row_factory(op=0x0000007a371d4040, closure=0x0000000000000000) at connection.c:564:12 [opt]

So, basically, until the tp_init is called, both row_factory and text_factory are NULL, but are treated as always being python objects, causing a number of segfaults.

This was discovered when fixing: gh-152817 / gh-152818 which was a follow-on from: gh-149738 / gh-149754

I think the fix here is to add a tp_new that initializes the fields before python has a chance to mess with the object, and then rely on the associated guards that prevent delattr on those fields (in the above PRs)

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

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

Python 3.16.0a0 (heads/main-dirty:c4739533f33, Jul 3 2026, 12:34:46) [Clang 21.0.0 (clang-2100.3.20.102)]

Linked PRs
  • gh-152956

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 with the sqlite3 connection implementation in connection.c, especially connection_get_row_factory, and review the reproducer showing access before tp_init. Check linked PR gh-152956 and run the reported new and subclass examples. Done means these cases no longer segfault and the relevant sqlite3 tests cover them.

Written by the indexing model from the issue text.

Assessment

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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.