Submit #881944: Systerel S2OPC OPC UA Toolkit 1.7.3 NULL Pointer Dereference (CWE-476)info

TitleSysterel S2OPC OPC UA Toolkit 1.7.3 NULL Pointer Dereference (CWE-476)
DescriptionI discovered a server-side denial of service vulnerability in S2OPC version 1.7.3, commit b4c5c7d63cd69698461d514b905a7c92b3c377c4. The flaw is reachable through the normal OPC UA subscription workflow when event management support is enabled. It is not a parser-only issue or a local misuse of private APIs. A remote OPC UA client can reach it by creating an Event MonitoredItem, causing event notifications to be queued, and then sending ModifyMonitoredItems to shrink the queue. The vulnerable code is in src/ClientServer/services/b2c/monitored_item_notification_queue_bs.c and is reached from src/ClientServer/services/bgenc/subscription_core.c during queue resize. The crash occurs in SOPC_InternalSetOverflowBitAfterDiscard() when the code unconditionally performs notifElt->value->Value.Status |= SOPC_DataValueOverflowStatusMask. For Event MonitoredItems, the internal queue element may legitimately contain an OpcUa_EventFieldList while the OpcUa_WriteValue pointer remains NULL. When the resize path later applies logic that only makes sense for DataChange notifications, the server dereferences the NULL value pointer and terminates. The root cause is a variant-handling error in the internal notification queue implementation. S2OPC uses a shared internal container, SOPC_InternalNotificationElement, to represent both DataChange and Event notifications. That structure contains both an OpcUa_WriteValue* named value and an OpcUa_EventFieldList* named eventValues. The valid active field depends on the monitored item type. In the DataChange case, value is populated and DataValue status-bit handling is meaningful. In the Event case, eventValues is populated and value may remain NULL. That distinction is respected in some code paths but is lost during queue shrinking. The event insertion path allocates a notification element and stores the event payload in eventValues. For normal event notifications it does not assign a WriteValue object to value. By contrast, the data change path allocates and fills value, and later manipulates value->Value.Status. The defect appears because the queue resize code assumes that every surviving element after discard can be treated as if it carried a valid WriteValue payload. The trigger sequence is straightforward. First, a remote client establishes a session to a server built with S2OPC_EVENT_MANAGEMENT=ON. Second, it creates a subscription and then an Event MonitoredItem with a queue size large enough to accumulate multiple event notifications. Third, it invokes the server-side test method used in the validation setup to generate events, so the queue becomes populated. Fourth, it sends ModifyMonitoredItems and reduces the queue size from 8 to 2. At that point the server enters the monitored item queue resize logic. Because the current queue length exceeds the new queue size, entries are discarded. After discarding, the resize path invokes overflow handling intended for discarded DataChange notifications. That code selects the replacement queue element and unconditionally writes to notifElt->value->Value.Status. If the selected element is an event notification, notifElt->value is NULL and the server crashes. I reproduced the issue with the official validation server binary built from the repository and a dedicated client PoC also built from the repository’s validation tests. The build configuration enabled AddressSanitizer and event management support. The exact commands were: cmake -S . -B build-asan-event -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_MODULE_PATH=/tmp/cmake-modules -DS2OPC_CLIENTSERVER_ONLY=ON -DENABLE_TESTING=ON -DENABLE_SAMPLES=ON -DWITH_ASAN=ON -DWARNINGS_AS_ERRORS=OFF -DS2OPC_EVENT_MANAGEMENT=ON cmake --build build-asan-event -j 8 --target toolkit_test_server toolkit_test_client_modify_event_queue The server was started with: env TEST_PASSWORD_PRIVATE_KEY=password ./toolkit_test_server events The PoC client was started with: ./toolkit_test_client_modify_event_queue The client successfully connected, created a subscription, created an Event MonitoredItem with revised queue size 8, invoked the event generation method, and then attempted ModifyMonitoredItems with target queue size 2. The client then reported failure because the server died while processing the request. On the server side, AddressSanitizer reported a write access to address 0x000000000060. The stack trace identified SOPC_InternalSetOverflowBitAfterDiscard at monitored_item_notification_queue_bs.c:174, then monitored_item_notification_queue_bs__resize_monitored_item_notification_queue at line 693, and subscription_core__modify_monitored_item at line 1305. This ties the crash to the network-facing service path. I also verified the fault in GDB and observed the relevant variables at several points. When a normal event notification element was first queued, notifElt pointed to a valid allocated object, notifElt->value was 0x0, notifElt->eventValues pointed to a valid OpcUa_EventFieldList, notifElt->isQueueOverflowEvent was false, and the monitored item still had queueSize 8 with discardOldest set to true. This shows that a standard event notification element does not carry a WriteValue payload and that value == NULL is intentional for that notification kind. At the entry of the resize function, the monitored item had already been updated to queueSize 2, discardOldest was still true, the queue length was 4, and the queue capacity was 9. The capacity is relevant because the event queue implementation allocates queueSize + 1 slots to reserve room for event overflow handling. The queue head at that moment still referenced a normal event element with value == NULL and eventValues != NULL. This confirms that the queue content and queue bookkeeping were coherent before the crashing write. The issue is therefore not a queue-length miscalculation and not evidence of earlier corruption. It is a semantic mismatch: an event element survives discard, but the code treats it as a data-change element. At the next breakpoint, after the resize logic had discarded excess entries and selected the surviving element that should carry the overflow indication, the chosen notifElt still had value == NULL, eventValues != NULL, and isQueueOverflowEvent == false. In other words, the code was operating on a normal event notification, not on a DataChange notification and not on a dedicated overflow marker object. Execution then reached notifElt->value->Value.Status |= SOPC_DataValueOverflowStatusMask and crashed exactly there. The AddressSanitizer report showed a write to the zero page at offset 0x60, which is consistent with a NULL base plus the member offset of the nested Status field. The security impact is a reliable server-side denial of service. An attacker who can establish an OPC UA session to an affected server instance and use the normal subscription services can crash the server process by orchestrating a legitimate event subscription workflow and then shrinking the queue. The issue was deterministic and repeatable. I did not observe evidence of code execution, privilege escalation, or direct information disclosure. The practical impact demonstrated so far is loss of availability. This issue should be treated as a security vulnerability rather than a mere robustness bug because the vulnerable path is reachable through standard protocol operations, affects the server role, and can be exercised against the project’s own validation server without patching the target or creating a synthetic harness. The proof of concept uses the official validation server and a client PoC from the repository’s test tree, and the crash occurs while the server processes a legitimate ModifyMonitoredItems request on an Event MonitoredItem whose queue was populated through normal event generation. The most appropriate remediation is to separate post-discard handling for DataChange and Event MonitoredItems. At minimum, the resize path should not call SOPC_InternalSetOverflowBitAfterDiscard() on event queues, or the helper should first verify that the selected element actually has a valid value payload before touching Value.Status. A more complete fix is to preserve the event-specific overflow semantics already reflected elsewhere: event queues reserve an extra slot for overflow signaling and should use the event overflow mechanism rather than the DataValue overflow bit. In summary, this is a real server-side NULL pointer dereference in S2OPC 1.7.3. The bug is caused by reusing DataChange-specific overflow propagation during Event queue shrinking. I found the issue through source analysis and reproduced it locally against the official validation server with AddressSanitizer and GDB. The crash is stable, the faulting statement is identified, the critical variables show that the selected element is an event notification with value == NULL, and the observable consequence is remote denial of service against affected server deployments that enable event management.
Source⚠️ https://gitlab.com/systerel/S2OPC/-/work_items/1788
User
 SCU_1CP (UID 99172)
Submission07/07/2026 06:51 (2 months ago)
Moderation08/22/2026 18:33 (2 months later)
StatusDuplicate
VulDB entry386446 [Systerel S2OPC 1.7.3 Queue denial of service]
Points0

Do you need the next level of professionalism?

Upgrade your account now!