Repository navigation
Follow the server's page size and page count only within SQLite's limits - #8
Conversation
The page size and page count of an Opened answer went unchecked into the page store: a page size of 0 divided by zero at the first read, a huge one sized every block, and a huge page count sized the bitmap of the local copy. The panic happened inside a VFS callback, which SQLite calls through extern "C", so it ended the process instead of failing the SQLite call. The server validates these values, so only a foreign or compromised server could send them; the same class kupferdraht closed for its backend's responses. An Opened answer now fails the open or the resume with a protocol error unless the page size is a power of two from 512 to 65536, on a resume the one in use, and the page count is at most SQLITE_MAX_PAGE_COUNT. The header of the file copy, which has no checksum, is not trusted with a page size of 0 or above 65536 or a page count beyond that limit, and the browser's local mode checks the stored head the same way. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A fetched block went into the page store as it came. The store slices blocks by the page size, so a shorter block panicked at the first read inside a VFS callback, and a longer one would have been committed back as it is. The protocol already says each block is exactly page_size bytes; the client now checks it for every block of a fetch and of a catch-up fetch, and fails with a protocol error otherwise. The browser's local mode checks the blocks the connection worker returns from IndexedDB the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, this looks good. I pushed one commit on top (b35b72e) that closes a gap of the same class: Block length. The blocks of a Tests: blocks of 0, 100, page size ± 1 fail the open with preload, a short block fails the on-demand read with Not changed: with 🤖 Generated with Claude Code |
Closes the class of gap kupferdraht closed for its backend's responses (Step 95c): values from the other side that go unchecked into arithmetic or allocations.
The gap
The page size and page count of an
Openedanswer went straight into the page store. A page size of 0 divided by zero at the first read, a huge one sized every block, and a huge page count sized the bitmap of the local copy. The panic happened inside a VFS callback, which SQLite reaches throughextern "C"and which rsqlite-vfs does not wrap incatch_unwind, so it ended the process instead of failing the SQLite call.sqlite-remote-server validates these values, so only a foreign or compromised server could send them. That is the adversary the README already names for the relay case.
What changes
Openedanswers (database.rs): the page size must be a power of two from 512 to 65536 and, on a resume, the one in use; the page count at mostSQLITE_MAX_PAGE_COUNT(4294967294). Anything else fails the open or the resume with a protocol error before it sizes anything.local.rs): it has no checksum, so a header with a page size of 0 or above 65536, or a page count beyond the limit, is not trusted. The page size is only bounded there, because the copy's fit is decided against the open database when it is used.local_database.rs): the same bounds on the head the connection worker returns from IndexedDB.pages.rs; one sentence in the README under How it works.Tests
Four new unit tests: refused page sizes (0, 100, 3000, 2^17,
u32::MAX), refused page counts beyond the limit, accepted limits (512, 65536, the largest page count on demand), and the damaged file header in three variants, with the undamaged one still read.Verified locally:
cargo fmt --check,cargo clippy --workspace --all-targets -- -D warnings, the same onwasm32-unknown-unknowninbrowser/,cargo test --workspace,cargo docwith-D warnings.Left as is: the reconnect loop retries a protocol error on resume until its deadline and then breaks the database as uncertain. Bounded, and a behaviour change there would need its own test.
🤖 Generated with Claude Code