[mac] enhance command ID validation and parsing for 2015-spec frames (#11787)

The treatment of the Command ID field in a MAC command frame is
dependent on the IEEE 802.15.4 version. In the 2015 specification,
it is part of the encrypted payload, while in earlier versions, it
is part of the unencrypted MAC header. The `FindPayloadIndex()`
method correctly accounts for both cases.

This commit enhances the frame parsing and validation logic to account
for this difference.
- `ValidatePsdu()` is updated to ensure the frame is long enough to
  contain the Command ID when validating a 2015-version frame.
- `GetCommandId()` is updated to validate the presence of the Command
  ID field before access, fixing a potential out-of-bounds read.
- A new `IsMacCommand()` helper method is introduced to improve code
  clarity and replace direct frame type checks.
This commit is contained in:
Abtin Keshavarzian
2025-08-06 19:04:13 -07:00
committed by GitHub
parent 7ab5c5c042
commit e76db3af61
2 changed files with 47 additions and 3 deletions
+39 -3
View File
@@ -274,6 +274,22 @@ Error Frame::ValidatePsdu(void) const
uint8_t index = FindPayloadIndex();
VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
if (IsMacCommand() && IsVersion2015())
{
// The treatment of the Command ID field in a MAC command frame
// is version-dependent. In the 2015 spec, it is part of the
// encrypted payload, while in earlier versions, it is part of
// the MAC header.
//
// `FindPayloadIndex()` accounts for this difference and returns
// the starting index of the payload. To correctly validate a
// 2015 frame, we must ensure it is long enough to contain the
// Command ID, so we include its size in the length check.
index += kCommandIdSize;
}
VerifyOrExit((index + GetFooterLength()) <= mLength, error = kErrorParse);
exit:
@@ -826,7 +842,18 @@ Error Frame::GetCommandId(uint8_t &aCommandId) const
VerifyOrExit(index != kInvalidIndex, error = kErrorParse);
aCommandId = mPsdu[IsVersion2015() ? index : (index - 1)];
// The treatment of the Command ID field in a MAC command frame
// is version-dependent. In the 2015 spec, it is part of the
// encrypted payload, while in earlier versions, it is part of
// the MAC header. `FindPayloadIndex() accounts for both cases.
if (!IsVersion2015())
{
index -= kCommandIdSize;
}
VerifyOrExit(index + kCommandIdSize + GetFooterLength() <= mLength, error = kErrorParse);
aCommandId = mPsdu[index];
exit:
return error;
@@ -837,7 +864,7 @@ bool Frame::IsDataRequestCommand(void) const
bool isDataRequest = false;
uint8_t commandId;
VerifyOrExit(GetType() == kTypeMacCmd);
VerifyOrExit(IsMacCommand());
SuccessOrExit(GetCommandId(commandId));
isDataRequest = (commandId == kMacCmdDataRequest);
@@ -1078,8 +1105,17 @@ uint8_t Frame::FindPayloadIndex(void) const
}
#endif // OPENTHREAD_CONFIG_MAC_HEADER_IE_SUPPORT
if (!IsVersion2015() && (GetFrameControlField() & kFcfFrameTypeMask) == kTypeMacCmd)
if (IsMacCommand() && !IsVersion2015())
{
// The treatment of the Command ID field in a MAC command frame
// is version-dependent. In IEEE 802.15.4-2015, it is part of
// the payload and therefore encrypted. In earlier versions, it
// is part of the MAC header and not encrypted.
//
// This adjusts the index to point to the start of the payload
// for pre-2015 frames. The `GetCommandId()` method also
// accounts for this version-specific difference.
index += kCommandIdSize;
}
+8
View File
@@ -178,6 +178,14 @@ public:
*/
bool IsAck(void) const { return GetType() == kTypeAck; }
/**
* Returns whether the frame is a MAC Command frame.
*
* @retval TRUE If this is a MAC Command frame.
* @retval FALSE If this is not a MAC Command Frame.
*/
bool IsMacCommand(void) const { return GetType() == kTypeMacCmd; }
#if OPENTHREAD_CONFIG_WAKEUP_COORDINATOR_ENABLE || OPENTHREAD_CONFIG_WAKEUP_END_DEVICE_ENABLE
/**
* This method returns whether the frame is an IEEE 802.15.4 Wake-up frame.