CVE-2023-20892
A heap overflow vulnerability exists in the request processing functionality of DCERPC library as used in VMware vCenter Server 7.0.3.01000 that can lead to use of uninitialized memory. A specially crafted network packet can cause use of uninitialized memory, which can lead to heap overflow and arbitrary code execution. Remote attacker can send a network request 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.
VMware vCenter Server 7.0.3.01000
vCenter Server - https://www.vmware.com/products/vcenter-server.html
7.5 - CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H
CWE-457 - Use of Uninitialized Variable
DCERPC is a remote procedure call protocol that is the basis for RPC functionality on Windows. DCERPC is used as a library in VMWare vCenter to implement this protocol and enable interoperability of Windows network services inside vCenter.
A version of the DCERPC codebase is used by VMWare vCenter server. VMware vCenter is a key component of VMware vSphere, typically used in cloud environments enabling advanced management of VMs. It enables a number of services, like certificate management, directory services, single sign-on, etc. Some services use the DCERPC protocol for communication, with the implementation provided by the Likewise-Open library. Specifically, it is used in the daemons for VMware Certificate Management Service (vmcad, port 2014), VMware Directory Service (vmdird, port 2012) and VMware Authentication Framework (vmafdd, port 2020), accessible by default from the local network.
There exists a vulnerability in a way that DCERPC framework processes request packets with MAYBE
semantics. In DCERPC specification, a packet coming from a client that is marked with a MAYBE
flag signifies that a client doesn’t expect a reply. In DCERPC framework implementation, this changes the codepath taken when sending out a call fault, which can result in access of an otherwise uninitialized memory. To reach the vulnerable codepath, a bind request
packet is sent first, to get to the proper state of the state machine. Regardless of the validity of the bind request, a subsequent rpc call request can trigger the vulnerable code path. When responding to a call request, function rpc__cn_call_end
is called and the following code is reached:
/*
* If there are iovector elements in the call_rep,
* free them all. There would not be any in the case
* of a maybe call.
*/
if (RPC_CN_CREP_IOVLEN (call_rep) > 0) [1]
{
for (cur_iov_index = 0;
cur_iov_index < RPC_CN_CREP_IOVLEN (call_rep);
cur_iov_index++)
{
if (RPC_CN_CREP_IOV (call_rep) [cur_iov_index].buff_dealloc != [2]
NULL)
{
(RPC_CN_CREP_IOV (call_rep) [cur_iov_index].buff_dealloc)
(RPC_CN_CREP_IOV (call_rep) [cur_iov_index].buff_addr);
}
RPC_CN_CREP_IOV (call_rep) [cur_iov_index].buff_addr = NULL;
}
}
The above code is responsible for freeing chunks of fragmented packets, and as the comment specifies, it expects that a maybe
call wouldn’t have any. A check at [1] simply translates to the following:
#define RPC_CN_CREP_IOVLEN(cp) (((cp)->buffered_output).iov.num_elt)
If num_elt
of IO vector isn’t 0 (that is, there are additional buffers allocated), it proceeds to iterate over them to perform cleanup. Conveniently, each IO vector element can contain a pointer to a function that is to be called to deallocate it. Otherwise, its associated buffer address is simply set to NULL
. From this, we can conclude that if num_elt
is somehow bigger than the actual number of elements, a heap overflow would result during dereference at [2].
If we consider the codepaths that lead us to here, one of them goes through function handle_first_frag_action_rtn
in which we can find the following code:
/*
* Copy the opnum field into the local call rep.
*/
call_rep->opnum = RPC_CN_PKT_OPNUM (request_header_p);
if (!(RPC_CN_PKT_FLAGS (request_header_p) & RPC_C_CN_FLAGS_MAYBE)) [3]
{
/*
* Fill in the fields of the response header if this is not
* a maybe call.
*/
RPC_CN_CREP_SIZEOF_HDR (call_rep) = RPC_CN_PKT_SIZEOF_RESP_HDR;
response_header_p = (rpc_cn_packet_p_t) RPC_CN_CREP_SEND_HDR (call_rep);
...
...
...
/*
* Initialize the iovector in the call_rep to contain only
* one initial element, pointing to the protocol header.
* Also, update pointers to show that we can copy data into
* the stub data area.
*/
RPC_CN_CREP_IOVLEN (call_rep) = 1; [4]
RPC_CN_CREP_CUR_IOV_INDX (call_rep) = 0;
RPC_CN_CREP_FREE_BYTES (call_rep) =
RPC_C_CN_SMALL_FRAG_SIZE - RPC_CN_PKT_SIZEOF_RESP_HDR;
RPC_CN_CREP_ACC_BYTCNT (call_rep) = RPC_CN_PKT_SIZEOF_RESP_HDR;
RPC_CN_CREP_FREE_BYTE_PTR(call_rep) =
RPC_CN_PKT_RESP_STUB_DATA (response_header_p);
(RPC_CN_CREP_IOV (call_rep)[0]).data_len = RPC_CN_PKT_SIZEOF_RESP_HDR;
At [3], true branch is taken only if the packet is not a MAYBE
call, and at [4] num_elt
is properly initialized to 1. No initialization happens if the request has a RPC_C_CN_FLAGS_MAYBE
flag set, in which case num_elt
remains uninitialized. If the uninitialized value happens to be non-zero, this can lead to a heap buffer overflow at [2].
With multiple simultaneous connections and calculated and timed allocations, enough control over memory layout could be achieved to precisely control the contents of the uninitialized memory, which could result in arbitrary code execution.
Note that the original DCERPC codebase was independently modified and reused by both VMWare in vCenter and Apple in macOS. Hence, this analysis is largely the same as the one described with more context in TALOS-2022-1677.
Crash happens as an invalid memory is used as a function pointer, thus hijacking control flow:
#0 0x0000011000000001 in ?? ()
#1 0x00007ffff6e82cac in rpc__cn_call_end (call_r=0x7fffea38bac0, st=0x7fffea38ba40) at ../../../dcerpc/ncklib/cncall.c:1794
#2 0x00007ffff6e91ca9 in rpc__cn_call_executor (arg=0x62f000000400, call_was_queued=0x0) at ../../../dcerpc/ncklib/cncthd.c:284
#3 0x00007ffff6e1c4e9 in cthread_call_executor (cthread=0x619000020228) at ../../../dcerpc/ncklib/comcthd.c:574
#4 0x00007ffff6cd73ee in proxy_start (arg=0x6030000562f0) at ../../../dcerpc/libdcethread/dcethread_create.c:100
#5 0x00007ffff6335f87 in start_thread () from /lib/libpthread.so.0
#6 0x00007ffff622662f in clone () from /lib/libc.so.6
The vendor provided an advisory and fixes: https://www.vmware.com/security/advisories/VMSA-2023-0014.html
2023-04-06 - Vendor Disclosure
2023-06-22 - Vendor Patch Release
2023-07-13 - Public Release
Discovered by Aleksandar Nikolic of Cisco Talos.