From 6c6306f3008f2c9b20a6443ddae3fbd1cab6e848 Mon Sep 17 00:00:00 2001 From: dengjfzh Date: Tue, 21 Mar 2023 15:45:59 +0800 Subject: [PATCH] rtsp: skip malformed RTSP interleaved frame data Some IP cameras send malformed RTSP interleaved frames sometimes, which can cause curl_easy_perform return 1 (CURLE_UNSUPPORTED_PROTOCOL). This change attempts to skip clearly incorrect RTSP interleaving frame data. Closes #10808 --- .mailmap | 1 + lib/rtsp.c | 181 ++++++++++++++++++++++++++++++++--------- lib/urldata.h | 3 + tests/data/test571 | 15 ++++ tests/libtest/lib571.c | 8 +- tests/server/rtspd.c | 16 ++-- 6 files changed, 174 insertions(+), 50 deletions(-) diff --git a/.mailmap b/.mailmap index 3e5b4d9637..8de0f9fb84 100644 --- a/.mailmap +++ b/.mailmap @@ -103,3 +103,4 @@ Stefan Eissing Michael Musset Andy Alt Thomas1664 on github <46387399+Thomas1664@users.noreply.github.com> +dengjfzh on github diff --git a/lib/rtsp.c b/lib/rtsp.c index aef3560a9a..c503cda427 100644 --- a/lib/rtsp.c +++ b/lib/rtsp.c @@ -45,8 +45,6 @@ #include "curl_memory.h" #include "memdebug.h" -#define RTP_PKT_CHANNEL(p) ((int)((unsigned char)((p)[1]))) - #define RTP_PKT_LENGTH(p) ((((int)((unsigned char)((p)[2]))) << 8) | \ ((int)((unsigned char)((p)[3])))) @@ -91,6 +89,8 @@ static int rtsp_getsock_do(struct Curl_easy *data, struct connectdata *conn, static CURLcode rtp_client_write(struct Curl_easy *data, char *ptr, size_t len); +static +CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport); /* @@ -594,11 +594,14 @@ static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, bool *readmore) { struct SingleRequest *k = &data->req; struct rtsp_conn *rtspc = &(conn->proto.rtspc); + unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; char *rtp; /* moving pointer to rtp data */ ssize_t rtp_dataleft; /* how much data left to parse in this round */ char *scratch; CURLcode result; + bool interleaved = false; + size_t skip_size = 0; if(rtspc->rtp_buf) { /* There was some leftover data the last time. Merge buffers */ @@ -621,52 +624,94 @@ static CURLcode rtsp_rtp_readwrite(struct Curl_easy *data, rtp_dataleft = *nread; } - while((rtp_dataleft > 0) && - (rtp[0] == '$')) { - if(rtp_dataleft > 4) { - int rtp_length; + while(rtp_dataleft > 0) { + if(rtp[0] == '$') { + if(rtp_dataleft > 4) { + unsigned char rtp_channel; + int rtp_length; + int idx; + int off; - /* Parse the header */ - /* The channel identifier immediately follows and is 1 byte */ - rtspc->rtp_channel = RTP_PKT_CHANNEL(rtp); + /* Parse the header */ + /* The channel identifier immediately follows and is 1 byte */ + rtp_channel = (unsigned char)rtp[1]; + idx = rtp_channel / 8; + off = rtp_channel % 8; + if(!(rtp_channel_mask[idx] & (1 << off))) { + /* invalid channel number, maybe not an RTP packet */ + rtp++; + rtp_dataleft--; + skip_size++; + continue; + } + if(skip_size > 0) { + DEBUGF(infof(data, "Skip the malformed interleaved data %lu " + "bytes", skip_size)); + } + skip_size = 0; + rtspc->rtp_channel = rtp_channel; - /* The length is two bytes */ - rtp_length = RTP_PKT_LENGTH(rtp); + /* The length is two bytes */ + rtp_length = RTP_PKT_LENGTH(rtp); - if(rtp_dataleft < rtp_length + 4) { - /* Need more - incomplete payload */ + if(rtp_dataleft < rtp_length + 4) { + /* Need more - incomplete payload */ + *readmore = TRUE; + break; + } + interleaved = true; + /* We have the full RTP interleaved packet + * Write out the header including the leading '$' */ + DEBUGF(infof(data, "RTP write channel %d rtp_length %d", + rtspc->rtp_channel, rtp_length)); + result = rtp_client_write(data, &rtp[0], rtp_length + 4); + if(result) { + *readmore = FALSE; + Curl_safefree(rtspc->rtp_buf); + rtspc->rtp_buf = NULL; + rtspc->rtp_bufsize = 0; + return result; + } + + /* Move forward in the buffer */ + rtp_dataleft -= rtp_length + 4; + rtp += rtp_length + 4; + + if(data->set.rtspreq == RTSPREQ_RECEIVE) { + /* If we are in a passive receive, give control back + * to the app as often as we can. + */ + k->keepon &= ~KEEP_RECV; + } + } + else { + /* Need more - incomplete header */ *readmore = TRUE; break; } - /* We have the full RTP interleaved packet - * Write out the header including the leading '$' */ - DEBUGF(infof(data, "RTP write channel %d rtp_length %d", - rtspc->rtp_channel, rtp_length)); - result = rtp_client_write(data, &rtp[0], rtp_length + 4); - if(result) { - failf(data, "Got an error writing an RTP packet"); - *readmore = FALSE; - Curl_safefree(rtspc->rtp_buf); - rtspc->rtp_buf = NULL; - rtspc->rtp_bufsize = 0; - return result; - } - - /* Move forward in the buffer */ - rtp_dataleft -= rtp_length + 4; - rtp += rtp_length + 4; - - if(data->set.rtspreq == RTSPREQ_RECEIVE) { - /* If we are in a passive receive, give control back - * to the app as often as we can. - */ - k->keepon &= ~KEEP_RECV; - } } else { - /* Need more - incomplete header */ - *readmore = TRUE; - break; + /* If the following data begins with 'RTSP/', which might be an RTSP + message, we should stop skipping the data. */ + /* If `k-> headerline> 0 && !interleaved` is true, we are maybe in the + middle of an RTSP message. It is difficult to determine this, so we + stop skipping. */ + size_t prefix_len = (rtp_dataleft < 5) ? rtp_dataleft : 5; + if((k->headerline > 0 && !interleaved) || + strncmp(rtp, "RTSP/", prefix_len) == 0) { + if(skip_size > 0) { + DEBUGF(infof(data, "Skip the malformed interleaved data %lu " + "bytes", skip_size)); + } + skip_size = 0; + break; /* maybe is an RTSP message */ + } + /* Skip incorrect data util the next RTP packet or RTSP message */ + do { + rtp++; + rtp_dataleft--; + skip_size++; + } while(rtp_dataleft > 0 && rtp[0] != '$' && rtp[0] != 'R'); } } @@ -822,7 +867,63 @@ CURLcode Curl_rtsp_parseheader(struct Curl_easy *data, char *header) (data->set.str[STRING_RTSP_SESSION_ID])[idlen] = '\0'; } } + else if(checkprefix("Transport:", header)) { + CURLcode result; + result = rtsp_parse_transport(data, header + 10); + if(result) + return result; + } return CURLE_OK; } +static +CURLcode rtsp_parse_transport(struct Curl_easy *data, char *transport) +{ + /* If we receive multiple Transport response-headers, the linterleaved + channels of each response header is recorded and used together for + subsequent data validity checks.*/ + /* e.g.: ' RTP/AVP/TCP;unicast;interleaved=5-6' */ + char *start; + char *end; + start = transport; + while(start && *start) { + while(*start && ISBLANK(*start) ) + start++; + end = strchr(start, ';'); + if(checkprefix("interleaved=", start)) { + long chan1, chan2, chan; + char *endp; + char *p = start + 12; + chan1 = strtol(p, &endp, 10); + if(p != endp && chan1 >= 0 && chan1 <= 255) { + unsigned char *rtp_channel_mask = data->state.rtp_channel_mask; + chan2 = chan1; + if(*endp == '-') { + p = endp + 1; + chan2 = strtol(p, &endp, 10); + if(p == endp || chan2 < 0 || chan2 > 255) { + infof(data, "Unable to read the interleaved parameter from " + "Transport header: [%s]", transport); + chan2 = chan1; + } + } + for(chan = chan1; chan <= chan2; chan++) { + long idx = chan / 8; + long off = chan % 8; + rtp_channel_mask[idx] |= (unsigned char)(1 << off); + } + } + else { + infof(data, "Unable to read the interleaved parameter from " + "Transport header: [%s]", transport); + } + break; + } + /* skip to next parameter */ + start = (!end) ? end : (end + 1); + } + return CURLE_OK; +} + + #endif /* CURL_DISABLE_RTSP or using Hyper */ diff --git a/lib/urldata.h b/lib/urldata.h index 1fc9d91ffa..15b285c09b 100644 --- a/lib/urldata.h +++ b/lib/urldata.h @@ -1362,6 +1362,9 @@ struct UrlState { long rtsp_next_client_CSeq; /* the session's next client CSeq */ long rtsp_next_server_CSeq; /* the session's next server CSeq */ long rtsp_CSeq_recv; /* most recent CSeq received */ + + unsigned char rtp_channel_mask[32]; /* for the correctness checking of the + interleaved data */ #endif curl_off_t infilesize; /* size of file to upload, -1 means unknown. diff --git a/tests/data/test571 b/tests/data/test571 index 3cc1b98b5f..e575343a6e 100644 --- a/tests/data/test571 +++ b/tests/data/test571 @@ -19,6 +19,7 @@ RTSP/1.0 200 OK Server: RTSPD/libcurl-test Session: asdf CSeq: 1 +Transport: RTP/AVP/TCP;unicast;interleaved=0-1 @@ -54,6 +55,7 @@ rtp: part 2 channel 0 size 500 rtp: part 2 channel 0 size 196 rtp: part 2 channel 0 size 124 rtp: part 2 channel 0 size 824 +rtp: part 2 channel 0 size 18 size_err -6 rtp: part 3 channel 1 size 10 rtp: part 3 channel 0 size 50 rtp: part 4 channel 0 size 798 @@ -62,6 +64,12 @@ rtp: part 4 channel 1 size 30 rtp: part 4 channel 0 size 2048 rtp: part 4 channel 0 size 85 rtp: part 4 channel 1 size 24 +rtp: part 4 channel 0 size 17 size_err -4 +rtp: part 4 channel 0 size 33 +rtp: part 4 channel 0 size 127 +rtp: part 4 channel 1 size 24 size_err 11 +rtp: part 4 channel 0 size 37 +rtp: part 4 channel 0 size 63 @@ -89,6 +97,7 @@ RTP: message size 500, channel 0 RTP: message size 196, channel 0 RTP: message size 124, channel 0 RTP: message size 824, channel 0 +RTP: message size 12, channel 0 RTP: message size 10, channel 1 RTP: message size 50, channel 0 RTP: message size 798, channel 0 @@ -97,6 +106,12 @@ RTP: message size 30, channel 1 RTP: message size 2048, channel 0 RTP: message size 85, channel 0 RTP: message size 24, channel 1 +RTP: message size 13, channel 0 +RTP: message size 33, channel 0 +RTP: message size 127, channel 0 +RTP: message size 35, channel 1 +RTP PAYLOAD END CORRUPTED (11), [$] +RTP: message size 63, channel 0 diff --git a/tests/libtest/lib571.c b/tests/libtest/lib571.c index 77aa8acc57..86106b21ea 100644 --- a/tests/libtest/lib571.c +++ b/tests/libtest/lib571.c @@ -48,7 +48,7 @@ ((int)((unsigned char)((p)[3])))) #define RTP_DATA_SIZE 12 -static const char *RTP_DATA = "$_1234\n\0asdf"; +static const char *RTP_DATA = "$_1234\n\0Rsdf"; static int rtp_packet_count = 0; @@ -76,14 +76,14 @@ static size_t rtp_write(void *ptr, size_t size, size_t nmemb, void *stream) if(message_size - i > RTP_DATA_SIZE) { if(memcmp(RTP_DATA, data + i, RTP_DATA_SIZE) != 0) { printf("RTP PAYLOAD CORRUPTED [%s]\n", data + i); - return failure; + /* return failure; */ } } else { if(memcmp(RTP_DATA, data + i, message_size - i) != 0) { printf("RTP PAYLOAD END CORRUPTED (%d), [%s]\n", message_size - i, data + i); - return failure; + /* return failure; */ } } } @@ -196,7 +196,7 @@ int test(char *URL) fprintf(stderr, "PLAY COMPLETE\n"); /* Use Receive to get the rest of the data */ - while(!res && rtp_packet_count < 13) { + while(!res && rtp_packet_count < 19) { fprintf(stderr, "LOOPY LOOP!\n"); test_setopt(curl, CURLOPT_RTSP_REQUEST, CURL_RTSPREQ_RECEIVE); res = curl_easy_perform(curl); diff --git a/tests/server/rtspd.c b/tests/server/rtspd.c index 8bf98ebdcc..3c0a697638 100644 --- a/tests/server/rtspd.c +++ b/tests/server/rtspd.c @@ -201,7 +201,7 @@ static const char *doc404_RTSP = "RTSP/1.0 404 Not Found\r\n" /* Default size to send away fake RTP data */ #define RTP_DATA_SIZE 12 -static const char *RTP_DATA = "$_1234\n\0asdf"; +static const char *RTP_DATA = "$_1234\n\0Rsdf"; static int ProcessRequest(struct httprequest *req) { @@ -304,6 +304,7 @@ static int ProcessRequest(struct httprequest *req) int rtp_channel = 0; int rtp_size = 0; + int rtp_size_err = 0; int rtp_partno = -1; char *rtp_scratch = NULL; @@ -320,6 +321,7 @@ static int ProcessRequest(struct httprequest *req) if(cmdsize) { logmsg("Found a reply-servercmd section!"); do { + rtp_size_err = 0; if(!strncmp(CMD_AUTH_REQUIRED, ptr, strlen(CMD_AUTH_REQUIRED))) { logmsg("instructed to require authorization header"); req->auth_req = TRUE; @@ -345,13 +347,15 @@ static int ProcessRequest(struct httprequest *req) logmsg("instructed to skip this number of bytes %d", num); req->skip = num; } - else if(3 == sscanf(ptr, "rtp: part %d channel %d size %d", - &rtp_partno, &rtp_channel, &rtp_size)) { + else if(3 <= sscanf(ptr, + "rtp: part %d channel %d size %d size_err %d", + &rtp_partno, &rtp_channel, &rtp_size, + &rtp_size_err)) { if(rtp_partno == req->partno) { int i = 0; - logmsg("RTP: part %d channel %d size %d", - rtp_partno, rtp_channel, rtp_size); + logmsg("RTP: part %d channel %d size %d size_err %d", + rtp_partno, rtp_channel, rtp_size, rtp_size_err); /* Make our scratch buffer enough to fit all the * desired data and one for padding */ @@ -364,7 +368,7 @@ static int ProcessRequest(struct httprequest *req) SET_RTP_PKT_CHN(rtp_scratch, rtp_channel); /* Length follows and is a two byte short in network order */ - SET_RTP_PKT_LEN(rtp_scratch, rtp_size); + SET_RTP_PKT_LEN(rtp_scratch, rtp_size + rtp_size_err); /* Fill it with junk data */ for(i = 0; i < rtp_size; i += RTP_DATA_SIZE) {