From 92d7b9f93f238a96fa57e4045939061477256030 Mon Sep 17 00:00:00 2001 From: Jonathan Hui Date: Thu, 7 May 2026 11:34:53 -0700 Subject: [PATCH] [ip6] cap recursion depth in HandleDatagram to 4 (#13065) This commit introduces a recursion depth limit of 4 in Ip6::HandleDatagram to prevent unbounded stack recursion from deeply nested IPv6-in-IPv6 tunnel packets (NextHeader = 41). This mirrors the safety limit fix implemented in the 6LoWPAN layer decompress path (issue #12669). A new Nexus test case `ipv6_recursion` has been added to construct and verify that packets exceeding the depth limit are correctly dropped with kErrorDrop, while valid nesting depth succeeds. --- src/core/net/ip6.cpp | 10 ++++--- src/core/net/ip6.hpp | 3 +- tests/nexus/test_ipv6_recursion.cpp | 44 +++++++++++++++++++++++++---- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp index 0c88143d2..4288ef21f 100644 --- a/src/core/net/ip6.cpp +++ b/src/core/net/ip6.cpp @@ -1280,10 +1280,12 @@ exit: return; } -Error Ip6::HandleDatagram(OwnedPtr aMessagePtr, bool aIsReassembled) +Error Ip6::HandleDatagram(OwnedPtr aMessagePtr, bool aIsReassembled, uint8_t aRecursionDepth) { - Error error; - Header header; + Error error; + Header header; + + VerifyOrExit(aRecursionDepth <= kMaxRecursionDepth, error = kErrorDrop); bool receive; bool forwardThread; bool forwardHost; @@ -1322,7 +1324,7 @@ Error Ip6::HandleDatagram(OwnedPtr aMessagePtr, bool aIsReassembled) Get().LogMessage(MeshForwarder::kMessageReceive, *messagePtr); - IgnoreError(HandleDatagram(messagePtr.PassOwnership(), aIsReassembled)); + SuccessOrExit(error = HandleDatagram(messagePtr.PassOwnership(), aIsReassembled, aRecursionDepth + 1)); receive = false; forwardHost = false; diff --git a/src/core/net/ip6.hpp b/src/core/net/ip6.hpp index 8a20b549b..b3f635345 100644 --- a/src/core/net/ip6.hpp +++ b/src/core/net/ip6.hpp @@ -179,7 +179,7 @@ public: * @retval kErrorNoRoute No route to host. * @retval kErrorParse Encountered a malformed header when processing the message. */ - Error HandleDatagram(OwnedPtr aMessagePtr, bool aIsReassembled = false); + Error HandleDatagram(OwnedPtr aMessagePtr, bool aIsReassembled = false, uint8_t aRecursionDepth = 0); /** * Sets the callback to provide received raw IPv6 datagrams. @@ -318,6 +318,7 @@ public: #endif private: + static constexpr uint8_t kMaxRecursionDepth = 4; static constexpr uint8_t kReassemblyTimeout = OPENTHREAD_CONFIG_IP6_REASSEMBLY_TIMEOUT; static constexpr uint16_t kMinimalMtu = 1280; diff --git a/tests/nexus/test_ipv6_recursion.cpp b/tests/nexus/test_ipv6_recursion.cpp index 67f648be7..d24447b80 100644 --- a/tests/nexus/test_ipv6_recursion.cpp +++ b/tests/nexus/test_ipv6_recursion.cpp @@ -43,8 +43,9 @@ void TestIPv6Recursion(void) * - Leader * * Description: - * The purpose of this test case is to validate that lowpan compression enforces - * the kMaxRecursionDepth limit to prevent excessive recursive stack usage. + * The purpose of this test case is to validate that both Ip6::HandleDatagram + * and lowpan compression enforce their respective kMaxRecursionDepth limit + * to prevent unbounded stack recursion/excessive recursive stack usage. */ Core nexus; @@ -94,9 +95,42 @@ void TestIPv6Recursion(void) Log("Test Case 1: Passed successfully!"); } - // Test Case 2: Direct call to Lowpan::Compress with depth 6 nested packet + // Test Case 2: Recursion depth 6 (exceeds limit of 4 in Ip6::HandleDatagram) { - Log("Test Case 2: Call Lowpan::Compress with depth 6 nested packet (should cap and fall back gracefully)"); + Log("Test Case 2: Send nested IPv6 packet with depth 6 (exceeds limit)"); + Message *message = leader.Get().NewMessage(); + VerifyOrQuit(message != nullptr); + + // Construct 6 nested headers: + // - i = 5 (innermost): NH = kProtoNone, PayloadLen = 0 + // - i = 0 to 4: NH = kProtoIp6, PayloadLen = (5 - i) * 40 + for (int i = 0; i < 6; i++) + { + Ip6::Header header; + header.InitVersionTrafficClassFlow(); + header.SetSource(selfAddress); + header.SetDestination(selfAddress); + if (i == 5) + { + header.SetNextHeader(Ip6::kProtoNone); + header.SetPayloadLength(0); + } + else + { + header.SetNextHeader(Ip6::kProtoIp6); + header.SetPayloadLength((5 - i) * sizeof(Ip6::Header)); + } + SuccessOrQuit(message->Append(header)); + } + + Error error = leader.Get().HandleDatagram(OwnedPtr(message)); + VerifyOrQuit(error == kErrorDrop); + Log("Test Case 2: Passed successfully (packet with depth 6 was dropped with kErrorDrop)!"); + } + + // Test Case 3: Direct call to Lowpan::Compress with depth 6 nested packet + { + Log("Test Case 3: Call Lowpan::Compress with depth 6 nested packet (should cap and fall back gracefully)"); Message *message = leader.Get().NewMessage(); VerifyOrQuit(message != nullptr); @@ -132,7 +166,7 @@ void TestIPv6Recursion(void) // and emit the remaining nested headers inline as opaque payload. Error error = leader.Get().Compress(*message, macAddrs, frameBuilder); VerifyOrQuit(error == kErrorNone); - Log("Test Case 2: Passed successfully (returned kErrorNone without excessive stack usage)!"); + Log("Test Case 3: Passed successfully (returned kErrorNone without excessive stack usage)!"); message->Free(); }