
SQLite 3.53.4 - High - Local - Info leak - WAL/DB page-size mismatch uninitialized page tail
abraxaslabs.tech · github.com/abraxas · @abraxas_null · [email protected] · sqlite-wal-pagesize
Class: Info leak Reach: Local
SQLite 3.53.4 (b09c88c) still short-reads a WAL frame into a full pager page with no tail memset. walIndexRecover accepts any power-of-two WAL page size in [512, SQLITE_MAX_PAGE_SIZE] and never compares it to the database page size. Recycled pcache slots then return prior heap/page bytes through ordinary SQL.
Lab: 4096-page WAL-mode .db, sibling -wal header page size 512, SELECT b FROM t returned 2000 bytes. The tail past the 512-byte frame was SQLITE-WAL-PAGESIZE-WITNESS. Not RCE.
| ID | no CVE yet |
| Class | Info leak (uninitialized pager tail through SQL) |
| Reach | Local (untrusted .db plus sibling -wal) |
| CWE | CWE-908, CWE-200 |
| CVSS | High: 7.5 CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N |
| Product | SQLite (Hwaci) |
| Affected | 3.53.4 (b09c88c14082339b66c7b7158d609a771e64ca69); SOURCE_ID 2026-07-24 19:02:57 bf7c7f30031888f4e796e429ab3978879485813aaca6f641c7b33e4e09459bcc |
| Auth | none (victim opens the attacker files) |
| License | GNU Affero GPL v3.0 |
| Lab | 127.0.0.1:18830 only |
Ship a WAL-mode .db plus a sibling -wal whose header page size is a smaller valid power of two than the database. Recovery accepts it. sqlite3WalReadFrame copies only the WAL frame size into a pager buffer sized from the .db. A table-leaf cell that claims a payload larger than that frame is satisfied from the recycled pcache tail. SELECT / substr() / hex() on that blob returns leftover pager/heap bytes from prior pages in the same process.
The inverse mismatch (WAL larger than the pager) is SQLITE_CORRUPT on checkpoint. This leftover is the smaller-WAL read path only. Not RCE.
Twenty source hunts of 3.53.4 after the later-trunk leftover matrix. Winter reported it on the forum as 2026-08-17T15:54:41Z. Trunk f9b4ee3695 / branch-3.53 4fde87b2a0 (2026-08-16) is after this GA (2026-07-24). sqlite3WalReadFrame on this pin is still (nOut>sz ? sz : nOut) with no memset of the tail.
Wrong turn: an early leaf cell was malformed and SELECT returned SQLITE_CORRUPT rc=11. The cell that works claims 2000 bytes of local payload across a 512-byte frame on a 4096-byte pager page.
cd lab
./run.sh
Builds SQLite 3.53.4 from the canonical source zip. Compose sqlite-wal-pagesize. Published 127.0.0.1:18830. Primes pcache with 48x4000-byte witness rows, then SELECT b FROM t on the short-read leaf.
SUCCESS sqlite-wal-pagesize how=wal-shortread-pcache-tail len=2000 rootpage=2 SQLITE-WAL-PAGESIZE-WITNESS
Compare WAL header page size to the database page size during recovery. When they disagree, refuse the WAL or fully initialize the pager page:
if( nOut>sz ){
memset(pOut+sz, 0, nOut-sz);
nOut = sz;
}
That hunk is on trunk after this GA. It is not in 3.53.4.
b09c88c14082339b66c7b7158d609a771e64ca69)src/wal.c sqlite3WalReadFrame, walIndexRecoversrc/pager.c readDbPage