fix(sip): reject non-char-boundary Content-Length in body extraction (#108) - #113
Merged
Merged
Conversation
…108) `parse_message` truncated the body with `body[..cl]`, where `cl` is the attacker-controlled Content-Length. Because the datagram is validated as UTF-8 first, `body` is a `&str` and slicing it on a byte index that lands inside a multi-byte character panics ("byte index N is not a char boundary"). That parse runs inline in the single main SIP event loop (stack.rs), so one malformed, unauthenticated UDP/TCP/TLS/WS datagram unwinds and kills the whole receive loop — a remote DoS. Truncate via `body.get(..cl)` and fall back to the full (already length-bounded) body when `cl` is not a char boundary. Char-boundary truncation is unchanged. Regression tests feed a body with a mid-character Content-Length (asserts no panic) and a boundary-aligned Content-Length (asserts truncation still works). Reverting the fix makes the first test panic with "end byte index 1 is not a char boundary". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XH6jhrG4ZCL8xfCktK4Zms
ryanmurf
pushed a commit
that referenced
this pull request
Jul 17, 2026
`extract_uri` computed `start = find('<')`, `end = find('>')` and sliced
`header_value[start+1..end]`. A SIP header whose value contains a '>' before
the URI's '<' — legal in a quoted display-name, e.g. `"a>b" <sip:carol@host>`
— makes `end < start`, a reversed byte range that panics. Reachable from the
request-handling path (dialog creation, registrar, ACL) on attacker-controlled
From/To/Contact headers; the panic aborts the SIP receive loop (DoS).
Search for '>' only in the tail after '<' (also more correct: it matches the
bracket that closes the URI, not an earlier one in the display-name).
Regression tests added; both panic-guards fail RED (panic at the slice) with
the fix reverted, while the well-formed guard passes in both states.
Fixes #114. (The Content-Length char-boundary panic originally bundled here,
#112, was fixed independently on master by #113/#108 — dropped from this PR.)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XH6jhrG4ZCL8xfCktK4Zms
This was referenced Jul 17, 2026
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.
What
Fixes #108 — a remotely-triggerable panic (DoS) in the SIP message parser.
parse_message()extracted the body withbody[..cl]whereclis theattacker-controlled
Content-Length. SinceSipMessage::parsevalidates thewhole datagram as UTF-8 first,
bodyis a&str, and slicing it on a byteindex that lands inside a multi-byte UTF-8 char panics.
Impact
SipMessage::parseruns inUdpTransport::recv()which is awaited inline inthe single main SIP event loop (
stack.rs:713). With the defaultpanic = "unwind", one malformed unauthenticated datagram unwinds and kills thereceive loop → whole-stack signaling DoS. Reachable identically over TCP/TLS/WS.
Fix
Truncate via
body.get(..cl); on a non-char-boundarycl, keep the full(already
MAX_CONTENT_LENGTH-bounded) body instead of aborting. Behaviour forwell-formed messages (ASCII/boundary-aligned
cl) is unchanged.Tests
test_content_length_mid_utf8_char_does_not_panic— body"é"(0xC3 0xA9),Content-Length: 1(mid-character): asserts it parses without panic.test_content_length_char_boundary_truncates— body"ab",Content-Length: 1: asserts it still truncates to"a".Red-control evidence (test is load-bearing)
With the fix reverted (restore
body[..cl].to_string()) and the tests kept:With the fix in place:
777 passed; 0 failed.cargo clippy -p asterisk-sip --all-targetsis clean.Toolchain
Rust 1.97.0.
cargo test -p asterisk-sip --libgreen; clippy-D warningsclean.