From 339008572029645d49ec8ee25e6554edbb252b67 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Wed, 11 Mar 2026 14:03:29 -0500 Subject: [PATCH] [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. --- src/core/thread/lowpan.cpp | 7 +++- src/core/thread/lowpan.hpp | 5 ++- tests/unit/test_lowpan.cpp | 78 ++++++++++++++++++++++++++++++++++++++ tests/unit/test_lowpan.hpp | 2 + 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/src/core/thread/lowpan.cpp b/src/core/thread/lowpan.cpp index 58790c0fc..b90865b87 100644 --- a/src/core/thread/lowpan.cpp +++ b/src/core/thread/lowpan.cpp @@ -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 { diff --git a/src/core/thread/lowpan.hpp b/src/core/thread/lowpan.hpp index 47c7494ff..ed12ecf6f 100644 --- a/src/core/thread/lowpan.hpp +++ b/src/core/thread/lowpan.hpp @@ -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; diff --git a/tests/unit/test_lowpan.cpp b/tests/unit/test_lowpan.cpp index 22e1e1139..9509b2097 100644 --- a/tests/unit/test_lowpan.cpp +++ b/tests/unit/test_lowpan.cpp @@ -2087,6 +2087,83 @@ void TestLowpanFragmentHeader(void) VerifyOrQuit(frameData.GetBytes() == frame); } +void TestLowpanDecompressRecursion(void) +{ + Instance *instance = testInitInstance(); + Lowpan::Lowpan &lowpan = instance->Get(); + MessagePool &messagePool = instance->Get(); + + 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; diff --git a/tests/unit/test_lowpan.hpp b/tests/unit/test_lowpan.hpp index 4862dde12..f867490dc 100644 --- a/tests/unit/test_lowpan.hpp +++ b/tests/unit/test_lowpan.hpp @@ -259,6 +259,8 @@ public: const char *mTestName; }; +void TestLowpanDecompressRecursion(void); + } // namespace ot #endif // OT_UNIT_TEST_LOWPAN_HPP_