Skip to content

testing#72423

Draft
Alami-Amine wants to merge 1 commit into
project-chip:masterfrom
Alami-Amine:ime-report-walk-snapshot-next
Draft

testing#72423
Alami-Amine wants to merge 1 commit into
project-chip:masterfrom
Alami-Amine:ime-report-walk-snapshot-next

Conversation

@Alami-Amine

Copy link
Copy Markdown
Contributor

Summary

InteractionModelEngine::OnUnsolicitedReportData walks mpActiveReadClientList and could continue to use a ReadClient that was destroyed mid-walk by its own notification.

Problem
  • OnUnsolicitedReportData iterates mpActiveReadClientList and calls OnUnsolicitedMessageFromPublisher() 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 its ReadClient from OnDone.
  • After that notification the loop kept using the same entry: IsSubscriptionActive(), IsMatchingSubscriptionId(), the readClient = readClient->GetNextClient() increment, and capturing it into foundSubscription for the post-loop dispatch.
Impact
  • The iterated ReadClient can be freed by the notification, so the subsequent reads and the loop increment operate on a destroyed object. The sibling walkers OnActiveModeNotification and OnPeerTypeChange already pre-cache the next pointer specifically to avoid this; OnUnsolicitedReportData did not.
Solution
  • In the OnUnsolicitedReportData walk, snapshot GetNextClient() 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 (ScheduleResubscription requires the Idle state), so the matched client is not the one torn down by its own notification.
  • In ReadClient::OnUnsolicitedMessageFromPublisher(), notify the callback before triggering the scheduled resubscribe. TriggerResubscribeIfScheduled() can destroy the ReadClient, so it must run last.
Caveats
  • The callback notification now runs before the resubscribe trigger rather than after it. The notification still fires in every case it did before (including for disconnected subscriptions, per the OnUnsolicitedMessageFromPublisher contract); only its ordering relative to the resubscribe trigger changed, which is not observable on the wire.

Testing

  • Added unit test TestUnsolicitedReportDataReadClientDestroyedDuringWalk in src/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.
  • Ran the surrounding suites under ASan with no regressions: TestInteractionModelEngine (15), TestReadInteraction (112), TestReportingEngine (3), TestBufferedReadCallback (2).

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1044 to +1047
// Notify Subscriptions about incoming communication from node.
readClient->OnUnsolicitedMessageFromPublisher();

if (!foundSubscription)
{
foundSubscription = readClient;
}
readClient = nextClient;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

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
  1. 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.

@Alami-Amine Alami-Amine changed the title [IM] Don't dereference a ReadClient destroyed during the OnUnsolicitedReportData walk testing Jun 5, 2026
…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>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

@github-actions

github-actions Bot commented Jun 5, 2026

Copy link
Copy Markdown

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)
platform target config section a6b6294 10a9e56 change % change
bl602 lighting-app bl602+mfd+littlefs+rpc FLASH 1094208 1094216 8 0.0
RAM 144882 144882 0 0.0
bl616 lighting-app bl616+thread FLASH 1105548 1105564 16 0.0
RAM 104280 104280 0 0.0
bl616+wifi+shell FLASH 1593296 1593304 8 0.0
RAM 98176 98176 0 0.0
bl702 lighting-app bl702+eth FLASH 1057210 1057218 8 0.0
RAM 108525 108525 0 0.0
bl702l contact-sensor-app bl702l+mfd+littlefs FLASH 896044 896052 8 0.0
RAM 105908 105908 0 0.0
cc13x4_26x4 lighting-app LP_EM_CC1354P10_6 FLASH 776952 776960 8 0.0
RAM 103404 103404 0 0.0
lock-ftd LP_EM_CC1354P10_6 FLASH 789704 789704 0 0.0
RAM 108684 108684 0 0.0
pump-app LP_EM_CC1354P10_6 FLASH 738852 738860 8 0.0
RAM 97612 97612 0 0.0
pump-controller-app LP_EM_CC1354P10_6 FLASH 719024 719024 0 0.0
RAM 97644 97644 0 0.0
cc32xx air-purifier CC3235SF_LAUNCHXL FLASH 568674 568674 0 0.0
RAM 205056 205056 0 0.0
lock CC3235SF_LAUNCHXL FLASH 596306 596306 0 0.0
RAM 205272 205272 0 0.0
efr32 lock-app BRD4187C FLASH 994148 994180 32 0.0
RAM 131292 131292 0 0.0
BRD4338a FLASH 798853 798869 16 0.0
RAM 243432 243432 0 0.0
window-app BRD4187C FLASH 1100720 1100752 32 0.0
RAM 130364 130364 0 0.0
esp32 all-clusters-app c3devkit DRAM 99724 99724 0 0.0
FLASH 1621700 1621704 4 0.0
IRAM 94776 94776 0 0.0
nrfconnect all-clusters-app nrf52840dk_nrf52840 FLASH 834304 834304 0 0.0
RAM 157548 157548 0 0.0
psoc6 all-clusters cy8ckit_062s2_43012 FLASH 1733836 1733852 16 0.0
RAM 215268 215268 0 0.0
all-clusters-minimal cy8ckit_062s2_43012 FLASH 1622884 1622900 16 0.0
RAM 211556 211556 0 0.0
light cy8ckit_062s2_43012 FLASH 1470100 1470116 16 0.0
RAM 197436 197436 0 0.0
lock cy8ckit_062s2_43012 FLASH 1503556 1503572 16 0.0
RAM 225268 225268 0 0.0
qpg lighting-app qpg6200+debug FLASH 844192 844208 16 0.0
RAM 127956 127956 0 0.0
lock-app qpg6200+debug FLASH 782268 782268 0 0.0
RAM 118864 118864 0 0.0
realtek light-switch-app rtl8777g FLASH 688752 688752 0 0.0
RAM 101780 101780 0 0.0
lighting-app rtl8777g FLASH 729696 729704 8 0.0
RAM 102052 102052 0 0.0
stm32 light STM32WB5MM-DK FLASH 478408 478408 0 0.0
RAM 141492 141492 0 0.0
telink all-devices-app tl7218x FLASH 813050 813054 4 0.0
RAM 97196 97196 0 0.0
tlsr9118bdk40d FLASH 606524 606528 4 0.0
RAM 120152 120152 0 0.0
bridge-app tl7218x FLASH 731478 731482 4 0.0
RAM 95876 95876 0 0.0
light-app-ota-compress-lzma-shell-factory-data tl3218x FLASH 851820 851824 4 0.0
RAM 44344 44344 0 0.0
tl7218x FLASH 843150 843154 4 0.0
RAM 99668 99668 0 0.0
light-switch-app-ota-compress-lzma-factory-data tl7218x_retention FLASH 731972 731976 4 0.0
RAM 55992 55992 0 0.0
light-switch-app-ota-compress-lzma-shell-factory-data tlsr9528a FLASH 795198 795202 4 0.0
RAM 75176 75176 0 0.0
light-switch-app-ota-factory-data tl3218x_retention FLASH 731900 731904 4 0.0
RAM 33480 33480 0 0.0
lighting-app-ota-factory-data tlsr9118bdk40d FLASH 614624 614628 4 0.0
RAM 118508 118508 0 0.0
lighting-app-ota-rpc-factory-data-4mb tlsr9518adk80d FLASH 841266 841274 8 0.0
RAM 97376 97376 0 0.0

@codecov

codecov Bot commented Jun 5, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 55.62%. Comparing base (d541ec4) to head (10a9e56).
⚠️ Report is 2 commits behind head on master.

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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants