Talos Vulnerability Report

TALOS-2023-1829

Weston Embedded uC-TCP-IP IP header loopback parsing double-free vulnerability

February 20, 2024
CVE Number

CVE-2023-38562

SUMMARY

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.

CONFIRMED VULNERABLE VERSIONS

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

PRODUCT URLS

uC-TCP-IP - https://weston-embedded.com/micrium/overview

CVSSv3 SCORE

8.7 - CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:C/C:N/I:H/A:H

CWE

CWE-415 - Double Free

DETAILS

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;

Crash Information

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)

Mitigation

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.

TIMELINE

2023-08-30 - Vendor Disclosure
2023-11-21 - Vendor Patch Release
2024-02-20 - Public Release

Credit

Discovered by Kelly Patterson of Cisco Talos.