| Beschreibung | S2OPC 1.7.3 contains a client-side out-of-bounds read in the high-level client wrapper when processing DeleteMonitoredItemsResponse messages. The issue is located in the client state machine responsible for asynchronous subscription service handling. In the affected path, the wrapper first sends a valid DeleteMonitoredItemsRequest and stores a deep copy of that request in its internal application context. Later, when the asynchronous DeleteMonitoredItemsResponse is received, the state machine iterates over the response results array by trusting the server-controlled NoOfResults field. However, it does not verify that this response-side count is consistent with the number of monitored item identifiers stored in the original local request copy. As a result, a malicious OPC UA server, or an on-path attacker able to tamper with responses, can return more result entries than the client originally requested, causing the wrapper to index beyond the end of the heap-allocated MonitoredItemIds array kept in the request context.
This is not a parser-only crash, not a synthetic unit-test issue, and not an API misuse case. The vulnerable path is reachable through the stock client wrapper sample distributed with the project. In the validated scenario, the official sample client first establishes a normal OPC UA session, performs CreateSession and ActivateSession successfully, creates a subscription, creates one monitored item, receives a valid data change notification, and only then sends a legitimate DeleteMonitoredItemsRequest for that single monitored item. The request parameters are valid and follow the documented API flow. The only abnormal condition is introduced by the remote side: the DeleteMonitoredItemsResponse is modified so that NoOfResults is larger than the number of monitored item IDs stored in the client request context. This demonstrates that the bug is a real protocol-facing client vulnerability in a supported code path, not an artifact of malformed local input or a misuse of internal interfaces.
The vulnerable behavior can be reproduced with the project’s own demo components. The server side can be the stock S2OPC demo server running in SecurityPolicy None mode. The client side can be the stock s2opc_wrapper_subscribe example. To trigger the bug reliably, a proxy or malicious server is placed between the two and only mutates one field in the first real DeleteMonitoredItemsResponse. In the validated test, the original response created by the legitimate server contains one result because the client requested deletion of exactly one monitored item. The proxy rewrites that response so that NoOfResults becomes 2 and duplicates a Good StatusCode element to keep the message structurally consistent enough to pass decoding and reach the wrapper logic. All preceding traffic, including session creation, subscription creation, monitored item creation, and Publish traffic, is forwarded normally. This makes the trigger condition very precise and strongly supports the conclusion that the crash is caused by a result-count mismatch in DeleteMonitoredItemsResponse handling.
The internal root cause is a classic trust-boundary violation between a remotely supplied length field and a locally allocated array. During request submission, the client wrapper deep-copies the DeleteMonitoredItemsRequest into its internal state machine context. At that point, the local request accurately contains one monitored item ID, and the copied MonitoredItemIds buffer is therefore allocated for exactly one 32-bit element. During response handling, the state machine retrieves both the server response object and the saved request copy. It then uses the response field NoOfResults as the loop bound, even though the request-side array length is determined by NoOfMonitoredItemIds from the original request. If the remote response says there are two results while the original request only contained one monitored item ID, the first iteration is safe but the second iteration performs a read from MonitoredItemIds[1], which is one element past the end of the locally allocated array. This is a direct out-of-bounds read caused by response/request count desynchronization.
Runtime evidence confirms this interpretation. AddressSanitizer reports a heap-buffer-overflow with a READ of size 4 in LockedStaMac_ProcessMsg_DeleteMonitoredItemsResponse at state_machine.c:2327. The reported faulting address is exactly one element beyond a 4-byte heap region, which matches the size of a single monitored item identifier stored in the copied request. The allocation stack shows that the over-read buffer originates from SOPC_EncodeableObject_Copy invoked by SOPC_StaMac_NewDeleteMonitoredItems during request duplication. This is important because it proves that the invalid access is not reading from caller-owned stack memory, not reading from a freed buffer, and not caused by the sample code itself. The invalid access targets a heap object allocated and managed by the wrapper state machine, which places responsibility squarely in the library implementation. GDB inspection further confirms the mismatch: at the response-processing entry, pMonItResp->NoOfResults equals 2 while pMonItReq->NoOfMonitoredItemIds equals 1, and the valid address range of pMonItReq->MonitoredItemIds covers exactly one uint32_t. Immediately before the crash, the code is about to evaluate pMonItReq->MonitoredItemIds[1], which is a textbook one-past-end heap read.
From a security perspective, the most conservative and well-supported impact is remote denial of service on the client side. A malicious OPC UA server, or a man-in-the-middle attacker controlling the response stream, can force the S2OPC-based client to crash during monitored item deletion. In industrial environments, the affected client is not merely a convenience tool: OPC UA client components are commonly used in HMIs, engineering workstations, telemetry collectors, gateways, monitoring agents, and protocol adapters. A crash at this layer can interrupt data acquisition, stop subscription processing, prevent alarm updates, leave stale values on operator screens, or break upstream-to-downstream forwarding logic. Even without evidence of code execution, a remotely triggerable client crash in a production OPC UA communication component is a meaningful security problem because it directly affects availability and operational visibility.
This issue should not be dismissed as server non-compliance. OPC UA clients process data originating from an untrusted peer and must validate consistency between response metadata and locally stored request state. Here, the wrapper implicitly assumes that DeleteMonitoredItemsResponse.Results is naturally aligned with the original request’s MonitoredItemIds array. That assumption is not defensible in an adversarial setting. The bug exists because the implementation fails to enforce the invariant that the number of response results must not exceed, and semantically should equal, the number of monitored item IDs originally requested for deletion. The sample client merely reaches the vulnerable path; it does not create the flaw. Likewise, this is not a use-after-free or uninitialized read. The memory is live and correctly allocated; the failure mode is a deterministic out-of-bounds read caused by indexing past the end of a one-element heap array.
The affected path spans the stock sample and the wrapper internals. The visible user entry point is the sample client at samples/ClientServer/client_wrapper/examples/subscribe.c, where the program eventually calls the public wrapper API to delete monitored items. That API flows into SOPC_ClientHelper_Subscription_DeleteMonitoredItems in libs2opc_client.c, which creates the internal state-machine context and deep-copies the request. The request duplication occurs around state_machine.c:1004. The vulnerable asynchronous response handling later occurs in LockedStaMac_ProcessMsg_DeleteMonitoredItemsResponse in state_machine.c, with the loop bound controlled near state_machine.c:2321 and the invalid array access occurring at state_machine.c:2327. This makes the flaw a library-level implementation bug in a real wrapper service path, not a superficial problem in example-only glue code.
The most appropriate weakness classification is CWE-125, Out-of-bounds Read. Although sanitizers report a generic heap-buffer-overflow, the observed primitive is a 4-byte read beyond the end of a heap-allocated array, not a heap write overflow. The reliable, evidence-based statement is that an attacker controlling DeleteMonitoredItemsResponse.NoOfResults can trigger a client-side heap out-of-bounds read and crash.
In summary, S2OPC 1.7.3 exposes a real client-side vulnerability in DeleteMonitoredItemsResponse handling. The stock wrapper sample can reach the flaw through a normal subscription lifecycle. |
|---|