Skip to content

Commit 8d2516a

Browse files
Do proper ACL checks on event reads/subscriptions. (#26761)
* Do proper ACL checks on event reads/subscriptions. This adds the following functionality: 1. We now correctly detect subsciptions that don't have any access to anything, even if they have an event path in the subscribe request. For paths with a wildcard event id, this check assumes read privileges are needed when event lists are disabled, and uses the actual event-specific privileges when event lists are enabled. 2. When doing reads of an unsupported event, correctly return an errors instead of an empty event list. 3. Fix various unit test mocks to provide the information needed for the new checks. 4. Update expectation in existing YAML test that was checking an "unimplemented event" case. * Address review comments. * Fix darwin build. * Fix Darwin tests, now that we get errors for unsupported events. * Move function declarations to a non-codegen-dependent header. * Handle ACL checks for event wildcards even if we have no EventList. * Update to spec change for unsupported event errors. * Address review comments.
1 parent ecd0f6f commit 8d2516a

18 files changed

+535
-123
lines changed

src/app/InteractionModelEngine.cpp

+159-17
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,11 @@
3030
#include "access/RequestPath.h"
3131
#include "access/SubjectDescriptor.h"
3232
#include <app/RequiredPrivilege.h>
33+
#include <app/util/af-types.h>
34+
#include <app/util/endpoint-config-api.h>
3335
#include <lib/core/TLVUtilities.h>
3436
#include <lib/support/CodeUtils.h>
3537

36-
extern bool emberAfContainsAttribute(chip::EndpointId endpoint, chip::ClusterId clusterId, chip::AttributeId attributeId);
37-
3838
namespace chip {
3939
namespace app {
4040

@@ -351,10 +351,8 @@ CHIP_ERROR InteractionModelEngine::ParseAttributePaths(const Access::SubjectDesc
351351
aHasValidAttributePath = false;
352352
aRequestedAttributePathCount = 0;
353353

354-
while (CHIP_NO_ERROR == (err = pathReader.Next()))
354+
while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag())))
355355
{
356-
VerifyOrReturnError(TLV::AnonymousTag() == pathReader.GetTag(), CHIP_ERROR_INVALID_TLV_TAG);
357-
358356
AttributePathIB::Parser path;
359357
//
360358
// We create an iterator to point to a single item object list that tracks the path we just parsed.
@@ -413,6 +411,152 @@ CHIP_ERROR InteractionModelEngine::ParseAttributePaths(const Access::SubjectDesc
413411
return err;
414412
}
415413

414+
static bool CanAccess(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteClusterPath & aPath,
415+
Access::Privilege aNeededPrivilege)
416+
{
417+
Access::RequestPath requestPath{ .cluster = aPath.mClusterId, .endpoint = aPath.mEndpointId };
418+
CHIP_ERROR err = Access::GetAccessControl().Check(aSubjectDescriptor, requestPath, aNeededPrivilege);
419+
return (err == CHIP_NO_ERROR);
420+
}
421+
422+
static bool CanAccess(const Access::SubjectDescriptor & aSubjectDescriptor, const ConcreteEventPath & aPath)
423+
{
424+
return CanAccess(aSubjectDescriptor, aPath, RequiredPrivilege::ForReadEvent(aPath));
425+
}
426+
427+
/**
428+
* Helper to handle wildcard events in the event path.
429+
*/
430+
static bool HasValidEventPathForEndpointAndCluster(EndpointId aEndpoint, const EmberAfCluster * aCluster,
431+
const EventPathParams & aEventPath,
432+
const Access::SubjectDescriptor & aSubjectDescriptor)
433+
{
434+
if (aEventPath.HasWildcardEventId())
435+
{
436+
#if CHIP_CONFIG_ENABLE_EVENTLIST_ATTRIBUTE
437+
for (decltype(aCluster->eventCount) idx = 0; idx > aCluster->eventCount; ++idx)
438+
{
439+
ConcreteEventPath path(aEndpoint, aCluster->clusterId, aCluster->eventList[idx]);
440+
// If we get here, the path exists. We just have to do an ACL check for it.
441+
bool isValid = CanAccess(aSubjectDescriptor, path);
442+
if (isValid)
443+
{
444+
return true;
445+
}
446+
}
447+
448+
return false;
449+
#else
450+
// We have no way to expand wildcards. Just assume that we would need
451+
// View permissions for whatever events are involved.
452+
ConcreteClusterPath clusterPath(aEndpoint, aCluster->clusterId);
453+
return CanAccess(aSubjectDescriptor, clusterPath, Access::Privilege::kView);
454+
#endif
455+
}
456+
457+
ConcreteEventPath path(aEndpoint, aCluster->clusterId, aEventPath.mEventId);
458+
if (CheckEventSupportStatus(path) != Status::Success)
459+
{
460+
// Not an existing event path.
461+
return false;
462+
}
463+
return CanAccess(aSubjectDescriptor, path);
464+
}
465+
466+
/**
467+
* Helper to handle wildcard clusters in the event path.
468+
*/
469+
static bool HasValidEventPathForEndpoint(EndpointId aEndpoint, const EventPathParams & aEventPath,
470+
const Access::SubjectDescriptor & aSubjectDescriptor)
471+
{
472+
if (aEventPath.HasWildcardClusterId())
473+
{
474+
auto * endpointType = emberAfFindEndpointType(aEndpoint);
475+
if (endpointType == nullptr)
476+
{
477+
// Not going to have any valid paths in here.
478+
return false;
479+
}
480+
481+
for (decltype(endpointType->clusterCount) idx = 0; idx < endpointType->clusterCount; ++idx)
482+
{
483+
bool hasValidPath =
484+
HasValidEventPathForEndpointAndCluster(aEndpoint, &endpointType->cluster[idx], aEventPath, aSubjectDescriptor);
485+
if (hasValidPath)
486+
{
487+
return true;
488+
}
489+
}
490+
491+
return false;
492+
}
493+
494+
auto * cluster = emberAfFindServerCluster(aEndpoint, aEventPath.mClusterId);
495+
if (cluster == nullptr)
496+
{
497+
// Nothing valid here.
498+
return false;
499+
}
500+
return HasValidEventPathForEndpointAndCluster(aEndpoint, cluster, aEventPath, aSubjectDescriptor);
501+
}
502+
503+
CHIP_ERROR InteractionModelEngine::ParseEventPaths(const Access::SubjectDescriptor & aSubjectDescriptor,
504+
EventPathIBs::Parser & aEventPathListParser, bool & aHasValidEventPath,
505+
size_t & aRequestedEventPathCount)
506+
{
507+
TLV::TLVReader pathReader;
508+
aEventPathListParser.GetReader(&pathReader);
509+
CHIP_ERROR err = CHIP_NO_ERROR;
510+
511+
aHasValidEventPath = false;
512+
aRequestedEventPathCount = 0;
513+
514+
while (CHIP_NO_ERROR == (err = pathReader.Next(TLV::AnonymousTag())))
515+
{
516+
EventPathIB::Parser path;
517+
ReturnErrorOnFailure(path.Init(pathReader));
518+
519+
EventPathParams eventPath;
520+
ReturnErrorOnFailure(path.ParsePath(eventPath));
521+
522+
++aRequestedEventPathCount;
523+
524+
if (aHasValidEventPath)
525+
{
526+
// Can skip all the rest of the checking.
527+
continue;
528+
}
529+
530+
// The definition of "valid path" is "path exists and ACL allows
531+
// access". We need to do some expansion of wildcards to handle that.
532+
if (eventPath.HasWildcardEndpointId())
533+
{
534+
for (uint16_t endpointIndex = 0; !aHasValidEventPath && endpointIndex < emberAfEndpointCount(); ++endpointIndex)
535+
{
536+
if (!emberAfEndpointIndexIsEnabled(endpointIndex))
537+
{
538+
continue;
539+
}
540+
aHasValidEventPath =
541+
HasValidEventPathForEndpoint(emberAfEndpointFromIndex(endpointIndex), eventPath, aSubjectDescriptor);
542+
}
543+
}
544+
else
545+
{
546+
// No need to check whether the endpoint is enabled, because
547+
// emberAfFindEndpointType returns null for disabled endpoints.
548+
aHasValidEventPath = HasValidEventPathForEndpoint(eventPath.mEndpointId, eventPath, aSubjectDescriptor);
549+
}
550+
}
551+
552+
if (err == CHIP_ERROR_END_OF_TLV)
553+
{
554+
err = CHIP_NO_ERROR;
555+
}
556+
557+
return err;
558+
}
559+
416560
Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest(Messaging::ExchangeContext * apExchangeContext,
417561
const PayloadHeader & aPayloadHeader,
418562
System::PacketBufferHandle && aPayload,
@@ -472,6 +616,7 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest
472616
size_t requestedEventPathCount = 0;
473617
AttributePathIBs::Parser attributePathListParser;
474618
bool hasValidAttributePath = false;
619+
bool hasValidEventPath = false;
475620

476621
CHIP_ERROR err = subscribeRequestParser.GetAttributeRequests(&attributePathListParser);
477622
if (err == CHIP_NO_ERROR)
@@ -489,14 +634,16 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest
489634
return Status::InvalidAction;
490635
}
491636

492-
EventPathIBs::Parser eventpathListParser;
493-
err = subscribeRequestParser.GetEventRequests(&eventpathListParser);
637+
EventPathIBs::Parser eventPathListParser;
638+
err = subscribeRequestParser.GetEventRequests(&eventPathListParser);
494639
if (err == CHIP_NO_ERROR)
495640
{
496-
TLV::TLVReader pathReader;
497-
eventpathListParser.GetReader(&pathReader);
498-
ReturnErrorCodeIf(TLV::Utilities::Count(pathReader, requestedEventPathCount, false) != CHIP_NO_ERROR,
499-
Status::InvalidAction);
641+
auto subjectDescriptor = apExchangeContext->GetSessionHandle()->AsSecureSession()->GetSubjectDescriptor();
642+
err = ParseEventPaths(subjectDescriptor, eventPathListParser, hasValidEventPath, requestedEventPathCount);
643+
if (err != CHIP_NO_ERROR)
644+
{
645+
return Status::InvalidAction;
646+
}
500647
}
501648
else if (err != CHIP_ERROR_END_OF_TLV)
502649
{
@@ -512,12 +659,7 @@ Protocols::InteractionModel::Status InteractionModelEngine::OnReadInitialRequest
512659
return Status::InvalidAction;
513660
}
514661

515-
//
516-
// TODO: We don't have an easy way to do a similar 'path expansion' for events to deduce
517-
// access so for now, assume that the presence of any path in the event list means they
518-
// might have some access to those events.
519-
//
520-
if (!hasValidAttributePath && requestedEventPathCount == 0)
662+
if (!hasValidAttributePath && !hasValidEventPath)
521663
{
522664
ChipLogError(InteractionModel,
523665
"Subscription from [%u:" ChipLogFormatX64 "] has no access at all. Rejecting request.",

src/app/InteractionModelEngine.h

+20-1
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
#include <app/CommandSender.h>
4747
#include <app/ConcreteAttributePath.h>
4848
#include <app/ConcreteCommandPath.h>
49+
#include <app/ConcreteEventPath.h>
4950
#include <app/DataVersionFilter.h>
5051
#include <app/EventPathParams.h>
5152
#include <app/ObjectList.h>
@@ -398,6 +399,19 @@ class InteractionModelEngine : public Messaging::UnsolicitedMessageHandler,
398399
AttributePathIBs::Parser & aAttributePathListParser, bool & aHasValidAttributePath,
399400
size_t & aRequestedAttributePathCount);
400401

402+
/**
403+
* This parses the event path list to ensure it is well formed. If so, for each path in the list, it will expand to a list
404+
* of concrete paths and walk each path to check if it has privileges to read that event.
405+
*
406+
* If there is AT LEAST one "existent path" (as the spec calls it) that has sufficient privilege, aHasValidEventPath
407+
* will be set to true. Otherwise, it will be set to false.
408+
*
409+
* aRequestedEventPathCount will be updated to reflect the number of event paths in the request.
410+
*/
411+
static CHIP_ERROR ParseEventPaths(const Access::SubjectDescriptor & aSubjectDescriptor,
412+
EventPathIBs::Parser & aEventPathListParser, bool & aHasValidEventPath,
413+
size_t & aRequestedEventPathCount);
414+
401415
/**
402416
* Called when Interaction Model receives a Read Request message. Errors processing
403417
* the Read Request are handled entirely within this function. If the
@@ -677,7 +691,12 @@ bool IsDeviceTypeOnEndpoint(DeviceTypeId deviceType, EndpointId endpoint);
677691
*
678692
* @retval The metadata of the attribute, will return null if the given attribute does not exists.
679693
*/
680-
const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aConcreteClusterPath);
694+
const EmberAfAttributeMetadata * GetAttributeMetadata(const ConcreteAttributePath & aPath);
695+
696+
/**
697+
* Returns the event support status for the given event, as an interaction model status.
698+
*/
699+
Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath);
681700

682701
} // namespace app
683702
} // namespace chip

src/app/reporting/Engine.cpp

+17-3
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ CHIP_ERROR Engine::BuildSingleReportDataAttributeReportIBs(ReportDataMessage::Bu
295295

296296
CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool & aHasEncodedData, ReadHandler * apReadHandler)
297297
{
298+
using Protocols::InteractionModel::Status;
299+
298300
CHIP_ERROR err = CHIP_NO_ERROR;
299301
for (auto current = apReadHandler->mpEventPathList; current != nullptr;)
300302
{
@@ -304,8 +306,21 @@ CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool &
304306
continue;
305307
}
306308

307-
Access::RequestPath requestPath{ .cluster = current->mValue.mClusterId, .endpoint = current->mValue.mEndpointId };
308309
ConcreteEventPath path(current->mValue.mEndpointId, current->mValue.mClusterId, current->mValue.mEventId);
310+
Status status = CheckEventSupportStatus(path);
311+
if (status != Status::Success)
312+
{
313+
TLV::TLVWriter checkpoint = aWriter;
314+
err = EventReportIB::ConstructEventStatusIB(aWriter, path, StatusIB(status));
315+
if (err != CHIP_NO_ERROR)
316+
{
317+
aWriter = checkpoint;
318+
break;
319+
}
320+
aHasEncodedData = true;
321+
}
322+
323+
Access::RequestPath requestPath{ .cluster = current->mValue.mClusterId, .endpoint = current->mValue.mEndpointId };
309324
Access::Privilege requestPrivilege = RequiredPrivilege::ForReadEvent(path);
310325

311326
err = Access::GetAccessControl().Check(apReadHandler->GetSubjectDescriptor(), requestPath, requestPrivilege);
@@ -316,8 +331,7 @@ CHIP_ERROR Engine::CheckAccessDeniedEventPaths(TLV::TLVWriter & aWriter, bool &
316331
else
317332
{
318333
TLV::TLVWriter checkpoint = aWriter;
319-
err = EventReportIB::ConstructEventStatusIB(aWriter, path,
320-
StatusIB(Protocols::InteractionModel::Status::UnsupportedAccess));
334+
err = EventReportIB::ConstructEventStatusIB(aWriter, path, StatusIB(Status::UnsupportedAccess));
321335
if (err != CHIP_NO_ERROR)
322336
{
323337
aWriter = checkpoint;

src/app/tests/TestAclAttribute.cpp

+12
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
#include "lib/support/CHIPMem.h"
2020
#include <access/examples/PermissiveAccessControlDelegate.h>
2121
#include <app/AttributeAccessInterface.h>
22+
#include <app/ConcreteAttributePath.h>
23+
#include <app/ConcreteEventPath.h>
2224
#include <app/InteractionModelEngine.h>
2325
#include <app/MessageDef/AttributeReportIBs.h>
2426
#include <app/MessageDef/EventDataIB.h>
@@ -140,6 +142,16 @@ bool ConcreteAttributePathExists(const ConcreteAttributePath & aPath)
140142
return aPath.mClusterId != kTestDeniedClusterId1;
141143
}
142144

145+
Protocols::InteractionModel::Status CheckEventSupportStatus(const ConcreteEventPath & aPath)
146+
{
147+
if (aPath.mClusterId == kTestDeniedClusterId1)
148+
{
149+
return Protocols::InteractionModel::Status::UnsupportedCluster;
150+
}
151+
152+
return Protocols::InteractionModel::Status::Success;
153+
}
154+
143155
class TestAclAttribute
144156
{
145157
public:

0 commit comments

Comments
 (0)