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.
Recommended fix
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)
PoC verification
Evidence pack
findings/poc/DF-2638 Β· 3 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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_testableConfirmed 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).
No comments yet.