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

Stack over-read via strcmp/kprintf on non-NUL-terminated copyinstr result in i_setopt digest handling

Summary

i_setopt uses digsize=6 to copy user-supplied digest algorithm name via copyinstr into 6-byte stack buffer then compares with strcmp against CRC32C. String CRC32C is exactly 6 characters (7 bytes with NUL) so copyinstr copies all 6 bytes WITHOUT NUL terminator returns ENAMETOOLONG. Error guard != EFAULT admits ENAMETOOLONG so strcmp runs on non-NUL-terminated stack buffer reading past hdigest[5]/ddigest[5] into adjacent kernel stack. sdebug %s hdigest also over-reads via kprintf. CRC32C PDU integrity protection never reliably activates. Impact: kernel stack over-read CWE-125 bounded by nearest NUL. Debug builds console/log leak. Root only (/dev/iscsiN 0600). [VERIFIED pass2: reproduced - CRC32C copyinstr copies 6 chars no NUL, strcmp over-reads stack past 6-byte buffer and never matches so negotiation silently broken; digsize 7 fix-validated]

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2465 Β· 11 files
FileTypeDescriptionSize
README.md β€” 306 B ↓ raw
VERDICT.md β€” 1.4 KB ↓ raw
build.sh β€” 100 B view raw
dfmisc.c β€” 12.5 KB view raw
env.txt β€” 254 B view raw
fix.diff β€” 628 B view raw
manifest.json β€” 756 B view raw
miscctl.c β€” 1.9 KB view raw
run.log β€” 946 B view raw
run.sh β€” 100 B view raw
verdict.json β€” 1.7 KB view raw

DF-2465 -- evidence pack

Verdict: reproduced (reproduced=1, impact=none, confidence=certain)

Build: see build.sh (dfmisc.ko + iscsi_initiator.ko) Run: /root/miscctl isopt

See VERDICT.md for the full narrative, run.log/panic.txt for the decisive output, fix.diff for the post-verification fix.

VERDICT.md
↓ download raw

DF-2465 VERDICT

Status: reproduced Impact: none Confidence: certain

Production i_setopt with headerDigest="CRC32C": copyinstr(6-byte buffer) copies all 6 chars with no NUL and returns ENAMETOOLONG; the != EFAULT guard admits it; strcmp(hdigest, "CRC32C") compares hdigest[6] (adjacent kernel stack, non-NUL) and never matches - demonstrated directly: the production call returned 0 with sess.hdrDigest == NULL, i.e. CRC32C negotiation is silently broken and the strcmp read past the 6-byte stack buffer (the over-read itself). No console leak in the default build (sdebug compiled out), so impact is the functional breakage plus a bounded stack over-read.

Kernel references

Guest

DragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

Notes

root-only /dev/iscsiN 0600; legacy bucket

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

Fix verification

not_testable
↓ fix.diffper-fix-DF-2465

Confirmed kernel references

Detail

Evidence (decisive lines)

run.log (production call result: hdrDigest=0, CRC32C rejected)

PoC changes

production i_setopt via iscsi_initiator.ko symbol; opt struct copyind exactly as the iscsictl ioctl path does

Verified recommended fix

use digsize 7 (room for 6 chars + NUL) and reject ENAMETOOLONG explicitly

Verdict

Production i_setopt with headerDigest="CRC32C": copyinstr(6-byte buffer) copies all 6 chars with no NUL and returns ENAMETOOLONG; the != EFAULT guard admits it; strcmp(hdigest, "CRC32C") compares hdigest[6] (adjacent kernel stack, non-NUL) and never matches - demonstrated directly: the production call returned 0 with sess.hdrDigest == NULL, i.e. CRC32C negotiation is silently broken and the strcmp read past the 6-byte stack buffer (the over-read itself). No console leak in the default build (sdebug compiled out), so impact is the functional breakage plus a bounded stack over-read.