Skip to content

Stack Out-of-Bounds Read in Blind EPath Decoding #582

@MrAlaskan

Description

@MrAlaskan

Vulnerability Description

OpENer 2.3.0 (commit 76b95cf) has an out-of-bounds read issue in CIP message parsing when handling malformed explicit requests with a forged EPath size. An attacker can send a valid ENIP SendRRData frame carrying a very short CIP payload whose path_size field claims that many more path words are present than are actually available. Because the parser trusts the attacker-controlled path_size and continues decoding path segments without a remaining-length boundary, it reads beyond the end of the stack receive buffer. This issue is remotely triggerable via network traffic and does not require authentication.

Root Cause

The root cause is the combination of attacker-controlled length trust and post-parse validation in the EPath decoder path:

  1. In DecodePaddedEPath() in source/src/cip/cipcommon.c, the code reads epath->path_size = *message_runner and then enters while(number_of_decoded_elements < epath->path_size) without knowing how many bytes remain in the input buffer.
  2. Inside that loop, the function directly dereferences *message_runner and also reads additional bytes for class / instance / attribute segments, but the function signature does not carry a "remaining readable length" argument to enforce per-read bounds checks.
  3. CreateMessageRouterRequestStructure() in source/src/cip/cipmessagerouter.c calls DecodePaddedEPath() first and only afterwards checks if(number_of_decoded_bytes > data_length). That check can detect an invalid path length only after decoding has already happened, so it cannot prevent the out-of-bounds read during decoding.
  4. When a malformed packet places only service + path_size inside the CIP item and relies on trailing CPF bytes to keep the decoder walking forward, DecodePaddedEPath() eventually advances beyond incoming_message in HandleDataOnTcpSocket(), which ASan reports as a stack-buffer-overflow.

Trigger Conditions

  1. The target runs OpENer (POSIX) and listens on the ENIP TCP port (default 44818).
  2. The attacker can establish a TCP connection and complete RegisterSession.
  3. The attack traffic uses SendRRData carrying a malformed unconnected CIP request with a forged large path_size.
  4. The CIP data item is intentionally truncated so that only the service byte and path_size are inside the nominal request, while trailing CPF bytes are positioned to be misinterpreted as path segments until the decoder reads beyond the 512-byte stack receive buffer.

Reproduction (Validated)

1) Build

Build OpENer in a POSIX environment (e.g. Ubuntu 22.04) with OpENer_TRACES and ASan enabled.

Edit bin/posix/setup_posix.sh to ensure the following flags are set:

  -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:

cd /home/user/OpENer/bin/posix
./setup_posix.sh
make

2) Run target

Start the target process:

./src/ports/POSIX/OpENer lo

3) Send attack traffic

In another terminal, send the PoC request:

PoC.zip

python3 PoC.py

4) Observe behavior

After receiving the malformed request, the target triggers an ASan stack-buffer-overflow. The stack trace points to DecodePaddedEPath -> CreateMessageRouterRequestStructure -> NotifyMessageRouter, and the process exits with ABORTING. Log output:

networkhandler: opened new TCP connection on fd 21
something is wrong with the length in Message Router @ CreateCommonPacketFormatStructure
=================================================================
==32626==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7ffec0669660 at pc 0x55a4bbe5e6ae bp 0x7ffec06689f0 sp 0x7ffec06689e0
READ of size 1 at 0x7ffec0669660 thread T0
    #0 0x55a4bbe5e6ad in DecodePaddedEPath /home/user/OpENer/source/src/cip/cipcommon.c:1403
    #1 0x55a4bbe7cbbe in CreateMessageRouterRequestStructure /home/user/OpENer/source/src/cip/cipmessagerouter.c:254
    #2 0x55a4bbe7c17c in NotifyMessageRouter /home/user/OpENer/source/src/cip/cipmessagerouter.c:188
    #3 0x55a4bbe8df8f in NotifyCommonPacketFormat /home/user/OpENer/source/src/enet_encap/cpf.c:60
    #4 0x55a4bbe96886 in HandleReceivedSendRequestResponseDataCommand /home/user/OpENer/source/src/enet_encap/encap.c:558
    #5 0x55a4bbe93840 in HandleReceivedExplictTcpData /home/user/OpENer/source/src/enet_encap/encap.c:186
    #6 0x55a4bbe4abe6 in HandleDataOnTcpSocket /home/user/OpENer/source/src/ports/generic_networkhandler.c:864
    #7 0x55a4bbe48d8a in NetworkHandlerProcessCyclic /home/user/OpENer/source/src/ports/generic_networkhandler.c:497
    #8 0x55a4bbe46513 in executeEventLoop /home/user/OpENer/source/src/ports/POSIX/main.c:261
    #9 0x55a4bbe463bd in main /home/user/OpENer/source/src/ports/POSIX/main.c:229
    #10 0x7f3450841082 in __libc_start_main ../csu/libc-start.c:308
    #11 0x55a4bbe45bed in _start (/home/user/OpENer/bin/posix/src/ports/POSIX/OpENer+0x89bed)

Address 0x7ffec0669660 is located in stack of thread T0 at offset 1312 in frame
    #0 0x55a4bbe4a27b 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:
  0x1000580c5270: 00 00 00 00 00 00 00 00 00 00 00 00 f2 f2 f2 f2
  0x1000580c5280: f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 f2 00 00 00 00
  0x1000580c5290: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000580c52a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000580c52b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x1000580c52c0: 00 00 00 00 00 00 00 00 00 00 00 00[f3]f3 f3 f3
  0x1000580c52d0: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000580c52e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000580c52f0: 00 00 f1 f1 f1 f1 06 f3 f3 f3 00 00 00 00 00 00
  0x1000580c5300: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x1000580c5310: 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
==32626==ABORTING

Impact Assessment

This issue is remotely reachable through the normal EtherNet/IP explicit-message path and results in a real out-of-bounds read in server-side packet parsing. The currently validated impact is a reliable process crash / denial of service: a network attacker can send a crafted RegisterSession + SendRRData sequence that drives DecodePaddedEPath() past the end of the stack-backed TCP receive buffer and causes the OpENer process to abort under ASan. Even without sanitizers, the decoder still reads beyond the intended request boundary and interprets adjacent stack bytes as CIP path data, which is undefined behavior and should be treated as a memory-safety bug with remote DoS impact.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions