RTSP RTP Interleaved Parser Assertion Failure (Zero-Length RTP Payload)

Disclosed: 2026-02-26 18:58:06 By davkor To curl
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 HackerOne
Report Stats
  • Report ID: 3575250
  • State: Closed
  • Substate: informative
  • Upvotes: 1
Share this report