syzbot
BUG: unable to handle kernel paging request in remove_spares
Status:
upstream: reported syz repro on 2026/11/05 11:09
Subsystems:
raid
Labels:
prio:low
[Documentation on labels]
Reported-by: syzbot+a53c2918175b36cfd601@syzkaller.appspotmail.com
First crash: 1d12h, last: 1d12h
✨ AI-generated root cause summary:
In the MD (Multiple Devices / RAID) subsystem, md_start_sync is scheduled as a work item on the md_misc workqueue to initiate resynchronization or manage spare devices. For read-only arrays (!md_is_rdwr(mddev)), md_start_sync executes remove_and_add_spares(), which invokes remove_spares(). However, md_start_sync fails to verify whether the array's personality (mddev->pers) is still valid before calling into personality operations, and array stop/detach sequences do not ensure that mddev->sync_work is completely halted or prevented from running after mddev->pers is cleared. When remove_spares() executes with mddev->pers == NULL, it attempts to invoke mddev->pers->hot_remove_disk (located at offset 0x50), triggering a NULL pointer dereference crash.
▶
▼
✨ Full AI root cause analysis
Root Cause Analysis
Involved Components
- Source File:
drivers/md/md.c
- Functions:
md_start_sync(), remove_and_add_spares(), remove_spares(), stop_sync_thread(), do_md_stop(), md_check_recovery()
- Structures and Fields:
struct mddev: sync_work, pers, ro, recovery
struct md_personality: hot_remove_disk (at byte offset 0x50 / bit offset 640), hot_add_disk, sync_request
Technical Mechanism and Lifecycle Violation
-
Scheduling sync_work in Read-Only State:
In the MD (Multiple Devices / RAID) subsystem, resynchronization or spare management is handled asynchronously by queuing mddev->sync_work onto md_misc_wq (running md_start_sync()). In md_check_recovery(), when an array is read-only (!md_is_rdwr(mddev), such as mddev->ro == MD_RDONLY), if spare disk reconfiguration is needed (md_spares_need_change(mddev)), it sets MD_RECOVERY_RUNNING and queues mddev->sync_work:
if (md_spares_need_change(mddev)) {
set_bit(MD_RECOVERY_RUNNING, &mddev->recovery);
queue_work(md_misc_wq, &mddev->sync_work);
}
Unlike the read-write path in md_check_recovery() (which checks !test_bit(MD_RECOVERY_FROZEN, &mddev->recovery)), the read-only branch does not check MD_RECOVERY_FROZEN before queuing sync_work.
-
Array Teardown / Personality Detach:
When an array is stopped or set inactive (e.g., via do_md_stop() through sysfs or ioctl), the array stop sequence invokes stop_sync_thread() and then __md_stop().
-
Missing pers Validation in md_start_sync():
Once do_md_stop() finishes and releases reconfig_mutex, md_start_sync() acquires mddev_lock_nointr(mddev). It assumes mddev->pers is always valid and does not check if (!mddev->pers) before dispatching operations.
Because !md_is_rdwr(mddev) is true, it enters:
if (!md_is_rdwr(mddev)) {
remove_and_add_spares(mddev, NULL);
goto not_running;
}
-
The Crash Site:
remove_and_add_spares() invokes remove_spares(mddev, NULL). When evaluating removable disks:
rdev_for_each(rdev, mddev) {
if ((this == NULL || rdev == this) && rdev_removeable(rdev) &&
!mddev->pers->hot_remove_disk(mddev, rdev)) {
remove_spares() dereferences mddev->pers->hot_remove_disk. Because mddev->pers is NULL, computing the function pointer address adds offset 0x50 (offsetof(struct md_personality, hot_remove_disk)) to NULL. KASAN intercepts the access at shadow address dfff800000000000 + (0x50 >> 3) = dfff80000000000a, triggering a kernel translation fault / NULL pointer dereference oops.
Sample crash report:
Unable to handle kernel paging request at virtual address dfff80000000000a
KASAN: null-ptr-deref in range [0x0000000000000050-0x0000000000000057]
Mem abort info:
ESR = 0x0000000096000005
EC = 0x25: DABT (current EL), IL = 32 bits
SET = 0, FnV = 0
EA = 0, S1PTW = 0
FSC = 0x05: level 1 translation fault
Data abort info:
ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000
CM = 0, WnR = 0, TnD = 0, TagAccess = 0
GCS = 0, Overlay = 0, DirtyBit = 0
[dfff80000000000a] address between user and kernel address ranges
Internal error: Oops: 0000000096000005 [#1] SMP
Modules linked in:
CPU: 1 UID: 0 PID: 4993 Comm: kworker/1:5 Not tainted syzkaller #0 PREEMPT
Hardware name: Google Google Compute Engine/Google Compute Engine, BIOS Google 09/23/2026
Workqueue: md_misc md_start_sync
pstate: 83400005 (Nzcv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--)
pc : remove_spares+0x100/0x3c8 drivers/md/md.c:10182
lr : remove_spares+0xe0/0x3c8 drivers/md/md.c:10181
sp : ffff800097277900
x29: ffff8000972779a0 x28: ffff0000f4c1c070 x27: 1fffe0001e98380c
x26: dfff800000000000 x25: ffff700012e4ef24 x24: 0000000000000050
x23: 0000000000000000 x22: ffff0000f4c1d108 x21: 0000000000000000
x20: ffff0000f4c1c058 x19: ffff0000f4c1c060 x18: 00000000ffffffff
x17: ffff80012364b000 x16: ffff80008f920000 x15: ffff80008b586000
x14: 00000000001b18f8 x13: 0000000000000001 x12: 0000000000000000
x11: 0000000000000000 x10: ffff60001e983a48 x9 : 0000000000000000
x8 : 000000000000000a x7 : 0000000000000000 x6 : 0000000000000000
x5 : 0000000000000001 x4 : 0000000000000008 x3 : ffff8000841063ac
x2 : 0000000000000000 x1 : ffff0000c4d55880 x0 : 0000000000000001
Call trace:
remove_spares+0x100/0x3c8 drivers/md/md.c:10182 (P)
remove_and_add_spares+0xf4/0x580 drivers/md/md.c:10207
md_start_sync+0x1f8/0xc80 drivers/md/md.c:10323
process_one_work kernel/workqueue.c:3495 [inline]
process_scheduled_works+0x91c/0x1250 kernel/workqueue.c:3578
worker_thread+0x798/0xbd0 kernel/workqueue.c:3659
kthread+0x304/0x3d4 kernel/kthread.c:436
ret_from_fork+0x10/0x20 arch/arm64/kernel/entry.S:854
Code: 972be018 f9400268 91014118 d343ff08 (387a6908)
---[ end trace 0000000000000000 ]---
----------------
Code disassembly (best guess):
0: 972be018 bl 0xfffffffffcaf8060
4: f9400268 ldr x8, [x19]
8: 91014118 add x24, x8, #0x50
c: d343ff08 lsr x8, x24, #3
* 10: 387a6908 ldrb w8, [x8, x26] <-- trapping instruction
* Struck through repros no longer work on HEAD.