Submit #858228: Davide Nardella Snap7 1.4.3 Heap-Based Intra-Object Out-of-Bounds Writeinfo

TitelDavide Nardella Snap7 1.4.3 Heap-Based Intra-Object Out-of-Bounds Write
BeschreibungA vulnerability has been identified in the Snap7 client implementation, affecting the official GitHub repository in the code line associated with version 1.4.3 and still reachable in commit 30f37da3114024a71ba93f7fd855c680b97a406f. The issue is not caused by the S7 protocol specification itself. Instead, it is caused by insufficient validation in the Snap7 client when it processes the negotiated PDU length returned by a remote PLC or any attacker-controlled server speaking the expected protocol sequence. Under a realistic attack scenario, a malicious PLC, a mock server, or an active man-in-the-middle capable of altering the negotiation response can return an oversized PDULength value. The Snap7 client accepts this value and later reuses it during local request construction, eventually causing an out-of-bounds write in heap memory. The vulnerable behavior is located in the client-side WriteArea request path. During connection establishment, the client negotiates the PDU size with the remote endpoint. In src/core/s7_peer.cpp, the function TSnap7Peer::NegotiatePDULength() parses the negotiation response and directly stores the returned PDULength in the client state. That value is not constrained against the actual size of the client’s internal request payload buffer. Later, in src/core/s7_micro_client.cpp, TSnap7MicroClient::opWriteArea() calculates the maximum number of writable elements from the negotiated PDULength and uses that attacker-influenced result to determine how many bytes can be copied into the outgoing request PDU. The final memory corruption occurs at the memcpy() sink inside opWriteArea(), where the implementation writes a very large attacker-influenced amount of data into a fixed-size local payload buffer. The core design mismatch is straightforward. Snap7 internally uses a fixed ISO payload buffer size of 4096 bytes, as defined by IsoPayload_Size in src/core/s7_isotcp.h. However, the negotiated PDULength accepted from the network can be much larger. In the reproduced case, the malicious server returned a negotiated PDU length of 65535. This value was accepted by the client and later used as a trusted size boundary in the write path. When opWriteArea() prepared the request, it computed RHSize as the size of the request header and parameter area, then calculated MaxElements = (PDULength - RHSize) / WordSize. Because WordSize was 1 in the reproduced byte-write scenario, the attacker-influenced PDULength of 65535 yielded a MaxElements value of 65507. The client was then willing to process a WriteArea operation with 65000 bytes in a single local request assembly step. This is where the actual memory corruption occurs. In the vulnerable path, the code derives ReqData and then Target from the internal PDU layout. The data pointer starts inside the request payload area, but the destination remains inside a fixed internal payload object whose total capacity is 4096 bytes. Despite that, the code executes memcpy(Target, Source, Size) with Size equal to 65000. As a result, the write begins at an offset inside PDU.Payload and continues far beyond the end of the legitimate payload region. The effective write range extends tens of thousands of bytes past the fixed payload boundary. This is not a harmless logical bug, not a mere oversized-request handling issue, and not a failed transmission. It is a real out-of-bounds write that corrupts the client’s heap memory before the packet exchange completes. The issue is especially important because the overflow does not only run past a standalone buffer into an immediately detected redzone. The corrupted destination is part of a larger heap-allocated Snap7 client object. In practice, the write crosses the internal payload member and continues into subsequent object members and adjacent intra-object regions. In the provided local reproduction, debugger observations showed that data written beyond PDU.Payload continued into later members such as Job, opSize, and opData. AddressSanitizer correspondingly reported the issue as an “intra-object-overflow”, which is fully consistent with the object layout and the write direction. This means the bug is not merely a cosmetic sanitizer artifact. It is an actual heap-based intra-object overflow that damages live client object state. The impact is significant. At a minimum, an attacker controlling the server side of the Snap7 session can cause a denial of service by crashing the client process during a WriteArea operation. Because the overwrite occurs inside a heap-allocated object that stores operational state, the corruption can also damage request metadata, size variables, or other control fields before process termination. Depending on compiler behavior, allocator state, object layout, and surrounding memory usage in the embedding application, the corrupted state may lead to further undefined behavior beyond an immediate crash. Therefore the practical consequence is not limited to loss of availability. It can also compromise the integrity of client-side communication state and destabilize higher-level industrial applications that rely on the Snap7 client library for control or data-writing operations. The attack preconditions are also realistic. The attacker does not need local access to the client system. The vulnerable client only needs to connect to a PLC endpoint that is malicious, impersonated, or protocol-compatible enough to complete the connection and PDU negotiation sequence. After that, the client application must perform a sufficiently large WriteArea operation. In the reproduced case, the client connected successfully, received a malicious negotiation response advertising pdu=65535, and then triggered the overflow when WriteArea was called with a payload length of 65000. The server-side output showed a valid connection, CR/CC exchange, receipt of the negotiation request with req_pdu=480, transmission of a negotiation response with pdu=65535, and then abrupt connection closure after the client corrupted itself. The client-side ASan output showed a successful ConnectTo return, followed by “AddressSanitizer: intra-object-overflow” and “WRITE of size 65000” in TSnap7MicroClient::opWriteArea(). This behavior also demonstrates why the vulnerability should not be dismissed as API misuse. The vulnerable library code is explicitly responsible for splitting write operations according to the negotiated PDU capacity. The caller does not directly write outside the internal payload buffer. Rather, the library miscomputes what it believes to be the safe amount of data per request because it trusts the negotiated PDULength without reconciling that network-derived value with the actual fixed local buffer size. In other words, the bug results from a broken trust boundary inside the library implementation. The network peer controls the negotiation result, but the client code incorrectly promotes that result to a trusted memory-capacity parameter. The affected call chain is straightforward and strengthens confidence in the finding. A user-level write request reaches TS7Client::WriteArea in release/wrappers/c-cpp/snap7.cpp, then Cli_WriteArea in src/lib/snap7_libmain.cpp, then TSnap7MicroClient::WriteArea and TSnap7MicroClient::PerformOperation in src/core/s7_micro_client.cpp, and finally TSnap7MicroClient::opWriteArea where the overflowing memcpy occurs. This is a normal public client path, not a synthetic parser-only harness, not dead code, and not an unreachable internal helper. The vulnerability is therefore located in a live, externally reachable operation of the official client library. From a weakness-classification perspective, the most accurate mapping is CWE-787: Out-of-bounds Write. A more precise descriptive label would be a client-side heap-based intra-object buffer overflow caused by missing upper-bound validation of a negotiated protocol length. The root cause is the combination of two assumptions that do not hold together: first, that a remote peer may be trusted to return a reasonable PDULength; second, that a negotiated PDU length can be used directly as a safe bound for the local request payload buffer. Once those assumptions are combined, the library effectively allows a network-derived field to dictate the size of a local memcpy into a fixed 4096-byte buffer. A robust fix should cap the negotiated PDULength to the local buffer capacity, or to the smaller of the protocol-allowed and locally supported limits, immediately after parsing the negotiation response in TSnap7Peer::NegotiatePDULength(). In addition, the write path in TSnap7MicroClient::opWriteArea() should independently validate that RHSize plus the computed write Size never exceeds the actual capacity of the PDU payload buffer before calling memcpy().
Quelle⚠️ https://github.com/davenardella/snap7/issues/17
Benutzer
 VULL (UID 98817)
Einreichung14.06.2026 16:31 (vor 1 Monat)
Moderieren18.07.2026 14:29 (1 month later)
StatusAkzeptiert
VulDB Eintrag380047 [davenardella snap7 bis 1.4.3 src/core/s7_peer.cpp NegotiatePDULength Pufferüberlauf]
Punkte20

Might our Artificial Intelligence support you?

Check our Alexa App!