schfs: records at arbitrary offsets are packed, and the compiler is told
A leaf's value area holds variable-length records end to end, so a record begins wherever the one before it finished -- a multiple of 8 only by luck. The code reads them by casting a pointer into the block, which tells the compiler they are aligned when they are not. That works on x86, which is why it has never shown as a bug, and it is undefined in C. A compiler entitled to assume 8-byte alignment may emit an instruction that requires it, and the failure would arrive at a different optimisation level or on a different architecture looking like filesystem corruption rather than like codegen. UndefinedBehaviorSanitizer reported 31 of them across obj.c, objw.c, attr.c and space.c. Seven types now carry SCHFS_PACKED: dirent_rec, extent, extref, index_rec, attr_rec, snap and inode. Two of those -- attr_rec and inode -- were never reported, because schfs_objtest does not reach an unaligned instance of either. They were latent rather than absent, and casting into a leaf is the same lie whether or not a test has caught it yet. NO ON-DISK LAYOUT CHANGED, and that was measured before the annotation was applied rather than assumed: every one of these already carries explicit `pad` fields and therefore has no implicit padding, so sizes and member offsets are identical with and without it. Seven _Static_asserts now pin the sizes, because a record that silently grows misreads every existing volume from that point on and looks like corruption rather than like a struct that changed. schfs_node_hdr, schfs_item, schfs_kptr and schfs_uberblock are deliberately left alone. The node header is exactly 32 bytes, so the items and key pointers after it are already 8-aligned, and annotating them would cost byte-wise access on the tree's hottest paths to fix a problem they do not have. Verified by reading an image written before the change: schfs_fsck --check passes on structure, checksums, key ordering, data blocks and derived free space, and the kernel mounts its own root unchanged. Also here: mkschfs passed a null pointer to qsort for an empty directory. Every implementation ignores it when the count is zero; the standard does not say they have to. make schfs-check now runs with no sanitizer output at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VQ7Qx8E1q1M1bbCq8QLHS2
This commit is contained in:
parent
25530e7eed
commit
7c8cf51941
5 changed files with 123 additions and 41 deletions
|
|
@ -121,20 +121,27 @@ linked into the kernel binary for a `ramroot` build.
|
|||
|
||||
---
|
||||
|
||||
## One known failure left
|
||||
## The suite is green
|
||||
|
||||
`make schfs-check` passes. UndefinedBehaviorSanitizer still reports **31
|
||||
misaligned reads** from casting pointers into tree leaves where records are not
|
||||
8-aligned — itemised in [../plan/1.0.md](../plan/1.0.md), and printed on stderr
|
||||
without failing the run.
|
||||
`make schfs-check` passes with no sanitizer output at all. It was not, until
|
||||
recently, and both failures are worth knowing about because neither was in the
|
||||
filesystem's logic:
|
||||
|
||||
The leak half of this is closed, and how it closed is the useful part: the test
|
||||
leaked 518 pages because the allocation map had no release path *at all*, which
|
||||
meant the kernel leaked the same map on every unmount. **A suite that leaks
|
||||
cannot detect a leak** — the filesystem's own allocations would have arrived as
|
||||
noise inside the harness's. `schfs_objtest` now asserts that every page it
|
||||
handed out came back, so the next one fails the suite rather than joining the
|
||||
background.
|
||||
**A leak with no API to fix it.** The test leaked 518 pages because the
|
||||
allocation map had no release path *at all* — which meant the kernel leaked the
|
||||
same map on every unmount. **A suite that leaks cannot detect a leak**: the
|
||||
filesystem's own allocations would have arrived as noise inside the harness's.
|
||||
`schfs_objtest` now asserts that every page it handed out came back.
|
||||
|
||||
**31 misaligned reads.** Records are read by casting a pointer into a tree leaf,
|
||||
where they sit at arbitrary offsets. That works on x86 and is undefined in C, so
|
||||
it had never shown as a bug and would have arrived as apparent filesystem
|
||||
corruption at some future optimisation level.
|
||||
|
||||
The shared lesson is the one this directory keeps relearning: **a sanitizer that
|
||||
is reliably noisy is a sanitizer nobody reads.** Both were dismissible as "known
|
||||
and harmless" for as long as they were there, and one of them was a real kernel
|
||||
leak.
|
||||
|
||||
## What is not tested
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue