testing#72423
Conversation
There was a problem hiding this comment.
Code Review
This pull request addresses potential use-after-free (UAF) vulnerabilities during the processing of unsolicited report data. Specifically, it updates InteractionModelEngine::OnUnsolicitedReportData to cache the next ReadClient pointer before invoking OnUnsolicitedMessageFromPublisher(), which can synchronously destroy the current client. It also ensures subscription matching checks are performed before the notification. In ReadClient.h, callback notifications are reordered to fire before triggering a resubscribe to prevent premature destruction of the client. A regression test is added to verify safe traversal when a client is destroyed mid-walk. The review feedback suggests further safeguarding against UAF by copying state to local variables before invoking callbacks that might deallocate their context objects.
| // Notify Subscriptions about incoming communication from node. | ||
| readClient->OnUnsolicitedMessageFromPublisher(); | ||
|
|
||
| if (!foundSubscription) | ||
| { | ||
| foundSubscription = readClient; | ||
| } | ||
| readClient = nextClient; |
There was a problem hiding this comment.
To prevent use-after-free (UAF) vulnerabilities when invoking a callback that might deallocate its context object (such as OnUnsolicitedMessageFromPublisher destroying the ReadClient), we should copy all necessary callback parameters and state to local variables first. Then, perform any cleanup or state updates that might invalidate the context (such as clearing foundSubscription and tracking its status via a local boolean). Finally, invoke the callback.
ReadClient * nextClient = readClient->GetNextClient();
bool isFoundSubscription = (foundSubscription == readClient);
if (isFoundSubscription)
{
foundSubscriptionFound = true;
foundSubscription = nullptr;
}
readClient->OnUnsolicitedMessageFromPublisher();
readClient = nextClient;References
- To prevent use-after-free vulnerabilities when invoking a callback that might deallocate its context object, first copy all necessary callback parameters to local variables. Then, perform any cleanup operations that might invalidate the context. Finally, invoke the callback using the saved local variables.
…dReportData walk InteractionModelEngine::OnUnsolicitedReportData walks mpActiveReadClientList and calls OnUnsolicitedMessageFromPublisher() on each entry. For a disconnected subscription that notification can synchronously run a resubscribe; if the session fails to re-establish, the client is closed (Close -> OnDone), and an application is allowed to destroy its ReadClient from OnDone. The walk then kept dereferencing that entry (IsSubscriptionActive(), IsMatchingSubscriptionId(), and the GetNextClient() loop increment) and could capture it into foundSubscription. Snapshot GetNextClient() before the notification and evaluate the subscription match before notifying, so a destroyed entry is never dereferenced or captured. This mirrors the OnActiveModeNotification / OnPeerTypeChange walkers, which already pre-cache the next pointer for the same reason. Matching before notifying is safe because an active subscription never has a resubscribe scheduled (ScheduleResubscription requires the Idle state), so the matched client is not the one torn down by its own notification. Also reorder ReadClient::OnUnsolicitedMessageFromPublisher() to notify the callback before triggering the scheduled resubscribe. TriggerResubscribeIfScheduled() can destroy the ReadClient, so it must run last. Note: the callback notification now runs before the resubscribe trigger instead of after it. The notification still fires in every case it did before (including for disconnected subscriptions, per its documented contract); only its ordering relative to the resubscribe trigger changed, and that ordering is not observable on the wire. Adds TestUnsolicitedReportDataReadClientDestroyedDuringWalk covering the walk over a subscription destroyed during its own notification. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
bc4359d to
10a9e56
Compare
|
PR #72423: Size comparison from a6b6294 to 10a9e56 Full report (35 builds for bl602, bl616, bl702, bl702l, cc13x4_26x4, cc32xx, efr32, esp32, nrfconnect, psoc6, qpg, realtek, stm32, telink)
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #72423 +/- ##
==========================================
+ Coverage 55.60% 55.62% +0.01%
==========================================
Files 1631 1631
Lines 111239 111241 +2
Branches 13400 13396 -4
==========================================
+ Hits 61857 61874 +17
+ Misses 49382 49367 -15 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Summary
InteractionModelEngine::OnUnsolicitedReportDatawalksmpActiveReadClientListand could continue to use aReadClientthat was destroyed mid-walk by its own notification.Problem
OnUnsolicitedReportDataiteratesmpActiveReadClientListand callsOnUnsolicitedMessageFromPublisher()on each entry. For a disconnected subscription that notification synchronously runs a scheduled resubscribe; if re-establishing the session fails, the client is closed (Close->OnDone), and an application is permitted to destroy itsReadClientfromOnDone.IsSubscriptionActive(),IsMatchingSubscriptionId(), thereadClient = readClient->GetNextClient()increment, and capturing it intofoundSubscriptionfor the post-loop dispatch.Impact
ReadClientcan be freed by the notification, so the subsequent reads and the loop increment operate on a destroyed object. The sibling walkersOnActiveModeNotificationandOnPeerTypeChangealready pre-cache the next pointer specifically to avoid this;OnUnsolicitedReportDatadid not.Solution
OnUnsolicitedReportDatawalk, snapshotGetNextClient()before the notification and evaluate the subscription match before notifying, so a destroyed entry is never dereferenced or captured. Matching before notifying is safe because an active subscription never has a resubscribe scheduled (ScheduleResubscriptionrequires theIdlestate), so the matched client is not the one torn down by its own notification.ReadClient::OnUnsolicitedMessageFromPublisher(), notify the callback before triggering the scheduled resubscribe.TriggerResubscribeIfScheduled()can destroy theReadClient, so it must run last.Caveats
OnUnsolicitedMessageFromPublishercontract); only its ordering relative to the resubscribe trigger changed, which is not observable on the wire.Testing
TestUnsolicitedReportDataReadClientDestroyedDuringWalkinsrc/app/tests/TestInteractionModelEngine.cpp, which drives a ReportData walk over a subscription destroyed during its own notification and verifies the walk completes cleanly. Confirmed it fails under ASan without the fix and passes with it.TestInteractionModelEngine(15),TestReadInteraction(112),TestReportingEngine(3),TestBufferedReadCallback(2).