diff --git a/lib/rtsp.c b/lib/rtsp.c index 8c5cdd5643..e76140216f 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -270,6 +270,20 @@ static CURLcode rtsp_setup_body(struct Curl_easy *data, return result; } +/* A value curl writes into an RTSP request line or header must not carry a + control byte: a CR or LF would end the line and let extra request lines be + injected into the control stream. */ +static bool rtsp_has_ctrl(const char *string) +{ + const unsigned char *s = (const unsigned char *)string; + while(*s) { + if(*s < 0x20) + return TRUE; + s++; + } + return FALSE; +} + static CURLcode rtsp_do(struct Curl_easy *data, bool *done) { struct connectdata *conn = data->conn; @@ -359,6 +373,11 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) } p_session_id = data->set.str[STRING_RTSP_SESSION_ID]; + if(p_session_id && rtsp_has_ctrl(p_session_id)) { + failf(data, "Control byte in RTSP session ID"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } if(!p_session_id && (rtspreq & ~(Curl_RtspReq)(RTSPREQ_OPTIONS | RTSPREQ_DESCRIBE | @@ -372,6 +391,11 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) /* Stream URI. Default to server '*' if not specified */ if(data->set.str[STRING_RTSP_STREAM_URI]) { p_stream_uri = data->set.str[STRING_RTSP_STREAM_URI]; + if(rtsp_has_ctrl(p_stream_uri)) { + failf(data, "Control byte in RTSP stream URI"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } } else { p_stream_uri = "*"; @@ -382,6 +406,11 @@ static CURLcode rtsp_do(struct Curl_easy *data, bool *done) if(rtspreq == RTSPREQ_SETUP && !p_transport) { /* New Transport: setting? */ if(data->set.str[STRING_RTSP_TRANSPORT]) { + if(rtsp_has_ctrl(data->set.str[STRING_RTSP_TRANSPORT])) { + failf(data, "Control byte in RTSP transport"); + result = CURLE_BAD_FUNCTION_ARGUMENT; + goto out; + } curlx_free(data->state.aptr.rtsp_transport); data->state.aptr.rtsp_transport = curl_maprintf("Transport: %s\r\n", diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index 45df544649..ced050c8b8 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -97,7 +97,8 @@ test618 test619 test620 test621 test622 test623 test624 test625 test626 \ test627 test628 test629 test630 test631 test632 test633 test634 test635 \ test636 test637 test638 test639 test640 test641 test642 test643 test644 \ test645 test646 test647 test648 test649 test650 test651 test652 test653 \ -test654 test655 test656 test658 test659 test660 test661 test662 test663 \ +test654 test655 test656 test657 test658 test659 test660 test661 test662 \ +test663 \ test664 test665 test666 test667 test668 test669 test670 test671 test672 \ test673 test674 test675 test676 test677 test678 test679 test680 test681 \ test682 test683 test684 test685 test686 test687 test688 test689 test690 \ diff --git a/tests/data/test657 b/tests/data/test657 new file mode 100644 index 0000000000..28d8aa4e2b --- /dev/null +++ b/tests/data/test657 @@ -0,0 +1,49 @@ + + + + +RTSP +RTSP CRLF injection + + + +# Server-side + + +RTSP/1.0 200 OK +Server: RTSPD/libcurl-test +CSeq: 1 +Public: OPTIONS, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE +Curl-Private: swsclose + + + + +# Client-Side + + +rtsp + + +lib%TESTNUMBER + + + +RTSP reject control bytes in stream URI, transport and session ID + + +rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER + + + +# Only the legitimate OPTIONS reaches the wire; the three requests carrying a +# CR or LF are refused before anything is sent. + + +OPTIONS rtsp://%HOSTIP:%RTSPPORT/%TESTNUMBER RTSP/1.0 +CSeq: 1 + + + + + diff --git a/tests/libtest/Makefile.inc b/tests/libtest/Makefile.inc index 140ec0bd58..3829c86876 100644 --- a/tests/libtest/Makefile.inc +++ b/tests/libtest/Makefile.inc @@ -79,7 +79,8 @@ TESTS_C = \ lib586.c lib589.c lib590.c lib591.c \ lib597.c lib598.c lib599.c \ lib643.c \ - lib650.c lib651.c lib652.c lib653.c lib654.c lib655.c lib658.c lib659.c \ + lib650.c lib651.c lib652.c lib653.c lib654.c lib655.c lib657.c lib658.c \ + lib659.c \ lib661.c lib666.c lib667.c lib668.c \ lib670.c lib674.c lib676.c lib677.c lib678.c \ lib694.c lib695.c \ diff --git a/tests/libtest/lib657.c b/tests/libtest/lib657.c new file mode 100644 index 0000000000..2307b65e54 --- /dev/null +++ b/tests/libtest/lib657.c @@ -0,0 +1,103 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) Daniel Stenberg, , et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at https://curl.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * SPDX-License-Identifier: curl + * + ***************************************************************************/ +#include "first.h" + +/* + * A CR or LF in an RTSP stream URI, Transport or Session ID must be rejected + * before the request is put on the control stream, otherwise the byte splits + * the request line/header and smuggles a second request. + */ +static CURLcode test_lib657(const char *URL) +{ + CURLcode result = TEST_ERR_MAJOR_BAD; + CURL *curl; + + if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) { + curl_mfprintf(stderr, "curl_global_init() failed\n"); + return TEST_ERR_MAJOR_BAD; + } + + curl = curl_easy_init(); + if(!curl) { + curl_mfprintf(stderr, "curl_easy_init() failed\n"); + curl_global_cleanup(); + return TEST_ERR_MAJOR_BAD; + } + + easy_setopt(curl, CURLOPT_HEADERDATA, stdout); + easy_setopt(curl, CURLOPT_WRITEDATA, stdout); + easy_setopt(curl, CURLOPT_URL, URL); + + /* a plain OPTIONS so the control connection is set up and the happy path is + exercised */ + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_OPTIONS); + result = curl_easy_perform(curl); + if(result) + goto test_cleanup; + + /* CRLF in the stream URI must be refused */ + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, + "rtsp://example/\r\nOPTIONS rtsp://evil/ RTSP/1.0"); + result = curl_easy_perform(curl); + if(result != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "control byte in stream URI not rejected: %d\n", + (int)result); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + /* CRLF in the Transport header must be refused */ + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); + easy_setopt(curl, CURLOPT_RTSP_TRANSPORT, + "RAW/RAW/UDP;unicast\r\nInjected: 1"); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_SETUP); + result = curl_easy_perform(curl); + if(result != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "control byte in transport not rejected: %d\n", + (int)result); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + /* CRLF in the Session ID must be refused */ + easy_setopt(curl, CURLOPT_RTSP_STREAM_URI, URL); + easy_setopt(curl, CURLOPT_RTSP_SESSION_ID, "1234\r\nInjected: 1"); + easy_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_PLAY); + result = curl_easy_perform(curl); + if(result != CURLE_BAD_FUNCTION_ARGUMENT) { + curl_mfprintf(stderr, "control byte in session ID not rejected: %d\n", + (int)result); + result = TEST_ERR_FAILURE; + goto test_cleanup; + } + + result = CURLE_OK; + +test_cleanup: + curl_easy_cleanup(curl); + curl_global_cleanup(); + + return result; +}