β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-5819

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-5819 Β· 2 files
FileTypeDescriptionSize
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_testable
baseline no→ patch + rebuild →patched clean

not_testable: triggering requires a media-read failure inside the embedded-data transition, not injectable on the QEMU virtio disk.

per-fix-DF-5819

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.