From f61c70f881f8f1821e7bb43501f3d56b3f09189b Mon Sep 17 00:00:00 2001 From: dot Date: Sat, 10 Oct 2026 18:41:07 +0900 Subject: [PATCH] Raise IndexError for oversized SQLite blob indices Use index-sized conversion, preserve __index__ exceptions, and bound native type names to the UTF-8-safe diagnostic limit. Include in-memory blob read and write regressions without changing callback ownership. Assisted-by: OpenAI Codex:model identifier unavailable --- crates/stdlib/src/_sqlite3.rs | 30 ++++++++++---- extra_tests/snippets/stdlib_sqlite.py | 56 +++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 7 deletions(-) diff --git a/crates/stdlib/src/_sqlite3.rs b/crates/stdlib/src/_sqlite3.rs index 3eaa9533f71..d0bb95a6c9f 100644 --- a/crates/stdlib/src/_sqlite3.rs +++ b/crates/stdlib/src/_sqlite3.rs @@ -2839,15 +2839,31 @@ mod _sqlite3 { } } - fn wrapped_index(index: PyIntRef, length: c_int, vm: &VirtualMachine) -> PyResult { - let mut index = index.try_to_primitive::(vm)?; + fn wrapped_index( + index: PyIntRef, + object: &PyObject, + length: c_int, + vm: &VirtualMachine, + ) -> PyResult { + let mut index = index.try_to_primitive::(vm).map_err(|_| { + let class = object.class(); + let type_name = class.slot_name(); + let mut end = type_name.len().min(200); + while !type_name.is_char_boundary(end) { + end -= 1; + } + vm.new_index_error(format!( + "cannot fit '{}' into an index-sized integer", + &type_name[..end] + )) + })?; if index < 0 { - index += length; + index += length as isize; } - if index < 0 || index >= length { + if index < 0 || index >= length as isize { Err(vm.new_index_error("Blob index out of range")) } else { - Ok(index) + Ok(index as c_int) } } @@ -2874,7 +2890,7 @@ mod _sqlite3 { let inner = self.inner(vm)?; if let Some(index) = needle.try_index_opt(vm) { let blob_len = inner.blob.bytes(); - let index = Self::wrapped_index(index?, blob_len, vm)?; + let index = Self::wrapped_index(index?, needle, blob_len, vm)?; let mut byte: u8 = 0; let ret = inner.blob.read_single(&mut byte, index); self.check(ret, vm).map(|_| vm.ctx.new_int(byte).into()) @@ -2937,7 +2953,7 @@ mod _sqlite3 { ))); }; let blob_len = inner.blob.bytes(); - let index = Self::wrapped_index(index?, blob_len, vm)?; + let index = Self::wrapped_index(index?, needle, blob_len, vm)?; // Mirror CPython ass_subscript_index: use PyLong_AsLong, treat any // overflow (e.g. 2**65) as -1, then validate the [0, 255] range. let val = int_val.as_bigint().to_i64().unwrap_or(-1); diff --git a/extra_tests/snippets/stdlib_sqlite.py b/extra_tests/snippets/stdlib_sqlite.py index 76c8b7adde1..36babea6144 100644 --- a/extra_tests/snippets/stdlib_sqlite.py +++ b/extra_tests/snippets/stdlib_sqlite.py @@ -306,3 +306,59 @@ def __del__(self): "replace_callback", ): check_nested_aggregate_destructor(operation) + + +class BlobIndex: + def __init__(self, value): + self.value = value + + def __index__(self): + return self.value + + +class BlobInt(int): + pass + + +class BrokenBlobIndex: + def __index__(self): + raise OverflowError("index conversion failed") + + +cx.execute("CREATE TABLE blobindices(b BLOB)") +cx.execute("INSERT INTO blobindices(b) VALUES (?)", (b"abc",)) +with cx.blobopen("blobindices", "b", 1) as blob: + assert blob[BlobIndex(-1)] == ord("c") + blob[BlobIndex(0)] = ord("A") + assert blob[0] == ord("A") + + test = unittest.TestCase() + for index in (sys.maxsize, -sys.maxsize - 1): + with test.assertRaisesRegex(IndexError, "^Blob index out of range$"): + blob[index] + with test.assertRaisesRegex(IndexError, "^Blob index out of range$"): + blob[index] = 0 + + for value in (sys.maxsize + 1, -sys.maxsize - 2): + for index in (value, BlobInt(value), BlobIndex(value)): + message = ( + f"^cannot fit '{type(index).__name__}' into an index-sized integer$" + ) + with test.assertRaisesRegex(IndexError, message): + blob[index] + with test.assertRaisesRegex(IndexError, message): + blob[index] = 0 + + for name, bounded_name in (("X" * 250, "X" * 200), ("€" * 67, "€" * 66)): + long_index = type(name, (), {"__index__": lambda self: sys.maxsize + 1})() + message = f"^cannot fit '{bounded_name}' into an index-sized integer$" + with test.assertRaisesRegex(IndexError, message): + blob[long_index] + with test.assertRaisesRegex(IndexError, message): + blob[long_index] = 0 + + with test.assertRaisesRegex(OverflowError, "^index conversion failed$"): + blob[BrokenBlobIndex()] + with test.assertRaisesRegex(OverflowError, "^index conversion failed$"): + blob[BrokenBlobIndex()] = 0 + assert blob[:] == b"Abc"