summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordengjfzh <dengjfzh@gmail.com>2023-03-21 15:45:59 +0800
committerDaniel Stenberg <daniel@haxx.se>2023-03-30 09:59:09 +0200
commit6c6306f3008f2c9b20a6443ddae3fbd1cab6e848 (patch)
tree3d096174b7881ef8419079bcf76fe4b0feed5a87
parent61f52a97e9fd1bb79f1b312fe031ceb95fd13d27 (diff)
downloadcurl-6c6306f3008f2c9b20a6443ddae3fbd1cab6e848.tar.gz
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
-rw-r--r--.mailmap1
-rw-r--r--lib/rtsp.c181
-rw-r--r--lib/urldata.h3
-rw-r--r--tests/data/test57115
-rw-r--r--tests/libtest/lib571.c8
-rw-r--r--tests/server/rtspd.c16
6 files changed, 174 insertions, 50 deletions
diff --git a/.mailmap b/.mailmap
index 3e5b4d963..8de0f9fb8 100644
--- a/.mailmap
+++ b/.mailmap
@@ -103,3 +103,4 @@ Stefan Eissing <stefan@eissing.org> <stefan.eissing@greenbytes.de>
Michael Musset <mickamusset@gmail.com>
Andy Alt <arch_stanton5995@protonmail.com>
Thomas1664 on github <46387399+Thomas1664@users.noreply.github.com>
+dengjfzh on github <dengjfzh@gmail.com>
diff --git a/lib/rtsp.c b/lib/rtsp.c
index aef3560a9..c503cda42 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 */
+ 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;
- /* Parse the header */
- /* The channel identifier immediately follows and is 1 byte */
- rtspc->rtp_channel = RTP_PKT_CHANNEL(rtp);
+ /* 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 */
+ *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;
+ }
- if(rtp_dataleft < rtp_length + 4) {
- /* Need more - incomplete payload */
+ /* 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 1fc9d91ff..15b285c09 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 3cc1b98b5..e575343a6 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
</data1>
@@ -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
</servercmd>
</reply>
@@ -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
</stdout>
<file name="log/protofile%TESTNUMBER.txt">
diff --git a/tests/libtest/lib571.c b/tests/libtest/lib571.c
index 77aa8acc5..86106b21e 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 8bf98ebdc..3c0a69763 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) {