Skip to content

sqlite3.Blob slice assignment with a step corrupts an immortal bytes singleton #155702

Description

@serhiy-storchaka

Crash report

import sqlite3
cx = sqlite3.connect(":memory:")
cx.execute("create table t(b blob)")
cx.execute("insert into t values (?)", (b"abcdefghij",))
blob = cx.blobopen("t", "b", 1)

blob[5:6:2] = b"\xab"

print(bytes([102]))     # b'\xab', expected b'f'
print(b"f" == b"\xab")  # True

ass_subscript_slice() reads the affected region, patches it and writes it back, using the object returned by read_multiple() as the buffer.
For a single byte that object is an immortal singleton, so writing into it changes the value of that byte in the whole process.

This is a regression in 243d599 (gh-129813, GH-138956), which changed read_multiple() to use PyBytesWriter.
Only main and 3.15 are affected.

Linked PRs

Activity

  1. vstinner commented on Aug 13, 2026

    @vstinner
    Member

    Well, _sqlite extension modifies an immutable bytes object. So yeah, bad things can happen if you do that.

            PyObject *blob_bytes = read_multiple(self, stop - start, start);
            if (blob_bytes != NULL) {
                char *blob_buf = PyBytes_AS_STRING(blob_bytes);
                for (Py_ssize_t i = 0, j = 0; i < len; i++, j += step) {
                    blob_buf[j] = ((char *)vbuf.buf)[i];
                }
                rc = inner_write(self, blob_buf, stop - start, start);
                Py_DECREF(blob_bytes);
            }

    where read_multiple() ends with PyBytesWriter_Finish().

    PyBytes_AS_STRING()/PyBytes_AsString() should return a const char* type instead, but changing that would break the API and affect many C extensions.

  2. added a commit that references this issue on Aug 14, 2026
  3. serhiy-storchaka commented on Aug 14, 2026

    @serhiy-storchaka
    MemberAuthor

    Previously, read_multiple() returned a bytes object created by PyBytes_FromStringAndSize(NULL, size), which was never shared (if not empty) and was safe to be modified. After the PyBytesWriter rewrite it returned the result of PyBytesWriter_Finish(), which can be shared bytes objects with length 1. And now the previously valid code that modifies it became invalid. Creation and modification were in different functions, so it was unnoticed.

  4. added a commit that references this issue on Aug 14, 2026
  5. added a commit that references this issue on Aug 14, 2026
  6. vstinner commented on Aug 14, 2026

    @vstinner
    Member

    Thanks for the fix.

  7. added a commit that references this issue on Aug 19, 2026
  8. vstinner commented on Sep 15, 2026

    @vstinner
    Member

    I wrote PR gh-157572 to detect such silent data corruption: in debug mode at Python exit, check if all immutable singletons have been modified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    3.15bugs and security fixes3.16new features, bugs and security fixesextension-modulesC modules in the Modules dirrelease-blockertopic-sqlite3type-crashA hard crash of the interpreter, possibly with a core dump

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions