[lowpan] limit recursion depth in 6LoWPAN decompression (#12669)

This commit introduces a maximum recursion depth limit for 6LoWPAN
decompression to prevent potential stack exhaustion from maliciously
crafted frames with deep IPv6-in-IPv6 encapsulation.

- Added a private constant kMaxRecursionDepth in the Lowpan
  class to define the maximum allowed recursion depth.
- Updated Lowpan::Decompress() to track and validate the current
  recursion depth, returning kErrorParse if the limit is exceeded.
- Added a new unit test TestLowpanDecompressRecursion in
  tests/unit/test_lowpan.cpp to verify the recursion limit and
  ensure it correctly handles both excessive and legitimate
  encapsulation levels.
This commit is contained in:
Jonathan Hui
2026-03-11 14:03:29 -05:00
committed by GitHub
parent 3e3690a068
commit 3390085720
4 changed files with 89 additions and 3 deletions
+5 -2
View File
@@ -977,7 +977,8 @@ exit:
Error Lowpan::Decompress(Message &aMessage,
const Mac::Addresses &aMacAddrs,
FrameData &aFrameData,
uint16_t aDatagramLength)
uint16_t aDatagramLength,
uint8_t aRecursionDepth)
{
Error error = kErrorParse;
Ip6::Header ip6Header;
@@ -985,6 +986,8 @@ Error Lowpan::Decompress(Message &aMessage,
uint16_t ip6PayloadLength;
uint16_t currentOffset = aMessage.GetOffset();
VerifyOrExit(aRecursionDepth <= kMaxRecursionDepth);
SuccessOrExit(DecompressBaseHeader(ip6Header, compressed, aMacAddrs, aFrameData));
SuccessOrExit(aMessage.Append(ip6Header));
@@ -1005,7 +1008,7 @@ Error Lowpan::Decompress(Message &aMessage,
aFrameData.SkipOver(sizeof(uint8_t));
SuccessOrExit(Decompress(aMessage, aMacAddrs, aFrameData, aDatagramLength));
SuccessOrExit(Decompress(aMessage, aMacAddrs, aFrameData, aDatagramLength, aRecursionDepth + 1));
}
else
{
+4 -1
View File
@@ -199,7 +199,8 @@ public:
Error Decompress(Message &aMessage,
const Mac::Addresses &aMacAddrs,
FrameData &aFrameData,
uint16_t aDatagramLength);
uint16_t aDatagramLength,
uint8_t aRecursionDepth = 0);
/**
* Decompresses a LOWPAN_IPHC header.
@@ -254,6 +255,8 @@ public:
void MarkCompressedEcn(Message &aMessage, uint16_t aOffset);
private:
static constexpr uint8_t kMaxRecursionDepth = 4;
static constexpr uint16_t kHcDispatch = 3 << 13;
static constexpr uint16_t kHcDispatchMask = 7 << 13;
+78
View File
@@ -2087,6 +2087,83 @@ void TestLowpanFragmentHeader(void)
VerifyOrQuit(frameData.GetBytes() == frame);
}
void TestLowpanDecompressRecursion(void)
{
Instance *instance = testInitInstance();
Lowpan::Lowpan &lowpan = instance->Get<Lowpan::Lowpan>();
MessagePool &messagePool = instance->Get<MessagePool>();
Mac::Addresses macAddrs;
macAddrs.mSource.SetShort(0x1234);
macAddrs.mDestination.SetShort(0x5678);
printf("\n=== Test name: Lowpan Decompress Recursion ===\n\n");
// Case 1: Excessive recursion (41 levels) -> Should fail with kErrorParse
{
uint8_t payload[256];
uint16_t length = 0;
for (int i = 0; i < 41; i++)
{
payload[length++] = 0x7e;
payload[length++] = 0x33;
payload[length++] = 0xee;
}
payload[length++] = 0x7a;
payload[length++] = 0x33;
payload[length++] = 0x11;
Message *message = messagePool.Allocate(Message::kTypeIp6);
VerifyOrQuit(message != nullptr, "Message::Allocate failed");
FrameData frameData;
frameData.Init(payload, length);
printf("Case 1: Excessive recursion (41 levels)...\n");
Error error = lowpan.Decompress(*message, macAddrs, frameData, 0);
printf("Decompression returned error: %s\n", ErrorToString(error));
VerifyOrQuit(error == kErrorParse, "Case 1 failed: should have returned kErrorParse");
message->Free();
}
// Case 2: Legitimate recursion (2 levels) -> Should succeed
{
uint8_t payload[256];
uint16_t length = 0;
// Level 0
payload[length++] = 0x7e;
payload[length++] = 0x33;
payload[length++] = 0xee;
// Level 1
payload[length++] = 0x7e;
payload[length++] = 0x33;
payload[length++] = 0xee;
// Level 2 (Terminal)
payload[length++] = 0x7a;
payload[length++] = 0x33;
payload[length++] = 0x11;
Message *message = messagePool.Allocate(Message::kTypeIp6);
VerifyOrQuit(message != nullptr, "Message::Allocate failed");
FrameData frameData;
frameData.Init(payload, length);
printf("Case 2: Legitimate recursion (2 levels)...\n");
Error error = lowpan.Decompress(*message, macAddrs, frameData, 0);
printf("Decompression returned error: %s\n", ErrorToString(error));
VerifyOrQuit(error == kErrorNone, "Case 2 failed: should have returned kErrorNone");
message->Free();
}
testFreeInstance(instance);
printf("PASS\n\n");
}
} // namespace ot
int main(void)
@@ -2094,6 +2171,7 @@ int main(void)
ot::TestLowpanIphc();
ot::TestLowpanMeshHeader();
ot::TestLowpanFragmentHeader();
ot::TestLowpanDecompressRecursion();
printf("All tests passed\n");
return 0;
+2
View File
@@ -259,6 +259,8 @@ public:
const char *mTestName;
};
void TestLowpanDecompressRecursion(void);
} // namespace ot
#endif // OT_UNIT_TEST_LOWPAN_HPP_