sd/configuration_option: enforce per-option byte budget on deserialize#1042
Open
SoundMatt wants to merge 1 commit into
Open
sd/configuration_option: enforce per-option byte budget on deserialize#1042SoundMatt wants to merge 1 commit into
SoundMatt wants to merge 1 commit into
Conversation
configuration_option_impl::deserialize had no per-option byte budget on its key=value-pair loop. The loop only stopped when an item-length of 0 was read or when the outer deserializer's remaining_ ran out, neither of which corresponds to the option's own declared length_ field. A multicast SD peer sending a malformed CONFIGURATION option (no trailing '\\0' terminator, or an item declaring a length larger than the option's payload) could make the loop greedily consume bytes from the next options in the SD message — silently desynchronising the option stream so that subsequent legitimate options (typically the IPv4/IPv6 endpoint options that drive subscription routing) were either dropped or misinterpreted as a different option type. Not a memory-safety bug — the deserializer's own remaining_ check keeps every read in-bounds of the SD message. But the parser-state corruption is reachable from any peer on the SD multicast group, without authentication, so it's worth fixing. Track bytes_remaining = length_ - 1 (the bytes that belong to this option after option_impl::deserialize consumed length+type+reserved) and reject: - length_ < 1 (option header smaller than the already-consumed reserved byte); - an item-length that exceeds bytes_remaining (lying length); - the loop running out of bytes without seeing the '\\0' terminator (missing terminator). Behaviour unchanged for well-formed inputs: a CONFIGURATION option whose payload is exactly reserved + items + '\\0' still decodes identically. The early-terminator-with-trailing-padding case (which isn't described by the SWS but does occur if a sender adds padding) continues to return success, matching the previous behaviour. Signed-off-by: Matt Jones <47545907+SoundMatt@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
configuration_option_impl::deserialize(implementation/service_discovery/src/configuration_option_impl.cpp:90-125) parses a key=value list with no per-option byte budget:The loop only stops on a 0-length item or when the outer deserializer's
remaining_runs out. Neither corresponds to this option's own declaredlength_field — and the parent dispatcher (message_impl::deserialize_option) does not scope the deserializer to a single option before calling it.So if a multicast SD peer sends a CONFIGURATION option that:
\0terminator, orthe loop greedily consumes bytes from the next option(s) in the SD message until it finds a 0 byte or runs out of
remaining_. Subsequent legitimate options (typically the IPv4/IPv6 endpoint options that drive subscription routing) are then either dropped or misinterpreted as a different option type, depending on which byte boundary the desync lands on.This is not a memory-safety bug —
deserializer::deserialize(string, len)enforceslen <= remaining_, so every read still lands inside the SD message buffer. But the parser-state corruption is reachable from any peer on the SD multicast group, with no authentication, so it's worth fixing.Fix
Track
bytes_remaining = length_ - 1(the bytes that belong to this option afteroption_impl::deserializeconsumed length+type+reserved) and reject malformed input at three points:length_ < 1— option header is smaller than the reserved byte thatoption_impl::deserializealready consumed.bytes_remaining— lying length, would otherwise consume bytes from the next option.\0terminator — missing terminator.Behaviour is unchanged for well-formed inputs: a CONFIGURATION option whose payload is exactly
reserved + items + '\0'still decodes identically. The early-terminator-with-trailing-padding case (not described by SWS_SD_00292 but tolerated by the previous code) continues to return success.Diff: +66 / −22 in a single file. Loop restructured from
do/whilewith implicitis_successfulchaining into awhilewith explicit early-return paths, which makes the budget-enforcement points easier to reason about.Notes
g++ -fsyntax-only).configuration_option_impldeserialization (grep -r configuration_option_impl test/returns nothing). I'd be happy to follow up with a malformed-input test case once I have the full vsomeip build working locally — pointers to where SD-option deserialize tests would naturally live intest/would be appreciated.deserializer::look_aheadremains; happy to file as a separate small PR if useful.