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

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.

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2641 Β· 2 files
FileTypeDescriptionSize
README.md file 2.4 KB ↓ raw
verdict.json file 2.2 KB view raw
README.md file
↓ download 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): reads chain->data->ipdata.meta.type with no chain->error check 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 by chain->error == 0, which the DF-2617 primitive (data_off==0, chain.c:938) leaves at 0 with chain->data == NULL.
  • :664-678 and :691-721 (hammer2_xop_nrename adjust blocks): after hammer2_chain_modify() β€” which succeeds with chain->data == NULL for data_off==0 chains (chain.c:1467-1473 skips the load when data_off & ~HAMMER2_OFF_MASK_RADIX is 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-modify parent->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_testable
per-fix-DF-2641

Confirmed 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.