Browse Source

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
dengjfzh 1 year ago
parent
commit
6c6306f300
6 changed files with 174 additions and 50 deletions
  1. 1 0
      .mailmap
  2. 141 40
      lib/rtsp.c
  3. 3 0
      lib/urldata.h
  4. 15 0
      tests/data/test571
  5. 4 4
      tests/libtest/lib571.c
  6. 10 6
      tests/server/rtspd.c

+ 1 - 0
.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>

+ 141 - 40
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 */

+ 3 - 0
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.

+ 15 - 0
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">

+ 4 - 4
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);

+ 10 - 6
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) {