Skip to content

fix(sip): reject non-char-boundary Content-Length in body extraction (#108) - #113

Merged
ryanmurf merged 1 commit into
masterfrom
hardening/mem-sip-body-charboundary
Jul 17, 2026
Merged

fix(sip): reject non-char-boundary Content-Length in body extraction (#108)#113
ryanmurf merged 1 commit into
masterfrom
hardening/mem-sip-body-charboundary

Conversation

@ryanmurf

Copy link
Copy Markdown
Owner

What

Fixes #108 — a remotely-triggerable panic (DoS) in the SIP message parser.

parse_message() extracted the body with body[..cl] where cl is the
attacker-controlled Content-Length. Since SipMessage::parse validates the
whole datagram as UTF-8 first, body is a &str, and slicing it on a byte
index that lands inside a multi-byte UTF-8 char panics.

Impact

SipMessage::parse runs in UdpTransport::recv() which is awaited inline in
the single main SIP event loop
(stack.rs:713). With the default
panic = "unwind", one malformed unauthenticated datagram unwinds and kills the
receive loop → whole-stack signaling DoS. Reachable identically over TCP/TLS/WS.

Fix

Truncate via body.get(..cl); on a non-char-boundary cl, keep the full
(already MAX_CONTENT_LENGTH-bounded) body instead of aborting. Behaviour for
well-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:

running 2 tests
test parser::tests::test_content_length_char_boundary_truncates ... ok
test parser::tests::test_content_length_mid_utf8_char_does_not_panic ... FAILED

---- test_content_length_mid_utf8_char_does_not_panic stdout ----
thread '...' panicked at crates/asterisk-sip/src/parser/mod.rs:799:17:
end byte index 1 is not a char boundary; it is inside 'é' (bytes 0..2 of string)

test result: FAILED. 1 passed; 1 failed

With the fix in place: 777 passed; 0 failed. cargo clippy -p asterisk-sip --all-targets is clean.

Toolchain

Rust 1.97.0. cargo test -p asterisk-sip --lib green; clippy -D warnings clean.

…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
ryanmurf merged commit 7543d01 into master Jul 17, 2026
3 checks passed
@ryanmurf
ryanmurf deleted the hardening/mem-sip-body-charboundary branch July 17, 2026 00:43
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SIP DoS: panic slicing message body on non-char-boundary Content-Length

2 participants