Talos Vulnerability Report

TALOS-2022-1663

EIP Stack Group OpENer Forward Open connection_management_entry use of uninitialized pointer vulnerability

February 23, 2023
CVE Number

CVE-2022-43606

SUMMARY

A use-of-uninitialized-pointer vulnerability exists in the Forward Open connection_management_entry functionality of EIP Stack Group OpENer development commit 58ee13c. A specially-crafted EtherNet/IP request can lead to use of a null pointer, causing the server to crash. An attacker can send a series of EtherNet/IP requests 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.

EIP Stack Group OpENer development commit 58ee13c

PRODUCT URLS

OpENer - https://github.com/EIPStackGroup/OpENer

CVSSv3 SCORE

7.5 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H

CWE

CWE-824 - Access of Uninitialized Pointer

DETAILS

OpENer is an EtherNet/IP stack for I/O adapter devices. It supports multiple I/O and explicit connections and includes objects and services for making EtherNet/IP-compliant products as defined in the ODVA specification.

When a LargeForwardOpen request is received, the CIP connection path is extracted into a ConnectionManagementEntry object in cipconnectionmanager.c:ParseConnectionPath. In this function the remaining_path variable gets set to the connection_path_size value extracted directly from the request. When connection_path_size is set to 0x00 and no connection path is provided in the message, the majority of the function’s parsing is bypassed to return successfully.

EipUint8 ParseConnectionPath(CipConnectionObject *connection_object,
                             CipMessageRouterRequest *message_router_request,
                             EipUint16 *extended_error) {
  const EipUint8 *message = message_router_request->data;
  const size_t connection_path_size = GetUsintFromMessage(&message); /* length in words */
  size_t remaining_path = connection_path_size;
  OPENER_TRACE_INFO("Received connection path size: %zu \n",
                    connection_path_size);
  CipClass *class = NULL;

  CipDword class_id = 0x0;
  CipDword instance_id = 0x0;

  /* with 256 we mark that we haven't got a PIT segment */
  ConnectionObjectSetProductionInhibitTime(connection_object, 256);

  size_t header_length = g_kForwardOpenHeaderLength;
  if(connection_object->is_large_forward_open) {
    header_length = g_kLargeForwardOpenHeaderLength;
  }

  if( (header_length + remaining_path * 2) <
      message_router_request->request_data_size ) {
    /* the received packet is larger than the data in the path */
    *extended_error = 0;
    return kCipErrorTooMuchData;
  }

  if( (header_length + remaining_path * 2) >
      message_router_request->request_data_size ) {
    /*there is not enough data in received packet */
    *extended_error = 0;
    OPENER_TRACE_INFO("Message not long enough for path\n");
    return kCipErrorNotEnoughData;
  }


    /* first look if there is an electronic key */
    if(kSegmentTypeLogicalSegment == GetPathSegmentType(message) ) {

    ...

  }

  OPENER_TRACE_INFO("Resulting PIT value: %u\n",
                    connection_object->production_inhibit_time);
  /*save back the current position in the stream allowing followers to parse anything thats still there*/
  message_router_request->data = message;
  return kEipStatusOk;
}

...

A successful result here allows for execution to make it to the GetConnectionManagementEntry function as shown in the snippet from cipconnectionmanager.c:HandleNonNullNonMatchingForwardOpenRequest.

  EipUint32 temp = ParseConnectionPath(&g_dummy_connection_object,
                                       message_router_request,
                                       &connection_status);
  if(kEipStatusOk != temp) {
    return AssembleForwardOpenResponse(&g_dummy_connection_object,
                                       message_router_response,
                                       temp,
                                       connection_status);
  }

/*parsing is now finished all data is available and check now establish the connection */
  ConnectionManagementHandling *connection_management_entry =
    GetConnectionManagementEntry( /* Gets correct open connection function for the targeted object */
       .configuration_path.class_id);

The application then attempts to retrieve the connection object associated with the current class id, as shown in cipconnectionmanager.c:GetConnectionManagementEntry.

ConnectionManagementHandling *
GetConnectionManagementEntry(const EipUint32 class_id) {
  ConnectionManagementHandling *connection_management_entry = NULL;
  for(size_t i = 0; i < g_kNumberOfConnectableObjects; ++i) {
    if(class_id == g_connection_management_list[i].class_id) {
      connection_management_entry = &(g_connection_management_list[i]);
      break;
    }
  }
  return connection_management_entry;
}

Prior initialization of g_connection_management_list creates a buffer of all null bytes, which would get overwritten with valid connection management entries during normal operation. This would cause an entry to inherently exist for class_id 0x00 when no valid connection management entries are provided. This can be seen in the following snippet from cipconnectionmanager.c:InitializeConnectionManagerData.

void InitializeConnectionManagerData() {
  memset(g_connection_management_list,
         0,
         g_kNumberOfConnectableObjects * sizeof(ConnectionManagementHandling) );
  InitializeClass3ConnectionData();
  InitializeIoConnectionData();
}

Since a connection_management_entry gets set, execution can continue to try to make the connection in cipconnectionmanager.c:HandleNonNullNonMatchingForwardOpenRequest. Since the connection_management_entry points to null bytes, however, the attempt to call open_connection_function ends up creating a jump to address 0x00, causing the server to crash.

  /*parsing is now finished all data is available and check now establish the connection */
  ConnectionManagementHandling *connection_management_entry =
    GetConnectionManagementEntry( /* Gets correct open connection function for the targeted object */
      g_dummy_connection_object.configuration_path.class_id);
  if(NULL != connection_management_entry) {
    temp = connection_management_entry->open_connection_function(
      &g_dummy_connection_object,
      &connection_status);
  } else {
    temp = kEipStatusError;
    connection_status =
      kConnectionManagerExtendedStatusCodeInconsistentApplicationPathCombo;
  }

Crash Information

Using host libthread_db library "/lib/aarch64-linux-gnu/libthread_db.so.1".                                                                                                           
                                                                                                                                                                                      
Program received signal SIGSEGV, Segmentation fault.                                                                                                                                  
0x0000000000000000 in ?? ()                                                                                                                                                           
(gdb) i r                                                                                                                                                                             
x0             0xaaaaaaad9860      187649984665696                                                                                                                                    
x1             0xffffffffe47a      281474976703610                                                                                                                                    
x2             0x0                 0                                                                                                                                                  
x3             0x0                 0                                                                                                                                                  
x4             0xaaaaaaab2d38      187649984507192                                                                                                                                    
x5             0xfffff7f80094      281474841968788                                                                                                                                    
x6             0xfffff7f80100      281474841968896                                                                                                                                    
x7             0xaeefacbc          2934942908                                                                                                                                         
x8             0xcd                205                                                                                                                                                
x9             0x164be38           23379512                                                                                                                                           
x10            0x18                24
x11            0x357ebf3f5065e6    15057533631751654
x12            0x36b8ff66b93       3760511675283
x13            0x7fffffff          2147483647 
x14            0x7365722f6374652f  8315177834867090735
x15            0x666e6f632e766c6f  7380959311078780015
x16            0x1                 1
x17            0xfffff7e49738      281474840696632
x18            0x0                 0
x19            0xaaaaaaac0150      187649984561488
x20            0x0                 0
x21            0xaaaaaaaa9e20      187649984470560
x22            0x0                 0
x23            0x0                 0
x24            0x0                 0
x25            0x0                 0
x26            0x0                 0
x27            0x0                 0
x28            0x0                 0
x29            0xffffffffe440      281474976703552
x30            0xaaaaaaab2e28      187649984507432
sp             0xffffffffe440      0xffffffffe440
pc             0x0                 0x0
cpsr           0x20001000          [ EL=0 SSBS C ]
fpsr           0x0                 0
fpcr           0x0                 0
pauth_dmask    0x7f000000000000    35747322042253312
pauth_cmask    0x7f000000000000    35747322042253312
(gdb)
(gdb)
(gdb) bt
#0  0x0000000000000000 in ?? ()
#1  0x0000aaaaaaab2e28 in HandleNonNullNonMatchingForwardOpenRequest ()
#2  0x0000aaaaaaab3128 in ForwardOpenRoutine ()
#3  0x0000aaaaaaab2f08 in LargeForwardOpen ()
#4  0x0000aaaaaaaae304 in NotifyClass ()
#5  0x0000aaaaaaab7c28 in NotifyMessageRouter ()
#6  0x0000aaaaaaabca3c in NotifyCommonPacketFormat ()
#7  0x0000aaaaaaabe91c in HandleReceivedSendRequestResponseDataCommand ()
#8  0x0000aaaaaaabdd14 in HandleReceivedExplictTcpData ()
#9  0x0000aaaaaaaab778 in HandleDataOnTcpSocket ()
#10 0x0000aaaaaaaaae6c in NetworkHandlerProcessCyclic ()
#11 0x0000aaaaaaaaa230 in executeEventLoop ()
#12 0x0000aaaaaaaaa160 in main ()
(gdb)
(gdb)
(gdb) x/i 0x0000aaaaaaab2e28-4
   0xaaaaaaab2e24 <HandleNonNullNonMatchingForwardOpenRequest+236>:     blr     x2
(gdb) 

Mitigation

Add a check in cipconnectionmanager.c:ParseConnectionPath to verify that the value of remaining_path is non-null. Additionally add a check in cipconnectionmanager.c:HandleNonNullNonMatchingForwardOpenRequest to verify that connection_management_entry->open_connection_function contains a valid pointer before executing.

TIMELINE

2022-12-06 - Vendor Disclosure
2022-12-14 - Vendor Patch Release
2023-02-23 - Public Release

Credit

Discovered by Jared Rittle of Cisco Talos.