EIPStackGroup / EIPStackGroup/OpENer
Integer Underflow Leading to Stack Buffer Overflow in CIP Connected Message Path
- Dominant language
- C
- Stars
- 857
- Forks
- 314
- Avg merge
- 18d 2h
- Merged PRs (30d)
- 1
Description
## Vulnerability Description
OpENer 2.3.0 (master branch up to commit 76b95cf) is vulnerable to a severe memory corruption issue caused by an integer underflow in the processing of connected explicit messages (`SendUnitData`).
Although a previously reported vulnerability identified an out-of-bounds read in `DecodePaddedEPath` via Unconnected messages (`SendRRData`), I have discovered a distinct **Integer Underflow** vulnerability in the Connected message path. When parsing a connected data item, the length is blindly decremented by 2 (`length - 2`) without checking if the length is at least 2. An attacker can intentionally provide a length of 1, resulting in an integer underflow that produces a negative length.
This negative length bypasses downstream size validations due to implicit unsigned conversions, allowing an attacker to supply a forged `path_size` that causes `DecodePaddedEPath` to read wildly out of bounds, resulting in a Stack Buffer Overflow.
## Root Cause
The root cause is an unsafe subtraction operation without bounds checking, which leads to an integer underflow.
1. **Integer Underflow**: In `source/src/enet_encap/cpf.c`, `NotifyConnectedCommonPacketFormat` processes a connected data item and calls `NotifyMessageRouter`:
```c
return_value = NotifyMessageRouter(buffer,
g_common_packet_format_data_item.data_item.length - 2, // <--- Underflow here if length < 2
&message_router_response, ...);
```
If the attacker sends a `SendUnitData` payload where `length = 1`, `1 - 2` results in `-1`.
2. **Propagating the Negative Length**: The `-1` is passed down to `CreateMessageRouterRequestStructure` in `source/src/cip/cipmessagerouter.c` as `data_length`, where it is further decremented:
```c
data_length--; // -1 becomes -2
```
3. **Bypassed Bounds Check**: After `DecodePaddedEPath` returns, there is a check:
```c
if(number_of_decoded_bytes > data_length)
```
Since `number_of_decoded_bytes` is a `size_t` (unsigned), the negative `data_length` (-2) is implicitly cast to a massive unsigned value (e.g., `4294967294`), permanently bypassing the check.
4. **Triggering OOB Read**: Because the size check is nullified, an attacker can append a forged, extremely large `path_size` to the payload. `DecodePaddedEPath` will trust this size and continuously read from the stack, overrunning the `incoming_message` buffer.
## Trigger Conditions
1. The target device is running OpENer and listening on the default ENIP TCP port (44818).
2. The attacker establishes a standard TCP connection and registers a session (`RegisterSession`).
3. The attacker establishes a CIP connection via `ForwardOpen` to obtain a valid Connection ID.
4. The attacker sends a `SendUnitData` request using the obtained Connection ID, deliberately setting `ConnectedDataItem.length = 1` to trigger the integer underflow, and appending a forged large `path_size` to force an immediate ASan crash.
## Reproduction (Validated)
**1) Build:**
Compile the OpENer project on a POSIX environment (e.g., ubuntu 20.04). Enable `OpENer_TRACES` and ASan to observe the crash.
Edit `bin/posix/setup_posix.sh` to ensure the following flags are set:
```bash
-DOpENer_TRACES:BOOL=ON \
-DOpENer_TRACE_LEVEL_ERROR:BOOL=ON \
-DCMAKE_C_FLAGS:STRING="-fsanitize=address -fno-omit-frame-pointer -DOPENER_TCPIP_IFACE_CFG_SETTABLE=1" \
```
Then build the project:
```bash
cd bin/posix
./setup_posix.sh
make
```
**2) Run target:**
Start the OpENer service, listening on the loopback interface:
```bash
./src/ports/POSIX/OpENer lo
```
**3) Send attack traffic:**
[PoC.zip](https://github.com/user-attachments/files/27699806/PoC.zip)
In a new terminal, execute the provided Python PoC script to establish a connection, send the `ForwardOpen`, and then fire the underflow payload:
```bash
python3 PoC.py
```
**4) Observe Phenomena:**
The OpENer server will immediately crash with a massive ASan `stack-buffer-overflow` trace originating from `DecodePaddedEPath`.
**Server Log Output:**
```text
networkhandler: opened new TCP connection on fd 21
/home/user/OpENer/source/src/enet_encap/endianconv.c:101:76: runtime error: left shift of 128 by 24 places cannot be represented in type 'int'
something is wrong with the length in Message Router @ CreateCommonPacketFormatStructure
=================================================================
==75107==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffd53493c90 at pc 0x561e52df66ae bp 0x7ffd53492fe0 sp 0x7ffd53492fd0
READ of size 1 at 0x7ffd53493c90 thread T0
#0 0x561e52df66ad in DecodePaddedEPath /home/user/OpENer/source/src/cip/cipcommon.c:1403
#1 0x561e52e14bbe in CreateMessageRouterRequestStructure /home/user/OpENer/source/src/cip/cipmessagerouter.c:254
#2 0x561e52e1417c in NotifyMessageRouter /home/user/OpENer/source/src/cip/cipmessagerouter.c:188
#3 0x561e52e27142 in NotifyConnectedCommonPacketFormat /home/user/OpENer/source/src/enet_encap/cpf.c:173
#4 0x561e52e2e555 in HandleReceivedSendUnitDataCommand /home/user/OpENer/source/src/enet_encap/encap.c:526
#5 0x561e52e2b865 in HandleReceivedExplictTcpData /home/user/OpENer/source/src/enet_encap/encap.c:191
#6 0x561e52de2be6 in HandleDataOnTcpSocket /home/user/OpENer/source/src/ports/generic_networkhandler.c:864
#7 0x561e52de0d8a in NetworkHandlerProcessCyclic /home/user/OpENer/source/src/ports/generic_networkhandler.c:497
#8 0x561e52dde513 in executeEventLoop /home/user/OpENer/source/src/ports/POSIX/main.c:261
#9 0x561e52dde3bd in main /home/user/OpENer/source/src/ports/POSIX/main.c:229
#10 0x7febaef02082 in __libc_start_main ../csu/libc-start.c:308
#11 0x561e52dddbed in _start (/home/user/OpENer/bin/posix/src/ports/POSIX/OpENer+0x89bed)
Address 0x7ffd53493c90 is located in stack of thread T0 at offset 1312 in frame
#0 0x561e52de227b in HandleDataOnTcpSocket /home/user/OpENer/source/src/ports/generic_networkhandler.c:720
This frame has 6 object(s):
[48, 52) 'remaining_bytes' (line 722)
[64, 68) 'fromlen' (line 852)
[80, 88) 'read_buffer' (line 760)
[112, 128) 'sender_address' (line 850)
[144, 672) 'outgoing_message' (line 862)
[800, 1312) 'incoming_message' (line 732) <== Memory access at offset 1312 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/user/OpENer/source/src/cip/cipcommon.c:1403 in DecodePaddedEPath
Shadow bytes around the buggy address:
0x10002a68a740: 00 00 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2
0x10002a68a750: f2 f2 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10002a68a760: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10002a68a770: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10002a68a780: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x10002a68a790: 00 00[f3]f3 f3 f3 f3 f3 f3 f3 00 00 00 00 00 00
0x10002a68a7a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10002a68a7b0: 00 00 00 00 00 00 00 00 f1 f1 f1 f1 06 f3 f3 f3
0x10002a68a7c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10002a68a7d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x10002a68a7e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
Shadow gap: cc
==75107==ABORTING
```
## Impact Assessment
This is a High severity memory corruption vulnerability. Because OpENer processes connected network packets without validating the length before performing a subtraction, an unauthenticated attacker can remotely trigger an integer underflow. This flaw completely bypasses downstream length validations and leads to a stack buffer overflow. While this PoC demonstrates a reliable DoS by crashing the service via an out-of-bounds read, integer underflows leading to broken length checks can potentially be leveraged to corrupt memory in more subtle ways, posing a significant risk to Industrial Control Systems (ICS) relying on this stack.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.