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