From f87d8a5c67acabb1b48c435debc506280d0901d7 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Mon, 2 Feb 2026 18:19:15 +0100 Subject: [PATCH 1/3] Fix SQLite3 Cell data overflow reading for databases with reserved_size set. --- dissect/database/sqlite3/sqlite3.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/dissect/database/sqlite3/sqlite3.py b/dissect/database/sqlite3/sqlite3.py index 0c6a6fb..422725e 100644 --- a/dissect/database/sqlite3/sqlite3.py +++ b/dissect/database/sqlite3/sqlite3.py @@ -402,7 +402,6 @@ def data(self) -> bytes: if not self._data: offset = self._offset + self._record_offset page_data = self.page.data - page_size = self.page.sqlite.page_size if self.size <= self.max_payload_size: size = max(self.size, 4) @@ -435,7 +434,7 @@ def data(self) -> bytes: # overflow_size is the size of the page data without the # extra 4 bytes for the next overflow page, so it needs to # be added. - data_size = min(overflow_size + 4, page_size) + data_size = min(overflow_size + 4, self.page.sqlite.usable_page_size) page_buf = self.page.sqlite.raw_page(overflow_page)[:data_size] overflow_page = c_sqlite3.uint32(page_buf[:4]) From 3a09e78f544b42eec7abd58f3854224934e25fd0 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Tue, 3 Feb 2026 18:41:18 +0100 Subject: [PATCH 2/3] add test --- tests/_data/sqlite3/overflow.db | 3 +++ tests/sqlite3/test_sqlite3.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 tests/_data/sqlite3/overflow.db diff --git a/tests/_data/sqlite3/overflow.db b/tests/_data/sqlite3/overflow.db new file mode 100644 index 0000000..e9ee293 --- /dev/null +++ b/tests/_data/sqlite3/overflow.db @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4e02f0db7d5dd90f2da8d06052214d5cac84f8400d54aa13ad8d36e53f2f973c +size 20480 diff --git a/tests/sqlite3/test_sqlite3.py b/tests/sqlite3/test_sqlite3.py index 23cecbb..c764bff 100644 --- a/tests/sqlite3/test_sqlite3.py +++ b/tests/sqlite3/test_sqlite3.py @@ -6,6 +6,7 @@ import pytest from dissect.database.sqlite3 import sqlite3 +from tests._util import absolute_path if TYPE_CHECKING: from pathlib import Path @@ -93,3 +94,34 @@ def test_empty(empty_db: BinaryIO) -> None: assert s.encoding == "utf-8" assert len(list(s.tables())) == 0 + + +def test_cell_overflow_reserved_page_size_regression() -> None: + """Test if we handle databases with reserve_bytes greater than 0 correctly. + + This test case emulates a database with a page size of 4kb and with reserve_bytes set to 32. + We then commit a row to a dummy table with a value of 8kb, forcing a cell overflow to a new page. + + Test data generated using: + + $ sqlite3 example.db + SQLite version 3.45.1 2024-01-30 16:01:20 + Enter ".help" for usage hints. + sqlite> .filectrl reserve_bytes 32 + 32 + sqlite> VACUUM; + sqlite> CREATE TABLE foo ("id" INTEGER PRIMARY KEY AUTOINCREMENT, "text" TEXT NOT NULL); + sqlite> .quit + + $ python + >>> import sqlite3 + >>> con = sqlite3.connect("example.db"); cur = con.cursor() + >>> cur.execute('INSERT INTO foo VALUES (1, ?)', ("A" * 8192,)) + >>> con.commit(); con.close() + """ + + db = sqlite3.SQLite3(absolute_path("_data/sqlite3/overflow.db")) + assert db.header.reserved_size == 32 + assert db.header.page_size == 4096 + assert db.usable_page_size == db.header.page_size - db.header.reserved_size + assert db.table("foo").row(0).text == "A" * 8192 From 7c44bf1af45b6125f8ea36b09489f8840ae3e7dd Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Fri, 6 Feb 2026 14:01:19 +0100 Subject: [PATCH 3/3] Fix linter --- tests/sqlite3/test_sqlite3.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/sqlite3/test_sqlite3.py b/tests/sqlite3/test_sqlite3.py index c764bff..c2afe8f 100644 --- a/tests/sqlite3/test_sqlite3.py +++ b/tests/sqlite3/test_sqlite3.py @@ -115,9 +115,11 @@ def test_cell_overflow_reserved_page_size_regression() -> None: $ python >>> import sqlite3 - >>> con = sqlite3.connect("example.db"); cur = con.cursor() - >>> cur.execute('INSERT INTO foo VALUES (1, ?)', ("A" * 8192,)) - >>> con.commit(); con.close() + >>> con = sqlite3.connect("example.db") + ... cur = con.cursor() + >>> cur.execute("INSERT INTO foo VALUES (1, ?)", ("A" * 8192,)) + >>> con.commit() + ... con.close() """ db = sqlite3.SQLite3(absolute_path("_data/sqlite3/overflow.db"))