Skip to content

Commit 2dd3bfd

Browse files
Fix crash if commissioning is canceled while waiting on an attestation delegate. (#32974)
When we extend the fail-safe after device attestation before calling into the delegate, we ended up with a dangling mInvokeCancelFn after the command finished and until CommissioningStageComplete(). That last would not happen until our delegate called back into us to continue commissioning. If an attempt was made to cancel commissioning during that time interval, we would end up crashing.
1 parent 1601e05 commit 2dd3bfd

File tree

3 files changed

+72
-18
lines changed

3 files changed

+72
-18
lines changed

src/controller/CHIPDeviceController.cpp

+20-12
Original file line numberDiff line numberDiff line change
@@ -1230,31 +1230,41 @@ void DeviceCommissioner::OnDeviceAttestationInformationVerification(
12301230
}
12311231

12321232
void DeviceCommissioner::OnArmFailSafeExtendedForDeviceAttestation(
1233-
void * context, const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data)
1233+
void * context, const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType &)
12341234
{
1235-
// If this function starts using "data", need to fix ExtendArmFailSafeForDeviceAttestation accordingly.
1235+
ChipLogProgress(Controller, "Successfully extended fail-safe timer to handle DA failure");
12361236
DeviceCommissioner * commissioner = static_cast<DeviceCommissioner *>(context);
12371237

1238-
if (!commissioner->mDeviceBeingCommissioned)
1238+
// We have completed our command invoke, but we're not going to finish the
1239+
// commissioning step until our client examines the attestation
1240+
// information. Clear out mInvokeCancelFn (which points at the
1241+
// CommandSender we just finished using) now, so it's not dangling.
1242+
commissioner->mInvokeCancelFn = nullptr;
1243+
1244+
commissioner->HandleDeviceAttestationCompleted();
1245+
}
1246+
1247+
void DeviceCommissioner::HandleDeviceAttestationCompleted()
1248+
{
1249+
if (!mDeviceBeingCommissioned)
12391250
{
12401251
return;
12411252
}
12421253

1243-
auto & params = commissioner->mDefaultCommissioner->GetCommissioningParameters();
1254+
auto & params = mDefaultCommissioner->GetCommissioningParameters();
12441255
Credentials::DeviceAttestationDelegate * deviceAttestationDelegate = params.GetDeviceAttestationDelegate();
12451256
if (deviceAttestationDelegate)
12461257
{
12471258
ChipLogProgress(Controller, "Device attestation completed, delegating continuation to client");
1248-
deviceAttestationDelegate->OnDeviceAttestationCompleted(commissioner, commissioner->mDeviceBeingCommissioned,
1249-
*commissioner->mAttestationDeviceInfo,
1250-
commissioner->mAttestationResult);
1259+
deviceAttestationDelegate->OnDeviceAttestationCompleted(this, mDeviceBeingCommissioned, *mAttestationDeviceInfo,
1260+
mAttestationResult);
12511261
}
12521262
else
12531263
{
12541264
ChipLogProgress(Controller, "Device attestation failed and no delegate set, failing commissioning");
12551265
CommissioningDelegate::CommissioningReport report;
1256-
report.Set<AttestationErrorInfo>(commissioner->mAttestationResult);
1257-
commissioner->CommissioningStageComplete(CHIP_ERROR_INTERNAL, report);
1266+
report.Set<AttestationErrorInfo>(mAttestationResult);
1267+
CommissioningStageComplete(CHIP_ERROR_INTERNAL, report);
12581268
}
12591269
}
12601270

@@ -1371,9 +1381,7 @@ void DeviceCommissioner::ExtendArmFailSafeForDeviceAttestation(const Credentials
13711381

13721382
if (!waitForFailsafeExtension)
13731383
{
1374-
// Callee does not use data argument.
1375-
const GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType data;
1376-
OnArmFailSafeExtendedForDeviceAttestation(this, data);
1384+
HandleDeviceAttestationCompleted();
13771385
}
13781386
}
13791387

src/controller/CHIPDeviceController.h

+1
Original file line numberDiff line numberDiff line change
@@ -956,6 +956,7 @@ class DLL_EXPORT DeviceCommissioner : public DeviceController,
956956
static void OnArmFailSafeExtendedForDeviceAttestation(
957957
void * context, const chip::app::Clusters::GeneralCommissioning::Commands::ArmFailSafeResponse::DecodableType & data);
958958
static void OnFailedToExtendedArmFailSafeDeviceAttestation(void * context, CHIP_ERROR error);
959+
void HandleDeviceAttestationCompleted();
959960

960961
static void OnICDManagementRegisterClientResponse(
961962
void * context, const app::Clusters::IcdManagement::Commands::RegisterClientResponse::DecodableType & data);

src/darwin/Framework/CHIPTests/MTRPairingTests.m

+51-6
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,29 @@
4848
// commissioning flows that have such a delegate.
4949
@interface NoOpAttestationDelegate : NSObject <MTRDeviceAttestationDelegate>
5050
@property (nonatomic) XCTestExpectation * expectation;
51+
@property (nonatomic) BOOL blockCommissioning;
5152

5253
- (instancetype)initWithExpectation:(XCTestExpectation *)expectation;
54+
// If blockCommissioning is YES, this delegate will never proceed from
55+
// its attestation verification callback.
56+
- (instancetype)initWithExpectation:(XCTestExpectation *)expectation blockCommissioning:(BOOL)blockCommissioning;
5357
@end
5458

5559
@implementation NoOpAttestationDelegate
60+
5661
- (instancetype)initWithExpectation:(XCTestExpectation *)expectation
62+
{
63+
return [self initWithExpectation:expectation blockCommissioning:NO];
64+
}
65+
66+
- (instancetype)initWithExpectation:(XCTestExpectation *)expectation blockCommissioning:(BOOL)blockCommissioning;
5767
{
5868
if (!(self = [super init])) {
5969
return nil;
6070
}
6171

6272
_expectation = expectation;
73+
_blockCommissioning = blockCommissioning;
6374
return self;
6475
}
6576

@@ -74,7 +85,10 @@ - (void)deviceAttestationCompletedForController:(MTRDeviceController *)controlle
7485
XCTAssertEqualObjects(attestationDeviceInfo.productID, @(0x8001));
7586
XCTAssertEqualObjects(attestationDeviceInfo.basicInformationVendorID, @(0xFFF1));
7687
XCTAssertEqualObjects(attestationDeviceInfo.basicInformationProductID, @(0x8000));
77-
[controller continueCommissioningDevice:opaqueDeviceHandle ignoreAttestationFailure:NO error:nil];
88+
89+
if (!self.blockCommissioning) {
90+
[controller continueCommissioningDevice:opaqueDeviceHandle ignoreAttestationFailure:NO error:nil];
91+
}
7892
}
7993

8094
@end
@@ -241,7 +255,7 @@ - (void)test004_PairWithAttestationDelegateFailsafeExtensionLong
241255
[self waitForExpectations:@[ expectation ] timeout:kTimeoutInSeconds];
242256
}
243257

244-
- (void)doPairingAndWaitForProgress:(NSString *)trigger
258+
- (void)doPairingAndWaitForProgress:(NSString *)trigger attestationDelegate:(nullable id<MTRDeviceAttestationDelegate>)attestationDelegate
245259
{
246260
XCTestExpectation * expectation = [self expectationWithDescription:@"Trigger message seen"];
247261
expectation.assertForOverFulfill = NO;
@@ -251,9 +265,19 @@ - (void)doPairingAndWaitForProgress:(NSString *)trigger
251265
}
252266
});
253267

268+
XCTestExpectation * attestationExpectation;
269+
if (attestationDelegate == nil) {
270+
attestationExpectation = [self expectationWithDescription:@"Attestation delegate called"];
271+
attestationDelegate = [[NoOpAttestationDelegate alloc] initWithExpectation:attestationExpectation];
272+
}
273+
274+
// Make sure we exercise the codepath that has an attestation delegate and
275+
// extends the fail-safe while waiting for that delegate. And make sure our
276+
// fail-safe extension is long enough that we actually trigger a fail-safe
277+
// extension (so longer than the 1-minute default).
254278
__auto_type * controllerDelegate = [[MTRPairingTestControllerDelegate alloc] initWithExpectation:nil
255-
attestationDelegate:nil
256-
failSafeExtension:nil];
279+
attestationDelegate:attestationDelegate
280+
failSafeExtension:@(90)];
257281
[sController setDeviceControllerDelegate:controllerDelegate queue:dispatch_get_main_queue()];
258282
self.controllerDelegate = controllerDelegate;
259283

@@ -264,13 +288,17 @@ - (void)doPairingAndWaitForProgress:(NSString *)trigger
264288
XCTAssertNil(error);
265289

266290
[self waitForExpectations:@[ expectation ] timeout:kPairingTimeoutInSeconds];
291+
292+
if (attestationExpectation) {
293+
[self waitForExpectations:@[ attestationExpectation ] timeout:kTimeoutInSeconds];
294+
}
267295
MTRSetLogCallback(0, nil);
268296
}
269297

270-
- (void)doPairingTestAfterCancellationAtProgress:(NSString *)trigger
298+
- (void)doPairingTestAfterCancellationAtProgress:(NSString *)trigger attestationDelegate:(nullable id<MTRDeviceAttestationDelegate>)attestationDelegate
271299
{
272300
// Run pairing up and wait for the trigger
273-
[self doPairingAndWaitForProgress:trigger];
301+
[self doPairingAndWaitForProgress:trigger attestationDelegate:attestationDelegate];
274302

275303
// Call StopPairing and wait for the commissioningComplete callback
276304
XCTestExpectation * expectation = [self expectationWithDescription:@"commissioningComplete delegate method called"];
@@ -289,6 +317,11 @@ - (void)doPairingTestAfterCancellationAtProgress:(NSString *)trigger
289317
[self doPairingTestWithAttestationDelegate:nil failSafeExtension:nil];
290318
}
291319

320+
- (void)doPairingTestAfterCancellationAtProgress:(NSString *)trigger
321+
{
322+
[self doPairingTestAfterCancellationAtProgress:trigger attestationDelegate:nil];
323+
}
324+
292325
- (void)test005_pairingAfterCancellation_ReadCommissioningInfo
293326
{
294327
// @"Sending read request for commissioning information"
@@ -306,4 +339,16 @@ - (void)test007_pairingAfterCancellation_FindOperational
306339
[self doPairingTestAfterCancellationAtProgress:@"FindOrEstablishSession:"];
307340
}
308341

342+
- (void)test008_pairingAfterCancellation_DeviceAttestationVerification
343+
{
344+
// Cancel pairing while we are waiting for our client to decide what to do
345+
// with the attestation information we got.
346+
XCTestExpectation * attestationExpectation = [self expectationWithDescription:@"Blocking attestation delegate called"];
347+
__auto_type * attestationDelegate = [[NoOpAttestationDelegate alloc] initWithExpectation:attestationExpectation blockCommissioning:YES];
348+
349+
[self doPairingTestAfterCancellationAtProgress:@"Successfully extended fail-safe timer to handle DA failure" attestationDelegate:attestationDelegate];
350+
351+
[self waitForExpectations:@[ attestationExpectation ] timeout:kTimeoutInSeconds];
352+
}
353+
309354
@end

0 commit comments

Comments
 (0)