From 0424ad20c56d6ce615ee6c934e1eb87c744a7b96 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Fri, 13 Feb 2026 13:55:06 +0100 Subject: [PATCH 1/5] Include observed WAL page_numbers when calculating SQLite3 page_count --- dissect/database/sqlite3/sqlite3.py | 7 +++++-- dissect/database/sqlite3/wal.py | 1 + 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/dissect/database/sqlite3/sqlite3.py b/dissect/database/sqlite3/sqlite3.py index ccdb0e4..f58f2d0 100644 --- a/dissect/database/sqlite3/sqlite3.py +++ b/dissect/database/sqlite3/sqlite3.py @@ -129,6 +129,9 @@ def __init__( else: self.checkpoint = checkpoint + # Determine the highest page count we have encountered while parsing the SQLite3 header and optionally WAL. + self.page_count = max(self.header.page_count, self.wal.highest_page_num) if self.wal else self.header.page_count + self.page = lru_cache(256)(self.page) def __enter__(self) -> Self: @@ -199,7 +202,7 @@ def raw_page(self, num: int) -> bytes: """ # Only throw an out of bounds exception if the header contains a page_count. # Some old versions of SQLite3 do not set/update the page_count correctly. - if (num < 1 or num > self.header.page_count) and self.header.page_count > 0: + if (num < 1 or num > self.page_count) and self.page_count > 0: raise InvalidPageNumber("Page number exceeds boundaries") data = None @@ -232,7 +235,7 @@ def page(self, num: int) -> Page: return Page(self, num) def pages(self) -> Iterator[Page]: - for i in range(self.header.page_count): + for i in range(self.page_count): yield self.page(i + 1) def cells(self) -> Iterator[Cell]: diff --git a/dissect/database/sqlite3/wal.py b/dissect/database/sqlite3/wal.py index 7d4ec76..ae01f34 100644 --- a/dissect/database/sqlite3/wal.py +++ b/dissect/database/sqlite3/wal.py @@ -39,6 +39,7 @@ def __init__(self, fh: Path | BinaryIO): raise InvalidDatabase("Invalid WAL header magic") self.checksum_endian = "<" if self.header.magic == WAL_HEADER_MAGIC_LE else ">" + self.highest_page_num = max(fr.page_number for commit in self.commits for fr in commit.frames if fr.valid) self.frame = lru_cache(1024)(self.frame) From d97238bb20ef30057ec4f925d4498fe6fa5048a3 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:46:17 +0100 Subject: [PATCH 2/5] add test --- tests/_data/sqlite3/page_count.db | 3 +++ tests/_data/sqlite3/page_count.db-shm | 3 +++ tests/_data/sqlite3/page_count.db-wal | 3 +++ tests/sqlite3/test_wal.py | 37 +++++++++++++++++++++++++++ 4 files changed, 46 insertions(+) create mode 100644 tests/_data/sqlite3/page_count.db create mode 100644 tests/_data/sqlite3/page_count.db-shm create mode 100644 tests/_data/sqlite3/page_count.db-wal diff --git a/tests/_data/sqlite3/page_count.db b/tests/_data/sqlite3/page_count.db new file mode 100644 index 0000000..b67b9d3 --- /dev/null +++ b/tests/_data/sqlite3/page_count.db @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ef1f7ec4df0e2e8a0bbe1bfeb89f8c04fe881cd5f2e5139c8cb94ec88bf53c5e +size 8192 diff --git a/tests/_data/sqlite3/page_count.db-shm b/tests/_data/sqlite3/page_count.db-shm new file mode 100644 index 0000000..69dd906 --- /dev/null +++ b/tests/_data/sqlite3/page_count.db-shm @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:784204f98daf25e64362a6e6cccbd5b3cd136f3b4a750542b010d5ade60a5119 +size 32768 diff --git a/tests/_data/sqlite3/page_count.db-wal b/tests/_data/sqlite3/page_count.db-wal new file mode 100644 index 0000000..a4781d6 --- /dev/null +++ b/tests/_data/sqlite3/page_count.db-wal @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ea21010a729e817d32f70f93024f96b03136c95047c8d76a8aa342f3e9391266 +size 16512 diff --git a/tests/sqlite3/test_wal.py b/tests/sqlite3/test_wal.py index 6d477fe..ee27e6b 100644 --- a/tests/sqlite3/test_wal.py +++ b/tests/sqlite3/test_wal.py @@ -5,6 +5,7 @@ import pytest from dissect.database.sqlite3 import sqlite3 +from tests._util import absolute_path if TYPE_CHECKING: from pathlib import Path @@ -162,3 +163,39 @@ def _assert_checkpoint_3(s: sqlite3.SQLite3) -> None: assert rows[9].id == 11 assert rows[9].name == "second checkpoint" assert rows[9].value == 101 + + +def test_wal_page_count() -> None: + """Test if we count the page numbers in the SQLite3 and WAL correctly. + + Test data generated using: + + $ sqlite3 tests/_data/sqlite3/page_count.db + SQLite version 3.45.1 2024-01-30 16:01:20 + Enter ".help" for usage hints. + sqlite> PRAGMA journal_mode = WAL; + wal + sqlite> CREATE TABLE t1 (a, b); + sqlite> .quit # commits wal + + $ python + >>> import sqlite3 + >>> con = sqlite3.connect("tests/_data/sqlite3/page_count.db") + ... cur = con.cursor() + >>> cur.execute("INSERT INTO t1 VALUES (1, ?)", ("A" * 8192,)) + >>> con.commit() + # Copy page_count.db* files before closing + """ + + db = sqlite3.SQLite3(absolute_path("_data/sqlite3/page_count.db")) + table = db.table("t1") + assert table.sql == "CREATE TABLE t1 (a, b)" + + row = next(table.rows()) + assert row.a == 1 + assert row.b == "A" * 8192 + + assert db.wal + assert db.wal.highest_page_num == 4 + assert db.header.page_count == 2 + assert db.page_count == 4 From ae0a8f0814acad79bb6c6da50d671729de5c2d44 Mon Sep 17 00:00:00 2001 From: Computer Network Investigation <121175071+JSCU-CNI@users.noreply.github.com> Date: Fri, 20 Feb 2026 11:58:20 +0100 Subject: [PATCH 3/5] Update dissect/database/sqlite3/sqlite3.py Co-authored-by: Erik Schamper <1254028+Schamper@users.noreply.github.com> --- dissect/database/sqlite3/sqlite3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/database/sqlite3/sqlite3.py b/dissect/database/sqlite3/sqlite3.py index 1a6c8ed..23bfad3 100644 --- a/dissect/database/sqlite3/sqlite3.py +++ b/dissect/database/sqlite3/sqlite3.py @@ -135,7 +135,7 @@ def __init__( self.page = lru_cache(256)(self.page) def __repr__(self) -> str: - return f"" # noqa: E501 + return f"" # noqa: E501 def __enter__(self) -> Self: """Return ``self`` upon entering the runtime context.""" From 00aab8af1e0fcc50323e039ca5ec5f6ba328259c Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:29:25 +0100 Subject: [PATCH 4/5] remove shm test file --- tests/_data/sqlite3/page_count.db-shm | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 tests/_data/sqlite3/page_count.db-shm diff --git a/tests/_data/sqlite3/page_count.db-shm b/tests/_data/sqlite3/page_count.db-shm deleted file mode 100644 index 69dd906..0000000 --- a/tests/_data/sqlite3/page_count.db-shm +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:784204f98daf25e64362a6e6cccbd5b3cd136f3b4a750542b010d5ade60a5119 -size 32768 From ed89ded5f2e59d67ee984d15e0e904259a831064 Mon Sep 17 00:00:00 2001 From: JSCU-CNI <121175071+JSCU-CNI@users.noreply.github.com> Date: Fri, 20 Feb 2026 12:31:35 +0100 Subject: [PATCH 5/5] fix docstring for sqlcipher too --- dissect/database/sqlite3/encryption/sqlcipher/sqlcipher.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dissect/database/sqlite3/encryption/sqlcipher/sqlcipher.py b/dissect/database/sqlite3/encryption/sqlcipher/sqlcipher.py index 4aee06a..63e1a30 100644 --- a/dissect/database/sqlite3/encryption/sqlcipher/sqlcipher.py +++ b/dissect/database/sqlite3/encryption/sqlcipher/sqlcipher.py @@ -150,7 +150,7 @@ def __repr__(self) -> str: f"fh={self.cipher_path or self.cipher_fh} " f"wal={self.wal} " f"checkpoint={bool(self.checkpoint)} " - f"pages={self.header.page_count}>" + f"pages={self.page_count}>" ) def close(self) -> None: