NULL Pointer Dereference via Status-Variable Clobbering in Notification Message Allocation

Description

While reviewing msg_subscription_publish_bs.c, which is also referenced in the context of CVE-2026-67865, I identified a separate status-propagation flaw in msg_subscription_publish_bs__alloc_notification_message_items().

The function creates notification objects for data-change and event notifications using a single shared status variable. If creation of the data-change notification fails but creation of the subsequent event notification succeeds, the second operation overwrites the failure status with SOPC_STATUS_OK.

The function then proceeds as though the data-change notification was successfully created and dereferences dataChangeNotif, even though the failed creation path has explicitly set that pointer to NULL.

The result is a NULL pointer dereference and potential process crash.

Expected: a failure of the data-change notification creation must not be masked by the later success of the event notification creation; the function must not dereference dataChangeNotif when it is NULL.

Code version and environment identification

  • Component: src/ClientServer/services/b2c/msg_subscription_publish_bs.c
  • Function: msg_subscription_publish_bs__alloc_notification_message_items()
  • Tested commit: adcfeea285f1017b220aadc39f6b2111634cff15 (2026-08-10)
  • CWE: CWE-476 (NULL Pointer Dereference)

Steps to reproduce

Important trigger limitation

I want to make the triggering conditions explicit: I did not demonstrate a crafted network packet that deterministically causes this condition.

The first SOPC_ExtensionObject_CreateObject() call currently reaches its failure path through SOPC_EncodeableObject_Create(), with allocation exhaustion/OOM being the relevant failure mechanism identified during source tracing. Therefore, this should currently be considered a verified logic and memory-safety defect with an OOM/resource-exhaustion trigger condition, rather than claiming a straightforward remotely exploitable one-shot crash.

Affected code

In msg_subscription_publish_bs__alloc_notification_message_items():

if (NULL != notifMsg->NotificationData)
{
    for (int32_t i = 0; i < notifMsg->NoOfNotificationData; i++)
    {
        SOPC_ExtensionObject* notifData = &notifMsg->NotificationData[i];
        SOPC_ExtensionObject_Initialize(notifData);

        if (dataToSet)
        {
            status = SOPC_ExtensionObject_CreateObject(
                notifData,
                &OpcUa_DataChangeNotification_EncodeableType,
                (void**) &dataChangeNotif);

            dataToSet = false;
        }
        else if (eventToSet)
        {
            status = SOPC_ExtensionObject_CreateObject(
                notifData,
                &OpcUa_EventNotificationList_EncodeableType,
                (void**) &eventNotifList);

            eventToSet = false;
        }
    }
}

Later, the function performs:

if (SOPC_STATUS_OK == status && hasData)
{
    dataChangeNotif->NoOfMonitoredItems =
        msg_subscription_publish_bs__p_nb_data_notifications;

    dataChangeNotif->MonitoredItems = SOPC_Malloc(...);
    ...
}

The problem is that status represents the result of the last SOPC_ExtensionObject_CreateObject() invocation rather than specifically the data-change notification creation.

Failure sequence

The problematic sequence is:

  1. dataToSet is true.

  2. SOPC_ExtensionObject_CreateObject() is called for dataChangeNotif.

  3. The allocation/creation fails.

  4. The failure path eventually calls SOPC_EncodeableObject_Delete().

  5. SOPC_EncodeableObject_Delete() explicitly sets the output pointer to NULL:

    *encObject = NULL;

    Therefore:

    dataChangeNotif == NULL
  6. The loop continues instead of stopping after the failure.

  7. eventToSet is true, so the event notification is created.

  8. The event creation succeeds and overwrites the shared status with SOPC_STATUS_OK.

  9. The later condition:

    if (SOPC_STATUS_OK == status && hasData)

    evaluates as true.

  10. dataChangeNotif->NoOfMonitoredItems is accessed while dataChangeNotif == NULL.

This produces a NULL pointer dereference.

Failure-path verification

I also traced the relevant allocator implementation.

SOPC_ExtensionObject_CreateObject() ultimately performs:

status = SOPC_EncodeableObject_Create(encTyp, encObject);

if (SOPC_STATUS_OK == status)
{
    ...
}
else
{
    SOPC_ReturnStatus deleteStatus =
        SOPC_EncodeableObject_Delete(encTyp, encObject);

    SOPC_ASSERT(SOPC_STATUS_OK == deleteStatus);
}

SOPC_EncodeableObject_Delete() then performs:

SOPC_EncodeableObject_Clear(encTyp, *encObject);
SOPC_Free(*encObject);
*encObject = NULL;
status = SOPC_STATUS_OK;

Thus the NULL state of dataChangeNotif after the failed creation is not speculative; it follows directly from the implementation of the failure path.

Relevant logs and/or screenshots

N/A


Analysis

Root cause

The root cause is failure-status clobbering.

Two independent object-creation operations share the same status variable:

data notification creation  -> status
event notification creation -> status

The second assignment destroys the result of the first operation.

There is also no early exit after the first failure.

Consequently, the later success of the event-notification creation can incorrectly authorize code that depends on successful data-notification creation.

Relationship to CVE-2026-67865

This finding is distinct from CVE-2026-67865.

CVE-2026-67865 concerns an out-of-bounds read in RepublishResponse handling. The present finding concerns a status-propagation error resulting in a NULL pointer dereference during notification construction.

The findings are nevertheless related by location/subsystem, since msg_subscription_publish_bs.c is involved in the existing advisory. I am therefore reporting the relationship explicitly rather than assuming complete independence from the previous fix.

I am also aware that S2OPC uses formal/B-method-based development and verification, so it may be useful to check whether the formal model has an assumption or invariant covering this failure interleaving that is not apparent from the generated C implementation.


Security impact

The immediate impact is a NULL pointer dereference in the notification construction path, which can terminate the affected S2OPC process and result in denial of service.

The affected function is used when a notification message contains both data-change and event notification data.

However, I am deliberately not claiming that this is currently a straightforward remotely triggerable vulnerability. The failure condition I traced depends on the first object allocation/creation failing, with allocation exhaustion/OOM being the relevant currently identified mechanism.


Possible fixes

The notification creation results should be tracked independently, or the operation should immediately stop when the first creation fails.

For example:

SOPC_ReturnStatus dataStatus = SOPC_STATUS_OK;
SOPC_ReturnStatus eventStatus = SOPC_STATUS_OK;

for (int32_t i = 0; i < notifMsg->NoOfNotificationData; i++)
{
    ...

    if (dataToSet)
    {
        dataStatus = SOPC_ExtensionObject_CreateObject(...);
        dataToSet = false;
    }
    else if (eventToSet)
    {
        eventStatus = SOPC_ExtensionObject_CreateObject(...);
        eventToSet = false;
    }
}

status = (dataStatus == SOPC_STATUS_OK &&
          eventStatus == SOPC_STATUS_OK)
             ? SOPC_STATUS_OK
             : SOPC_STATUS_NOK;

if (SOPC_STATUS_OK == dataStatus && hasData)
{
    ...
}

if (SOPC_STATUS_OK == eventStatus && hasEvent)
{
    ...
}

An alternative would be to return immediately when the first creation fails, provided the surrounding cleanup/error-handling semantics are handled correctly.

Verification and limitations

I confirmed the issue through source-level tracing of:

  • msg_subscription_publish_bs__alloc_notification_message_items()
  • SOPC_ExtensionObject_CreateObject()
  • SOPC_EncodeableObject_Create()
  • SOPC_EncodeableObject_Delete()

I did not set up a live OPC-UA server/client session or deliberately exhaust memory to reproduce the final crash.

No exploit was developed.

I am reporting this confidentially so the S2OPC team can independently validate the behavior and determine the appropriate severity and remediation.

Researcher: Harsh Raj Singhania

Edited by Vincent Lacroix