Talos Vulnerability Report

TALOS-2022-1676

Apple DCERPC association groups heap overflow

July 13, 2023
CVE Number

CVE-2023-27935

SUMMARY

A heap overflow vulnerability exists in the way DCERPC library as used in Apple macOS 12.6.1 keeps track of association groups related to BIND requests. A specially-crafted network packet can cause an integer overflow, which can result in heap overflow, possibly leading to arbitrary code execution. An authenticated remote attacker can send a network request to trigger this vulnerability. A local attacker can write to a local socket to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Apple macOS 12.6.1

PRODUCT URLS

macOS - https://apple.com

CVSSv3 SCORE

7.5 - CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-190 - Integer Overflow or Wraparound

DETAILS

DCERPC is a remote procedure call protocol that is the basis for RPC functionality on Windows. DCERPC framework on macOS implements this protocol and enables interoperability of Windows network services on macOS. For example, it is used on top of SMB, through which support for Active Directory is implemented. DCERPC framework is employed by rpcsvchost binary, which opens a number of UNIX sockets that expose different RPC functionality.

In DCERPC framework, BIND PDU structure is defined like so:

typedef union
{
    double force_alignment;            /* Get highest alignment possible */
    struct
    {
        rpc_cn_common_hdr_t common_hdr;/* 00:16 common to all packets */
        unsigned16 max_xmit_frag;      /* 16:02 max transmit frag size, kb. */
        unsigned16 max_recv_frag;      /* 18:02 max receive  frag size, kb. */
        unsigned32 assoc_group_id;     /* 20:04 incarnation of client-server             [1]
                                        * group */
                                       /* 24:xx presentation context list */
        /* rpc_cn_pres_cont_list_t pres_context_list; */

        /* restore 4-byte alignment */
        /* rpc_cn_auth_tlr_t auth_tlr; */ /* if auth_len != 0 */
    } hdr;
} rpc_cn_bind_hdr_t, *rpc_cn_bind_hdr_p_t;

At [1], the structure has a 32-bit assoc_group_id value, which is used to associate client and server for this particular packet.

When processing incoming requests in rpc__cn_sm_eval_event function, depending on state machine state, function do_assoc_req_action_rtn will be called on received BIND PDUs. Eventually, the following code is reached:

        /*
         * Determine whether the rpc_bind PDU contains an association
         * group id.
         */
        if (RPC_CN_PKT_ASSOC_GROUP_ID (req_header) != 0)
        {
       ...
        } /* end if (RPC_CN_PKT_ASSOC_GROUP_ID (req_header) != 0) */
        else /* (RPC_CN_PKT_ASSOC_GROUP_ID (req_header) == 0) */                                            [2]
        {
            /*
             * A new association group needs to be created.
             */
            assoc->assoc_grp_id = rpc__cn_assoc_grp_alloc (assoc->cn_ctlblk.rpc_addr,                         [3]
                                                           assoc->transport_info,
                                                           RPC_C_CN_ASSOC_GRP_SERVER,
                                                           0,
                                                           &(assoc->assoc_status));
            assoc_grp = RPC_CN_ASSOC_GRP (assoc->assoc_grp_id);
        } /* end else (RPC_CN_PKT_ASSOC_GROUP_ID (req_header) == 0) */

If no group is specified in the incoming packet, else branch is taken at [2], which results in new association group being allocated at [3]. Further , in rpc__cn_assoc_grp_alloc we can see the following code:

if (!found_assoc_grp)                                                               [4]
{
    /*
     * The association group table will have to be expanded to
     * get a free group.
     */
    grp_id = rpc__cn_assoc_grp_create (st);                                           [5]
    if (!RPC_CN_LOCAL_ID_VALID (grp_id))
    {
        return (grp_id);
    }
    else
    {
        assoc_grp = RPC_CN_ASSOC_GRP (grp_id);
    }
}

In certain conditions (like multiple ongoing connections) if statement at [4] will be reached, and the expansion of table storing group associations is performed in a call to rpc__cn_assoc_grp_create at [5]. Inside rpc__cn_assoc_grp_create we can see (abbreviated):

unsigned16          old_count;
unsigned16          new_count;
unsigned32          i;

RPC_CN_DBG_RTN_PRINTF(rpc__cn_assoc_grp_create);
CODING_ERROR (st);

/*
 * Compute the size of the new table.
 */
old_count = rpc_g_cn_assoc_grp_tbl.grp_count;
new_count = old_count + RPC_C_ASSOC_GRP_ALLOC_SIZE;                             [6]

/*
 * First allocate a new association group table larger than the
 * existing by a fixed amount.
 */
RPC_MEM_ALLOC (new_assoc_grp,
               rpc_cn_assoc_grp_p_t,
               sizeof(rpc_cn_assoc_grp_t) * new_count,                                   [7]
               RPC_C_MEM_CN_ASSOC_GRP_BLK,
               RPC_C_MEM_WAITOK);
if (new_assoc_grp == NULL) {
    *st = rpc_s_no_memory;
    RPC_CN_LOCAL_ID_CLEAR (grp_id);
    return (grp_id);
}

/*
 * If there is an old association group table copy it into the
 * new table and free it.
 */
if (rpc_g_cn_assoc_grp_tbl.assoc_grp_vector != NULL)
{
    memcpy (new_assoc_grp,                            
            rpc_g_cn_assoc_grp_tbl.assoc_grp_vector,
            (old_count * sizeof (rpc_cn_assoc_grp_t)));                             [8]

The above code tries to expand the group table by allocating a bigger piece of memory and copying the old table to it. At [6], the new size is calculated, which is then used in the multiplication at [7] to actually allocate the memory. Finally, at [8], the old table is copied into newly allocated memory.

Note that new_count variable is an unsigned 2-byte integer, which has a relatively small maximum value. Addition at [6] can result in an integer overflow, which would then cause a relatively small buffer to be allocated at [7]. The memcpy call at [8] will then use original size (not overflown) of the group memory buffer to copy a very large chunk of memory into a small buffer. This constitutes a heap overflow, which will lead to sensitive memory overwrite, memory corruption and possibly arbitrary code execution.

In order to trigger this vulnerability, a very large number of simultaneous connections would be needed because new_count is only incremented in increments of RPC_C_ASSOC_GRP_ALLOC_SIZE (which is 10). However, we can exploit another vulnerability, TALOS-2022-1679, to trigger this vulnerability with a single network packet.

This is what the attached proof of concept exploit does. It crafts two BIND PDUs to trigger TALOS-2022-1679 in a way that will reach the vulnerable code and trigger a heap overflow.

Crash Information

==733==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x619000000920 at pc 0x00010057bcfc bp 0x70000c8f3d90 sp 0x70000c8f3550
WRITE of size 15202960 at 0x619000000920 thread T15
==733==WARNING: invalid path to external symbolizer!
==733==WARNING: Failed to use and restart external symbolizer!
    #0 0x10057bcfb in wrap_memmove+0x2ab (./libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x1ccfb)
    #1 0x1003a3cbf in rpc__cn_assoc_grp_create+0x45f (./DCERPC:x86_64+0x284cbf)
    #2 0x1003a26e1 in rpc__cn_assoc_grp_alloc+0x571 (./DCERPC:x86_64+0x2836e1)
    #3 0x10040777d in do_assoc_req_action_rtn+0x25dd (./DCERPC:x86_64+0x2e877d)
    #4 0x10040e18f in do_assoc_action_rtn+0xff (./DCERPC:x86_64+0x2ef18f)
    #5 0x10041f992 in rpc__cn_sm_eval_event+0x662 (./DCERPC:x86_64+0x300992)
    #6 0x1003fa6d1 in receive_dispatch+0xa5d1 (./DCERPC:x86_64+0x2db6d1)
    #7 0x1003ede30 in rpc__cn_network_receiver+0x1b40 (./DCERPC:x86_64+0x2cee30)
    #8 0x1001248b2 in proxy_start+0x1e2 (./DCERPC:x86_64+0x58b2)
    #9 0x7fff6ce82108 in _pthread_start+0x93 (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x6108)
    #10 0x7fff6ce7db8a in thread_start+0xe (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x1b8a)

0x619000000920 is located 0 bytes to the right of 928-byte region [0x619000000580,0x619000000920)
allocated by thread T15 here:
    #0 0x1005a74f0 in wrap_malloc+0xa0 (./libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x484f0)
    #1 0x100303c2d in rpc__mem_alloc+0x1d (./DCERPC:x86_64+0x1e4c2d)
    #2 0x1003a3ace in rpc__cn_assoc_grp_create+0x26e (./DCERPC:x86_64+0x284ace)
    #3 0x1003a26e1 in rpc__cn_assoc_grp_alloc+0x571 (./DCERPC:x86_64+0x2836e1)
    #4 0x10040777d in do_assoc_req_action_rtn+0x25dd (./DCERPC:x86_64+0x2e877d)
    #5 0x10040e18f in do_assoc_action_rtn+0xff (./DCERPC:x86_64+0x2ef18f)
    #6 0x10041f992 in rpc__cn_sm_eval_event+0x662 (./DCERPC:x86_64+0x300992)
    #7 0x1003fa6d1 in receive_dispatch+0xa5d1 (./DCERPC:x86_64+0x2db6d1)
    #8 0x1003ede30 in rpc__cn_network_receiver+0x1b40 (./DCERPC:x86_64+0x2cee30)
    #9 0x1001248b2 in proxy_start+0x1e2 (./DCERPC:x86_64+0x58b2)
    #10 0x7fff6ce82108 in _pthread_start+0x93 (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x6108)
    #11 0x7fff6ce7db8a in thread_start+0xe (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x1b8a)

Thread T15 created by T4 here:
    #0 0x1005a167c in wrap_pthread_create+0x5c (./libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4267c)
    #1 0x10012448b in dcethread_create+0x3fb (./DCERPC:x86_64+0x548b)
    #2 0x100124b6c in dcethread_create_throw+0x2c (./DCERPC:x86_64+0x5b6c)
    #3 0x1003a15af in rpc__cn_assoc_acb_create+0x47f (./DCERPC:x86_64+0x2825af)
    #4 0x100302d54 in rpc__list_element_alloc+0x10a4 (./DCERPC:x86_64+0x1e3d54)
    #5 0x10038bdb7 in rpc__cn_assoc_acb_alloc+0x107 (./DCERPC:x86_64+0x26cdb7)
    #6 0x100392671 in rpc__cn_assoc_listen+0x251 (./DCERPC:x86_64+0x273671)
    #7 0x1003db80e in rpc__cn_network_select_dispatch+0x12ce (./DCERPC:x86_64+0x2bc80e)
    #8 0x100364e1f in lthread_loop+0x65f (./DCERPC:x86_64+0x245e1f)
    #9 0x100363c8c in lthread+0x28c (./DCERPC:x86_64+0x244c8c)
    #10 0x1001248b2 in proxy_start+0x1e2 (./DCERPC:x86_64+0x58b2)
    #11 0x7fff6ce82108 in _pthread_start+0x93 (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x6108)
    #12 0x7fff6ce7db8a in thread_start+0xe (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x1b8a)

Thread T4 created by T2 here:
    #0 0x1005a167c in wrap_pthread_create+0x5c (./libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4267c)
    #1 0x10012448b in dcethread_create+0x3fb (./DCERPC:x86_64+0x548b)
    #2 0x100124b6c in dcethread_create_throw+0x2c (./DCERPC:x86_64+0x5b6c)
    #3 0x1003639a3 in rpc__nlsn_activate_desc+0xc3 (./DCERPC:x86_64+0x2449a3)
    #4 0x10035bc5b in rpc_server_listen+0x47b (./DCERPC:x86_64+0x23cc5b)
    #5 0x10000316b in run_dcerpc_svc(void*)+0x1c (/usr/libexec/rpcsvchost:x86_64+0x10000316b)
    #6 0x7fff6ce82108 in _pthread_start+0x93 (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x6108)
    #7 0x7fff6ce7db8a in thread_start+0xe (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x1b8a)

Thread T2 created by T0 here:
    #0 0x1005a167c in wrap_pthread_create+0x5c (./libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4267c)
    #1 0x100002c1f in main+0x13ff (/usr/libexec/rpcsvchost:x86_64+0x100002c1f)
    #2 0x7fff6cc7dcc8 in start+0x0 (/usr/lib/system/libdyld.dylib:x86_64+0x1acc8)

SUMMARY: AddressSanitizer: heap-buffer-overflow (./libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x1ccfb) in wrap_memmove+0x2ab
Shadow bytes around the buggy address:
  0x1c32000000d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c32000000e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c32000000f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c3200000100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c3200000110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1c3200000120: 00 00 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa
  0x1c3200000130: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c3200000140: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c3200000150: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c3200000160: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c3200000170: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
VENDOR RESPONSE

Fixed by Apple on 2023-03-27, patch information available at: https://support.apple.com/en-us/HT213670

TIMELINE

2022-12-06 - Vendor Disclosure
2023-03-27 - Vendor Patch Release
2023-07-13 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.