DF-5190 / negative-kvmalloc-div0.txt
DF-5190 pass-2 negative evidence — "kvmalloc_array divide-by-zero on size==0" candidate
=======================================================================================
File audited: sys/dev/drm/linux_vmalloc.c:135-145 (kvmalloc_array)
void *
kvmalloc_array(size_t n, size_t size, gfp_t flags)
{
if (n == 0)
return NULL;
if (n > SIZE_MAX / size) /* <-- SIZE_MAX / 0 is UB / would be #DE */
return NULL;
return kmalloc(n * size, M_DRM, flags);
}
Claim under test: with n>0 and size==0 the guard executes an integer division
by zero in kernel context and panics.
Caller survey (all in-tree callers pass constant, nonzero element sizes):
radeon_cs.c:90,346 sizeof(struct radeon_bo_list) / sizeof(uint32_t)
radeon_vm.c:135 sizeof(struct radeon_bo_list)
radeon_ring.c:317 sizeof(uint32_t)
amdgpu_vm.c:981 sizeof(*entry->entries)
amdgpu_cs.c:169,666 sizeof(uint32_t) / sizeof(struct page*)
amdgpu_vram_mgr.c:188 sizeof(*nodes)
amdgpu_bo_list.c:226 info_size = sizeof(struct drm_amdgpu_bo_list_entry) (const)
i915_gem_execbuffer.c:1608,2116,2515,2517,2593 1 / sizeof(...)
i915_gem_userptr.c:517,653 / i915_gem_gtt.c:3783 / i915_gem.c:3163 sizeof(...)
ttm_tt.c:92,101,113 sizeof(...)
=> No in-tree trigger.
Empirical refutation on the audit guest (shipped binary):
Guest: DragonFly dfbsd 6.5-DEVELOPMENT #0 X86_64_GENERIC (Jul 2 2026)
cc 8.3 [DragonFly] Release/2019-02-22
PoC kld (built on the guest against /usr/src, loaded AFTER `kldload drm`
so the call lands in the *shipped* drm.ko's kvmalloc_array):
extern void *kvmalloc_array(size_t n, size_t size, unsigned int flags);
...
case MOD_LOAD:
kprintf("DF5189: calling kvmalloc_array(1, 0, GFP_KERNEL)\n");
p = kvmalloc_array(1, 0, 0 /* GFP_KERNEL */);
kprintf("DF5189: returned %p (no fault)\n", p);
Serial console (vm.sh log, decisive lines):
login: DF5189: calling kvmalloc_array(1, 0, GFP_KERNEL)
DF5189: returned 0xfffffffffffffff8 (no fault)
=> No fault. The kernel kept running; the function returned ZERO_LENGTH_PTR
(-8) because execution reached kmalloc(0, M_DRM, flags).
Why (disassembly of the shipped /boot/kernel/drm.ko, objdump):
0000000000049220 <kvmalloc_array>:
49220: mov %edx,%ecx
49222: test %rdi,%rdi ; n == 0?
49225: je 49240 ; -> return NULL
49227: mov %rdi,%rax
4922a: mul %rsi ; n * size (64-bit)
4922d: mov %rax,%rdi
49230: jo 49240 ; mul overflow -> return NULL
49232: mov %ecx,%edx
49234: mov $0x0,%rsi ; (M_DRM reloc)
4923b: jmpq 49240 ; tail-jump reloc to kmalloc
49240: xor %eax,%eax
49242: retq
cc 8.3 -O2 legally rewrote `n > SIZE_MAX / size` (UB for size==0, so the
compiler may assume size != 0) into the equivalent multiply-overflow check
`mul/jo` and fused it with the subsequent `n * size`. There is NO divq in
kvmalloc_array; the only divq anywhere in drm.ko is at 0x2e955 (unrelated
function). A division-by-zero fault is impossible on this binary.
Conclusion: source-level UB only; behavior with the shipping compiler/version
is correct-or-benign (size==0 flows to kmalloc(0) -> ZERO_LENGTH_PTR, callers
treat non-NULL as success which is also Linux-like). Candidate KILLED — not
filed as a finding. Recorded here so a future re-audit does not re-derive it.
(Guest was reset with `vm.sh reset with-src` after this experiment.)