Skip to content

OpENer DecodePaddedEPath EPATH path_size OOB read (unauthenticated ENIP) — OpENer @ 36943e6 #606

Description

@entropy1337

Summary
OpENer decodes a CIP EPATH (electronic path) by reading a path_size count byte from the request and then walking that many path words in DecodePaddedEPath — without a remaining-buffer-length argument or bound. A request whose EPATH path_size is larger than the actual data (e.g. path_size = 0xFF with a couple of non-terminating segment bytes) walks the segment loop off the end of the fixed incoming_message receive buffer (~512 bytes), reading adjacent memory. Reachable unauthenticated over ENIP (TCP/44818) via RegisterSession → SendRRData → MessageRouter request. Confirmed via a faithful ASan model: heap-buffer-overflow READ past the buffer end.

  1. Affected Version & Scope
    Repository / product: OpENer — https://github.com/EIPStackGroup/OpENer
    Pinned commit / firmware build: 36943e6 (HEAD at analysis)
    Affected range (if known): commit 36943e6 and earlier; present at HEAD.
    Build/config preconditions: default build; the EPATH decode path is on the standard MessageRouter request-handling flow.

Discovery of the bug
Manual source-to-sink trace of the EPATH decoder. The tell: number_of_decoded_elements = message (:1397, the wire path_size) drives while (number_of_decoded_elements < path_size) (:1404), and the function signature takes only the message pointer — no remaining-length argument. Each iteration consumes 2+ bytes ((message+1), message += 2) with no end check, so path_size = 0xFF plus non-terminating logical-segment bytes (0x20) keeps the loop consuming past the buffer. A faithful ASan model (a 300-byte redzoned buffer modelling the tail of incoming_message; buf[0] = 255; segment bytes 0x20 driving the walk) — a full OpENer build was avoided — reproduces heap-buffer-overflow READ of size 1 past the region.

Technical Details & Root Cause
source/src/cip/cipcommon.c, DecodePaddedEPath:

number_of_decoded_elements = message; / :1397 path_size, one wire byte, 0..255 words — attacker-controlled /
++message;
while (number_of_decoded_elements < path_size) { /
:1404 no check vs the received buffer end */
if ((message & kSegmentTypeReserved) == kSegmentTypeReserved) { ... } / :1405 /
/
e.g. class/instance/attribute segments: read *(message+1), message += 2 */
}

Reproduction — numbered steps
clang -fsanitize=address -g -O1 poc/opener_epath_oob_read_repro.c -o repro_OPENER-01.
./repro_OPENER-01.
Observed: AddressSanitizer: heap-buffer-overflow READ of size 1 — the EPATH walk driven by path_size=255 runs off the 300-byte buffer end.
On-wire: an unauthenticated ENIP client (TCP/44818) sends RegisterSession, then a SendRRData whose CPF data item is a MessageRouter request with an EPATH of path_size=0xFF and no matching data; poc/gen_opener_epath_oob.py prints both packets.

Sanitizer / crash evidence
OPENER-01-asan.txt:

[opener] path_size=255 drives the walk with no buffer-end check (buf=300B)

==319685==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61200000016c ...
READ of size 1 at 0x61200000016c thread T0
#0 main .../poc/opener_epath_oob_read_repro.c:24:17

0x61200000016c is located 0 bytes to the right of 300-byte region [0x612000000040,0x61200000016c)
allocated by thread T0 here:
#0 __interceptor_malloc
#1 main .../poc/opener_epath_oob_read_repro.c:14:20
SUMMARY: AddressSanitizer: heap-buffer-overflow .../opener_epath_oob_read_repro.c:24:17 in main
==319685==ABORTING

Note: if you could provide the CVE once confirmed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions