WARNING: zero-size vmalloc in cache_ctr

Status: upstream: reported C repro on 2026/10/31 17:03
Subsystems: dm
Labels: prio:low
[Documentation on labels]
Reported-by: syzbot+c8564fc0a96e3ecbe4d4@syzkaller.appspotmail.com
First crash: 4d03h, last: 1d16h
✨ AI-generated root cause summary:

In drivers/md/dm-cache-target.c, calculate_discard_block_size() doubles discard_block_size beyond 32 bits when origin_sectors is very large, and calls sector_div() which truncates the divisor to a 32-bit uint32_t, resulting in division by zero. On arm64, division by zero architecturally yields 0, setting discard_nr_blocks to 0 and triggering a zero-size vzalloc() that hits WARN_ON_ONCE(!size) in __vmalloc_node_range_noprof; the error is subsequently caught by dm-cache-target and cleanly returned as -ENOMEM. On x86_64, the same division by zero triggers an unhandled #DE divide error kernel oops.

▶ ▼ ✨ Full AI root cause analysis
Based on kernel commit 7a19f0cc94ad:

Root Cause Analysis

1. Involved Functions, Structs, and Files

  • Files:
  • Functions & Structs:
    • struct cache: Fields cache->origin_sectors (sector_t), cache->discard_block_size (uint32_t), cache->discard_nr_blocks (dm_dblock_t), cache->discard_bitset (unsigned long *).
    • calculate_discard_block_size() / too_many_discard_blocks(): Computes discard block size such that origin sectors divided by discard block size does not exceed MAX_DISCARD_BLOCKS (16384).
    • dm_sector_div_up(): Computes ceil(n / sz) using sector_div().
    • alloc_bitset(): Computes bytes needed for a bitset and invokes vzalloc().
    • __vmalloc_node_range_noprof(): Checks WARN_ON_ONCE(!size).

2. Trigger Mechanism and Bug Flow

  1. Large Origin Size Specification: During a DM_TABLE_LOAD ioctl, a cache target is created with a very large target sector length (cache->origin_sectors = ti->len = 0x3fffffffffffff) and block size ca->block_size = 1024.

  2. Discard Block Size Calculation Loop: In cache_create() (drivers/md/dm-cache-target.c:2524), the kernel calculates:

    cache->discard_block_size =
        calculate_discard_block_size(cache->sectors_per_block, cache->origin_sectors);
    

    calculate_discard_block_size() doubles discard_block_size while too_many_discard_blocks(discard_block_size, origin_size) returns true:

    static bool too_many_discard_blocks(sector_t discard_block_size, sector_t origin_size)
    {
        (void) sector_div(origin_size, discard_block_size);
        return origin_size > MAX_DISCARD_BLOCKS;
    }
    

    Because origin_sectors is huge (0x3fffffffffffff), discard_block_size (starting at 1024) is doubled in a loop. Even at 1ULL << 31 (2147483648), 0x3fffffffffffff / (1ULL << 31) = 0x7fffff = 8388607 > MAX_DISCARD_BLOCKS (16384). Therefore, the loop doubles discard_block_size once more to 1ULL << 32 (0x100000000).

  3. Divisor 32-bit Truncation in sector_div(): sector_div(n, base) is defined as do_div(n, base). Per include/asm-generic/div64.h:

    #define do_div(n, base) ({ \
        uint32_t __base = (base); \
        ...
    

    The divisor is cast and truncated to a 32-bit uint32_t. When discard_block_size == 1ULL << 32, (uint32_t)(1ULL << 32) == 0.

  4. Architectural Divergence on Division by Zero:

    • On ARM64 (the architecture of this report): ARM64 hardware integer division (udiv) architecturally specifies that division by zero does not trap and returns 0. Consequently, sector_div(origin_size, 0) sets origin_size = 0. The condition 0 > 16384 evaluates to false, exiting the loop and returning 1ULL << 32.
    • On x86_64: Division by zero triggers a CPU Division Error (#DE) exception, resulting in an immediate kernel panic/oops in kernel mode.
  5. Subsequent Zero-Size Allocation on ARM64: On ARM64, execution continues:

    • Line 2527 computes cache->discard_nr_blocks = to_dblock(dm_sector_div_up(cache->origin_sectors, cache->discard_block_size));. In dm_sector_div_up(), sector_div() is called with sz = 1ULL << 32 (again truncated to 0), yielding 0. Thus cache->discard_nr_blocks = 0.
    • Line 2529 calls alloc_bitset(from_dblock(cache->discard_nr_blocks)), which calculates: bitset_size_in_bytes(0) = sizeof(unsigned long) * dm_div_up(0, BITS_PER_LONG) = 0.
    • vzalloc(0) invokes __vmalloc_node_range_noprof(0, ...) with size = 0.
    • Line 4037 of mm/vmalloc.c:
      if (WARN_ON_ONCE(!size))
          return NULL;
      
      triggers the syzkaller warning splat.

3. Runtime Consequences in a Production Build

  • On the corresponding ARM64 production build (without panic_on_warn):
    • WARN_ON_ONCE(!size) is non-fatal and outputs a diagnostic message without halting the kernel.
    • __vmalloc_node_range_noprof() returns NULL.
    • cache_create() inspects the return value:
      if (!cache->discard_bitset) {
          *error = "could not allocate discard bitset";
          goto bad;
      }
      
      and enters error recovery at bad:, calling __destroy(cache) to free all allocated resources and return -ENOMEM.
    • The ioctl returns -ENOMEM to userspace with device-mapper error messages logged. No locks are leaked, no state is corrupted, and execution proceeds normally. Under the criteria for WARNING reports with graceful error handling, RawDenialOfService is false.
  • On x86_64 production builds:
    • sector_div with a divisor truncated to zero triggers an unhandled #DE exception and halts execution (kernel panic / oops).

4. Classification Summary

  • MemoryCorruption: false (No memory corruption, out-of-bounds writes, or use-after-free occurs; on ARM64 the allocation returns NULL and errors out, and on x86_64 execution traps on division by zero).
  • InfoLeak: "none" (No kernel memory contents are exposed to userspace).
  • RawDenialOfService: false (On the corresponding ARM64 production build matching the report, execution is not halted: WARN_ON_ONCE(!size) is caught and gracefully handled by cache_create(), unwinding all state and returning -ENOMEM).
  • IsNetCandidate: false (Storage / Device Mapper subsystem; no network stack involvement).
  • IsKVMCandidate: false (No KVM or virtualization stack involvement).
▶ ▼ ✨ AI Jobs (1)
ID Workflow Result Correct Bug Created Started Finished Revision
3c4219f8 assessment-security DenialOfService: ❌ Exploitable: ❌ FilesystemTrigger: ❌ FuzzingArtifact: ❌ MemoryCorruption: ❌ NetworkNeedsSetup: ❌ NetworkTrigger: ❌ PeripheralTrigger: ❌ RemoteTrigger: ❌ Unprivileged: ❌ UserNamespace: ❌ VMGuestTrigger: ❌ VMHostTrigger: ❌ ❓ WARNING: zero-size vmalloc in cache_ctr 2026/10/28 00:00 2026/10/28 00:01 2026/10/28 01:04 96923c60
▶ ▼ Discussions (1)
Title Replies (including bot) Last reply
[syzbot] [dm?] WARNING: zero-size vmalloc in cache_ctr 0 (1) 2026/10/31 17:03

Sample crash report:
------------[ cut here ]------------
WARNING: mm/vmalloc.c:4037 at __vmalloc_node_range_noprof+0xe7c/0xe9c mm/vmalloc.c:-1, CPU#1: syz.0.17/4933
Modules linked in:
CPU: 1 UID: 0 PID: 4933 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT 
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 08/14/2026
pstate: 83400005 (Nzcv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
pc : __vmalloc_node_range_noprof+0xe7c/0xe9c mm/vmalloc.c:-1
lr : __vmalloc_node_range_noprof+0xe78/0xe9c mm/vmalloc.c:4037
sp : ffff800098787520
x29: ffff800098787660 x28: 00e8000000000703 x27: 0000000000000000
x26: 0000000000000000 x25: ffff7000130f0ec0 x24: fffffdffbf800000
x23: 0000000000000000 x22: 00000000ffffffff x21: 0000000000000000
x20: 0000000000000dc0 x19: dfff800000000000 x18: ffff8001237d4000
x17: ffff0001acf92e25 x16: ffff0000ca311d94 x15: 0000000000000009
x14: 00000000006328cc x13: 0000000000000001 x12: 0000000000000000
x11: ffffc1ffbf800000 x10: 000000f000000000 x9 : 0000000000000000
x8 : 0000000000000000 x7 : 00000000ffffffff x6 : 0000000000000000
x5 : 00e8000000000703 x4 : 0000000000000dc0 x3 : ffff800080aa2768
x2 : 0000000000000000 x1 : ffff0000ca311d80 x0 : 0000000000000000
Call trace:
 __vmalloc_node_range_noprof+0xe7c/0xe9c mm/vmalloc.c:-1 (P)
 __vmalloc_node_noprof+0xec/0x14c mm/vmalloc.c:4165
 vzalloc_noprof+0x3c/0x4c mm/vmalloc.c:4243
 alloc_bitset drivers/md/dm-cache-policy-internal.h:126 [inline]
 cache_create drivers/md/dm-cache-target.c:2529 [inline]
 cache_ctr+0x1258/0x1f1c drivers/md/dm-cache-target.c:2649
 dm_table_add_target+0x44c/0xb18 drivers/md/dm-table.c:749
 populate_table drivers/md/dm-ioctl.c:1602 [inline]
 table_load+0x27c/0x764 drivers/md/dm-ioctl.c:1645
 ctl_ioctl drivers/md/dm-ioctl.c:2221 [inline]
 dm_ctl_ioctl+0x884/0xbbc drivers/md/dm-ioctl.c:2243
 vfs_ioctl fs/ioctl.c:51 [inline]
 __do_sys_ioctl fs/ioctl.c:597 [inline]
 __se_sys_ioctl fs/ioctl.c:583 [inline]
 __arm64_sys_ioctl+0x154/0x1cc fs/ioctl.c:583
 __invoke_syscall arch/arm64/kernel/syscall.c:35 [inline]
 invoke_syscall+0x98/0x244 arch/arm64/kernel/syscall.c:49
 el0_svc_common+0xec/0x23c arch/arm64/kernel/syscall.c:121
 do_el0_svc+0x4c/0x5c arch/arm64/kernel/syscall.c:140
 el0_svc+0x60/0x1d8 arch/arm64/kernel/entry-common.c:796
 el0t_64_sync_handler+0x44/0x104 arch/arm64/kernel/entry-common.c:815
 el0t_64_sync+0x198/0x19c arch/arm64/kernel/entry.S:606
irq event stamp: 8668
hardirqs last  enabled at (8667): [<ffff800080ade8fc>] seqcount_lockdep_reader_access+0x68/0xcc include/linux/seqlock.h:75
hardirqs last disabled at (8668): [<ffff8000868b91fc>] el1_brk64+0x20/0x54 arch/arm64/kernel/entry-common.c:483
softirqs last  enabled at (8614): [<ffff80008013c764>] local_bh_enable include/linux/bottom_half.h:33 [inline]
softirqs last  enabled at (8614): [<ffff80008013c764>] put_cpu_fpsimd_context arch/arm64/kernel/fpsimd.c:251 [inline]
softirqs last  enabled at (8614): [<ffff80008013c764>] kernel_neon_begin+0x294/0x400 arch/arm64/kernel/fpsimd.c:1924
softirqs last disabled at (8612): [<ffff80008013c5ec>] local_bh_disable include/linux/bottom_half.h:20 [inline]
softirqs last disabled at (8612): [<ffff80008013c5ec>] get_cpu_fpsimd_context arch/arm64/kernel/fpsimd.c:234 [inline]
softirqs last disabled at (8612): [<ffff80008013c5ec>] kernel_neon_begin+0x11c/0x400 arch/arm64/kernel/fpsimd.c:1883
---[ end trace 0000000000000000 ]---
device-mapper: table: 248:0: cache: could not allocate discard bitset (-ENOMEM)
device-mapper: ioctl: error adding target to table

Crashes (4):
Time Kernel Commit Syzkaller Config Log Report Syz repro C repro VM info Assets (help?) Manager Title
2026/10/27 18:12 git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-kernelci 7a19f0cc94ad 96923c60 .config console log report syz / log C [disk image] [vmlinux] [kernel image] ci-upstream-gce-arm64 WARNING: zero-size vmalloc in cache_ctr
2026/10/27 17:28 git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-kernelci 7a19f0cc94ad 96923c60 .config console log report syz / log C [disk image] [vmlinux] [kernel image] ci-upstream-gce-arm64 WARNING: zero-size vmalloc in cache_ctr
2026/10/30 03:56 git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-kernelci 7a19f0cc94ad b498adee .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-gce-arm64 WARNING: zero-size vmalloc in cache_ctr
2026/10/27 16:51 git://git.kernel.org/pub/scm/linux/kernel/git/arm64/linux.git for-kernelci 7a19f0cc94ad 96923c60 .config console log report info [disk image] [vmlinux] [kernel image] ci-upstream-gce-arm64 WARNING: zero-size vmalloc in cache_ctr
* Struck through repros no longer work on HEAD.