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)
PoC verification
Evidence pack
findings/poc/DF-0879 Β· 16 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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 |
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_cd9660never returns. The kernel spins incd9660_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_cd9660fails 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)
cd9660_mountβcd9660_vfsops.c:472callscd9660_rrip_offset(rootp, isomp)cd9660_rrip_offset(cd9660_rrip.c:702) checks for SP entry, then callscd9660_rrip_loop(isodir, &analyze, rrip_table_extref)at line 719cd9660_rrip_loop(cd9660_rrip.c:469) enterswhile(1)at line 503- CE entries are processed by
cd9660_rrip_cont(cd9660_rrip.c:429) which stores the continuation block/offset/length - At line 544: termination only if
fields == 0 || iso_ce_len == 0 - At lines 550-555: bounds check only validates block < volume_space_size and offset+len <= logical_block_size β no depth limit, no cycle detection
- 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=1with 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.
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)
-
mount_cd9660βcd9660_mountreads the PVD and root directory extent from the ISO image (sys/vfs/isofs/cd9660/cd9660_vfsops.c:326-430). -
At
cd9660_vfsops.c:472, mount callscd9660_rrip_offset(rootp, isomp)to detect Rock Ridge extensions. -
cd9660_rrip_offset(cd9660_rrip.c:702) checks for the SP signature atisodir->name + 1(line 708-714), then callscd9660_rrip_loop(isodir, &analyze, rrip_table_extref)at line 719. -
cd9660_rrip_loop(cd9660_rrip.c:469) enters the outerwhile (1)loop at line 503. Each iteration: - Resetsana->iso_ce_len = 0(line 504). - Processes SUSP entries in the current record's System Use area (inner loop, lines 509-539). When aCEentry is found,cd9660_rrip_cont(cd9660_rrip.c:429-436) stores the continuation block/offset/length inana->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): validatesiso_ce_blk < volume_space_sizeandiso_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
fixedVALIDATED. 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.
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).
No comments yet.