Repository navigation
core/wal: read raw WAL frames into a buffer the read owns - #10245
turso-github-handyman[bot] wants to merge 1 commit into
Conversation
Autofixer merge readiness: ✅ Recommended
TL;DR: If IO step() failed mid-read, Least sure about: How the audit checked itChange: Invariant violation · Risk if wrong: medium · Behaviour: checked · Audited:
Commands the audit ran (7)
Autofixer's recommendation; a maintainer decides. Updates when the patch, CI, or main changes. |
Autofixer breaker follow-upsThe breaker found 1 defect that
|
|
Autofixer pushed 8af08fe to rebase onto main. |
1dc5981 to
8af08fe
Compare
Autofixer verifier historyVerifiers raised 1 blocker or major finding while Autofixer worked on this pull request. Later rounds may have fixed them; the merge audit re-checks each one. Findings, oldest first
|
read_frame_raw handed the IO completion a raw pointer to the caller's slice. If IO::step() failed while the read was queued (io_uring does this when submit_and_wait fails), wait_for_completion returned the error but the read stayed queued, and a later step() wrote into the caller's slice after wal_get_frame had returned. If the caller had freed that memory, this was a write to freed memory. read_frame_raw now takes an Arc<Buffer>, the same way read_frame and issue_wal_read_into_buffer already do. The completion closure holds a clone of the Arc, so a read that finishes late writes only into memory it keeps alive. Connection::wal_get_frame copies that buffer into the caller's slice only after wait_for_completion succeeds. On error the caller's slice is left untouched. Correctness argument: - Other transactions: the change only affects where the bytes of a WAL frame read land in memory. It reads no other frames and changes no read marks, WAL locks, or snapshot handling, so what other connections see before or after a commit is unchanged. - Abort and rollback: wal_get_frame is a read and has nothing to undo. A failed call leaves the caller's buffer as it was, and the orphaned read writes only into its own buffer, which is freed when the completion is dropped. - Crash recovery: nothing is written to the database or WAL file, so a crash at any point leaves the on-disk state as it was. - Locks: no lock is added or taken in a new order, so no deadlock is possible. No new state is shared between connections. The buffer belongs to one call and its completion. Tests: wal_get_frame_does_not_write_into_buffer_after_it_returns_error fails on the base and passes with this change.
|
Autofixer pushed c41d452 to rebase onto main. |
8af08fe to
c41d452
Compare
Description
Connection::wal_get_framecould write into the caller's buffer after it had already returned an error. If the IOstep()failed while the frame read was queued (io_uring does this whensubmit_and_waitfails),wait_for_completionreturned the error but the read stayed queued. A laterstep()then wrote the frame into the caller's memory, which the caller may already have freed.The cause was in
WalFile::read_frame_raw: it turned the caller's&mut [u8]into a raw address and the read callback wrote through it withunsafe. Nothing kept that memory alive for as long as the read could still run.read_frame_rawnow takes anArc<Buffer>that the callback keeps alive, which removes the raw pointer and theunsafeblock.Connection::wal_get_framekeeps its public signature: it reads into a temporary buffer and copies into the caller's slice only after the read succeeds.changed_pages_afterreuses one owned buffer for all frames.Motivation and context
Fixes #9338
Description of AI Usage
Autofixer wrote this change with Claude Code in an isolated sandbox. A separate verifier agent tested and reviewed it in its own container before this PR was opened. A maintainer must review it before merge.
To have Autofixer address review feedback, leave your comments and then mention @turso-github-handyman; it handles all unanswered comments from maintainers in one round.