CVE-2023-38562
A double-free vulnerability exists in the IP header loopback parsing functionality of Weston Embedded uC-TCP-IP v3.06.01. A specially crafted set of network packets can lead to memory corruption, potentially resulting in code execution. An attacker can send a sequence of unauthenticated packets 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.
Weston Embedded uC-TCP-IP v3.06.01
uC-TCP-IP - https://weston-embedded.com/micrium/overview
8.7 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:H
CWE-415 - Double Free
The uC-TCP-IP protocol stack is open source and optimized for embedded systems. It is designed for embedded systems running the µC/OS II or µC/OS III RTOS kernels and provides API’s for integration with other operating system kernels. The protocol stack features dual IPv4 and IPv6 support and an SSL/TLS socket option, as well as support for Ethernet, Wi-Fi and PHY controllers.
This double free vulnerability occurs when a packet is received with an identical source and uC-TCP-IP address and an invalid IP header. The subsequent double allocation of memory could lead to code execution.
When the IP header parsing code detects an error with the TCP/IP options [1,] it calls NetICMPv4_TxMsgErr
, which allocates a buffer [2] for transmitting that ICMP error message. Next, NetIPv4_Tx
is called, swapping the src/dest from the received packet for the transmitted packet [4][5]. Continuing down the call stack for NetIPv4_Tx
[3], the function NetIPv4_TxPktDatagramRouteSel
determines that the transmitted packet is destined for localhost [8], which results in if_nbr_tx
being set to the loopback interface number [9]. Next, continuing down the call stack from NetIF_Get
, NetIF_IsValidHandler
will check if the interface has been initialized [12]. This check will result in a failure, because the loopback interface is not enabled. When this error is detected within NetIF_TxHandler
[10], NetIF_TxPktDiscard
[11] is called, which eventually calls Mem_PoolBlkFree
for the transmit buffer p_buf
. This is the first free. NetIPv4_Tx
returns the error to it’s caller NetICMPv4_TxMsgErr
[6]. A different discard function NetICMPv4_TxPktDiscard
[7] is called, which also eventually calls Mem_PoolBlkFree
for the transmit buffer p_msg_err
, which points to the same memory location as pbuf
. This results in the second free of the same memory allocation.
File: net_ipv4.c
5813: static void NetIPv4_RxPktValidateOpt (NET_BUF *p_buf,
5814: NET_BUF_HDR *p_buf_hdr,
5815: NET_IPv4_HDR *p_ip_hdr,
5816: CPU_INT08U ip_hdr_len_size,
5817: NET_ERR *p_err)
5818: {
...
6016: if (opt_err != DEF_NO) { /* If ANY opt errs, tx ICMP err msg. */
6017: NET_CTR_ERR_INC(Net_ErrCtrs.IPv4.RxInvOptsCtr);
6018: #ifdef NET_ICMPv4_MODULE_EN
6019: opt_ix_err = NET_ICMPv4_PTR_IX_IP_OPTS + (opt_list_len_size - opt_list_len_rem);
6020: NetICMPv4_TxMsgErr(p_buf, // [1]
6021: NET_ICMPv4_MSG_TYPE_PARAM_PROB,
6022: NET_ICMPv4_MSG_CODE_PARAM_PROB_IP_HDR,
6023: opt_ix_err,
6024: &err);
********************************************************************************************************************************
File: net_icmpv4.c
876: void NetICMPv4_TxMsgErr (NET_BUF *p_buf,
877: CPU_INT08U type,
878: CPU_INT08U code,
879: CPU_INT08U ptr,
880: NET_ERR *p_err)
881: {
...
993: p_msg_err = NetBuf_Get((NET_IF_NBR ) p_buf_hdr->IF_Nbr, // [2]
994: (NET_TRANSACTION) NET_TRANSACTION_TX,
995: (NET_BUF_SIZE ) msg_size_tot,
996: (NET_BUF_SIZE ) msg_ix,
997: (NET_BUF_SIZE *)&msg_ix_offset,
998: (NET_BUF_FLAGS ) NET_BUF_FLAG_NONE,
999: (NET_ERR *)&err);
...
1091: /* ---------------- TX ICMPv4 ERR MSG ----------------- */
1092: NetIPv4_Tx((NET_BUF *)p_msg_err, // [3]
1093: (NET_IPv4_ADDR )p_buf_hdr->IP_AddrDest, // [4] src
1094: (NET_IPv4_ADDR )p_buf_hdr->IP_AddrSrc, // [5] dest
1095: (NET_IPv4_TOS )NET_IPv4_TOS_DFLT, /* See Note #1d1. */
1096: (NET_IPv4_TTL )NET_IPv4_TTL_DFLT,
1097: (NET_IPv4_FLAGS)NET_IPv4_FLAG_NONE,
1098: (void *)0,
1099: (NET_ERR *)p_err);
1100:
1101:
1102:
1103: /* ------ FREE ICMPv4 ERR MSG / UPDATE TX STATS ------- */
1104: switch (*p_err) { // [6]
...
1128: default:
1129: NetICMPv4_TxPktDiscard(p_msg_err, p_err); // [7]
1130: return;
1131: }
********************************************************************************************************************************
File: net_ipv4.c
9798: static void NetIPv4_TxPktDatagramRouteSel (NET_BUF_HDR *p_buf_hdr,
9799: NET_ERR *p_err)
9800: {
...
File: net_ipv4.c
9829: /* ---------- CHK CFG'D HOST ADDR(S) ---------- */
9830: /* Chk cfg'd host addr(s) [see Note #3a2A]. */
9831: addr_cfgd = NetIPv4_IsAddrHostCfgdHandler(addr_dest);
9832: if (addr_cfgd == DEF_YES) {
9833: NET_CTR_STAT_INC(Net_StatCtrs.IPv4.TxDestThisHostCtr);
9834: p_buf_hdr->IP_AddrNextRoute = addr_dest;
9835: *p_err = NET_IPv4_ERR_TX_DEST_LOCAL_HOST; // [8]
********************************************************************************************************************************
File: net_if.c
7065: static void NetIF_TxHandler (NET_BUF *p_buf,
7066: NET_ERR *p_err)
7067: {
...
7093: /* --------------- GET TX PKT's NET IF ---------------- */
7094: p_if_tx = NetIF_Get(if_nbr_tx, p_err); // [9]
7095: if (*p_err != NET_IF_ERR_NONE) { // [10]
7096: NetIF_TxPktDiscard(p_buf, DEF_YES, &err); // [11]
7097: return;
7098: }
********************************************************************************************************************************
File: net_if.c
2057: CPU_BOOLEAN NetIF_IsValidHandler (NET_IF_NBR if_nbr,
2058: NET_ERR *p_err)
2059: {
...
2074: /* ----------------- VALIDATE NET IF ------------------ */
2075: p_if = &NetIF_Tbl[if_nbr];
2076: CPU_CRITICAL_ENTER();
2077: init = p_if->Init;
2078: CPU_CRITICAL_EXIT();
2079: if (init != DEF_YES) { // [12]
2080: *p_err = NET_IF_ERR_INVALID_IF;
2081: return (DEF_NO);
2082: }
2083:
Memory for the network buffers of uC-TCP-IP is allocated using memory pools within uC-LIB. When an allocated pointer is freed, it is placed at the end of an array known as the BlkFreeTbl
. The next allocation is retrieved from the last occupied position in a LIFO (last in, first out) fashion. When the transmit buffer above is freed twice, it is placed into the BlkFreeTbl
in two at two different positions at the end of the array [1]. Which makes it available to be doubly allocated for the next two net buffers that are allocated.
File: lib_mem.c
1685: void Mem_PoolBlkFree (MEM_POOL *p_pool,
1686: void *p_blk,
1687: LIB_ERR *p_err)
1688: {
...
1735: p_pool->BlkFreeTbl[p_pool->BlkFreeTblIx] = p_blk; // [1]
1736: p_pool->BlkFreeTblIx += 1u;
Next, two different net buffer objects are allocated, which point to the same memory region, a result of the previous double free. This double allocation results in a crash where one instance of the net buffer object clears a pointer and the other instance of the net buffer object expects the pointer to be initialized. The effect of this is a crash where the program attempts to write 6 bytes to a NULL pointer.
The first of the subsequent allocations is pseg_sync
which is allocated in the function NetTCP_TxConnSync
[1]. In the call stack for NetTCP_TxPktHandlerIPv4
, NetIF_802x_TxPktPrepareFrame
is called [2], which initializes the pointer p_buf_hdr->ARP_AddrHW_Ptr
[4]. The value of this pointer is important because it is the reason for the crash. Next NetARP_CacheHandler
is called [3], which stores the pointer to this buffer at p_cache_addr_arp->TxQ_Head
[5].
File: net_tcp.c
20593: static void NetTCP_TxConnSync (NET_TCP_CONN *p_conn,
20594: NET_BUF_HDR *p_buf_hdr,
20595: NET_TCP_CONN_STATE state,
20596: NET_ERR *p_err)
20597: {
...
20714: pseg_sync = NetBuf_Get(if_nbr, NET_TRANSACTION_TX, data_len, data_ix, &data_ix_offset, NET_BUF_FLAG_NONE, &err); //[1]
...
20920: NetTCP_TxPktHandlerIPv4((NET_BUF *)pseg_sync,
20921: (NET_IPv4_ADDR )src_addrv4,
20922: (NET_TCP_PORT_NBR)src_port,
20923: (NET_IPv4_ADDR )dest_addrv4,
20924: (NET_TCP_PORT_NBR)dest_port,
20925: (NET_TCP_SEQ_NBR )seq_nbr,
20926: (NET_TCP_SEQ_NBR )ack_nbr,
20927: (NET_TCP_WIN_SIZE)win_size,
20928: (NET_IPv4_TOS )TOS,
20929: (NET_IPv4_TTL )TTL,
20930: (NET_TCP_FLAGS )flags_tcp,
20931: (NET_IPv4_FLAGS )flags_ipv4,
20932: (void *)p_opt_cfg_max_seg_size,
20933: (void *)0, /* See Note #7. */
20934: (NET_ERR *)p_err);
********************************************************************************************************************************
676: void NetIF_802x_Tx (NET_IF *p_if,
677: NET_BUF *p_buf,
678: NET_CTR_IF_802x_STATS *p_ctrs_stat,
679: NET_CTR_IF_802x_ERRS *p_ctrs_err,
680: NET_ERR *p_err)
681: {
...
720: /* -------------- PREPARE 802x TX FRAME --------------- */
721: NetIF_802x_TxPktPrepareFrame(p_if, // [2]
722: p_buf,
723: p_buf_hdr,
724: p_ctrs_stat,
725: p_ctrs_err,
726: p_err);
...
742: NetARP_CacheHandler(p_buf, p_err); // [3]
********************************************************************************************************************************
File: net_if_802x.c
2617: static void NetIF_802x_TxPktPrepareFrame (NET_IF *p_if,
2618: NET_BUF *p_buf,
2619: NET_BUF_HDR *p_buf_hdr,
2620: NET_CTR_IF_802x_STATS *p_ctrs_stat,
2621: NET_CTR_IF_802x_ERRS *p_ctrs_err,
2622: NET_ERR *p_err)
2623: {
...
2747: p_buf_hdr->ARP_AddrHW_Ptr = &p_if_hdr_ether->AddrDest[0]; //[4]
********************************************************************************************************************************
File: net_arp.c
1060: void NetARP_CacheHandler (NET_BUF *p_buf,
1061: NET_ERR *p_err)
1062: {
...
1156: p_cache_addr_arp->TxQ_Head = (NET_BUF *)p_buf; // [5]
1157: p_cache_addr_arp->TxQ_Tail = (NET_BUF *)p_buf;
The second of the subsequent allocations is for the transmitted arp request. It is allocated by NetARP_Tx
[1]. When allocating, memory via NetBuf_Get
is called, which clears the allocated memory with a call to NetBuf_ClrHdr
. This is significant because it is double allocation, and the memory being cleared is used by another variable. Clearing this memory wipes out all of the values that were initialized by the previous calls, including p_buf_hdr->ARP_AddrHW_Ptr
[2].
File: net_arp.c
3094: static void NetARP_Tx (NET_IF_NBR if_nbr,
3095: CPU_INT08U *p_addr_hw_sender,
3096: CPU_INT08U *p_addr_hw_target,
3097: CPU_INT08U *p_addr_protocol_sender,
3098: CPU_INT08U *p_addr_protocol_target,
3099: CPU_INT16U op_code,
3100: NET_ERR *p_err)
3101: {
...
3175: p_buf = NetBuf_Get((NET_IF_NBR ) if_nbr, // [1]
3176: (NET_TRANSACTION) NET_TRANSACTION_TX,
3177: (NET_BUF_SIZE ) NET_ARP_MSG_LEN_DATA,
3178: (NET_BUF_SIZE ) msg_ix,
3179: (NET_BUF_SIZE *)&msg_ix_offset,
3180: (CPU_INT16U ) NET_BUF_FLAG_NONE,
3181: (NET_ERR *) p_err);
********************************************************************************************************************************
File: net_buf.c
2920: static void NetBuf_ClrHdr (NET_BUF_HDR *p_buf_hdr)
2921: {
...
2978: p_buf_hdr->ARP_AddrHW_Ptr = (CPU_INT08U *)0; // [2]
When an ARP reply is received for the same IP address for which an ARP request was sent by the uC-TCP-IP server, NetARP_RxPktCacheUpdate
is called. It uses the previously allocated p_cache_addr_arp->TxQ_Head
[1] as a transmit buffer, and it expects ARP_AddrHW_Ptr
to be initialized to a valid pointer [2]. But, because of the double allocation, it has been previously set to NULL. This results in the program crashing when attempting to write to NULL [3]. The ability to write to NULL could have a significant impact on a system where important information is stored at memory address 0x0000000, like configuration data. This vulnerability could lead to data corruption. Similarly, on a system where initialization functions or other function pointers are stored at memory that is mapped to address 0x00000000, this vulnerability would allow that function pointer to be overwritten, which could lead to code execution.
File: net_arp.c
2539: static void NetARP_RxPktCacheUpdate (NET_IF_NBR if_nbr,
2540: NET_ARP_HDR *p_arp_hdr,
2541: NET_ERR *p_err)
2542: {
...
2579: switch (p_cache_arp->State) {
2580: case NET_ARP_CACHE_STATE_PEND: /* If ARP cache pend, add sender's hw addr, ... */
...
2595: p_buf_head = p_cache_addr_arp->TxQ_Head; // [1]
...
2602: NetCache_TxPktHandler(NET_PROTOCOL_TYPE_ARP,
2603: p_buf_head, /* ... & handle/tx cache's buf Q. */
2604: p_addr_sender_hw);
********************************************************************************************************************************
File: net_cache.c
1304: void NetCache_TxPktHandler (NET_PROTOCOL_TYPE proto_type,
1305: NET_BUF *pbuf_q,
1306: CPU_INT08U *paddr_hw)
1307: {
...
1328: pbuf_list = pbuf_q;
1329: while (pbuf_list != (NET_BUF *)0) { /* Handle ALL buf lists in Q. */
1330: pbuf_hdr = &pbuf_list->Hdr;
...
1341: pbuf_addr_hw = pbuf_hdr->ARP_AddrHW_Ptr; // [2]
1342: Mem_Copy((void *)pbuf_addr_hw, // [3]
1343: (void *)paddr_hw,
1344: (CPU_SIZE_T)NET_IF_HW_ADDR_LEN_MAX);
1345: break;
Thread 3 "app" received signal SIGSEGV, Segmentation fault.
[Switching to Thread 0xf7f2eb40 (LWP 116218)]
Mem_Copy (pdest=0x0, psrc=0x5668a226 <Mem_Heap+13478>, size=6) at uc-lib/lib_mem.c:468
468 *pmem_08_dest++ = *pmem_08_src++; /* ... copy psrc to pdest by octets. */
(gdb) bt
#0 Mem_Copy (pdest=0x0, psrc=0x5668a226 <Mem_Heap+13478>, size=6) at uc-lib/lib_mem.c:468
#1 0x56659c63 in NetCache_TxPktHandler (proto_type=NET_PROTOCOL_TYPE_ARP, pbuf_q=0x5668dd50 <Mem_Heap+28624>, paddr_hw=0x5668a226 <Mem_Heap+13478> "\022\064Vx\220\022\n\n\n\002b\237]k\t\226\n\n\n@")
at uc-tcp-ip/Source/net_cache.c:1342
#2 0x5666b9e5 in NetARP_RxPktCacheUpdate (if_nbr=1 '\001', p_arp_hdr=0x5668a21e <Mem_Heap+13470>, p_err=0xf7f2e2d4) at uc-tcp-ip/IP/IPv4/net_arp.c:2602
#3 0x5666af67 in NetARP_Rx (p_buf=0x5668df38 <Mem_Heap+29112>, p_err=0xf7f2e2d4) at uc-tcp-ip/IP/IPv4/net_arp.c:1598
#4 0x566660ba in NetIF_802x_RxPktFrameDemux (p_if=0x56684f00 <NetIF_Tbl+128>, p_buf=0x5668df38 <Mem_Heap+29112>, p_buf_hdr=0x5668df38 <Mem_Heap+29112>, p_if_hdr=0x5668a210 <Mem_Heap+13456>, p_ctrs_stat=0x5668581c <Net_StatCtrs+156>,
p_ctrs_err=0x56685524 <Net_ErrCtrs+132>, p_err=0xf7f2e2d4) at uc-tcp-ip/IF/net_if_802x.c:2061
#5 0x566654a4 in NetIF_802x_Rx (p_if=0x56684f00 <NetIF_Tbl+128>, p_buf=0x5668df38 <Mem_Heap+29112>, p_ctrs_stat=0x5668581c <Net_StatCtrs+156>, p_ctrs_err=0x56685524 <Net_ErrCtrs+132>, p_err=0xf7f2e2d4)
at uc-tcp-ip/IF/net_if_802x.c:579
#6 0x566600a4 in NetIF_Ether_Rx (p_if=0x56684f00 <NetIF_Tbl+128>, p_buf=0x5668df38 <Mem_Heap+29112>, p_err=0xf7f2e2d4) at uc-tcp-ip/IF/net_if_ether.c:306
#7 0x56663e96 in NetIF_RxPkt (p_if=0x56684f00 <NetIF_Tbl+128>, p_err=0xf7f2e2d4) at uc-tcp-ip/IF/net_if.c:6644
#8 0x56663bc4 in NetIF_RxHandler (if_nbr=1 '\001') at uc-tcp-ip/IF/net_if.c:6390
#9 0x56663a41 in NetIF_RxTaskHandler () at uc-tcp-ip/IF/net_if.c:6251
#10 0x566639a4 in NetIF_RxTask (p_data=0x0) at uc-tcp-ip/IF/net_if.c:6188
#11 0x566743da in KAL_TaskFnctWrapper (p_arg=0xffeb76b4) at uc-common/KAL/POSIX/kal.c:1343
#12 0xf7ef760a in start_thread (arg=<optimized out>) at pthread_create.c:477
#13 0xf7e06d2a in clone () from /lib32/libc.so.6
(gdb) i r
eax 0x0 0
ecx 0x1 1
edx 0x12 18
ebx 0x56683f10 1449672464
esp 0xf7f2e058 0xf7f2e058
ebp 0xf7f2e088 0xf7f2e088
esi 0xf7f2e280 -135077248
edi 0x0 0
eip 0x5665d37f 0x5665d37f <Mem_Copy+252>
eflags 0x10206 [ PF IF RF ]
cs 0x23 35
ss 0x2b 43
ds 0x2b 43
es 0x2b 43
fs 0x0 0
gs 0x63 99
(gdb)
This vulnerability can be mitigated by enabling the configuration option NET_ERR_CFG_ARG_CHK_DBG_EN in net_cfg.h Ex.
net_cfg.h
#define NET_ERR_CFG_ARG_CHK_DBG_EN DEF_ENABLED
The description for this configuration option is misleading. The documentation states “Allows code to be generated which checks to make sure that pointers passed to functions are not NULL, and that arguments are within range, etc.” Enabling a double free check is something that should be default and not optionally included with other argument checks.
2023-08-30 - Vendor Disclosure
2023-11-21 - Vendor Patch Release
2024-02-20 - Public Release
Discovered by Kelly Patterson of Cisco Talos.