Summary
module_control0() (kernel side) frees and re-allocates the shared per-module mod->ctl_args buffer with no synchronization. Two threads issuing SUPERCALL_KPM_CONTROL to the same module concurrently can kvfree() the same pointer twice → vmalloc metadata corruption → kernel panic (device reboot).
Environment
|
|
| Kernel |
5.15.119-android13-8 |
| KernelPatch |
0.13.3 |
Reproduction
Load any KPM with a ctl0 handler , then issue concurrent ctl supercalls from ≥2 threads:
syscall(45, "su",
(0x0d03UL << 32) | (0x1158UL << 16) | 0x1022, /* SUPERCALL_KPM_CONTROL */
"Zenith", "hide 0x7a30dd2000", out, outlen);
4 threads looping hide/show panics the device within a few hundred iterations.
Root cause
kernel/patch/module/module.c, module_control0():
if (mod->ctl_args) kvfree(mod->ctl_args); /* no lock */
mod->ctl_args = vmalloc(args_len + 1);
if (!mod->ctl_args) { rc = -ENOMEM; goto out; }
strcpy(mod->ctl_args, ctl_args);
rc = (*mod->ctl0)(mod->ctl_args, out_msg, outlen);
- Two threads can both pass the
kvfree() before either re-allocates → double-free.
rcu_read_lock() only guards find_module(), not the ctl_args free/realloc/use cycle.
unload_module() also does kvfree(mod->ctl_args) (marked // todo: lock), which can race a concurrent module_control0 the same way.
Crash signature:
Trying to vfree() nonexistent vm area (000000008d0cb753)
Unable to handle kernel paging request at virtual address 006eba311db8b31a
PC is at __vunmap+0xf0/0x2e0
Call trace:
__vunmap+0xf0/0x2e0
kvfree+0x88/0xc8
...
invoke_syscall+0x60/0x150
Suggested fix
Serialize the ctl_args lifecycle (free → vmalloc → copy → ctl0). mod->ctl0 may sleep (many handlers take locks / do GUP), so a mutex (not a spinlock):
static DEFINE_MUTEX(kp_module_ctl_lock);
long module_control0(...)
{
...
mutex_lock(&kp_module_ctl_lock);
if (mod->ctl_args) kvfree(mod->ctl_args);
mod->ctl_args = vmalloc(args_len + 1);
if (!mod->ctl_args) {
mutex_unlock(&kp_module_ctl_lock);
rc = -ENOMEM;
goto out;
}
strcpy(mod->ctl_args, ctl_args);
rc = (*mod->ctl0)(mod->ctl_args, out_msg, outlen);
mutex_unlock(&kp_module_ctl_lock);
...
}
unload_module() must take the same lock before kvfree(mod->ctl_args) / freeing mod.
Callers must not issue concurrent SUPERCALL_KPM_CONTROL calls to the same module. A module cannot defend against this race itself: the double-free happens in KP's code before its ctl0 handler runs.
Summary
module_control0()(kernel side) frees and re-allocates the shared per-modulemod->ctl_argsbuffer with no synchronization. Two threads issuingSUPERCALL_KPM_CONTROLto the same module concurrently cankvfree()the same pointer twice → vmalloc metadata corruption → kernel panic (device reboot).Environment
5.15.119-android13-8Reproduction
Load any KPM with a
ctl0handler , then issue concurrent ctl supercalls from ≥2 threads:4 threads looping
hide/showpanics the device within a few hundred iterations.Root cause
kernel/patch/module/module.c,module_control0():kvfree()before either re-allocates → double-free.rcu_read_lock()only guardsfind_module(), not thectl_argsfree/realloc/use cycle.unload_module()also doeskvfree(mod->ctl_args)(marked// todo: lock), which can race a concurrentmodule_control0the same way.Crash signature:
Suggested fix
Serialize the
ctl_argslifecycle (free → vmalloc → copy → ctl0).mod->ctl0may sleep (many handlers take locks / do GUP), so a mutex (not a spinlock):unload_module()must take the same lock beforekvfree(mod->ctl_args)/ freeingmod.Callers must not issue concurrent
SUPERCALL_KPM_CONTROLcalls to the same module. A module cannot defend against this race itself: the double-free happens in KP's code before itsctl0handler runs.