Skip to content

[Bug] module_control0 double-free on concurrent ctl supercalls #295

Description

@kiriashi

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions