Talos Vulnerability Report

TALOS-2023-1799

VMWare vCenter Server DCERPC association groups use-after-free vulnerability

July 13, 2023
CVE Number

CVE-2023-20893

SUMMARY

A use-after-free vulnerability exists in the library supporting DCERPC functionality in VMWare vCenter Server 7.0.3.01000. A series of specially crafted network packets trigger a use-after-free condition which can lead to memory corruption and arbitrary code execution. A remote attacker can send a network request 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.

VMware vCenter Server 7.0.3.01000

PRODUCT URLS

vCenter Server - https://www.vmware.com/products/vcenter-server.html

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-416 - Use After Free

DETAILS

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.

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.

The DCERPC specification defines an association as a utility object aiding the communication between server and client. These associations correspond to one transport connection and are logically grouped in so-called association groups. For every RPC session a new association object is allocated. In the codebase, an association object has a type of rpc_cn_assoc_s_t.

struct rpc_cn_assoc_s_t
{
    rpc_list_t                          link;   /* MUST BE 1ST */       [1]
    rpc_cn_sm_ctlblk_t                  assoc_state;
    unsigned32                          assoc_status;
    unsigned32                          assoc_local_status;
    unsigned16                          assoc_flags;
    unsigned16                          assoc_ref_count;
    unsigned16                          assoc_acb_ref_count;
    ...
}
...
typedef struct
{
    pointer_t   next;   /* next element of list                     */
    pointer_t   last;   /* last element of list in a descriptor or  */
                        /* pointer to the prior element in an element */
} rpc_list_t, *rpc_list_p_t;

The associations are logically put into association groups. Typically a server and client communication will create one association group, with each association being responsible for an RPC session. An association group is a doubly linked list holding association objects, with every association object holding a next and last pointer for the corresponding list elements at [1] above.

Through the do_assoc_req_action_rtn() and accept_add_action_rtn() RPC calls, the function rpc__cn_assoc_grp_alloc() is called, which quite predictably allocates a new association group and adds the association to the group.

PRIVATE rpc_cn_local_id_t rpc__cn_assoc_grp_alloc
(
  rpc_addr_p_t            rpc_addr,
  rpc_transport_info_p_t  transport_info,
  unsigned32              type,
  unsigned32              rem_id,
  unsigned32              *st
)
{
    ...
    /*
     * Ideally we'd like to locate an association group in the
     * existing table which is not being used. Worst case we'll
     * increase the size of the table to get a new group.
     */
    assoc_grp = rpc_g_cn_assoc_grp_tbl.assoc_grp_vector;
    for (i = 0, found_assoc_grp = false;
         i < rpc_g_cn_assoc_grp_tbl.grp_count;
         i++)
    {
        if (assoc_grp[i].grp_state.cur_state == RPC_C_ASSOC_GRP_CLOSED)         [2]
        {
            assoc_grp = &assoc_grp[i];
            found_assoc_grp = true;
            break;
        }
    }

    if (!found_assoc_grp)
    {
        /*
         * The association group table will have to be expanded to
         * get a free group.
         */
        grp_id = rpc__cn_assoc_grp_create (st);                                 [3]
        ...
    }

    ...

    /*
     * Finally send a new association event through the group state
     * machine.
     */
    RPC_CN_ASSOC_GRP_EVAL_EVENT (assoc_grp,
                                 RPC_C_ASSOC_GRP_NEW,
                                 NULL,
                                 assoc_grp->grp_status);
    ...
}

If no available association group is found at [2], the code proceeds to create a new group in rpc__cn_assoc_grp_create() at [3] above.

#define RPC_C_ASSOC_GRP_ALLOC_SIZE              10

INTERNAL rpc_cn_local_id_t rpc__cn_assoc_grp_create
(
  unsigned32              *st
)
{
    ...
    new_count = old_count + RPC_C_ASSOC_GRP_ALLOC_SIZE;                                [4]
    ...
    /*
     * 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,
                   RPC_C_MEM_CN_ASSOC_GRP_BLK,
                   RPC_C_MEM_WAITOK);

    /*
     * 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)));
        for (i = 0; i < old_count; i++)
        {
            /*
             * Relocate the "last" pointer in the head of the grp_assoc_list.
             * We don't check group's state because they must be all active.
             * Otherwise, this function never get called. (grp_assoc_list.next
             * shouldn't be NULL.)
             */
            if (new_assoc_grp[i].grp_assoc_list.next != NULL)
            {
                ((rpc_list_p_t)(new_assoc_grp[i].grp_assoc_list.next))->last =          [5]
                    (pointer_t)&new_assoc_grp[i].grp_assoc_list;
            }
        }
        ...
    }
    ...
}

At [4], it is evident that the code allocates groups in increments of 10. So at first call, memory for 10 new groups is allocated, then memory for 20 groups is allocated, 10 previous groups and 10 new groups, etc. Next, the old groups are copied to the newly allocated memory. Finally at [5], an update at the linked list is performed for each head element, setting the value of the last pointer to the head of the group linked list.

When the connection between client and server terminates, the association object is freed in rpc__cn_assoc_acb_dealloc(). Many cleanup actions are performed; however, no care is employed to make sure that the association is removed from a group. In essence, if an association is deallocated for any reason, a dangling reference of the association object may still exist in an association group.

We saw that the code allocates groups in increments of 10. After 10 association requests by a client, in the 11th association request a new association group will need to be allocated. Then at [5] the last pointer of the head element of each association group will be updated. Naturally, if the head element is a previously freed association, a use-after-free scenario can occur. Timing of free and reuse depend on network connections and are under attacker control. With precise timing and memory layout control, this use-after-free can result in memory corruption, which can ultimately lead to 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 in TALOS-2022-1717.

Crash Information

==72659==ERROR: AddressSanitizer: heap-use-after-free on address 0x616000020488 at pc 0x7ffff6e6ac40 bp 0x7fffc8f60c70 sp 0x7fffc8f60c68
WRITE of size 8 at 0x616000020488 thread T92
    #0 0x7ffff6e6ac3f in rpc__cn_assoc_grp_create ../../../dcerpc/ncklib/cnassoc.c:4958
    #1 0x7ffff6e6b059 in rpc__cn_assoc_grp_alloc ../../../dcerpc/ncklib/cnassoc.c:5086
    #2 0x7ffff6e993d9 in do_assoc_req_action_rtn ../../../dcerpc/ncklib/cnsassm.c:2006
    #3 0x7ffff6e9b4b2 in do_assoc_action_rtn ../../../dcerpc/ncklib/cnsassm.c:3461
    #4 0x7ffff6ea5d69 in rpc__cn_sm_eval_event ../../../dcerpc/ncklib/cnsm.c:771
    #5 0x7ffff6ea980a in _RPC_CN_ASSOC_EVAL_NETWORK_EVENT ../../../dcerpc/ncklib/cninline.c:129
    #6 0x7ffff6e933c1 in receive_dispatch ../../../dcerpc/ncklib/cnrcvr.c:1256
    #7 0x7ffff6e8d7d1 in rpc__cn_network_receiver ../../../dcerpc/ncklib/cnrcvr.c:348
    #8 0x7ffff6cc73fd in proxy_start ../../../dcerpc/libdcethread/dcethread_create.c:100
    #9 0x7ffff631ff86  (/lib/libpthread.so.0+0x7f86)
    #10 0x7ffff621062e in __clone (/lib/libc.so.6+0xf362e)

0x616000020488 is located 8 bytes inside of 584-byte region [0x616000020480,0x6160000206c8)
freed by thread T87 here:
    #0 0x7ffff72f6688 in free (/usr/lib/libasan.so+0xdc688)
    #1 0x7ffff6e43345 in rpc__mem_free ../../../dcerpc/ncklib/rpcmem.c :121
    #2 0x7ffff6e42e78 in rpc__list_element_free ../../../dcerpc/ncklib/rpclist.c:468
    #3 0x7ffff6e69dd5 in rpc__cn_assoc_acb_dealloc ../../../dcerpc/ncklib/cnassoc.c:4586
    #4 0x7ffff6e8dc6a in rpc__cn_network_receiver ../../../dcerpc/ncklib/cnrcvr.c:420
    #5 0x7ffff6cc73fd in proxy_start ../../../dcerpc/libdcethread/dcethread_create.c:100
    #6 0x7ffff631ff86  (/lib/libpthread.so.0+0x7f86)

previously allocated by thread T10 here:
    #0 0x7ffff72f69e0 in __interceptor_malloc (/usr/lib/libasan.so+0xdc9e0)
    #1 0x7ffff6e42f0b in rpc__mem_alloc ../../../dcerpc/ncklib/rpcmem.c:51
    #2 0x7ffff6e4291f in rpc__list_element_alloc ../../../dcerpc/ncklib/rpclist.c:192
    #3 0x7ffff6e68d2d in rpc__cn_assoc_acb_alloc ../../../dcerpc/ncklib/cnassoc.c:4338
    #4 0x7ffff6e5ff02 in rpc__cn_assoc_listen ../../../dcerpc/ncklib/cnassoc.c:1029
    #5 0x7ffff6e867b6 in rpc__cn_network_select_dispatch ../../../dcerpc/ncklib/cnnet.c:1014
    #6 0x7ffff6e2da4e in lthread_loop ../../../dcerpc/ncklib/comnlsn.c:503
    #7 0x7ffff6e2d260 in lthread ../../../dcerpc/ncklib/comnlsn.c:348
    #8 0x7ffff6cc73fd in proxy_start ../../../dcerpc/libdcethread/dcethread_create.c:100
    #9 0x7ffff631ff86  (/lib/libpthread.so.0+0x7f86)

Thread T92 created by T10 here:
    #0 0x7ffff7254150 in pthread_create (/usr/lib/libasan.so+0x3a150)
    #1 0x7ffff6cc7927 in dcethread_create ../../../dcerpc/libdcethread/dcethread_create.c:144
    #2 0x7ffff6cc7b23 in dcethread_create_throw ../../../dcerpc/libdcethread/dcethread_create.c:173
    #3 0x7ffff6e6a0b2 in rpc__cn_assoc_acb_create ../../../dcerpc/ncklib/cnassoc.c:4650
    #4 0x7ffff6e42a0f in rpc__list_element_alloc ../../../dcerpc/ncklib/rpclist.c:217
    #5 0x7ffff6e68d2d in rpc__cn_assoc_acb_alloc ../../../dcerpc/ncklib/cnassoc.c:4338
    #6 0x7ffff6e5ff02 in rpc__cn_assoc_listen ../../../dcerpc/ncklib/cnassoc.c:1029
    #7 0x7ffff6e867b6 in rpc__cn_network_select_dispatch ../../../dcerpc/ncklib/cnnet.c:1014
    #8 0x7ffff6e2da4e in lthread_loop ../../../dcerpc/ncklib/comnlsn.c:503
    #9 0x7ffff6e2d260 in lthread ../../../dcerpc/ncklib/comnlsn.c:348
    #10 0x7ffff6cc73fd in proxy_start ../../../dcerpc/libdcethread/dcethread_create.c:100
    #11 0x7ffff631ff86  (/lib/libpthread.so.0+0x7f86)

Thread T10 created by T8 here:
    #0 0x7ffff7254150 in pthread_create (/usr/lib/libasan.so+0x3a150)
    #1 0x7ffff6cc7927 in dcethread_create ../../../dcerpc/libdcethread/dcethread_create.c:144
    #2 0x7ffff6cc7b23 in dcethread_create_throw ../../../dcerpc/libdcethread/dcethread_create.c:173
    #3 0x7ffff6e2cabf in rpc__nlsn_activate_desc ../../../dcerpc/ncklib/comnlsn.c:185
    #4 0x7ffff6e2718b in rpc_server_listen ../../../dcerpc/ncklib/comnet.c:368
    #5 0x55555556288e in VMCAListenRpcServer ../../../../../vmca/service/rpc.c:145
    #6 0x7ffff631ff86  (/lib/libpthread.so.0+0x7f86)

Thread T8 created by T0 here:
    #0 0x7ffff7254150 in pthread_create (/usr/lib/libasan.so+0x3a150)
    #1 0x7ffff6cc7927 in dcethread_create ../../../dcerpc/libdcethread/dcethread_create.c:144
    #2 0x5555555643ed in InitializeRPCServer ../../../../../vmca/service/service.c:94
    #3 0x5555555643ed in VMCARPCInit ../../../../../vmca/service/service.c:45

Thread T87 created by T10 here:
    #0 0x7ffff7254150 in pthread_create (/usr/lib/libasan.so+0x3a150)
    #1 0x7ffff6cc7927 in dcethread_create ../../../dcerpc/libdcethread/dcethread_create.c:144
    #2 0x7ffff6cc7b23 in dcethread_create_throw ../../../dcerpc/libdcethread/dcethread_create.c:173
    #3 0x7ffff6e6a0b2 in rpc__cn_assoc_acb_create ../../../dcerpc/ncklib/cnassoc.c:4650
    #4 0x7ffff6e42a0f in rpc__list_element_alloc ../../../dcerpc/ncklib/rpclist.c:217
    #5 0x7ffff6e68d2d in rpc__cn_assoc_acb_alloc ../../../dcerpc/ncklib/cnassoc.c:4338
    #6 0x7ffff6e5ff02 in rpc__cn_assoc_listen ../../../dcerpc/ncklib/cnassoc.c:1029
    #7 0x7ffff6e867b6 in rpc__cn_network_select_dispatch ../../../dcerpc/ncklib/cnnet.c:1014
    #8 0x7ffff6e2da4e in lthread_loop ../../../dcerpc/ncklib/comnlsn.c:503
    #9 0x7ffff6e2d260 in lthread ../../../dcerpc/ncklib/comnlsn.c:348
    #10 0x7ffff6cc73fd in proxy_start ../../../dcerpc/libdcethread/dcethread_create.c:100
    #11 0x7ffff631ff86  (/lib/libpthread.so.0+0x7f86)

SUMMARY: AddressSanitizer: heap-use-after-free ../../../dcerpc/ncklib/cnassoc.c:4958 in rpc__cn_assoc_grp_create
Shadow bytes around the buggy address:
  0x0c2c7fffc040: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c2c7fffc050: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c2c7fffc060: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c2c7fffc070: fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa
  0x0c2c7fffc080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c2c7fffc090: fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c2c7fffc0a0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c2c7fffc0b0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c2c7fffc0c0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c2c7fffc0d0: fd fd fd fd fd fd fd fd fd fa fa fa fa fa fa fa
  0x0c2c7fffc0e0: 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
==72659==ABORTING
VENDOR RESPONSE

The vendor provided an advisory and fixes: https://www.vmware.com/security/advisories/VMSA-2023-0014.html

TIMELINE

2023-04-06 - Vendor Disclosure
2023-06-22 - Vendor Patch Release
2023-07-13 - Public Release

Credit

Discovered by Dimitrios Tatsis of Cisco Talos.