[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.
This commit is contained in:
Jonathan Hui
2026-05-07 11:34:53 -07:00
committed by GitHub
parent 91e7c33733
commit 92d7b9f93f
3 changed files with 47 additions and 10 deletions
+6 -4
View File
@@ -1280,10 +1280,12 @@ exit:
return;
}
Error Ip6::HandleDatagram(OwnedPtr<Message> aMessagePtr, bool aIsReassembled)
Error Ip6::HandleDatagram(OwnedPtr<Message> 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<Message> aMessagePtr, bool aIsReassembled)
Get<MeshForwarder>().LogMessage(MeshForwarder::kMessageReceive, *messagePtr);
IgnoreError(HandleDatagram(messagePtr.PassOwnership(), aIsReassembled));
SuccessOrExit(error = HandleDatagram(messagePtr.PassOwnership(), aIsReassembled, aRecursionDepth + 1));
receive = false;
forwardHost = false;
+2 -1
View File
@@ -179,7 +179,7 @@ public:
* @retval kErrorNoRoute No route to host.
* @retval kErrorParse Encountered a malformed header when processing the message.
*/
Error HandleDatagram(OwnedPtr<Message> aMessagePtr, bool aIsReassembled = false);
Error HandleDatagram(OwnedPtr<Message> 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;
+39 -5
View File
@@ -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<Ip6::Ip6>().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<Ip6::Ip6>().HandleDatagram(OwnedPtr<Message>(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<Ip6::Ip6>().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<Lowpan::Lowpan>().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();
}