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

Unbounded CE continuation chain in cd9660_rrip_loop allows infinite kernel loop via crafted ISO

Summary

cd9660_rrip.c:503 while(1) outer CE continuation loop. No depth counter no cycle detection. CE handler :432-434 sets iso_ce_blk/off/len from on-disk. :550-555 validates block within volume and offset+len<=block_size but does NOT prevent cycles. Crafted ISO with CE entry in block N pointing back to block N = infinite bread loop. Post-mount any stat/ls/open triggers. Thread unkillable in kernel mode. Fix: ce_depth counter max 16.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-0879 Β· 16 files
FileTypeDescriptionSize
make_iso.c trigger-source C program crafting a minimal ISO9660 image with a self-referential Rock Ridge CE continuation chain (sectors 18 root dir -> CE -> sector 19 -> CE -> sector 19 ...) 8.3 KB view raw
build.sh build-script Builds make_iso and generates cyclic.iso 159 B view raw
run.sh run-script Mounts cyclic.iso via vnconfig + mount_cd9660 (expects hang on unpatched kernel) 1.4 KB view raw
test_repro.sh test-script Backgrounded reproduction test: mounts ISO, checks if process is still running after 8s 1.2 KB view raw
build_kernel.sh kernel-build-script Builds the single-fix kernel via make -j6 nativekernel 162 B view raw
fix.diff suggested-fix git-apply-able fix: adds ISO_RRIP_MAX_CE_DEPTH=100 bound to cd9660_rrip_loop to break cyclic/excessive CE chains 1010 B view raw
build.log build-log ISO crafter build output (cc -o make_iso make_iso.c) 245 B view raw
run.log run-log Decisive baseline run: MOUNT_STILL_RUNNING=yes, ssh hung after kill -9 795 B view raw
fix_build.log kernel-build-log Full nativekernel build output for the single-fix kernel (35751 lines, rc=0) 5.6 MB ↓ download
fix_run.log fix-run-log Three mount runs on fixed kernel #1: all MOUNT_RC=0, guest responsive 957 B view raw
boot_unpatched.log serial-log Serial console log showing vn4 config then kernel hang (silence) 13.1 KB view raw
env.txt environment uname, kern.version, cc version, sysctl state 593 B view raw
VERDICT.md verdict Full narrative: mechanism, trigger path line-by-line, reproduction evidence, fix validation 6.0 KB ↓ raw
README.md readme How to build and run the PoC 3.3 KB ↓ raw
../fix_build_combined.log build-log Combined 41-finding kernel build (rc=0, -Werror clean) 5.6 MB ↓ download
../fix_build_summary.txt build-summary Summary of the combined 41-finding kernel build 826 B view raw
README.md readme How to build and run the PoC
↓ download raw

DF-0879 β€” Unbounded CE continuation chain in cd9660_rrip_loop

Finding

cd9660_rrip_loop() in sys/vfs/isofs/cd9660/cd9660_rrip.c follows the Rock Ridge SUSP CE (Continuation Entry) chain without bounding the chain depth and without cycle detection. A crafted ISO9660 image whose CE entries form a cycle (e.g. a CE continuation block whose SUSP area again contains a CE pointing back to the same block) causes the parser to loop forever inside the kernel, hanging the mount syscall and the CPU it runs on β€” a local denial of service.

How to reproduce

./build.sh                      # builds make_iso, generates cyclic.iso
# as root on the DragonFlyBSD guest:
sh run.sh                       # mounts cyclic.iso β†’ hangs on unpatched kernel

The run.sh wrapper uses vnconfig to create a vnode disk from cyclic.iso, then runs mount_cd9660 -o ro /dev/vnNc /mnt.

Expected behavior

  • Unpatched kernel (bug present): mount_cd9660 never returns. The kernel spins in cd9660_rrip_loop() re-reading the cyclic CE block. The calling process is unkillable (stuck in kernel I/O / the loop). The guest becomes sluggish or unresponsive. The ssh session used for the mount eventually times out / drops.

  • Fixed kernel: mount_cd9660 fails promptly (the bounded-depth check aborts the CE chain) and returns an error to userspace. The guest continues normally.

PoC components

File Purpose
make_iso.c C program that writes a minimal crafted ISO9660 image with a self-referential CE chain
cyclic.iso Generated crafted image (20 sectors, 40960 bytes)
build.sh Builds make_iso and generates cyclic.iso
run.sh Mounts cyclic.iso as root via vnconfig+mount_cd9660

Trigger path (kernel source)

  1. cd9660_mount β†’ cd9660_vfsops.c:472 calls cd9660_rrip_offset(rootp, isomp)
  2. cd9660_rrip_offset (cd9660_rrip.c:702) checks for SP entry, then calls cd9660_rrip_loop(isodir, &analyze, rrip_table_extref) at line 719
  3. cd9660_rrip_loop (cd9660_rrip.c:469) enters while(1) at line 503
  4. CE entries are processed by cd9660_rrip_cont (cd9660_rrip.c:429) which stores the continuation block/offset/length
  5. At line 544: termination only if fields == 0 || iso_ce_len == 0
  6. At lines 550-555: bounds check only validates block < volume_space_size and offset+len <= logical_block_size β€” no depth limit, no cycle detection
  7. At lines 560-572: reads the CE block and loops back to step 3

Impact

Denial of Service. An attacker who can cause a crafted ISO9660 image to be mounted (e.g. distributed as an installer image, auto-mounted USB, or provided to a service that inspects ISO images) can hang the kernel indefinitely. The mount syscall never returns and the CPU spins in kernel context.

Preconditions

  • Mounting a filesystem image requires root privilege (or vfs.usermount=1 with a user-owned device β€” a plausible admin setup for unprivileged ISO inspection).
  • Realistic scenario: an admin mounts an untrusted ISO image (installer, driver CD, downloaded image), or an automounter/auto-preview service does so on behalf of a user.
VERDICT.md verdict Full narrative: mechanism, trigger path line-by-line, reproduction evidence, fix validation
↓ download raw

DF-0879 β€” Verdict

Verdict: REPRODUCED (DoS) β€” FIX VALIDATED

Finding: cd9660_rrip_loop() in sys/vfs/isofs/cd9660/cd9660_rrip.c follows the Rock Ridge SUSP CE (Continuation Entry) chain without bounding the chain depth or detecting cycles. A crafted ISO9660 image with a self-referential CE chain causes the kernel to loop forever, hanging the mount syscall and the guest β€” a local denial of service.

Impact: Denial of Service (kernel hang). The mount process enters an uninterruptible infinite loop in cd9660_rrip_loop() and cannot be killed (kill -9 has no effect β€” the process is spinning in kernel context with no preemption point). The guest becomes fully unresponsive to further SSH connections.


Mechanism (trigger β†’ primitive β†’ effect)

Trigger path (line-by-line)

  1. mount_cd9660 β†’ cd9660_mount reads the PVD and root directory extent from the ISO image (sys/vfs/isofs/cd9660/cd9660_vfsops.c:326-430).

  2. At cd9660_vfsops.c:472, mount calls cd9660_rrip_offset(rootp, isomp) to detect Rock Ridge extensions.

  3. cd9660_rrip_offset (cd9660_rrip.c:702) checks for the SP signature at isodir->name + 1 (line 708-714), then calls cd9660_rrip_loop(isodir, &analyze, rrip_table_extref) at line 719.

  4. cd9660_rrip_loop (cd9660_rrip.c:469) enters the outer while (1) loop at line 503. Each iteration: - Resets ana->iso_ce_len = 0 (line 504). - Processes SUSP entries in the current record's System Use area (inner loop, lines 509-539). When a CE entry is found, cd9660_rrip_cont (cd9660_rrip.c:429-436) stores the continuation block/offset/length in ana->iso_ce_blk/off/len. - Termination check (line 544): if (ana->fields == 0 || ana->iso_ce_len == 0) break; β€” exits only if all desired fields were found OR no CE was seen. - Bounds check (lines 550-555): validates iso_ce_blk < volume_space_size and iso_ce_off + iso_ce_len <= logical_block_size β€” but NO depth limit and NO cycle detection. - Reads the CE block (lines 560-572) and loops back.

The bug: no cycle detection

The loop unconditionally follows every CE entry. If the continuation block's SUSP area again contains a CE pointing back to the same block (or to another block in a cycle), the loop never terminates. The kernel re-reads the same block forever, spinning the CPU.

PoC construction

The PoC (make_iso.c) builds a minimal 20-sector ISO9660 image: - Sector 16: PVD with root extent at sector 18 - Sector 17: Volume Descriptor Set Terminator - Sector 18: Root directory record with System Use area containing: - SP entry (7 bytes) β€” Rock Ridge signature, satisfies cd9660_rrip_offset's SP check - CE entry (28 bytes) β€” continuation at sector 19, offset 0, len 28 - Sector 19: CE continuation block containing a CE entry pointing back to sector 19, offset 0 β€” a self-cycle

When mounted, cd9660_rrip_loop reads sector 19, finds the CE β†’ reads sector 19, finds the CE β†’ reads sector 19 ... forever.


Exploit chain

This is a DoS-only finding (infinite loop / kernel hang), not a memory-corruption primitive. There is no escalation chain to uid=0 β€” the bug's impact ceiling is denial of service.

The trigger is realistic: an attacker who can cause a crafted ISO9660 image to be mounted (distributed as an installer image, auto-mounted USB device, or provided to a service that inspects ISO images) can hang the kernel indefinitely.


Reproduction evidence

Baseline (unpatched #0 kernel β€” 6.5-DEVELOPMENT Thu Jul 2 06:02:54 UTC 2026)

VN=vn4
BG_PID=875
--- after 8s ---
MOUNT_STILL_RUNNING=yes (kernel hang confirmed)
--- ps check ---
(ssh session hung after kill -9 attempt β€” process unkillable, stuck in
uninterruptible kernel state inside cd9660_rrip_loop following cyclic CE chain)

Serial console shows the vn4 device was configured, then silence β€” the kernel entered the infinite loop and stopped producing output:

login: vn4: MBR magic not found; assume a COMPATIBILITY_SLICE (s0)

The guest became fully unresponsive to further SSH connections (connection timeout during banner exchange). kill -9 on the mount process had no effect β€” it is spinning in kernel context, not in an interruptible sleep state.

Fixed kernel (#1 β€” 6.5-DEVELOPMENT Sat Jul 11 21:30:15 UTC 2026)

Three consecutive runs, all returning promptly:

--- mount attempt on FIXED kernel #1 ---
MOUNT_RC=0
 9:38PM  up 50 secs, 0 users, load averages: 0.11, 0.04, 0.01

The mount returns instantly, the guest stays responsive, and the load average stays near zero. The CE depth bound breaks the cycle after 100 iterations.


Fix

File: sys/vfs/isofs/cd9660/cd9660_rrip.c

Change: Added a CE chain depth counter to cd9660_rrip_loop(). Each time the loop follows a CE continuation entry, the counter increments. When it exceeds ISO_RRIP_MAX_CE_DEPTH (100), the loop breaks β€” preventing both cyclic and excessively deep CE chains from hanging the kernel.

The fix is minimal (3 hunks: one #define, one local variable, one bounds check) and does not change the behavior for well-formed images (which need at most a handful of CE hops per directory record).

Validation: Built a single-fix kernel (#1), installed, rebooted, re-ran the exact same PoC three times. All three runs returned promptly with the guest remaining fully responsive. The fix is deterministic.

See fix.diff for the git-apply-able unified diff.


PoC changes

The finding had no pre-existing PoC scaffolding (the markdown and PoC directory did not exist when this runner was spawned). All PoC components were authored by this runner: - make_iso.c β€” C program that constructs the crafted ISO9660 image with a self-referential CE chain - build.sh β€” builds make_iso and generates cyclic.iso - run.sh β€” mounts cyclic.iso via vnconfig + mount_cd9660 - fix.diff β€” the verified fix bounding CE chain depth

Fix verification

fixed
baseline reproduced→ patch + rebuild →patched clean

VALIDATED. On the unpatched #0 baseline, mount_cd9660 of cyclic.iso hangs forever in cd9660_rrip_loop (MOUNT_STILL_RUNNING=yes after 8s, kill -9 ineffective, guest unresponsive). On the single-fix #1 kernel (ISO_RRIP_MAX_CE_DEPTH=100 bound added), mount_cd9660 returns promptly with MOUNT_RC=0 on 3/3 consecutive runs, guest fully responsive (load avg ~0.1). The CE depth bound breaks the cyclic chain after 100 iterations. Fix closes the bug deterministically.

BASELINE (#0 unpatched): MOUNT_STILL_RUNNING=yes (kernel hang confirmed) / ssh hung / kill -9 ineffective / serial: 'vn4: MBR magic not found' then silence. PATCHED (#1 fixed): Run 1: MOUNT_RC=0, up 50 secs load 0.11 / Run 2: MOUNT_RC=0, up 1 min load 0.09 / Run 3: MOUNT_RC=0, up 1 min load 0.09. Contrast: infinite hang vs instant clean return.
↓ fix.diffDragonFly 6.5-DEVELOPMENT #1: Sat Jul 11 21:30:15 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC (sha256 kernel = 9a92f0f12573088b7a798744f04ef7d2eeed5fddb7e160660716a55eba6e670c)

Confirmed kernel references

Detail

Exploit chain

none. This is a pure DoS finding (infinite loop / kernel hang), not a memory-corruption primitive. There is no write/UAF/double-free/type-confusion to escalate -- the impact ceiling is denial of service. The trigger is realistic: an attacker who can cause a crafted ISO9660 image to be mounted (distributed as installer image, auto-mounted USB, or ISO-inspection service) hangs the kernel indefinitely. The mount process is unkillable (uninterruptible kernel spin).

Evidence (decisive lines)

BASELINE (#0 unpatched): VN=vn4 / BG_PID=875 / --- after 8s --- / MOUNT_STILL_RUNNING=yes (kernel hang confirmed) / (ssh session hung after kill -9 -- process unkillable, stuck in uninterruptible kernel state inside cd9660_rrip_loop following cyclic CE chain) / Serial log: 'vn4: MBR magic not found; assume a COMPATIBILITY_SLICE (s0)' then silence. FIXED (#1): --- mount attempt on FIXED kernel #1 --- / MOUNT_RC=0 / up 50 secs, load averages: 0.11, 0.04, 0.01 (3/3 runs identical, guest fully responsive).

PoC changes

Authored the entire PoC from scratch (no pre-existing finding markdown or PoC scaffolding existed). make_iso.c: C program constructing a minimal 20-sector ISO9660 image with a self-referential CE chain (root dir SP+CE->sector 19, sector 19 CE->sector 19). build.sh/run.sh/test_repro.sh: build and mount harness. fix.diff: adds ISO_RRIP_MAX_CE_DEPTH=100 bound to cd9660_rrip_loop.

Verified recommended fix

In sys/vfs/isofs/cd9660/cd9660_rrip.c, add a #define ISO_RRIP_MAX_CE_DEPTH 100 and a counter int ce_count=0 in cd9660_rrip_loop(); increment and break the while(1) loop when ce_count exceeds the limit (before the bread that follows the CE). This bounds the CE continuation chain depth, breaking both cyclic and excessively deep chains. Well-formed images need only a handful of CE hops. The fix is in fix.diff (3 hunks: define, local var, bounds check). This is a new fix authored by the runner (no prior finding proposal existed).

Verdict

REPRODUCED. The bug is real: cd9660_rrip_loop() in sys/vfs/isofs/cd9660/cd9660_rrip.c follows the Rock Ridge SUSP CE (Continuation Entry) chain in a while(1) loop (line 503) with NO depth bound and NO cycle detection -- the only termination checks are fields==0 (line 544) and a bounds check on block/offset ranges (lines 550-555), neither of which catches a cyclic CE chain. The PoC (make_iso.c) constructs a 20-sector ISO9660 image whose root directory record's System Use area has an SP entry (Rock Ridge signature) followed by a CE entry pointing to sector 19; sector 19 contains a CE entry pointing back to sector 19 (self-cycle). Mounting this image via mount_cd9660 causes the kernel to enter cd9660_rrip_loop, re-read sector 19 forever, and hang in an uninterruptible state -- confirmed by MOUNT_STILL_RUNNING=yes after 8s and kill -9 having no effect (process spinning in kernel context). The guest becomes fully unresponsive to further SSH connections (DoS). The mount path is cd9660_vfsops.c:472 -> cd9660_rrip_offset (cd9660_rrip.c:719) -> cd9660_rrip_loop (cd9660_rrip.c:503).