Skip to content

bound option tag lookahead to the body in parse_supported_body - #4291

Open
Sahana2524 wants to merge 1 commit into
OpenSIPS:masterfrom
Sahana2524:supported-tag-bounds
Open

Sahana2524 wants to merge 1 commit into
OpenSIPS:masterfrom
Sahana2524:supported-tag-bounds

Conversation

@Sahana2524

Copy link
Copy Markdown

Summary

parse_supported_body() reads past the end of the Supported header body while scanning for option tags. The reads are one to four bytes, but they are outside the str the function was handed, and the tag matching currently depends on one of them.

Details

Two lookaheads are unbounded:

  1. val = LOWER_DWORD(READ(p)) loads four bytes with no check on what is left. The delimiter-skip loop above it can walk pos all the way up to len, so a body like Supported: , has the dword read starting at the end of the body. A short element such as Supported: x reads three bytes past it.
  2. Every recognized tag checks pos + N <= len and then reads p[N], which needs pos + N < len. So a tag sitting flush against the end of the body (path, gruu, timer, 100rel, eventlist) reads the byte right after it.

The body is attacker supplied, and parse_supported() runs on anything the registrar, sst, rr/path, tm or rls logic looks at. Driving the function through the guard-page harness already in parser/test/test_oob.c faults at parse_supported.c:55, :60, :69, :80, :91 and :102, one per site.

Case 2 also costs correctness today, which is the easier half to see. An option tag at the very end of the body is only matched because the byte that follows it inside msg->buf happens to be the CR of the header's own CRLF. Hand parse_supported_body() a str that is not backed by a message buffer and Supported: path stops setting F_SUPPORTED_PATH: no guard page needed, 8 of the new test's plain assertions fail on master for that reason alone.

Solution

The dword read is capped by the bytes that remain. An element shorter than four characters cannot be any of the tags below, so it falls through to the existing skip path, which is where the old code ended up anyway once the garbage dword failed to match.

IS_TAG_END() sits next to IS_DELIM() and treats the end of the body as a tag terminator rather than something to read across, so the five per-tag checks stop depending on a byte they do not own.

parser/test/test_parse_supported.c covers both halves: functional cases for each recognized tag (alone, flush against the end, and in a list), and a test_oob() sweep over the inputs that hit the six sites. Against master it fails 8 assertions and then dies with SIGBUS on the first guard-page case; with the patch it is 197/197.

Compatibility

No behavior change for a Supported header parsed out of a SIP message, since the bytes the old code reached for there were the trailing CRLF, and CR is a delimiter. The one visible difference is that a tag ending exactly at the end of the body is now recognized without looking at what follows, which is what the old code was already doing by accident.

Closing issues

None.

The leading dword read and the per-tag delimiter checks were never capped by body->len, so a Supported header body ending in a delimiter, a short element, or an option tag flush against the end of the body made the scan read outside the buffer it was given.
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.

1 participant