hammer2_vop_nsymlink unconditionally discards the symlink-data write error ('/* XXX handle error */ error = 0;')
Summary
The symlink target write via hammer2_write_file(IO_APPEND) has its error overwritten with 0. On any write failure (EIO from the DIRECTDATA-transition read) symlink(2) returns success and the namecache commits to a symlink whose target is truncated or zero-filled; the direct call also bypasses every gate hammer2_vop_write applies (enospace classification, RLIMIT_FSIZE). Static code-path verification only: the only reachable error source is a media read failure not injectable on the QEMU virtio guest β honest not_reproduced. Propagate-error + unlink-finisher-cleanup fix proposed.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-5819 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | β | 1.5 KB | β raw | |
| verdict.json | β | 2.0 KB | view raw |
DF-5819 β hammer2_vop_nsymlink discards the symlink-data write error
What this pack contains
Static verification only β the trigger requires an I/O error inside
hammer2_write_file (bread_kvabio of the direct-data buffer), which cannot
be injected on the stock guest without a fault-injection device.
Code path (certain)
hammer2_vop_nsymlink (sys/vfs/hammer2/hammer2_vnops.c:1829-1852) writes the
symlink target with:
error = hammer2_write_file(nip, &auio, IO_APPEND, 0); /* XXX handle error */ error = 0; (:1847-1849)
Any failure of that write (EIO from the bread_kvabio in the DIRECTDATA
transition at :1320, uiomovebp fault) is overwritten with 0. The VOP then
returns success, the namecache is committed to the new name (:1863-1866),
and the symlink exists with truncated or zero-filled content while
symlink(2) reported success. Note also that this call bypasses every gate
hammer2_vop_write applies (ronly is checked, but enospace classification
and the RLIMIT_FSIZE check are skipped entirely because write_file is called
directly).
PoC sketch (why not run)
The only reachable error source is a media read failure of block 0 during the embedded-data transition β on the QEMU virtio disk this never fails. Attempting to force it would require a dm-like fault injector DragonFly does not expose to unprivileged users.
Fix
error = hammer2_write_file(nip, &auio, IO_APPEND, 0);
if (error) {
/* target truncated/failed: unwind the symlink */
...unlink_finisher path or return error...
}
(error = 0 must go.)
Fix verification
not_testablenot_testable: triggering requires a media-read failure inside the embedded-data transition, not injectable on the QEMU virtio disk.
Confirmed kernel references
Detail
Evidence (decisive lines)
README.md code-path trace with exact path:line; no runtime artifact (fault injection unavailable).
PoC changes
no runnable trigger possible without a fault-injecting block device; static verification only.
Verified recommended fix
Propagate the error: on failure run the unlink-finisher cleanup for nip (as the create-error path does) and return the error instead of 'error = 0'.
Verdict
hammer2_vop_nsymlink unconditionally discards the result of hammer2_write_file for the symlink target (hammer2_vnops.c:1847-1849, explicit '/ XXX handle error / error = 0;'). The bug is certain in source: on any write failure the VOP returns success and the namecache commits to a symlink whose target is truncated or zero-filled; the call also bypasses the enospace classification and RLIMIT_FSIZE gate that hammer2_vop_write applies because write_file is invoked directly. Runtime trigger requires an I/O error from the bread_kvabio DIRECTDATA-transition read (hammer2_vnops.c:1320) which cannot be injected on the stock virtio guest, so no live reproduction was attempted beyond the code-path trace.
No comments yet.