Apple XNU Kernel – Memory Corruption due to Integer Overflow in __offsetof Usage in posix_spawn on 32-bit Platforms

  • 作者: Google Security Research
    日期: 2017-12-12
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/43325/
  • posix_spawn is a complex syscall which takes a lot of arguments from userspace. The third argument
    is a pointer to a further arguments descriptor in userspace with the following structure (on 32-bit):
    
    struct user32__posix_spawn_args_desc {
    uint32_tattr_size;/* size of attributes block */
    uint32_tattrp;/* pointer to block */
    uint32_tfile_actions_size;/* size of file actions block */
    uint32_tfile_actions;/* pointer to block */
    uint32_tport_actions_size;/* size of port actions block */
    uint32_tport_actions;/* pointer to block */
    uint32_tmac_extensions_size;
    uint32_tmac_extensions;
    uint32_tcoal_info_size;
    uint32_tcoal_info;
    uint32_tpersona_info_size;
    uint32_tpersona_info;
    }
    
    port_actions then points to another structure in userspace of this type:
    
    struct _posix_spawn_port_actions {
    intpspa_alloc;
    intpspa_count;
    _ps_port_action_t pspa_actions[];
    }
    
    and finally _ps_port_action_t looks like this:
    
    struct _ps_port_action {
    pspa_tport_type;
    exception_mask_tmask;
    mach_port_name_tnew_port;
    exception_behavior_tbehavior;
    thread_state_flavor_tflavor;
    intwhich;
    }
    
    Note that pspa_actions is a zero-sized array. pspa_count is supposed to be the number of entries
    in this array.
    
    The following constraints are checked in posix_spawn in kern_exec.c:
    
    if (px_args.port_actions_size != 0) {
    /* Limit port_actions to one page of data */
    if (px_args.port_actions_size < PS_PORT_ACTIONS_SIZE(1) ||
    px_args.port_actions_size > PAGE_SIZE) {
    error = EINVAL;
    goto bad;
    
    
    PS_PORT_ACTIONS_SIZE is defined like this:
    
    #definePS_PORT_ACTIONS_SIZE(x)\
    __offsetof(struct _posix_spawn_port_actions, pspa_actions[(x)])
    
    if port_actions_size passes this then we reach the following code:
    
    MALLOC(px_spap, _posix_spawn_port_actions_t,
    px_args.port_actions_size, M_TEMP, M_WAITOK);
    if (px_spap == NULL) {
    error = ENOMEM;
    goto bad;
    }
    
    imgp->ip_px_spa = px_spap;
    
    if ((error = copyin(px_args.port_actions, px_spap,
    px_args.port_actions_size)) != 0)
    goto bad;
    
    This allocates a kernel heap buffer to hold the port_actions buffer and copies from userspace into it.
    
    The code then attempts to check whether the pspa_count valid is correct:
    
    /* Verify that the action count matches the struct size */
    if (PS_PORT_ACTIONS_SIZE(px_spap->pspa_count) != px_args.port_actions_size) {
    error = EINVAL;
    goto bad;
    }
    
    There is an integer overflow here because offsetof is just simple arithmetic. With a carefully chosen
    value for pspa_count we can make it very large but when it's passed to the PS_PORT_ACTIONS_SIZE macro
    the result is equal to port_actions_size. Nothing bad has happened yet but we can now get pspa_count
    to be much larger than it should be.
    
    Later on we reach the following code:
    
    if (px_spap->pspa_count != 0 && is_adaptive) {
    portwatch_count = px_spap->pspa_count;
    MALLOC(portwatch_ports, ipc_port_t *, (sizeof(ipc_port_t) * portwatch_count), M_TEMP, M_WAITOK | M_ZERO);
    } else {
    portwatch_ports = NULL;
    }
    
    if ((error = exec_handle_port_actions(imgp, &portwatch_present, portwatch_ports)) != 0)
    
    We can cause another integer overflow here, sizeof(ipc_port_t) is 4 (on 32-bit) so with a carefully chosen value of pspa_count
    we can cause the integer overflow here and earlier too whilst still passing the checks.
    
    exec_handle_port_actions then uses portwatch ports like this:
    
    for (i = 0; i < pacts->pspa_count; i++) {
    act = &pacts->pspa_actions[i];
    
    if (MACH_PORT_VALID(act->new_port)) {
    kr = ipc_object_copyin(get_task_ipcspace(current_task()),
     act->new_port, MACH_MSG_TYPE_COPY_SEND,
     (ipc_object_t *) &port);
    ...
    switch (act->port_type) {
    ...
    case PSPA_IMP_WATCHPORTS:
    if (portwatch_ports != NULL && IPC_PORT_VALID(port)) {
     *portwatch_present = TRUE;
    /* hold on to this till end of spawn */
    portwatch_ports[i] = port;
    
    
    note that pspa_actions was allocated earlier also based on the result of an integer overflow.
    This means we can cause an OOB write to portwatch_ports only if we can successfully read suitable valid
    values OOB of pspa_actions. That's why this PoC first fills a kalloc.1024 buffer with suitable values before
    freeing it and then hoping that it will get reallocated as pspa_actions (but less thatn 1024 bytes will be written)
    such that we control what's read OOB and the ipc_object_copyin will succeed.
    
    This seems to be pretty reliable. You can use this to build a nice primitive of a heap overflow with pointers
    to ipc_port structures.
    
    I don't believe there are any iOS 11 32-bit iPod/iPhone/iPad/AppleTV devices but the new Apple Watch Series 3
    is running essentially the same kernel but has a 32-bit CPU. This PoC is provided as an Apple watch app
    and has been tested on Apple Watch Series 3 (Watch3,2) running WatchOS 4.0.1. I also tested on an older 32-bit iOS 9 device.
    
    Apple Watch Series 3 now has its own LTE modem and can be used without an iPhone making it a suitably interesting target for exploitation
    by itself.
    
    Note that all the uses of offsetof in those posix_spawn macros are quite wrong, I think you might be able to get
    a kernel memory disclosure with one of them also on 64-bit platforms. The fix is to add correct bounds checking.
    
    Please also note that this really shouldn't be attack surface reachable from an app sandbox. The MAC hook in posix_spawn
    is very late and there's a *lot* of code which you can hit before it.
    
    
    Proof of Concept:
    https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/43325.zip