Talos Vulnerability Report

TALOS-2022-1688

Apple DCERPC array marshaling uninitialized memory disclosure vulnerability

July 13, 2023
CVE Number

CVE-2023-27953

SUMMARY

There exists a vulnerability in the array marshaling code of DCERPC library as used in Apple macOS 12.6.1 that can lead to use of uninitialized memory. A specially-crafted network packet can cause parts of uninitialized memory to be disclosed. 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:H/I:N/A:N

CWE

CWE-457 - Use of Uninitialized Variable

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.

An RPC service implementation based on DCERPC.framework will have code that performs, for each RPC function invocation, unmarshalling of input parameters, actual function call with those parameters and then marshalling of output parameters. Unmarshalling and marshalling are handeled by the rpc_ss_ndr_unmar_interp and rpc_ss_ndr_mar_interp functions respectively. These two functions are guided by code generated from RPC service IDL file and, in short, are tasked with interpreting incoming data into expected types and packing results into data to be sent in a reply. There exists a sequence of events in which an allocated but uninitialized piece of memory can be processed and ultimately returned as part of an RPC reply. More specifically, when preparing a reply that has an open array as an output parameter, function rpc_ss_ndr_marsh_open_arr will be invoked:

void rpc_ss_ndr_marsh_open_arr
(
    /* [in] */  idl_ulong_int defn_index,
    /* [in] */  rpc_void_p_t array_addr,     [1]
    /* [in] */ idl_ulong_int flags,
    IDL_msp_t IDL_msp
)

Second argument, at [1], is the pointer to array contents which are allocated during a call to rpc_ss_alloc_out_conf_array.

    case IDL_DT_ALLOCATE:
        /* Indicates an [out]-only conformant or open array must
            be allocated. Should only appear on server side */
        rpc_ss_alloc_out_conf_array( &type_vec_ptr,
                                     &IDL_param_vector[param_index],                [2]
                                     &num_conf_char_arrays,
                                     IDL_msp );
        break;

Pointer to allocated memory is held in IDL_param_vector[param_index] at [2] above. As indicated by the comment, this array is allocated as part of an out-only parameter. The allocation happens during unmarshalling, while preparing for an RPC function call. When preparing a reply, during a call to rpc_ss_ndr_mar_interp that marshals parameters, rpc_ss_ndr_marsh_open_arr will be called, which will ultimately lead to the following code inside rpc_ss_build_range_list_2+0x1c4d:

    else if (limit_kind == IDL_LIMIT_STRING)
    {
        element_size = (idl_ulong_int)*defn_vec_ptr;
        /* Element size byte is discarded as we align to discard dummy
            longword */
        IDL_DISCARD_LONG_FROM_VECTOR(defn_vec_ptr);
        if (unmarshalled_list == NULL || unmarshalled_list[i])
        {
            if (element_size == 1)
                data_limit = (idl_long_int) strlen(array_addr) + 1;                      [3]
            else
                data_limit = rpc_ss_strsiz( (idl_char *)array_addr,
                                                             element_size );
            range_list[i].upper = range_list[i].lower + data_limit;
            if ( ! (*p_add_null) )
            {
                if ( range_list[i].upper > (bounds_list[i].upper
                                        - bounds_list[i].lower + 1) )
                    DCETHREAD_RAISE( rpc_x_invalid_bound );
            }
        }
        else
            range_list[i].upper = -1;
    }

Same array allocated at [2] will be used in a strlen call at [3] to determine the length of the string before it’s marshalled. However, there is no guarantee that this piece of memory will ever be initialized. It could potentially contain previously used data, which would be leaked in the marshalled data of a reply.

One instance of this vulnerabillity can be reached through mdssvc service on macOS. Among others, mdssvc exposes an RPC function called mdssvc_open with an IDL specification, roughly as follows:

void mdssvc_open(
	[in,out,ref]                             uint32         *device_id,
	[in,out,ref]                             uint32         *unkn2, /* always 0x17 ? */
	[in,out,ref]                             uint32         *unkn3, /* always 0 ? */
	[in][string,charset(UTF8),size_is(1025)] uint8           share_mount_path[],
	[in][string,charset(UTF8),size_is(1025)] uint8           share_name[],
	[out,string,charset(UTF8),size_is(1025)] uint8           share_path[],
	[out,ref]                                policy_handle  *handle
);

Task of this RPC function is to open the specified network share. There are two out parameters, one of which is a variably sized string array, which is exactly what the above described codepath will be used for. We can observe this behaviour in the debugger by placing breakpoints at points of array allocation and use:

* thread #15, stop reason = breakpoint 1.1
    frame #0: 0x000000010026141f DCERPC`rpc_ss_ndr_unmar_interp(IDL_parameter_count=1, IDL_type_index=160, IDL_param_vector=0x00007000024737d0, IDL_msp=0x00007000024733e0) at ndrui.c:2199:21
Target 0: (rpcsvchost) stopped.
(lldb) bt
* thread #15, stop reason = breakpoint 1.1
  * frame #0: 0x000000010026141f DCERPC`rpc_ss_ndr_unmar_interp(IDL_parameter_count=1, IDL_type_index=160, IDL_param_vector=0x00007000024737d0, IDL_msp=0x00007000024733e0) at ndrui.c:2199:21
    frame #1: 0x0000000101b8e874 mdssvc.bundle`op0_ssr + 579
    frame #2: 0x00000001003d67b7 DCERPC`rpc__cn_call_executor(arg=0x000062f000000400, call_was_queued=0) at cncthd.c:284:13
    frame #3: 0x000000010033de5c DCERPC`cthread_call_executor(cthread=0x0000619000030128) at comcthd.c:611:3
    frame #4: 0x00000001001248b3 DCERPC`proxy_start(arg=0x000060300003c1f0) at dcethread_create.c:106:14
    frame #5: 0x00007fff6ce82109 libsystem_pthread.dylib`_pthread_start + 148
    frame #6: 0x00007fff6ce7db8b libsystem_pthread.dylib`thread_start + 15
(lldb) next
Process 52078 stopped
* thread #15, stop reason = step over
    frame #0: 0x0000000100261424 DCERPC`rpc_ss_ndr_unmar_interp(IDL_parameter_count=1, IDL_type_index=160, IDL_param_vector=0x00007000024737d0, IDL_msp=0x00007000024733e0) at ndrui.c:2203:21
Target 0: (rpcsvchost) stopped.
(lldb) p IDL_param_vector[param_index]
(rpc_void_p_t) $41 = 0x0000619000040680
(lldb) memory read 0x0000619000040680
0x619000040680: be be be be be be be be be be be be be be be be  
0x619000040690: be be be be be be be be be be be be be be be be  
(lldb)

First breakpoint that is hit is in a call to rpc_ss_alloc_out_conf_array inside rpc_ss_ndr_unmar_interp—that is, during unmarshalling. Pointer to allocated memory is saved in IDL_param_vector[param_index], and we can observe that it is uninitialized (signified by 0xbebebebe bytes due to use of Address Sanitizer). Continuing execution:

 * thread #15, stop reason = breakpoint 2.1
    frame #0: 0x00000001001ae478 DCERPC`rpc_ss_ndr_marsh_open_arr(defn_index=591, array_addr=0x0000619000040680, flags=22, IDL_msp=0x00007000024733e0) at ndrmi.c:1566:5
Target 0: (rpcsvchost) stopped.
(lldb) next
Process 52078 stopped
* thread #15, stop reason = step over
    frame #0: 0x00000001001ae47c DCERPC`rpc_ss_ndr_marsh_open_arr(defn_index=591, array_addr=0x0000619000040680, flags=22, IDL_msp=0x00007000024733e0) at ndrmi.c:1568:5
Target 0: (rpcsvchost) stopped.
(lldb) p array_addr
(rpc_void_p_t) $42 = 0x0000619000040680
(lldb) memory read 0x0000619000040680
0x619000040680: be be be be be be be be be be be be be be be be  
0x619000040690: be be be be be be be be be be be be be be be be  
(lldb) bt
* thread #15, stop reason = step over
  * frame #0: 0x00000001001ae47c DCERPC`rpc_ss_ndr_marsh_open_arr(defn_index=591, array_addr=0x0000619000040680, flags=22, IDL_msp=0x00007000024733e0) at ndrmi.c:1568:5
    frame #1: 0x00000001001b9ab5 DCERPC`rpc_ss_ndr_marsh_interp(IDL_parameter_count=2, IDL_type_index=240, IDL_param_vector=0x00007000024737d0, IDL_msp=0x00007000024733e0) at ndrmi.c:1868:21
    frame #2: 0x0000000101b8e92e mdssvc.bundle`op0_ssr + 765
    frame #3: 0x00000001003d67b7 DCERPC`rpc__cn_call_executor(arg=0x000062f000000400, call_was_queued=0) at cncthd.c:284:13
    frame #4: 0x000000010033de5c DCERPC`cthread_call_executor(cthread=0x0000619000030128) at comcthd.c:611:3
    frame #5: 0x00000001001248b3 DCERPC`proxy_start(arg=0x000060300003c1f0) at dcethread_create.c:106:14
    frame #6: 0x00007fff6ce82109 libsystem_pthread.dylib`_pthread_start + 148
    frame #7: 0x00007fff6ce7db8b libsystem_pthread.dylib`thread_start + 15
(lldb)

Second breakpoint is hit at the start of rpc_ss_ndr_marsh_open_arr during a call to rpc_ss_ndr_marsh_interp. This happens after the RPC function invocation, while marshalling results for a reply. Even at this point in execution, we can observe that the same piece of memory is uninitialized. Continuing execution further results in the following crash:

* thread #15, stop reason = Heap buffer overflow
    frame #0: 0x00000001005b0950 libclang_rt.asan_osx_dynamic.dylib`__asan::AsanDie()
libclang_rt.asan_osx_dynamic.dylib`__asan::AsanDie:
->  0x1005b0950 <+0>: push   rbp
    0x1005b0951 <+1>: mov    rbp, rsp
    0x1005b0954 <+4>: push   rbx
    0x1005b0955 <+5>: push   rax
Target 0: (rpcsvchost) stopped.
(lldb) bt
* thread #15, stop reason = Heap buffer overflow
  * frame #0: 0x00000001005b0950 libclang_rt.asan_osx_dynamic.dylib`__asan::AsanDie()
    frame #1: 0x00000001005c957f libclang_rt.asan_osx_dynamic.dylib`__sanitizer::Die() + 175
    frame #2: 0x00000001005aec23 libclang_rt.asan_osx_dynamic.dylib`__asan::ScopedInErrorReport::~ScopedInErrorReport() + 419
    frame #3: 0x00000001005ae516 libclang_rt.asan_osx_dynamic.dylib`__asan::ReportGenericError(unsigned long, unsigned long, unsigned long, unsigned long, bool, unsigned long, unsigned int, bool) + 1462
    frame #4: 0x0000000100578774 libclang_rt.asan_osx_dynamic.dylib`wrap_strlen + 420
    frame #5: 0x000000010016b18e DCERPC`rpc_ss_build_range_list_2(p_defn_vec_ptr=0x0000700002470ae0, array_addr=0x0000619000040680, struct_addr=0x0000000000000000, struct_offset_vec_ptr=0x0000000000000000, dimensionality=1, bounds_list=0x0000700002470b20, unmarshalled_list=0x0000000000000000, p_range_list=0x0000700002470be0, p_add_null="", IDL_msp=0x00007000024733e0) at interpsh.c:1067:49
    frame #6: 0x000000010016952f DCERPC`rpc_ss_build_range_list(p_defn_vec_ptr=0x0000700002470ae0, array_addr=0x0000619000040680, struct_addr=0x0000000000000000, struct_offset_vec_ptr=0x0000000000000000, dimensionality=1, bounds_list=0x0000700002470b20, p_range_list=0x0000700002470be0, p_add_null="", IDL_msp=0x00007000024733e0) at interpsh.c:959:12
    frame #7: 0x00000001001ae873 DCERPC`rpc_ss_ndr_marsh_open_arr(defn_index=591, array_addr=0x0000619000040680, flags=22, IDL_msp=0x00007000024733e0) at ndrmi.c:1602:5
    frame #8: 0x00000001001b9ab5 DCERPC`rpc_ss_ndr_marsh_interp(IDL_parameter_count=2, IDL_type_index=240, IDL_param_vector=0x00007000024737d0, IDL_msp=0x00007000024733e0) at ndrmi.c:1868:21
    frame #9: 0x0000000101b8e92e mdssvc.bundle`op0_ssr + 765
    frame #10: 0x00000001003d67b7 DCERPC`rpc__cn_call_executor(arg=0x000062f000000400, call_was_queued=0) at cncthd.c:284:13
    frame #11: 0x000000010033de5c DCERPC`cthread_call_executor(cthread=0x0000619000030128) at comcthd.c:611:3
    frame #12: 0x00000001001248b3 DCERPC`proxy_start(arg=0x000060300003c1f0) at dcethread_create.c:106:14
    frame #13: 0x00007fff6ce82109 libsystem_pthread.dylib`_pthread_start + 148
    frame #14: 0x00007fff6ce7db8b libsystem_pthread.dylib`thread_start + 15

Above crash is due to out of bounds access during a call to strlen. Buffer allocated for the uninitialized array is of size 1025. Since it’s filled with 0xbe bytes by address sanitizer, strlen will continue to count bytes outside the buffer, causing a crash.

Without Address Sanitizer in place, call to strlen will read uninitialized garbage data and will return an undefined length value. In most cases, with an idle service, uninitialized memory will contain zeroes, so strlen would return zero and no ill effect would be achieved. But, with careful memory layout control with multiple requests, other previously used data can still be present in the allocated buffer. This would be copied into a reply. This can contain fragments of other requests and replies as well as memory pointers, which could be used as a mitigation bypass component of an exploit.

To trigger this vulnerability through mdssvc, RPC service a simply valid , but with a non-existent target share, a call to mdssvc_open could be made to /var/rpc/ncacn_np/mdssvc endpoint directly or over SMB.

Crash Information

=================================================================
==52078==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x619000040a81 at pc 0x000100578755 bp 0x700002470270 sp 0x70000246fa30
READ of size 1026 at 0x619000040a81 thread T14
==52078==WARNING: invalid path to external symbolizer!
==52078==WARNING: Failed to use and restart external symbolizer!
    #0 0x100578754 in wrap_strlen+0x184 (/Users/anikolich/libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x19754)
    #1 0x10016b18d in rpc_ss_build_range_list_2+0x1c4d (/Users/anikolich/DCERPC:x86_64+0x4c18d)
    #2 0x10016952e in rpc_ss_build_range_list+0x6e (/Users/anikolich/DCERPC:x86_64+0x4a52e)
    #3 0x1001ae872 in rpc_ss_ndr_marsh_open_arr+0x5c2 (/Users/anikolich/DCERPC:x86_64+0x8f872)
    #4 0x1001b9ab4 in rpc_ss_ndr_marsh_interp+0xa624 (/Users/anikolich/DCERPC:x86_64+0x9aab4)
    #5 0x101b8e92d in op0_ssr+0x2fc (/usr/lib/rpcsvc/mdssvc.bundle:x86_64+0x292d)
    #6 0x1003d67b6 in rpc__cn_call_executor+0x1366 (/Users/anikolich/DCERPC:x86_64+0x2b77b6)
    #7 0x10033de5b in cthread_call_executor+0x4fb (/Users/anikolich/DCERPC:x86_64+0x21ee5b)
    #8 0x1001248b2 in proxy_start+0x1e2 (/Users/anikolich/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)

0x619000040a81 is located 0 bytes to the right of 1025-byte region [0x619000040680,0x619000040a81)
allocated by thread T14 here:
    #0 0x1005a74f0 in wrap_malloc+0xa0 (/Users/anikolich/libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x484f0)
    #1 0x1001731e4 in rpc_ss_default_alloc+0x14 (/Users/anikolich/DCERPC:x86_64+0x541e4)
    #2 0x10012e7da in rpc_sm_mem_alloc+0x10a (/Users/anikolich/DCERPC:x86_64+0xf7da)
    #3 0x10012e5c8 in rpc_ss_mem_alloc+0x128 (/Users/anikolich/DCERPC:x86_64+0xf5c8)
    #4 0x100202a87 in rpc_ss_ndr_alloc_storage+0xa7 (/Users/anikolich/DCERPC:x86_64+0xe3a87)
    #5 0x10026681f in rpc_ss_alloc_out_conf_array+0xccf (/Users/anikolich/DCERPC:x86_64+0x14781f)
    #6 0x100261423 in rpc_ss_ndr_unmar_interp+0x20023 (/Users/anikolich/DCERPC:x86_64+0x142423)
    #7 0x101b8e873 in op0_ssr+0x242 (/usr/lib/rpcsvc/mdssvc.bundle:x86_64+0x2873)
    #8 0x1003d67b6 in rpc__cn_call_executor+0x1366 (/Users/anikolich/DCERPC:x86_64+0x2b77b6)
    #9 0x10033de5b in cthread_call_executor+0x4fb (/Users/anikolich/DCERPC:x86_64+0x21ee5b)
    #10 0x1001248b2 in proxy_start+0x1e2 (/Users/anikolich/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 T14 created by T2 here:
    #0 0x1005a167c in wrap_pthread_create+0x5c (/Users/anikolich/libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x4267c)
    #1 0x10012448b in dcethread_create+0x3fb (/Users/anikolich/DCERPC:x86_64+0x548b)
    #2 0x100124b6c in dcethread_create_throw+0x2c (/Users/anikolich/DCERPC:x86_64+0x5b6c)
    #3 0x10033d3ee in cthread_create+0x3ae (/Users/anikolich/DCERPC:x86_64+0x21e3ee)
    #4 0x10033464e in cthread_pool_start+0x68e (/Users/anikolich/DCERPC:x86_64+0x21564e)
    #5 0x100338586 in rpc__cthread_start_all+0x176 (/Users/anikolich/DCERPC:x86_64+0x219586)
    #6 0x10035bd2b in rpc_server_listen+0x54b (/Users/anikolich/DCERPC:x86_64+0x23cd2b)
    #7 0x10000316b in run_dcerpc_svc(void*)+0x1c (/usr/libexec/rpcsvchost:x86_64+0x10000316b)
    #8 0x7fff6ce82108 in _pthread_start+0x93 (/usr/lib/system/libsystem_pthread.dylib:x86_64+0x6108)
    #9 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 (/Users/anikolich/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 (/Users/anikolich/libclang_rt.asan_osx_dynamic.dylib:x86_64h+0x19754) in wrap_strlen+0x184
Shadow bytes around the buggy address:
  0x1c3200008100: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c3200008110: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c3200008120: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c3200008130: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c3200008140: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1c3200008150:[01]fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c3200008160: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x1c3200008170: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c3200008180: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c3200008190: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1c32000081a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
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/HT213677

TIMELINE

2023-01-16 - Vendor Disclosure
2023-03-27 - Vendor Patch Release
2023-07-13 - Public Release

Credit

Discovered by Aleksandar Nikolic of Cisco Talos.