CVE-2022-43606
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.
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
OpENer - https://github.com/EIPStackGroup/OpENer
7.5 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE-824 - Access of Uninitialized Pointer
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;
}
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)
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.
2022-12-06 - Vendor Disclosure
2022-12-14 - Vendor Patch Release
2023-02-23 - Public Release
Discovered by Jared Rittle of Cisco Talos.