From ca372b8e9927589e8c13cd0d4bea224bffc7b97b Mon Sep 17 00:00:00 2001 From: Abtin Keshavarzian Date: Mon, 18 Aug 2025 10:39:39 -0700 Subject: [PATCH] [ip6] update MPL option processing and validation (#11816) This commit enhances MPL option processing and validation. Previously, the MPL option was processed immediately upon being found, and multiple MPL options in the same message were accepted. This could allow a malformed IPv6 message to be buffered for MPL re-transmission multiple times with different seed IDs. This change ensures the code first iterates through and validates all included options within a Hop-by-Hop extension header, enforcing that at most one MPL option is present before processing. This is implemented by splitting `Mpl::ProcessOption()` into `ReadAndValidateOption()` and a new version of `ProcessOption()` that acts on a pre-validated `MplOption`. --- src/core/net/ip6.cpp | 20 ++++++++++++++++++-- src/core/net/ip6_mpl.cpp | 33 +++++++++++++++++++++------------ src/core/net/ip6_mpl.hpp | 28 ++++++++++++++++++++-------- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/src/core/net/ip6.cpp b/src/core/net/ip6.cpp index c8b75c8a7..8e571cca9 100644 --- a/src/core/net/ip6.cpp +++ b/src/core/net/ip6.cpp @@ -508,15 +508,22 @@ exit: Error Ip6::HandleOptions(Message &aMessage, const Header &aHeader, bool &aReceive) { - Error error = kErrorNone; + Error error = kErrorNone; + bool hasMplOption = false; HopByHopHeader hbhHeader; Option option; OffsetRange offsetRange; + MplOption mplOption; offsetRange.InitFromMessageOffsetToEnd(aMessage); SuccessOrExit(error = ReadHopByHopHeader(aMessage, offsetRange, hbhHeader)); + // `ReadHopByHopHeader()` updates `offsetRange` to refer to the + // location of the options within the HBH header. We first + // validate all options, ensuring there is at most one MPL + // option, before processing it. + for (; !offsetRange.IsEmpty(); offsetRange.AdvanceOffset(option.GetSize())) { SuccessOrExit(error = option.ParseFrom(aMessage, offsetRange)); @@ -528,13 +535,22 @@ Error Ip6::HandleOptions(Message &aMessage, const Header &aHeader, bool &aReceiv if (option.GetType() == MplOption::kType) { - SuccessOrExit(error = mMpl.ProcessOption(aMessage, offsetRange, aHeader.GetSource(), aReceive)); + VerifyOrExit(!hasMplOption, error = kErrorDrop); + hasMplOption = true; + + SuccessOrExit(error = mMpl.ReadAndValidateOption(aMessage, offsetRange, aHeader.GetSource(), mplOption)); + continue; } VerifyOrExit(option.GetAction() == Option::kActionSkip, error = kErrorDrop); } + if (hasMplOption) + { + SuccessOrExit(error = mMpl.ProcessOption(aMessage, mplOption, aReceive)); + } + aMessage.SetOffset(offsetRange.GetEndOffset()); exit: diff --git a/src/core/net/ip6_mpl.cpp b/src/core/net/ip6_mpl.cpp index f36ffd35c..0163395b0 100644 --- a/src/core/net/ip6_mpl.cpp +++ b/src/core/net/ip6_mpl.cpp @@ -83,25 +83,27 @@ void Mpl::InitOption(MplOption &aOption, const Address &aAddress) aOption.SetSequence(mSequence++); } -Error Mpl::ProcessOption(Message &aMessage, const OffsetRange &aOffsetRange, const Address &aAddress, bool &aReceive) +Error Mpl::ReadAndValidateOption(Message &aMessage, + const OffsetRange &aOffsetRange, + const Address &aAddress, + MplOption &aOption) { - Error error; - MplOption option; + Error error; // Read the min size bytes first, then check the expected // `SeedIdLength` and read the full `MplOption` if needed. - SuccessOrExit(error = aMessage.Read(aOffsetRange, &option, MplOption::kMinSize)); + SuccessOrExit(error = aMessage.Read(aOffsetRange, &aOption, MplOption::kMinSize)); - switch (option.GetSeedIdLength()) + switch (aOption.GetSeedIdLength()) { case MplOption::kSeedIdLength0: // Retrieve Seed ID from the IPv6 Source Address RLOC. - VerifyOrExit(aAddress.GetIid().IsLocator(), error = kErrorDrop); - option.SetSeedId(aAddress.GetIid().GetLocator()); + VerifyOrExit(aAddress.GetIid().IsLocator(), error = kErrorParse); + aOption.SetSeedId(aAddress.GetIid().GetLocator()); break; case MplOption::kSeedIdLength2: - SuccessOrExit(error = aMessage.Read(aOffsetRange, option)); + SuccessOrExit(error = aMessage.Read(aOffsetRange, aOption)); break; case MplOption::kSeedIdLength8: @@ -109,13 +111,21 @@ Error Mpl::ProcessOption(Message &aMessage, const OffsetRange &aOffsetRange, con ExitNow(error = kErrorParse); } +exit: + return error; +} + +Error Mpl::ProcessOption(Message &aMessage, const MplOption &aOption, bool &aReceive) +{ + Error error; + // Check if the MPL Data Message is new. - error = UpdateSeedSet(option.GetSeedId(), option.GetSequence()); + error = UpdateSeedSet(aOption.GetSeedId(), aOption.GetSequence()); if (error == kErrorNone) { #if OPENTHREAD_FTD - AddBufferedMessage(aMessage, option.GetSeedId(), option.GetSequence()); + AddBufferedMessage(aMessage, aOption.GetSeedId(), aOption.GetSequence()); #endif } else if (!aMessage.IsOriginThreadNetif()) @@ -123,10 +133,9 @@ Error Mpl::ProcessOption(Message &aMessage, const OffsetRange &aOffsetRange, con aReceive = false; // In case MPL Data Message is generated locally, ignore potential error of the MPL Seed Set // to allow subsequent retransmissions with the same sequence number. - ExitNow(error = kErrorNone); + error = kErrorNone; } -exit: return error; } diff --git a/src/core/net/ip6_mpl.hpp b/src/core/net/ip6_mpl.hpp index 38d930a6d..856f8ed62 100644 --- a/src/core/net/ip6_mpl.hpp +++ b/src/core/net/ip6_mpl.hpp @@ -171,21 +171,33 @@ public: void InitOption(MplOption &aOption, const Address &aAddress); /** - * Processes an MPL option. When the MPL module acts as an MPL Forwarder - * it disseminates MPL Data Message using Trickle timer expirations. When acts as an - * MPL Seed it allows to send the first MPL Data Message directly, then sets up Trickle - * timer expirations for subsequent retransmissions. + * Reads and validates an MPL option from a given message. * - * @param[in] aMessage A reference to the message. - * @param[in] aOffsetRange The offset range in @p aMessage to read the MPL option. - * @param[in] aAddress A reference to the IPv6 Source Address. + * @param[in] aMessage The message from which to read the option. + * @param[in] aOffsetRange The offset range within @p aMessage to read from. + * @param[in] aAddress A reference to the IPv6 source address. + * @param[out] aOption An `MplOption` object to populate with the read option. + * + * @retval kErrorNone Successfully read and validated the MPL option. + * @retval kErrorParse Failed to parse the option. Invalid format. + */ + Error ReadAndValidateOption(Message &aMessage, + const OffsetRange &aOffsetRange, + const Address &aAddress, + MplOption &aOption); + + /** + * Processes a previously read and validated MPL option. + * + * @param[in] aMessage The message. + * @param[in] aOption The MPL option. * @param[out] aReceive Set to FALSE if the MPL message is a duplicate and must not * go through the receiving process again, untouched otherwise. * * @retval kErrorNone Successfully processed the MPL option. * @retval kErrorDrop The MPL message is a duplicate and should be dropped. */ - Error ProcessOption(Message &aMessage, const OffsetRange &aOffsetRange, const Address &aAddress, bool &aReceive); + Error ProcessOption(Message &aMessage, const MplOption &aOption, bool &aReceive); #if OPENTHREAD_FTD /**