CVE-2023-27953
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.
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
macOS - https://apple.com
5.3 - CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:N/A:N
CWE-457 - Use of Uninitialized Variable
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.
=================================================================
==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
Fixed by Apple on 2023-03-27, patch information available at: https://support.apple.com/en-us/HT213677
2023-01-16 - Vendor Disclosure
2023-03-27 - Vendor Patch Release
2023-07-13 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.