RTSP RTP Interleaved Parser Assertion Failure (Zero-Length RTP Payload)
None
Vulnerability Details
## Summary:
I am submitting this as a security issue primarily due to how it was discovered and that it's my first Curl submission, but I suspect I might be overly cautious here.
This issue was discovered as part of the AIXCC competition, and I am assisting on reporting true positive findings to upstream repositories, facilitated by way of of OpenSSF and OSTIF.
AI was used to find and root-cause this issue.
The issue is that a debug assertion (https://github.com/curl/curl/blob/3cf86508fdc3f54bb2a3f42c8c0bd464ea39883d/lib/rtsp.c#L784) is triggerable from the curl_fuzzer_rtsp (build from OSS-Fuzz). When I disable that specific assertion and run the crashing input, it passes without issues so I suspect release mode is fine.
## Affected version
latest commit on GitHub.
## Steps To Reproduce:
```bash
# 1. Clone oss-fuzz (or use existing checkout)
git clone https://github.com/google/oss-fuzz.git
cd oss-fuzz
# 2. Build the curl fuzzers (requires Docker)
python3 infra/helper.py build_fuzzers curl
# 3. Replay against the attached payload
python3 infra/helper.py reproduce curl curl_fuzzer_rtsp /tmp/curl_crash_input.bin
...
Running: /testcase
curl_fuzzer_rtsp: /src/curl/lib/rtsp.c:784: CURLcode rtsp_filter_rtp(struct Curl_easy *, struct rtsp_conn *, const char *, size_t, size_t *): Assertion `rtp_len < rtspc->rtp_len' failed.
AddressSanitizer:DEADLYSIGNAL
=================================================================
==14==ERROR: AddressSanitizer: ABRT on unknown address 0x00000000000e (pc 0x7fde2c69eb2c bp 0x7fff96291630 sp 0x7fff962915f0 T0)
SCARINESS: 10 (signal)
#0 0x7fde2c69eb2c in pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x9eb2c) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#1 0x7fde2c64527d in raise (/lib/x86_64-linux-gnu/libc.so.6+0x4527d) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#2 0x7fde2c6288fe in abort (/lib/x86_64-linux-gnu/libc.so.6+0x288fe) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#3 0x7fde2c62881a (/lib/x86_64-linux-gnu/libc.so.6+0x2881a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#4 0x7fde2c63b516 in __assert_fail (/lib/x86_64-linux-gnu/libc.so.6+0x3b516) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#5 0x5564d89cb51e in rtsp_filter_rtp /src/curl/lib/rtsp.c:784:7
#6 0x5564d89c8c8f in rtsp_rtp_write_resp /src/curl/lib/rtsp.c:893:16
#7 0x5564d8893930 in Curl_xfer_write_resp /src/curl/lib/transfer.c:772:14
#8 0x5564d888fd2a in sendrecv_dl /src/curl/lib/transfer.c:298:14
#9 0x5564d888fd2a in Curl_sendrecv /src/curl/lib/transfer.c:370:14
#10 0x5564d88572a6 in state_performing /src/curl/lib/multi.c:1938:12
#11 0x5564d88572a6 in multi_runsingle /src/curl/lib/multi.c:2672:17
#12 0x5564d884e9a2 in multi_perform /src/curl/lib/multi.c:2775:17
#13 0x5564d87fef55 in fuzz_handle_transfer(fuzz_data*) /src/curl_fuzzer/curl_fuzzer.cc:341:3
#14 0x5564d87fe317 in LLVMFuzzerTestOneInput /src/curl_fuzzer/curl_fuzzer.cc:97:3
#15 0x5564d869a57d in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:619:13
#16 0x5564d8685302 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:329:6
#17 0x5564d868b1d0 in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:865:9
#18 0x5564d86b6cf2 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
#19 0x7fde2c62a1c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#20 0x7fde2c62a28a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#21 0x5564d867e3e4 in _start (/out/curl_fuzzer_rtsp+0x6723e4)
DEDUP_TOKEN: pthread_kill--raise--abort
==14==Register values:
rax = 0x0000000000000000 rbx = 0x000000000000000e rcx = 0x00007fde2c69eb2c rdx = 0x0000000000000006
rdi = 0x000000000000000e rsi = 0x000000000000000e rbp = 0x00007fff96291630 rsp = 0x00007fff962915f0
r8 = 0x00000000000000bc r9 = 0x00007e1e2b7e0000 r10 = 0x0000000000000008 r11 = 0x0000000000000246
r12 = 0x0000000000000006 r13 = 0x00005564d955a520 r14 = 0x0000000000000016 r15 = 0x00005564d955b200
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: ABRT (/lib/x86_64-linux-gnu/libc.so.6+0x9eb2c) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb) in pthread_kill
```
### Root Cause
The RTSP protocol supports interleaving RTP data within the TCP connection
using a `$`-framed binary format (RFC 2326, Section 10.12):
```
'$' <1-byte channel> <2-byte big-endian length> <length bytes of data>
```
curl's RTP interleaved parser (`rtsp_filter_rtp`) uses a 4-state machine:
1. `RTP_PARSE_SKIP` — scanning for `$` frame start
2. `RTP_PARSE_CHANNEL` — reading the 1-byte channel number
3. `RTP_PARSE_LEN` — reading the 2-byte payload length field
4. `RTP_PARSE_DATA` — reading the RTP payload data
The bug is in the transition from `RTP_PARSE_LEN` to `RTP_PARSE_DATA`.
In `RTP_PARSE_LEN` (line 762), once both length bytes have been collected
(buffer contains 4 bytes: `$`, channel, len_hi, len_lo), the expected total
frame size is computed:
```c
rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4;
```
Where `RTP_PKT_LENGTH` extracts the 16-bit big-endian value from bytes 2-3:
```c
#define RTP_PKT_LENGTH(p) ((((unsigned int)((unsigned char)((p)[2]))) << 8) | \
((unsigned int)((unsigned char)((p)[3]))))
```
**When the payload length field is `0x0000` (zero):**
- `RTP_PKT_LENGTH(rtp_buf)` returns `0`
- `rtspc->rtp_len = 0 + 4 = 4`
- The dynamic buffer (`rtspc->buf`) already contains exactly 4 bytes
(`$` + channel + 2 length bytes)
- State transitions to `RTP_PARSE_DATA`
In `RTP_PARSE_DATA` (line 781):
- `rtp_len = curlx_dyn_len(&rtspc->buf)` → **4**
- `rtspc->rtp_len` → **4**
- `DEBUGASSERT(rtp_len < rtspc->rtp_len)` → `DEBUGASSERT(4 < 4)` → **FAILS**
The assertion correctly detects an invariant violation: the parser entered the
"read data" state when there is no data to read.
## Impact
## Summary:
The impact is that in debug builds an assertion is triggerable. In release builds, as far as I can tell, the issue does not have adverse effect other than the `rtsp_filter_rtp` state machine processes an invalid zero-length RTP frame and delivers a 4-byte header to the RTP write callback as if they were valid RTP data.
I considered making this public due to issues such as https://github.com/curl/curl/issues/12701 but, again, I'm being a bit cautious here. I am happy to submit this issue on GitHub if Curl maintainers confirm it's okay to do so.
Actions
View on HackerOneReport Stats
- Report ID: 3575250
- State: Closed
- Substate: informative
- Upvotes: 1