curl · Report #3575250
RTSP RTP Interleaved Parser Assertion Failure (Zero-Length RTP Payload)
How it was found
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:
# 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:
RTP_PARSE_SKIP— scanning for$frame startRTP_PARSE_CHANNEL— reading the 1-byte channel numberRTP_PARSE_LEN— reading the 2-byte payload length fieldRTP_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:
rtspc->rtp_len = RTP_PKT_LENGTH(rtp_buf) + 4;
Where RTP_PKT_LENGTH extracts the 16-bit big-endian value from bytes 2-3:
#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)returns0rtspc->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)→ 4rtspc->rtp_len→ 4DEBUGASSERT(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.