| Descrição | A server-side heap-use-after-free vulnerability exists in open62541 in the local MonitoredItem publishing path. The issue affects the master branch code pattern analyzed here and is triggered when an application uses a local data-change MonitoredItem and deletes that same MonitoredItem from inside its callback. The defect is located in the lifetime management interplay between UA_Subscription_localPublish, UA_Server_deleteMonitoredItem, UA_MonitoredItem_delete, and UA_Notification_delete. The bug is not a parser-only problem and not a synthetic direct-call crash. It is reachable through a real OPC UA network interaction in which a remote client sends a valid WriteRequest that updates a node observed by a local MonitoredItem on the server side.
The vulnerable control flow is as follows. When the monitored node is modified, the server processes the incoming Write service and creates a data-change notification through the normal server path. In the observed execution, allocation originates from Operation_Write, then flows through triggerImmediateDataChange, UA_MonitoredItem_processSampledValue, UA_MonitoredItem_createDataChangeNotification, and finally UA_Notification_new. The resulting UA_Notification object is then inserted into the server-side queues used for local publishing. In open62541, local MonitoredItems are not published through a remote subscription response. Instead, they are delivered asynchronously by the server through the internal admin subscription and a delayed callback into UA_Subscription_localPublish. This means the current notification object remains owned and referenced by the subscription publishing logic while user callback code is executed.
The root cause is a lifetime mismatch for the current UA_Notification object. UA_Subscription_localPublish iterates over the notification queue, obtains the current notification pointer n, and passes the address of n->data.dataChange.value into the registered local data change callback. After the callback returns, the same function continues to operate on the original notification object and on its queue linkage. In particular, the code still performs list navigation using the current notification pointer and then proceeds with notification cleanup. This design assumes that the current UA_Notification remains valid across callback execution.
That assumption is false if the callback calls the public API UA_Server_deleteMonitoredItem for the current local MonitoredItem. The deletion path reaches Operation_DeleteMonitoredItem and then UA_MonitoredItem_delete. UA_MonitoredItem_delete synchronously traverses the monitored item queue and destroys queued notifications by calling UA_Notification_delete. UA_Notification_delete removes the notification from both the monitored-item queue and the subscription queue, clears the payload, and frees the UA_Notification object itself. As a result, the very same notification object that UA_Subscription_localPublish still holds in its local variable is released during callback execution. When control returns to UA_Subscription_localPublish, it continues to dereference the stale pointer and accesses freed queue metadata. This produces a classic heap-use-after-free.
The defect is therefore not merely that a MonitoredItem can be deleted in a callback. The actual flaw is that the implementation protects, at most, the broader monitored-item lifecycle but does not protect the currently processed notification object that is still in active use by the publishing frame. In other words, the missing protection is on the lifetime of the current UA_Notification, not on the abstract existence of the monitored item. This distinction matters because the crash occurs after the callback returns, exactly when the publishing function resumes queue operations on an object that has already been freed from underneath it.
In the reproduced case, the server first created a local monitored item successfully and entered the local callback once during initialization. A remote client then connected over TCP to the OPC UA endpoint, completed a normal session setup, and issued a valid WriteRequest containing two write operations. The client received a successful result for the write service. After that legitimate network request, the server entered the second local callback, logged that it was deleting the monitored item from inside the callback, and the deletion API returned Good. Immediately afterward, AddressSanitizer reported a heap-use-after-free in UA_Subscription_localPublish when reading from the freed notification object. This sequence demonstrates an end-to-end network-triggerable server crash in real protocol handling rather than a local-only misuse scenario.
The sanitizer evidence is strong and precise. The crash site is a READ of size 8 in UA_Subscription_localPublish while processing the local publish queue. The freeing stack shows that the memory was released by UA_Notification_delete, called from UA_MonitoredItem_delete, reached through UA_Server_deleteMonitoredItem from the user callback. The allocation stack shows that the same memory was originally allocated by UA_Notification_new and was created as part of the normal Write-service-driven data-change notification flow. This three-part evidence chain, allocation, free, and use-after-free, confirms that the issue is a real memory-safety violation inside the library and not an artifact of instrumentation or undefined user code outside the open62541 API contract.
From a security perspective, the most defensible current impact is remote denial of service. A network client that can cause a legitimate write to a node monitored by a vulnerable local callback configuration can drive the server process into an AddressSanitizer abort and, in a non-sanitized build, into a crash or other unstable behavior associated with use-after-free. Since the underlying flaw is a heap-use-after-free rather than a simple null dereference or assertion failure, the issue has clearer security significance than an ordinary logic bug. At the same time, a conservative assessment should avoid overstating exploitation beyond what is demonstrated. The reproduced effect is a remote server crash. More advanced consequences have not been established here and should not be claimed without additional evidence.
An important nuance is that this issue does not reproduce on a stock official example binary without modification. The trigger requires a real application pattern in which a local MonitoredItem exists and its callback deletes the current MonitoredItem via the public deletion API. However, this does not reduce the issue to mere API misuse. The reproduction uses only documented public capabilities: creating a local data-change MonitoredItem, receiving its callback, and deleting it through UA_Server_deleteMonitoredItem. The crash point is in open62541 internal queue and lifetime management, not in external harness memory handling. The harness simply exercises a valid but specialized public API combination that exposes the library flaw.
At code level, the vulnerable design can be summarized as follows. First, a WriteRequest causes the server to generate a data-change notification for a local MonitoredItem. Second, the notification is enqueued for local publishing. Third, UA_Subscription_localPublish invokes the user callback while still retaining a live pointer to the current notification object. Fourth, the callback invokes UA_Server_deleteMonitoredItem on the current item. Fifth, UA_MonitoredItem_delete synchronously destroys the queued notification and frees it through UA_Notification_delete. Sixth, control returns to UA_Subscription_localPublish, which still treats the freed notification as valid and continues queue navigation and cleanup using that stale pointer. The bug is therefore a reentrancy-sensitive lifetime management error in the server-side subscription subsystem.
The practical remediation direction is also clear. The library should ensure that the current notification object cannot be freed while UA_Subscription_localPublish is still using it. This can be implemented in more than one way: by decoupling callback input from the live notification object, by temporarily pinning or reference-counting the current notification during callback execution, by deferring destruction of the current notification when deletion occurs from within the callback, or by restructuring the local publish path so that no queue navigation uses the current notification after user code returns. Any complete fix must specifically protect the currently processed UA_Notification and not only the outer MonitoredItem container.
In summary, this is a genuine server-side heap-use-after-free in open62541. The flaw arises from incomplete lifetime protection in the local MonitoredItem callback deletion path. |
|---|