CVE-2024-39590,CVE-2024-39589
Multiple invalid pointer dereference vulnerabilities exist in the OpenPLC Runtime EtherNet/IP parser functionality of OpenPLC_v3 16bf8bac1a36d95b73e7b8722d0edb8b9c5bb56a. A specially crafted EtherNet/IP request can lead to denial of service. An attacker can send a series of EtherNet/IP requests to trigger these vulnerabilities.
The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.
OpenPLC _v3 16bf8bac1a36d95b73e7b8722d0edb8b9c5bb56a
OpenPLC_v3 - https://github.com/thiagoralves/OpenPLC_v3
7.5 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H
CWE-704 - Incorrect Type Conversion or Cast
OpenPLC is an open-source programmable logic controller (PLC) designed to provide a low cost option for automation. The platform consists of two parts: the Runtime and the Editor. The Runtime can be deployed on a variety of platforms including Windows, Linux, and various microcontrollers. Common uses for OpenPLC include home automation and industrial security research. OpenPLC supports communication across a variety of protocols, including Modbus and EtherNet/IP. The runtime additionally provides limited support for PCCC transported across EtherNet/IP.
When a valid PCCC Protected Logical Read
or Protected Logical Write
request is sent to OpenPLC, the request data is processed in preparation to create a response. During the course of this processing a few fields of the response buffer are filled with values from the request buffer. Three fields in particular (header.RP_CMD_Code
, header.HD_Status
, and header.HD_TransactionNum
) are populated through use of a memmove
call to extract bytes out of the request pccc_header
structure. Within these memmove
calls the pointer provided as the src
argument is cast to the type unsigned int
, as shown below:
memmove(&buffer[0], (unsigned int)header.RP_CMD_Code, 1);
memmove(&buffer[1], (unsigned int)header.HD_Status, 1);
memmove(&buffer[2], (unsigned int)header.HD_TransactionNum, 2);
As an unsigned int
is typically defined as 32 bits in size this causes the pointer address to get truncated on systems with addresses greater than 32 bits.
When this occurs in a PCCC Protected Logical Read
request using function code 0xA2 the response buffer is prepared in the function Protected_Logical_Read_Reply
. The snippet below shows the header.RP_CMD_CODE
buffer address being loaded into $x0
([0]
) , truncated to a 32-bit value ([1]
), and finally the truncated address being dereferenced ([2]
).
(gdb) disass
Dump of assembler code for function _Z28Protected_Logical_Read_Reply11pccc_headerPhi:
...
0x0000aaaad58a1678 <+336>: ldr x0, [x19, #72] [0]
0x0000aaaad58a167c <+340>: mov w0, w0 [1]
0x0000aaaad58a1680 <+344>: ldrb w1, [x0] [2]
...
(gdb)
When the truncated address does not exist, and is subsequently dereferenced, a SIGSEV
is raised causing the OpenPLC server to crash.
(gdb) x/i $pc
=> 0xaaaad6ab167c <_Z28Protected_Logical_Read_Reply11pccc_headerPhi+340>: mov w0, w0
(gdb) i r
x0 0xffff9b31b598 281473285469592
x1 0xaaaad6b210e8 187650723156200
...
sp 0xffff9b31ac20 0xffff9b31ac20
pc 0xaaaad6ab167c 0xaaaad6ab167c <Protected_Logical_Read_Reply(pccc_header, unsigned char*, int)+340>
...
(gdb) stepi
(gdb) x/i $pc
=> 0xaaaad6ab1680 <_Z28Protected_Logical_Read_Reply11pccc_headerPhi+344>: ldrb w1, [x0]
(gdb) i r
x0 0x9b31b598 2603726232
x1 0xaaaad6b210e8 187650723156200
...
sp 0xffff9b31ac20 0xffff9b31ac20
pc 0xaaaad6ab1680 0xaaaad6ab1680 <Protected_Logical_Read_Reply(pccc_header, unsigned char*, int)+344>
...
(gdb) stepi
Thread 20 "openplc" received signal SIGSEGV, Segmentation fault.
0x0000aaaad6ab1680 in Protected_Logical_Read_Reply(pccc_header, unsigned char*, int) ()
(gdb) bt
#0 0x0000aaaad6ab1680 in Protected_Logical_Read_Reply(pccc_header, unsigned char*, int) ()
#1 0x0000aaaad6ab1450 in Command_Protocol(pccc_header, unsigned char*, int) ()
#2 0x0000aaaad6ab1390 in ParsePCCCData(unsigned char*, int) ()
#3 0x0000aaaad6ab12c8 in processPCCCMessage(unsigned char*, int) ()
#4 0x0000aaaad6a9f9f4 in sendRRData(int, enip_header*, enip_data_Unknown*, enip_data_Unconnected*, enip_data_Connected*) ()
#5 0x0000aaaad6aa000c in processEnipMessage(unsigned char*, int) ()
#6 0x0000aaaad6ab32f0 in processMessage(unsigned char*, int, int, int) ()
#7 0x0000aaaad6ab3450 in handleConnections(void*) ()
#8 0x0000ffff9cbcd5c8 in start_thread (arg=0x0) at ./nptl/pthread_create.c:442
#9 0x0000ffff9cc35edc in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:79
(gdb) i r
x0 0x9b31b598 2603726232
x1 0xaaaad6b210e8 187650723156200
x2 0x0 0
x3 0x1 1
x4 0xaaaad6b210f8 187650723156216
x5 0x17f 383
x6 0x203a726576726553 2322294337967383891
x7 0x6320646165726854 7142819378486208596
x8 0x3f 63
x9 0x6e65696c6320726f 7954880231060304495
x10 0x6320646165726854 7142819378486208596
x11 0x6620646574616572 7358992178030404978
x12 0x6e65696c6320726f 7954880231060304495
x13 0xa36203a44492074 735811023747489908
x14 0x6461657268742067 7233174018586845287
x15 0x65687420726f6620 7307218078116308512
x16 0x1 1
x17 0xffff9cbd21b0 281473311383984
x18 0x0 0
x19 0xffff9b31b080 281473285468288
x20 0xffff9b31f4fc 281473285485820
x21 0xffff9bb2e2be 281473293935294
x22 0x80e920 8448288
x23 0xffff9bb2e2bf 281473293935295
x24 0x0 0
x25 0xffff9ab10000 281473277034496
x26 0x80e920 8448288
x27 0xffff9bb2f0e0 281473293938912
x28 0xffff9ab10000 281473277034496
x29 0xffff9b31ac20 281473285467168
x30 0xaaaad6ab15b8 187650722698680
sp 0xffff9b31ac20 0xffff9b31ac20
pc 0xaaaad6ab1680 0xaaaad6ab1680 <Protected_Logical_Read_Reply(pccc_header, unsigned char*, int)+344>
cpsr 0x60201000 [ EL=0 BTYPE=0 SSBS SS C Z ]
fpsr 0x3 [ IOC DZC ]
fpcr 0x0 [ RMode=0 ]
pauth_dmask 0x7f000000000000 35747322042253312
pauth_cmask 0x7f000000000000 35747322042253312
(gdb)
Update your version of OpenPLC one that has this issue patched.
If that is not possible, modify the source code to remove the three pointer casts in the Protected_Logical_Read_Reply
memmove
calls, similar to the following:
memmove(&buffer[0], header.RP_CMD_Code, 1);
memmove(&buffer[1], header.HD_Status, 1);
memmove(&buffer[2], header.HD_TransactionNum, 2);
When this occurs in a PCCC Protected Logical Write
request using function code 0xAA or 0xAB the response buffer is prepared in the function Protected_Logical_Write_Reply
. The snippet below shows the header.RP_CMD_CODE
buffer address being loaded into $x0
([0]
) , truncated to a 32-bit value ([1]
), and finally the truncated address being dereferenced ([2]
).
(gdb) disass
Dump of assembler code for function _Z29Protected_Logical_Write_Reply11pccc_headerPhi:
...
0x0000aaaae961172c <+48>: ldr x0, [x19, #72] [0]
0x0000aaaae9611730 <+52>: mov w0, w0 [1]
0x0000aaaae9611734 <+56>: ldrb w1, [x0] [2]
...
(gdb)
When the truncated address does not exist, and is subsequently dereferenced, a SIGSEV
is raised causing the OpenPLC server to crash.
(gdb) x/i $pc
=> 0xaaaae9611730 <_Z29Protected_Logical_Write_Reply11pccc_headerPhi+52>: mov w0, w0
(gdb) i r
x0 0xffff9eb1b598 281473344189848
x1 0xffff9eb1c11d 281473344192797
...
sp 0xffff9eb1b010 0xffff9eb1b010
pc 0xaaaae9611730 0xaaaae9611730 <Protected_Logical_Write_Reply(pccc_header, unsigned char*, int)+52>
...
(gdb) stepi
(gdb) x/i $pc
=> 0xaaaae9611734 <_Z29Protected_Logical_Write_Reply11pccc_headerPhi+56>: ldrb w1, [x0]
(gdb) i r
x0 0x9eb1b598 2662446488
x1 0xffff9eb1c11d 281473344192797
...
sp 0xffff9eb1b010 0xffff9eb1b010
pc 0xaaaae9611734 0xaaaae9611734 <Protected_Logical_Write_Reply(pccc_header, unsigned char*, int)+56>
...
(gdb) stepi
Thread 7 "openplc" received signal SIGSEGV, Segmentation fault.
0x0000aaaae9611734 in Protected_Logical_Write_Reply(pccc_header, unsigned char*, int) ()
(gdb) bt
#0 0x0000aaaae9611734 in Protected_Logical_Write_Reply(pccc_header, unsigned char*, int) ()
#1 0x0000aaaae96114c0 in Command_Protocol(pccc_header, unsigned char*, int) ()
#2 0x0000aaaae9611390 in ParsePCCCData(unsigned char*, int) ()
#3 0x0000aaaae96112c8 in processPCCCMessage(unsigned char*, int) ()
#4 0x0000aaaae95ff9f4 in sendRRData(int, enip_header*, enip_data_Unknown*, enip_data_Unconnected*, enip_data_Connected*) ()
#5 0x0000aaaae960000c in processEnipMessage(unsigned char*, int) ()
#6 0x0000aaaae96132f0 in processMessage(unsigned char*, int, int, int) ()
#7 0x0000aaaae9613450 in handleConnections(void*) ()
#8 0x0000ffff9f3ad5c8 in start_thread (arg=0x0) at ./nptl/pthread_create.c:442
#9 0x0000ffff9f415edc in thread_start () at ../sysdeps/unix/sysv/linux/aarch64/clone.S:79
(gdb) i r
x0 0x9eb1b598 2662446488
x1 0xffff9eb1c11d 281473344192797
x2 0xbee2 48866
x3 0xffff9eb1b758 281473344190296
x4 0xffff9eb1b7c8 281473344190408
x5 0x4 4
x6 0x203a726576726553 2322294337967383891
x7 0x6320646165726854 7142819378486208596
x8 0x3f 63
x9 0x6e65696c6320726f 7954880231060304495
x10 0x6320646165726854 7142819378486208596
x11 0x6620646574616572 7358992178030404978
x12 0x6e65696c6320726f 7954880231060304495
x13 0xa34203a44492074 735248073794068596
x14 0x6461657268742067 7233174018586845287
x15 0x65687420726f6620 7307218078116308512
x16 0xaaaae963fbb8 187651036806072
x17 0xffff9f407d10 281473353547024
x18 0x0 0
x19 0xffff9eb1b080 281473344188544
x20 0xffff9eb1f4fc 281473344206076
x21 0xffff9e30e2be 281473335747262
x22 0x80e920 8448288
x23 0xffff9e30e2bf 281473335747263
x24 0x0 0
x25 0xffff9e310000 281473335754752
x26 0x80e920 8448288
x27 0xffff9e30f0e0 281473335750880
x28 0xffff9e310000 281473335754752
x29 0xffff9eb1b010 281473344188432
x30 0xaaaae96114c0 187651036615872
sp 0xffff9eb1b010 0xffff9eb1b010
pc 0xaaaae9611734 0xaaaae9611734 <Protected_Logical_Write_Reply(pccc_header, unsigned char*, int)+56>
cpsr 0x60201000 [ EL=0 BTYPE=0 SSBS SS C Z ]
fpsr 0x0 [ ]
fpcr 0x0 [ RMode=0 ]
pauth_dmask 0x7f000000000000 35747322042253312
pauth_cmask 0x7f000000000000 35747322042253312
(gdb)
Update your version of OpenPLC one that has this issue patched.
If that is not possible, modify the source code to remove the three pointer casts in the Protected_Logical_Write_Reply
memmove
calls, similar to the following:
memmove(&buffer[0], header.RP_CMD_Code, 1);
memmove(&buffer[1], header.HD_Status, 1);
memmove(&buffer[2], header.HD_TransactionNum, 2);
2024-07-15 - Vendor Disclosure
2024-09-17 - Vendor Patch Release
2024-09-18 - Public Release
Discovered by Jared Rittle of Cisco Talos.