jkrh / kvms

ARMv8 hypervisor. Custom Linux KVM variant with the guest and the host memory protection, integrity verification and encryption support.
GNU General Public License v2.0
19 stars 14 forks source link

Write-What-Where may lead to guest to EL2 code execution #2

Closed Captainarash closed 3 years ago

Captainarash commented 3 years ago

There is a Write-What-Where vulnerability exposed through SMC calls to the guest kernel which may lead to code execution in EL2. The function psci_reg() is accessible from the smccall() handler at [1] (core/smccall.c).

At [2] (core/psci.c), the variable target_core is set to an attacker-controlled value if the call is coming from a guest kernel. At [3] we have a check but this check will always pass because target_core is unsigned and combined checks are always ordered so the second part of the check is always ignored here.

This leads to a Write-What-Where at [4] when we use target_core to index into memory and write another attacker-controlled value a2 to that address.

Due to the ease of exploitation, the degree of privilege escalation (Guest to EL2) and the convenience of the vulnerability, I rate this bug as critical.


//file: core/smccall.c
unsigned int smccall(register_t cn, register_t a1, register_t a2, register_t a3,
             register_t a4, register_t a5, register_t a6, register_t a7,
             register_t a8, register_t a9)
{
    uint64_t cmd = cn;

    switch (cmd) {
    case PSCI_CPU_SUSPEND_SMC64:
    case PSCI_CPU_ON_SMC64:
        psci_reg(cn, a1, a2, a3, a4, a5);   <------- [1]
        break;
    case 0xFFFFFFFE:
        LOG("Identity query...\n");
        break;
    default:
        break;
    }

    return cmd;
}

//file: core/psci.c
void psci_reg(register_t cn, register_t a1, register_t a2, register_t a3,
          register_t a4, register_t a5)
{
    uint64_t vmid, cpuid, target_core, maxcpu, hcr_el2;
    kvm_guest_t *guest;
    kernel_func_t **cpu_map;

    vmid = get_current_vmid();
    if (vmid == HOST_VMID)
        maxcpu = PLATFORM_CORE_COUNT;
    else
        maxcpu = NUM_CPUS;

    guest = get_guest(vmid);
    if (!guest)
        return;

    cpu_map = guest->cpu_map;
    cpuid = smp_processor_id();

    switch (cn) {
        // redacted

    case PSCI_CPU_ON_SMC64:
        if (vmid == HOST_VMID) {
            /* a1 contain target core MPIDR */
            target_core = (a1 & (0x700)) >> 8;
        } else {
            /* a1 has the core number  */
            target_core = a1;   <------- [2]
        }
        if ((target_core >= 0) || (target_core < maxcpu)) { <-------[3]
            /* a2 has the core entry address */
            cpu_map[target_core] = (kernel_func_t *)a2;    <------ [4]
            dmb();
        }
        break;

        // redacted
    }
}
jkrh commented 3 years ago

Resolved in commit e4ab41c0f07bd573cf07305c0c3030d889ef574d.