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

hammer2_inode_chain_sync clears RESIZED/MODIFIED before the backend sync and never restores them on error β€” silent metadata loss

Field Value
ID DF-2638
Status new
Severity Low
CVSS 3.1 CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:N
CWE CWE-755 Improper Handling of Exceptional Conditions
File sys/vfs/hammer2/hammer2_inode.c
Lines 1718-1733
Area vfs
Confidence likely
Discovered 2026-08-29
Pass 2 (GLM 5.3 second pass)
Bucket hammer2
Reported pending
Known CVE none
CVE match novel

Summary

hammer2_inode_chain_sync() clears HAMMER2_INODE_RESIZED|MODIFIED (inode.c:1718-1719) BEFORE starting the backend chain-sync xop; if the xop fails, the flag-restore code is commented out (XXX return error somehow?, :1725-1733) and the error is swallowed. Pending metadata (size after truncate/extend, times, mode β€” including the RESIZED osize used to clear DIRECTDATA) silently drops out of the dirty state, so in-memory meta can desync from the chain/data topology (stale meta.size after remount). Reached from every hammer2 fsync and from the syncer (hammer2_vfsops.c:2699).

Threat model & preconditions

Silent loss of inode metadata on backend errors (I/O failure, ENOSPC pressure per DF-2633) β€” data-integrity issue; no memory-safety primitive.

Proof of concept

Trigger sketch (guest-run skipped per contract β€” Low): fill a PFS to ENOSPC, truncate + fsync in a loop, observe size changes vanishing after remount.

Uncomment the restore and propagate errno: atomic_set_int(&ip->flags, xop->ipflags & (HAMMER2_INODE_RESIZED|HAMMER2_INODE_MODIFIED)); on error, plus return the errno to callers.

References

  • DF-2633 (the ENOSPC storm that reaches the swallowed-error path)

Timeline

  • 2026-08-29 Discovered during pass-2 audit of hammer2_inode.c (GLM 5.3).

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2638 Β· 3 files
FileTypeDescriptionSize
README.md β€” 2.1 KB ↓ raw
verdict.json β€” 1.3 KB view raw
manifest.json β€” 632 B view raw

DF-2638 β€” hammer2_inode_chain_sync clears RESIZED/MODIFIED before the backend sync and never restores them on error

What

sys/vfs/hammer2/hammer2_inode.c:1718-1733 (hammer2_inode_chain_sync):

    atomic_clear_int(&ip->flags, HAMMER2_INODE_RESIZED |
                     HAMMER2_INODE_MODIFIED);
    hammer2_xop_start(&xop->head, &hammer2_inode_chain_sync_desc);
    error = hammer2_xop_collect(&xop->head, 0);
    hammer2_xop_retire(&xop->head, HAMMER2_XOPMASK_VOP);
    ...
    if (error) {
        kprintf("hammer2: unable to fsync inode %p\n", ip);
        /*
        atomic_set_int(&ip->flags,
                   xop->ipflags & (HAMMER2_INODE_RESIZED |
                           HAMMER2_INODE_MODIFIED));
        */
        /* XXX return error somehow? */
    }

The dirty flags are cleared before the backend xop runs; if the xop fails (I/O error, ENOSPC-pressure conditions per DF-2633), the restore is commented out and the error is swallowed. Consequences: the in-memory meta changes (size after truncate/extend, times, mode) are silently dropped from the dirty state β€” the inode can go out of sync with its chains/data, and a subsequent truncate that shrank the file can leave meta.size stale relative to the data topology after remount (data-integrity / silent-metadata-loss; the caller believes the sync succeeded only partially because errno is 0 on the next call).

Reached from every hammer2 fsync/sync path (hammer2_inode_chain_sync is called by vop_fsync and the syncer, hammer2_vfsops.c:2699).

Trigger sketch

Fill a PFS to ENOSPC, truncate+fsync in a loop; backend chain_sync xop fails; flags already cleared; meta.size desyncs from media. Not guest-verified this run (Low severity, speculative end-impact; skipped per audit contract β€” see verdict.json).

Fix

Restore the flags on error (uncomment and keep xop->ipflags) and propagate the error:

    if (error) {
-       kprintf("hammer2: unable to fsync inode %p\n", ip);
-       /*
        atomic_set_int(&ip->flags,
                   xop->ipflags & (HAMMER2_INODE_RESIZED |
                           HAMMER2_INODE_MODIFIED));
-       */
+       kprintf("hammer2: unable to fsync inode %ld\n",
+           (long)ip->meta.inum);
    }

Fix verification

not_testable
per-fix-DF-2638

Confirmed kernel references

Detail

Evidence (decisive lines)

README.md quotes the code with the commented-out restore

Verified recommended fix

Uncomment the flag restore on error and propagate errno.

Verdict

hammer2_inode_chain_sync (hammer2_inode.c:1718-1733) clears HAMMER2_INODE_RESIZED|MODIFIED before starting the backend xop; on xop failure the flag-restore code is commented out and the error swallowed ('XXX return error somehow?'), so pending metadata (size/times/mode, especially truncate RESIZED with its osize) is silently dropped from the dirty state. Data-integrity bug (silent metadata loss / potential stale meta.size vs data topology after remount); no memory-safety primitive demonstrated. Guest verification intentionally skipped per audit contract (Low).