⬢ DragonFlyBSD Kernel Audit
DF-5190 / negative-kvmalloc-div0.txt
← back to finding ↓ download raw
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.)