| Descrição | CIPster contains a remotely reachable out-of-bounds read in symbolic application path parsing on the current master code line tested at commit 632336d414ef708a542377c1aa8d6fdb7c70a760. The issue is present in the server/adapter side request handling path and can be reproduced against the project’s own examples/POSIX sample program, without requiring a nonstandard harness or an artificial unit-level parser entry. A network client can establish a normal EtherNet/IP session and then send a crafted SendRRData packet carrying an unconnected explicit message whose Message Router request path uses a truncated symbolic segment. The request reaches CipAppPath::deserialize_symbolic() and triggers an out-of-bounds read because the implementation trusts the segment-declared symbolic length and performs memcpy from the input buffer without first verifying that the remaining BufReader size is large enough for the requested copy.
The vulnerable logic is in source/src/cip/cipepath.cc inside CipAppPath::deserialize_symbolic(). In the symbolic segment branch, the code derives symbol_size from the first path byte by masking the low bits of the segment header, then directly copies symbol_size bytes from in.data() into a local tag buffer. The copy is done before validating whether symbol_size is less than or equal to the remaining readable bytes in the input stream. As a result, the parser can accept a request in which the outer Message Router path length is formally valid, while the inner segment-specific length is inconsistent with the actual bytes available for that segment. When such a malformed path is supplied, memcpy reads past the end of the valid request data. In the tested configuration, this becomes a stable global-buffer-overflow read because the malformed path is intentionally placed near the end of the fixed TCP receive buffer s_buf in source/src/enet_encap/networkhandler.cc.
This is not a case of direct parser misuse. The bug is reachable through the normal protocol stack used by the official sample. The tested path is: HandleDataOnTcpSocket() in source/src/enet_encap/networkhandler.cc, then Encapsulation::HandleReceivedExplicitTcpData() in source/src/enet_encap/encap.cc, then Cpf::NotifyCommonPacketFormat() in source/src/enet_encap/cpf.cc, then CipMessageRouterRequest::DeserializeMRReq() in source/src/cip/cipmessagerouter.cc, then CipAppPath::DeserializeAppPath(), and finally CipAppPath::deserialize_symbolic() in source/src/cip/cipepath.cc. This means the flaw is exposed by real TCP traffic processed by the server’s ordinary EtherNet/IP explicit messaging state machine.
The root cause is a mismatch between outer length validation and inner segment validation. CipMessageRouterRequest::DeserializeMRReq() reads the service code and the path word count, computes byte_count as path_word_count * 2, and verifies only that the total request path byte count does not exceed the remaining request buffer. That outer check is necessary but insufficient. It only proves that the request still contains byte_count bytes in total; it does not prove that each embedded path segment is internally self-consistent. The malformed proof-of-concept uses a path word count of 1, so the total path length is 2 bytes and the outer check passes. However, those 2 bytes are “7f 41”, where 0x7f encodes a symbolic segment whose declared symbolic length is 31, while only one actual symbol byte, 0x41, follows. In other words, the outer request path length is acceptable, but the symbolic segment’s internal length field is false. Because deserialize_symbolic() trusts that internal length, it advances the reader and then copies 31 bytes from a buffer that contains only 1 byte of legitimate segment data.
The reproduction is straightforward with the project sample and a session-aware client. The server is started from the project’s examples/POSIX application using an AddressSanitizer/UndefinedBehaviorSanitizer build. The client first performs RegisterSession normally and receives a valid session handle. It then sends a SendRRData packet with a CPF item list whose final item is an UnconnectedDataItem of length 4 containing the Message Router payload “0e 01 7f 41”. Here 0x0e is GetAttributeSingle, 0x01 is the path word count, 0x7f is the symbolic segment header whose low five bits imply a symbolic length of 31, and 0x41 is the only actual symbol byte provided. Before this final item, the client inserts many SockAddrInfo items. Their purpose is not to create the bug, but to place the final Message Router payload close to the end of s_buf so that the over-read becomes deterministic and visible as a global-buffer-overflow under ASan.
The runtime observations confirm the defect in a source-level consistent manner. In the server-side debug session, the overall TCP read size is 1784 bytes. The encapsulation command is 0x006f, i.e. SendRRData. The encapsulation payload length is 1760 bytes. The CPF item count is 89. During CPF parsing, the last item has type 0x00b2, i.e. UnconnectedDataItem, with length 4 and payload “0e 01 7f 41”. When execution reaches CipMessageRouterRequest::DeserializeMRReq(), the request path byte_count is 2, matching the remaining bytes at that level, so the outer length check succeeds. When execution reaches CipAppPath::deserialize_symbolic(), the first path byte is 0x7f, which yields symbol_size = 31. After consuming the segment header, only one byte remains in the reader. The subsequent memcpy(tag, in.data(), symbol_size) therefore copies 31 bytes starting from a location where only 1 byte belongs to the current symbolic segment. In the captured debug run, the read begins at s_buf + 1783, while s_buf ends at offset 1800, leaving only 17 bytes in the global buffer. Consequently, the copy crosses the right boundary of s_buf and enters the ASan global redzone. The first invalid address is exactly s_buf + 1800.
AddressSanitizer reports a global-buffer-overflow READ of size 31, with the crashing stack pointing to __asan_memcpy, then CipAppPath::deserialize_symbolic() at source/src/cip/cipepath.cc:360, then CipAppPath::DeserializeAppPath(), CipMessageRouterRequest::DeserializeMRReq(), Cpf::NotifyCommonPacketFormat(), Encapsulation::HandleReceivedExplicitTcpData(), HandleDataOnTcpSocket(), NetworkHandlerProcessOnce(), and main() in examples/POSIX/main.cc. The report additionally states that the accessed address is 0 bytes to the right of global variable s_buf defined in source/src/enet_encap/networkhandler.cc with size 1800. On the client side, the proof-of-concept times out waiting for a response because the server aborts first under ASan. This behavior is consistent with a remotely triggerable server-side memory safety violation causing denial of service.
From a security perspective, the most conservative demonstrated impact is unauthenticated remote denial of service against a CIPster-based adapter/server built from the provided example. The proof currently demonstrates an out-of-bounds read that terminates the process under ASan. In non-sanitized builds, the exact observable effect may vary depending on compiler, memory layout, and surrounding data, but the implementation still performs a memory read beyond the valid segment boundary and therefore constitutes a genuine memory safety flaw. The issue should not be dismissed as a mere malformed-request rejection problem because the parser does not reject the malformed request before touching out-of-bounds memory.
A minimal and appropriate fix is to validate the remaining input length before copying symbolic data, and to perform a similar bounds check before consuming any optional pad byte. Specifically, in the symbolic segment branch the code should verify that symbol_size is not greater than in.size() before executing memcpy. If the condition fails, the parser should reject the path and return an error. The same style of check should also be applied in the sibling ANSI Extended Symbol branch, which performs memcpy(tag, in.data(), byte_count) under the same assumption pattern. In addition, after consuming the symbolic data, the code should verify that a pad byte is actually present before incrementing the reader for alignment handling. More generally, the implementation would benefit from treating the symbolic segment’s internal length as untrusted data that must be validated against the remaining bytes of the BufReader, rather than relying solely on the outer Message Router path length.
The vulnerability is reproducible in the official examples program, on the server side, from real network input, and the observed crash site is consistent with the parser logic and the supplied malformed symbolic path. This makes the issue suitable for vulnerability tracking as a real protocol-reachable memory safety flaw in CIPster master at commit 632336d414ef708a542377c1aa8d6fdb7c70a760. |
|---|