Skip to content

Commit

Permalink
Fix CASEServer cleanup to not nuke unrelated CASE handshakes.
Browse files Browse the repository at this point in the history
We could run into a problem when an invalid Sigma1 (e.g. not matching any
fabric) was received, because we would do the following:

1) Call CASEServer::OnSessionEstablishmentError from inside
   CASESession::OnMessageReceived, which would queue a task to blow away the
   CASEServer state and start listening for new CASE handshakes
   (PrepareForSessionEstablishment).

2) Return an error from CASESession::OnMessageReceived, which would cause us to
   synchronously do call PrepareForSessionEstablishment.

If we now got a new Sigma1 before the async task ran (e.g. if it was already
waiting in the queue), we could respond to the Sigma1 with Sigma2Resume, then
the async task would run and blow away our state.

We would end up in a situation where the other side then sends a StatusResponse
and thinks CASE is established, but on our end we don't recognize the session ID
for the session they think has been established.

The fix is to restrict the async behavior to the one case it's actually needed
(OnSessionReleased), for both CASE and PASE, to synchronously call
PrepareForSessionEstablishment from OnSessionEstablishmentError, and to remove
the now-redundant PrepareForSessionEstablishment call on error return from
CASESession::OnMessageReceived.
  • Loading branch information
bzbarsky-apple committed Nov 16, 2022
1 parent bbf3111 commit 2b06024
Show file tree
Hide file tree
Showing 6 changed files with 38 additions and 19 deletions.
18 changes: 3 additions & 15 deletions src/protocols/secure_channel/CASEServer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,8 @@ CHIP_ERROR CASEServer::OnMessageReceived(Messaging::ExchangeContext * ec, const
SuccessOrExit(err);

exit:
if (err != CHIP_NO_ERROR)
{
PrepareForSessionEstablishment();
}

// CASESession::OnMessageReceived guarantees that it will call
// OnSessionEstablishmentError if it returns error, so nothing else to do here.
return err;
}

Expand Down Expand Up @@ -157,16 +154,7 @@ void CASEServer::OnSessionEstablishmentError(CHIP_ERROR err)
{
ChipLogError(Inet, "CASE Session establishment failed: %" CHIP_ERROR_FORMAT, err.Format());

//
// We're not allowed to call methods that will eventually result in calling SessionManager::AllocateSecureSession
// from a SessionDelegate::OnSessionReleased callback. Schedule the preparation as an async work item.
//
mSessionManager->SystemLayer()->ScheduleWork(
[](auto * systemLayer, auto * appState) -> void {
CASEServer * _this = static_cast<CASEServer *>(appState);
_this->PrepareForSessionEstablishment();
},
this);
PrepareForSessionEstablishment();
}

void CASEServer::OnSessionEstablished(const SessionHandle & session)
Expand Down
4 changes: 2 additions & 2 deletions src/protocols/secure_channel/CASESession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,9 @@ CASESession::~CASESession()

void CASESession::OnSessionReleased()
{
// Call into our super-class before we clear our state.
PairingSession::OnSessionReleased();
Clear();
// Do this last in case the delegate frees us.
NotifySessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED);
}

void CASESession::Clear()
Expand Down
4 changes: 2 additions & 2 deletions src/protocols/secure_channel/PASESession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,9 +75,9 @@ PASESession::~PASESession()

void PASESession::OnSessionReleased()
{
// Call into our super-class before we clear our state.
PairingSession::OnSessionReleased();
Clear();
// Do this last in case the delegate frees us.
NotifySessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED);
}

void PASESession::Finish()
Expand Down
24 changes: 24 additions & 0 deletions src/protocols/secure_channel/PairingSession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,4 +182,28 @@ void PairingSession::NotifySessionEstablishmentError(CHIP_ERROR error)
delegate->OnSessionEstablishmentError(error);
}

void PairingSession::OnSessionReleased()
{
if (mRole == CryptoContext::SessionRole::kInitiator) {
NotifySessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED);
return;
}

// Send the error notification async, because our delegate is likely to want
// to create a new session to listen for new connection attempts, and doing
// that under an OnSessionReleased notification is not safe.
if (!mSessionManager)
{
return;
}

mSessionManager->SystemLayer()->ScheduleWork(
[](auto * systemLayer, auto * appState) -> void {
ChipLogError(Inet, "ASYNC CASE Session establishment failed");
auto * _this = static_cast<PairingSession *>(appState);
_this->NotifySessionEstablishmentError(CHIP_ERROR_CONNECTION_ABORTED);
},
this);
}

} // namespace chip
1 change: 1 addition & 0 deletions src/protocols/secure_channel/PairingSession.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class DLL_EXPORT PairingSession : public SessionDelegate

// Implement SessionDelegate
NewSessionHandlingPolicy GetNewSessionHandlingPolicy() override { return NewSessionHandlingPolicy::kStayAtOldSession; }
void OnSessionReleased() override;

Optional<uint16_t> GetLocalSessionId() const
{
Expand Down
6 changes: 6 additions & 0 deletions src/protocols/secure_channel/tests/TestPASESession.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ void SecurePairingHandshakeTestCommon(nlTestSuite * inSuite, void * inContext, S
NL_TEST_ASSERT(inSuite, session.HasValue());
session.Value()->AsSecureSession()->MarkForEviction();

// Evicting a session async notifies the PASESession's delegate. Normally
// that notification is what would delete the PASESession, but in our case
// that will happen as soon as things come off the stack. So make sure to
// process the async bits before that happens.
ctx.DrainAndServiceIO();

// And check that this did not result in any new notifications.
NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingErrors == 0);
NL_TEST_ASSERT(inSuite, delegateAccessory.mNumPairingComplete == 1);
Expand Down

0 comments on commit 2b06024

Please sign in to comment.