Skip to content

Commit 3d4638c

Browse files
bzbarsky-appleaustina-csa
authored andcommitted
Fix shutdown ordering issue in Server.cpp. (project-chip#34035)
We could end up shutting down the exchange manager while we still had live sessions/exchanges, at which point shutting down those would crash as they tried to get information from the exchange manager. The fix is to document the required shutdown ordering and enforce it correctly in CHIPDeviceControllerFactory and Server. Fixes project-chip#20880
1 parent 6d8dcaf commit 3d4638c

File tree

5 files changed

+65
-5
lines changed

5 files changed

+65
-5
lines changed

src/app/server/Server.cpp

+6
Original file line numberDiff line numberDiff line change
@@ -596,6 +596,12 @@ void Server::Shutdown()
596596
#if CHIP_CONFIG_ENABLE_ICD_SERVER
597597
app::InteractionModelEngine::GetInstance()->SetICDManager(nullptr);
598598
#endif // CHIP_CONFIG_ENABLE_ICD_SERVER
599+
// Shut down any remaining sessions (and hence exchanges) before we do any
600+
// futher teardown. CASE handshakes have been shut down already via
601+
// shutting down mCASESessionManager and mCASEServer above; shutting
602+
// down mCommissioningWindowManager will shut down any PASE handshakes we
603+
// have going on.
604+
mSessions.ExpireAllSecureSessions();
599605
mCommissioningWindowManager.Shutdown();
600606
mMessageCounterManager.Shutdown();
601607
mExchangeMgr.Shutdown();

src/controller/CHIPDeviceControllerFactory.cpp

+9
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,15 @@ void DeviceControllerSystemState::Shutdown()
476476
mCASESessionManager = nullptr;
477477
}
478478

479+
// The above took care of CASE handshakes, and shutting down all the
480+
// controllers should have taken care of the PASE handshakes. Clean up any
481+
// outstanding secure sessions (shouldn't really be any, since controllers
482+
// should have handled that, but just in case).
483+
if (mSessionMgr != nullptr)
484+
{
485+
mSessionMgr->ExpireAllSecureSessions();
486+
}
487+
479488
// mCASEClientPool and mSessionSetupPool must be deallocated
480489
// after mCASESessionManager, which uses them.
481490

src/messaging/ExchangeMgr.h

+5-1
Original file line numberDiff line numberDiff line change
@@ -80,9 +80,13 @@ class DLL_EXPORT ExchangeManager : public SessionMessageDelegate
8080
* Shutdown the ExchangeManager. This terminates this instance
8181
* of the object and releases all held resources.
8282
*
83+
* Please see documentation for SessionManager::Shutdown() for ordering
84+
* dependecies between that and this Shutdown() method.
85+
*
8386
* @note
8487
* The protocol should only call this function after ensuring that
85-
* there are no active ExchangeContext objects. Furthermore, it is the
88+
* there are no active ExchangeContext objects (again, see
89+
* SessionManager::Shutdown() documentation). Furthermore, it is the
8690
* onus of the application to de-allocate the ExchangeManager
8791
* object after calling ExchangeManager::Shutdown().
8892
*/

src/transport/SessionManager.cpp

+17-4
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,15 @@ void SessionManager::Shutdown()
166166
// Ensure that we don't create new sessions as we iterate our session table.
167167
mState = State::kNotReady;
168168

169-
mSecureSessions.ForEachSession([&](auto session) {
170-
session->MarkForEviction();
171-
return Loop::Continue;
172-
});
169+
// Just in case some consumer forgot to do it, expire all our secure
170+
// sessions. Note that this stands a good chance of crashing with a
171+
// null-deref if there are in fact any secure sessions left, since they will
172+
// try to notify their exchanges, which will then try to operate on
173+
// partially-shut-down objects.
174+
ExpireAllSecureSessions();
175+
176+
// We don't have a safe way to check or affect the state of our
177+
// mUnauthenticatedSessions. We can only hope they got shut down properly.
173178

174179
mMessageCounterManager = nullptr;
175180

@@ -542,6 +547,14 @@ void SessionManager::ExpireAllPASESessions()
542547
});
543548
}
544549

550+
void SessionManager::ExpireAllSecureSessions()
551+
{
552+
mSecureSessions.ForEachSession([&](auto session) {
553+
session->MarkForEviction();
554+
return Loop::Continue;
555+
});
556+
}
557+
545558
void SessionManager::MarkSessionsAsDefunct(const ScopedNodeId & node, const Optional<Transport::SecureSession::Type> & type)
546559
{
547560
mSecureSessions.ForEachSession([&node, &type](auto session) {

src/transport/SessionManager.h

+28
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,20 @@ class DLL_EXPORT SessionManager : public TransportMgrDelegate, public FabricTabl
345345
return CHIP_NO_ERROR;
346346
}
347347

348+
/**
349+
* Expire all sessions for a given peer, as identified by a specific fabric
350+
* index and node ID.
351+
*/
348352
void ExpireAllSessions(const ScopedNodeId & node);
353+
354+
/**
355+
* Expire all sessions associated with the given fabric index.
356+
*
357+
* *NOTE* This is generally all sessions for a given fabric _EXCEPT_ if there are multiple
358+
* FabricInfo instances in the FabricTable that collide on the same logical fabric (i.e
359+
* root public key + fabric ID tuple). This can ONLY happen if multiple controller
360+
* instances on the same fabric is permitted and each is assigned a unique fabric index.
361+
*/
349362
void ExpireAllSessionsForFabric(FabricIndex fabricIndex);
350363

351364
/**
@@ -376,6 +389,12 @@ class DLL_EXPORT SessionManager : public TransportMgrDelegate, public FabricTabl
376389

377390
void ExpireAllPASESessions();
378391

392+
/**
393+
* Expire all secure sessions. See documentation for Shutdown on when it's
394+
* appropriate to use this.
395+
*/
396+
void ExpireAllSecureSessions();
397+
379398
/**
380399
* @brief
381400
* Marks all active sessions that match provided arguments as defunct.
@@ -422,6 +441,15 @@ class DLL_EXPORT SessionManager : public TransportMgrDelegate, public FabricTabl
422441
* @brief
423442
* Shutdown the Secure Session Manager. This terminates this instance
424443
* of the object and reset it's state.
444+
*
445+
* The proper order of shutdown for SessionManager is as follows:
446+
*
447+
* 1) Call ExpireAllSecureSessions() on the SessionManager, and ensure that any unauthenticated
448+
* sessions (e.g. CASEServer and CASESessionManager instances, or anything that does PASE
449+
* handshakes) are released.
450+
* 2) Shut down the exchange manager, so that it's no longer referencing
451+
* the to-be-shut-down SessionManager.
452+
* 3) Shut down the SessionManager.
425453
*/
426454
void Shutdown();
427455

0 commit comments

Comments
 (0)