Untrusted child MLE messages reached a parser loop that ignored read errors.
In Thread mesh networks, child nodes—such as Sleepy End Devices (SEDs) and Minimal End Devices (MEDs)—register their IPv6 unicast addresses with their parent Full Thread Device (FTD) router using the Mesh Link Establishment (MLE) Address Registration TLV.
During incoming message processing in Mle::ProcessAddressRegistrationTlv(), the router decodes individual address entries inside a while loop. The parser used ReadAndAdvance() to extract the 1-byte entry control header and the subsequent 8-byte Interface Identifier (IID) or full 16-byte IPv6 address. Both operations were wrapped in IgnoreError().
When malformed or truncated TLV entries caused ReadAndAdvance() to return kErrorParse, the failure was silently suppressed. The function continued executing subsequent address logic, calling aChild.AddIp6Address() and synchronizing child tables with uninitialized or partial buffer data. This created state corruption within the parent router's child address table and risked route misdirection or denial of service for the child node.
MLE Address Registration maintains critical child-to-parent routing mappings.
Under the Thread Specification, an FTD router acts as a proxy for its attached end devices. Because sleepy end devices frequently power down their radios to conserve battery, the parent router must maintain an accurate table of all IPv6 addresses configured on each child. When incoming IPv6 datagrams reach the router destined for a child, the router buffers the packet until the child polls for data.
The Address Registration TLV (Type 19) carries one or more address registration entries. Each entry begins with a 1-byte control header containing a compression bit (b0) and a 4-bit Context ID (CID). If compressed, an 8-byte IID follows; if uncompressed, a full 16-byte IPv6 address follows. Because this TLV directly dictates the router's routing table for child nodes, parser robustness is essential.
Silent error suppression allowed partial reads to update router state.
The flaw resided in src/core/thread/mle_ftd.cpp inside the Mle::ProcessAddressRegistrationTlv(RxInfo &aRxInfo, Child &aChild) member function. The affected loop parsed entries sequentially from the message buffer until reaching endOffset:
// src/core/thread/mle_ftd.cpp (prior to PR #13646)
while (aRxInfo.mMessage.GetOffset() < endOffset)
{
AddressRegistrationEntry entry;
// FLAW: If message is truncated, ReadAndAdvance() returns kErrorParse,
// but IgnoreError() discards it and execution continues unconditionally.
IgnoreError(aRxInfo.mMessage.ReadAndAdvance(entry));
if (entry.IsCompressed())
{
Ip6::InterfaceIdentifier iid;
// FLAW: Truncated IID read error is also discarded
IgnoreError(aRxInfo.mMessage.ReadAndAdvance(iid));
// State mutation occurs with corrupted/uninitialized data:
aChild.AddIp6Address(address);
}
else
{
Ip6::Address address;
// FLAW: Uncompressed IPv6 address read error is also discarded
IgnoreError(aRxInfo.mMessage.ReadAndAdvance(address));
aChild.AddIp6Address(address);
}
}
If an incoming TLV ended prematurely—for example, if the TLV declared a length that exceeded the physical packet bounds, or contained an odd number of remaining bytes—ReadAndAdvance() returned kErrorParse. Because the return value was wrapped in IgnoreError(), the parsing loop did not exit. Instead, the local stack variables entry, iid, or address contained uninitialized stack memory or partial bytes from previous operations.
The router then invoked aChild.AddIp6Address(address) using this invalid data. In addition, when child address registration finishes, the router compares the newly registered address list against the child's existing addresses, removing any address not included in the update. Consequently, an aborted or corrupted parsing loop could both inject garbage addresses and trigger unintended removal of legitimate child addresses.
State corruption compromised child reachability and router memory.
| Security property | Observed scope |
|---|---|
| Child Table Integrity | Invalid or uninitialized addresses added to child table |
| Address Cleanup State | Premature or erroneous eviction of legitimate child addresses |
| Network Reachability | Denial of Service (DoS) for child communication in Thread mesh |
| Confidentiality | No remote information disclosure identified |
| Remote Execution | No memory corruption or code execution identified |
The practical vulnerability is a targeted denial of service and state corruption against the child-parent relationship. In a Thread mesh network, an attacker or malfunctioning node could send crafted Child Update Requests that destabilize the parent FTD's internal address registry for child nodes.
PR #13646 enforces strict error propagation with SuccessOrExit.
Zain Nadeem authored the remediation patch and submitted it upstream as PR #13646. The fix replaces all instances of IgnoreError() in the address registration loop with OpenThread's idiomatic SuccessOrExit(error = ...) macro, immediately terminating parsing and returning kErrorParse if any read fails.
// src/core/thread/mle_ftd.cpp (patched in PR #13646)
while (aRxInfo.mMessage.GetOffset() < endOffset)
{
AddressRegistrationEntry entry;
SuccessOrExit(error = aRxInfo.mMessage.ReadAndAdvance(entry));
if (entry.IsCompressed())
{
Ip6::InterfaceIdentifier iid;
SuccessOrExit(error = aRxInfo.mMessage.ReadAndAdvance(iid));
// Validated address successfully populated
SuccessOrExit(error = networkData.GetContext(entry.GetContextId(), prefix));
address.SetPrefix(prefix);
address.SetIid(iid);
}
else
{
Ip6::Address address;
SuccessOrExit(error = aRxInfo.mMessage.ReadAndAdvance(address));
}
SuccessOrExit(error = aChild.AddIp6Address(address));
}
exit:
return error;
With this change, any truncated, malformed, or out-of-bounds Address Registration TLV immediately aborts parsing at the exact byte where the read fails, preventing invalid state changes and discarding malformed registration packets before they can contaminate the child address table.
Simulating boundary violations without destabilizing live mesh networks.
Validation of the fix was conducted using OpenThread's simulation harness (`ot-cli-ftd` in POSIX simulation mode). A mock Child Update Request containing a truncated Address Registration TLV was sent to the FTD:
// Truncated TLV payload: Type 19, Length 5 (requires at least 9 bytes for compressed IID)
uint8_t truncatedTlv[] = {
0x13, // TLV Type: Address Registration
0x05, // TLV Length: 5 bytes (incomplete entry)
0x80, // Entry Control: Compressed = 1, Context ID = 0
0x11, 0x22, 0x33, 0x44 // Truncated IID: only 4 bytes provided instead of 8
};
Prior to PR #13646, the router logged no parsing error and added a corrupted IPv6 address with partial stack garbage to the child entry. With PR #13646 applied, ReadAndAdvance(iid) returned kErrorParse, triggering SuccessOrExit, immediately aborting the handler, logging an error, and leaving the child address table completely clean.
Coordinated disclosure through Google OSS VRP.
This vulnerability was handled according to responsible disclosure principles through Google's Open Source Software Vulnerability Reward Program:
- Discovery & Root Cause Analysis: Zain Nadeem identified the error suppression vulnerability in
mle_ftd.cppduring code review of OpenThread's MLE parser. - Vulnerability Report: Submitted through the Google OSS VRP portal (Report Reference #565787780).
- Remediation Development: Zain Nadeem developed the upstream fix and submitted pull request PR #13646 to
openthread/openthread. - Upstream Merge: Following review and validation by OpenThread core maintainers, PR #13646 was approved and merged into
main. - Status: Marked "Fixed" by Google Bug Hunters. Final VRP reward eligibility decision remains pending.
Defensive protocol parsing requires fail-fast error semantics.
- Never ignore parsing return codes: Wrapping deserialization functions like
ReadAndAdvance()inIgnoreError()turns malformed network inputs into undefined internal state. - State mutation must be atomic with validation: Do not update internal routing or address tables until the entire incoming data structure has been fully validated.
- Truncated packets are common attack vectors: Packet truncation, whether caused by network MTU truncation or malicious crafts, must always result in immediate parse termination.
- Idiomatic macros protect control flow: Using standardized error-handling macros such as
SuccessOrExitensures consistent failure paths across large C++ codebases.