Talos Vulnerability Report

TALOS-2022-1679

Apple DCERPC zero length BIND packet infinite loop

July 13, 2023
CVE Number

None

SUMMARY

An infinite loop vulnerability exists in the way DCERPC library as used in Apple macOS 12.6.1 deals with fragment sizes. A specially-crafted network packet can cause an infinite loop, which can result in unconstrained resource utilization and denial of service. 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

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

CWE

CWE-835 - Loop with Unreachable Exit Condition (‘Infinite Loop’)

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.

A structure common to all DCERPC packets is as follows:

typedef struct
{
    unsigned8  rpc_vers;               /* 00:01 RPC version - major */
    unsigned8  rpc_vers_minor;         /* 01:01 RPC version - minor */
    unsigned8  ptype;                  /* 02:01 packet type */
    unsigned8  flags;                  /* 03:01 flags */
    unsigned8  drep[4];                /* 04:04 ndr format */
    unsigned16 frag_len;               /* 08:02 fragment length */                      [1]
    unsigned16 auth_len;               /* 10:02 authentication length */
    unsigned32 call_id;                /* 12:04 call identifier */
} rpc_cn_common_hdr_t, *rpc_cn_common_hdr_p_t;

Packets are processed in a loop inside dispatch_receive function, which will in turn call receive_packet. Function receive_packet will either read data from a socket or, if enough data was read, return the data for further processing. In either case, the following code is invoked:

if (fbp->data_size >= RPC_C_CN_FRAGLEN_HEADER_BYTES)                                  [2]
{
    /*
     * Okay, we have enough of the header to figure out how big
     * this fragment is.
     */
    frag_length = RPC_CN_PKT_FRAG_LEN ((rpc_cn_packet_p_t)(fbp->data_p));               [3]
   ...
    /*
     * Figure out how many bytes we need.
     */
    need_bytes = frag_length - fbp->data_size;                                          [4]

First, at [2], size of received data is compared against minimal header size. If enough data is received, the parser can continue. At [3], frag_len field from the common header [1] is read directly from the incoming data. Then, at [4], code tries to calculate how many more bytes it needs to complete the packet. Normally, frag_length would be positive and either bigger or smaller than the actually-read incoming data. Variable need_bytes is signed, and the possible negative value is expected. Negative value would signify that a complete packet has already been received and that parsing can fully continue:

if (need_bytes < 0)
{
    /*
     * Get an overflow fragment buffer.
     */
    *ovf_fragbuf_p = rpc__cn_fragbuf_alloc (true);                                     [5]
    (*ovf_fragbuf_p)->data_size = abs(need_bytes);

    /*
     * Set the fragbuf data size to the fragment length and copy the
     * excess data to the overflow fragment buffer.
     */
    fbp->data_size = frag_length;                                                    [7]
    memcpy ((*ovf_fragbuf_p)->data_p,                                                [6]
            (dce_pointer_t)((unsigned8 *)(fbp->data_p) + fbp->data_size),
            (*ovf_fragbuf_p)->data_size);
}

Another buffer is allocated at [5] based on the difference, and packet data is copied into it at [6]. Notice, though, that at [7], frag_length is used to set the data_size of this fragment. Since frag_length comes from the packet directly and isn’t being checked, this constitutes a discrepancy. Following this, the function returns and the packet continues to be processed. However, since data_size comes from frag_length, which is directly controlled by incoming packet data, it can be set to zero. This results in zero bytes being consumed from the incoming packet. This will cause the RPC server to loop indefinitely while trying to process packets. It will continue to allocate memory and send failure packets to the connecting client as long as the connection is kept open.

To trigger this vulnerability and cause denial of service, the attached PoC sends one buffer that contains two BIND packets. The first BIND packet is properly formed, has the expected fragment_length value, and its purpose is to set the state machine. Second BIND packet is truncated and has its fragment_length set to zero.

Since the service supported by DCERPC is inherently multithreaded, and connection timing is under attacker control, this can lead to immediate crash on a NULL pointer dereference or further memory corruption. This could be abused with other vulnerabilities. Complete resource exhaustion, which can lead to full system instability and reboot, is also possible.

Crash Information

Process 725 stopped
* thread #16, stop reason = EXC_BAD_ACCESS (code=1, address=0x5c)
    frame #0: 0x000000010039922b DCERPC`rpc__cn_assoc_send_frag(assoc=0x000061600000ff80, iovector=0x00007000063678c0, sec=0x0000000000000000, st=0x000061600000ffd8) at cnassoc.c:2019:17
Target 0: (rpcsvchost) stopped.
(lldb) bt
* thread #16, stop reason = EXC_BAD_ACCESS (code=1, address=0x5c)
  * frame #0: 0x000000010039922b DCERPC`rpc__cn_assoc_send_frag(assoc=0x000061600000ff80, iovector=0x00007000063678c0, sec=0x0000000000000000, st=0x000061600000ffd8) at cnassoc.c:2019:17
    frame #1: 0x000000010039bce8 DCERPC`rpc__cn_assoc_send_fragbuf(assoc=0x000061600000ff80, fragbuf=0x0000631008124800, sec=0x0000000000000000, freebuf=1, st=0x000061600000ffd8) at cnassoc.c:2220:5
    frame #2: 0x0000000100401025 DCERPC`reject_assoc_action_rtn(spc_struct=0x000061600000ff80, event_param=0x0000631008124800, sm=0x000061600000ff90) at cnsassm.c:1259:5
    frame #3: 0x000000010041f993 DCERPC`rpc__cn_sm_eval_event(event_id=100, event_parameter=0x0000631008750800, spc_struct=0x000061600000ff80, sm=0x000061600000ff90) at cnsm.c:346:3
    frame #4: 0x00000001003fa6d2 DCERPC`receive_dispatch(assoc=0x000061600000ff80) at cnrcvr.c:1256:13
    frame #5: 0x00000001003ede31 DCERPC`rpc__cn_network_receiver(assoc=0x000061600000ff80) at cnrcvr.c:367:21
    frame #6: 0x00000001001248b3 DCERPC`proxy_start(arg=0x000060300003a720) at dcethread_create.c:106:14
    frame #7: 0x00007fff6ce82109 libsystem_pthread.dylib`_pthread_start + 148
    frame #8: 0x00007fff6ce7db8b libsystem_pthread.dylib`thread_start + 15
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.