| 설명 | A stack-based buffer overflow exists in the server-side ReadVar response handling path of the current upstream Snap7 codebase. The issue is triggered when a Snap7 server processes a single S7 ReadVar request containing multiple items and assembles the reply inside a fixed stack-allocated TS7Answer23 object. The vulnerable logic resides in TS7Worker::PerformFunctionRead() and TS7Worker::ReadArea() in src/core/s7_server.cpp.
The root cause is an accounting mismatch between the remaining-PDU check and the actual serialized response layout written into the stack buffer. In PerformFunctionRead(), the server initializes a stack buffer named Answer of type TS7Answer23 and then places the response parameter block and each per-item result consecutively into that same object by advancing an Offset cursor. For each item, ResData[c] is assigned to PResFunReadItem(pbyte(ResParams) + Offset), and ReadArea() is called to populate the item result. After ReadArea() returns, PerformFunctionRead() advances Offset by the item payload length plus the 4-byte per-item response header. In addition, for every non-final item whose payload size is odd, one extra alignment byte is inserted before the next item. Therefore, the true amount of stack space consumed per item is: data length + 4-byte item header + optional 1-byte padding.
However, ReadArea() does not validate or reserve space according to that full serialized layout. Instead, it computes the data payload size from the request parameters and decreases PDURemainder using only the raw data length Size. In other words, the code verifies only that the sum of the item data payloads remains below the negotiated PDU length. It does not include the mandatory per-item response metadata or alignment padding in that decision. As a result, requests can pass the PDURemainder checks while the actual write cursor within Answer moves beyond the end of the stack buffer.
This bug is not a theoretical length miscalculation in a later send routine. The overflow occurs during response construction itself. Specifically, once ReadArea() has accepted an item, it performs memcpy(&ResItemData->Data, Source, Size). When the current ResItemData pointer has already been pushed too close to the end of Answer by the earlier items, this memcpy becomes an out-of-bounds write into adjacent stack memory. Therefore the vulnerability is a real stack buffer overflow and not merely an oversized response length reported to the network layer.
The issue is reachable remotely through the normal server request path. A standard client can establish an ISO-on-TCP / S7 session, negotiate a large PDU, and then send one crafted ReadVar request containing the maximum supported number of items. In the tested case, the server listens normally, a DB area is registered, the client completes PeerConnect successfully, and the negotiated PDU becomes 4096. The crash is then triggered by a single ReadVar request containing 20 items of 201 bytes each. This is important because the path does not rely on malformed transport framing, authentication bypass, memory corruption in a client wrapper, or local misuse of internal APIs. It is reachable in the intended server protocol implementation.
The dynamic behavior is fully consistent with the source code layout. Snap7 uses IsoPayload_Size = 4096, and TS7Answer23 is a fixed response container built around that size. TResFunReadParams occupies 2 bytes, and each TResFunReadItem contributes a 4-byte result header before the Data field. MaxVars is 20, so a single ReadVar request may contain up to 20 items. If each item requests 201 bytes, the raw payload sum is 20 * 201 = 4020, which is still below the negotiated 4096-byte PDU and therefore accepted by the PDURemainder logic. But the actual serialized footprint in the response buffer is larger: 12 bytes for TS7ResHeader23, 2 bytes for TResFunReadParams, 19 non-final odd-sized items taking 19 * (201 + 1 + 4) bytes, and the last item taking 201 + 4 bytes. The total becomes 4133 bytes, which exceeds the 4096-byte stack buffer by 37 bytes. By contrast, 20 items of 200 bytes stay within the buffer boundary and do not trigger the overflow under the same conditions. This boundary difference strongly supports the correctness of the root-cause analysis.
AddressSanitizer confirms the memory corruption as a stack-buffer-overflow with a WRITE of size 201 in TS7Worker::ReadArea() at the memcpy site. The call stack shows the request flowing through TS7Worker::PerformFunctionRead(), TS7Worker::PerformPDURequest(), and TS7Worker::IsoPerformCommand() on the server side. The faulting frame is TS7Worker::PerformFunctionRead(), where the Answer object resides on the stack. The ASan frame layout shows Answer occupying [32, 4128), with the invalid write occurring at offset 4128, i.e., immediately beyond the end of the Answer object. ASan also reports that the write underflows subsequent stack variables such as PDURemainder and EV, which confirms that adjacent stack state is being corrupted.
Manual debugging is consistent with the sanitizer report. Before the last copy operation, Offset has already advanced so that the destination pointer for the final item is too close to the end of Answer. At that point, PDURemainder still appears sufficient because it has only been reduced by raw payload bytes. In the reproduced session, only 164 bytes remained from the current destination pointer to the end of Answer, while the final memcpy attempted to write 201 bytes. The result is a 37-byte overwrite beyond the stack object. This demonstrates precisely why the overflow happens during the last memcpy rather than only after the loop computes a final oversized packet length.
The security impact is at least a remotely reachable denial of service through stack memory corruption in the Snap7 server process. Because the write crosses the boundary of a stack-allocated response object and reaches adjacent local state, the bug should be classified as a real memory corruption vulnerability. The currently demonstrated impact is a reliable crash. The available evidence does not require any claim of code execution to establish vulnerability severity.
A minimal fix is to unify response-space accounting so that the same model is used both for validation and for cursor advancement. Before calling ReadArea() or before accepting an item into the response, the implementation should compute the full serialized cost of that item, including the 4-byte TResFunReadItem header and any required odd-byte padding for non-final items, and verify that ResHeaderSize23 + Offset + serialized_item_size does not exceed sizeof(TS7Answer23). Alternatively, PDURemainder can be changed to represent actual remaining response-buffer capacity rather than raw payload bytes only. In all cases, the code should reject the request with a proper protocol error once the remaining space is insufficient, instead of proceeding to memcpy into the stack buffer.
In summary, the vulnerability is a server-side stack-based out-of-bounds write caused by inconsistent size accounting during multi-item ReadVar response assembly. The bug is reproducible against the current upstream Snap7 repository, is reachable through a normal negotiated client/server session, and results in confirmed stack memory corruption in TS7Worker::PerformFunctionRead() / TS7Worker::ReadArea(). |
|---|