Latent unguarded chain->data derefs in xop_nrename/xop_unlink/chain_sync (539 lacks even the error check its siblings have; 670-678/1618 are NULL-offset WRITE variants after modify-succeeds-with-NULL)
| Field | Value |
|---|---|
| ID | DF-2641 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:N/A:H |
| CWE | CWE-476 NULL Pointer Dereference (defense-in-depth) |
| File | sys/vfs/hammer2/hammer2_xops.c |
| Lines | 403, 539, 670-678, 718, 737, 1618 |
| Area | vfs |
| Confidence | certain |
| Discovered | 2026-08-29 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | hammer2 |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
xops.c:539 (nrename ip4 type read) dereferences chain->data with NO
chain->error check at all (inconsistent with :386/:630); :403 is
guarded only by error==0 which the DF-2617 primitive leaves at 0 with
data==NULL; :664-678/:691-721 bzero/bcopy into chain->data->ipdata
after hammer2_chain_modify() which SUCCEEDS with data==NULL for
data_off==0 chains (chain.c:1467-1473 skips the load) β a write-side
NULL+0x100 fault; :737 runs with no error/data guard; :1618
(chain_sync meta write) same class in DF-0800's function.
Threat model & preconditions
Latent: on every traced path an earlier same-class consumer faults first (the dirent_test call sites β DF-2640's verified crash β or the frontend hammer2_inode_get gdata read at inode.c:866; on multi-node clusters the corrupt slave's backend crashes in dirent_test during the nresolve that created the in-memory inode). Defense-in-depth gaps closed at the source by the DF-2617 root fix plus DF-2640's dirent_test/site guards.
Proof of concept
No independent trigger (shadowed by DF-2640's verified first-touch crash β see findings/poc/DF-2641/README.md for the site-by-site shadow analysis). All sites are guarded by findings/poc/DF-2640/fix.diff, which was regression-validated on the rebuilt kernel in the same run.
Recommended fix
Included and validated in findings/poc/DF-2640/fix.diff: chain->data /
chain->error guards at xops.c:402/543, post-modify data==NULL β EIO
checks in both nrename adjust blocks, error==0 && chain->data != NULL
on the iparent update (:755), and a post-modify parent->data check in
chain_sync (:1636).
References
- DF-2640 (primary, verified), DF-2617 (root primitive), DF-0800
Timeline
- 2026-08-29 Discovered during pass-2 audit of hammer2_xops.c (GLM 5.3).
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2641 Β· 2 files| File | Type | Description | Size | |
|---|---|---|---|---|
| README.md | file | 2.4 KB | β raw | |
| verdict.json | file | 2.2 KB | view raw |
DF-2641 β PoC evidence pack (skipped: Low/hardening, latent sinks)
Latent unguarded chain->data derefs in hammer2 xops backends β DF-2617
silent-NULL class, shadowed on every reachable path by earlier consumers.
Sites (all in sys/vfs/hammer2/hammer2_xops.c):
- :539 (
hammer2_xop_nrename, ip4 target-directory type read): readschain->data->ipdata.meta.typewith nochain->errorcheck at all β inconsistent with its siblings xops.c:386 / :630. Also :536-546 has no data guard. A chain with a live I/O/CHECK error (data NULL) faults at VA 0x50. - :403 (
hammer2_xop_unlink): guarded only bychain->error == 0, which the DF-2617 primitive (data_off==0, chain.c:938) leaves at 0 withchain->data == NULL. - :664-678 and :691-721 (
hammer2_xop_nrenameadjust blocks): afterhammer2_chain_modify()β which succeeds withchain->data == NULLfor data_off==0 chains (chain.c:1467-1473 skips the load whendata_off & ~HAMMER2_OFF_MASK_RADIXis 0) βwipdata = &chain->data->ipdata; bzero(wipdata->filename, 256); bcopy(name2, β¦)is a write variant faulting at the fixed address NULL+0x100. - :737 (iparent update): runs even when a prior step set
error != 0, with no data guard. - :1618 (
hammer2_xop_inode_chain_sync): post-modifyparent->data->ipdata.meta = xop->metaβ NULL write sink in the same function DF-0800 covers (DF-0800 is the parent==NULL variant at :1603).
Why Low / not verified to crash on their own lines: reaching any of these
with chain->data == NULL requires an in-memory inode (or a lookup result)
whose chain is corrupt. Every route there first passes (a) the
hammer2_chain_dirent_test() call sites at xops.c:282/368/610/771 β the
DF-2640 first-touch crash (verified: Fatal trap 12 in bcmp), or (b) the
frontend hammer2_inode_get() gdata read (inode.c:866). On multi-node
clusters the corrupt slave's backend crashes in the same dirent_test during
the nresolve that created the in-memory inode. So these lines are
defense-in-depth gaps that DF-2617's root fix (error on data_off==0) plus
DF-2640's dirent_test guards close at the source.
Fix: all of these sites are guarded by DF-2640's verified fix.diff
(same build, same regression run β see ../DF-2640/). Per the audit contract,
Phase V is skipped for Low/Info hardening findings; verdict below records
untested with this pointer.
Fix verification
not_testableConfirmed kernel references
Detail
Evidence (decisive lines)
['README.md (site-by-site analysis with fault addresses)', '../DF-2640/VERDICT.md (shadow-path analysis + verified fix covering these lines)', '../DF-2640/fix.diff (guards at xops.c:402/543/post-modify/755/1636)']
PoC changes
n/a (skip; no trigger built for these latent lines)
Verified recommended fix
Already included and regression-validated in DF-2640's fix.diff: chain->data/chain->error guards at all listed sites.
Verdict
Low/hardening skip per audit contract (Low/Info findings). The sinks at xops.c:403/539/664-678/737/1618 are provably unguarded consumers of the DF-2617 silent-NULL primitive (chain.c:938 load early-return; chain.c:1467 modify-succeeds-with-NULL), but on every traced path an earlier same-class consumer faults first - the dirent_test call sites at xops.c:282/368/610/771 (crash verified as DF-2640 on this guest: 'Fatal trap 12, fault VA 0x0, bcmp+0xf0, current process Idle') or the frontend hammer2_inode_get gdata read (inode.c:866). They are therefore latent/defense-in-depth: DF-2640's fix.diff (validated on rebuilt kernel #1 in ../DF-2640/) guards every one of these lines in the same regression-tested build.
No comments yet.