EIPStackGroup / EIPStackGroup/OpENer
OpENer DecodePaddedEPath EPATH path_size OOB read (unauthenticated ENIP) — OpENer @ 36943e6
- Dominant language
- C
- Stars
- 857
- Forks
- 314
- Avg merge
- 18d 2h
- Merged PRs (30d)
- 1
Description
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.
2. 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.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in source/src/cip/cipcommon.c at DecodePaddedEPath and trace its MessageRouter callers to determine the received-buffer length available at each call. Build and run poc/opener_epath_oob_read_repro.c with AddressSanitizer, then exercise the generated ENIP packets; done means a short malformed EPATH is rejected without an out-of-bounds read and the sanitizer reproduction no longer reports an overflow.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- c
- Domain
- networking, security
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100