CVE-2016-10190: Improper Restriction of Operations within the Bounds of a Memory Buffer (CWE-119)
value: Heap-based buffer overflow in libavformat/http.c in FFmpeg before 2.8.10, 3.0.x before 3.0.5, 3.1.x before 3.1.6, and 3.2.x before 3.2.2 allows remote web servers to execute arbitrary code via a negative chunk size in an HTTP response.
CWE description: The product performs operations on a memory buffer, but it reads from or writes to a memory location outside the buffer's intended boundary. This may result in read or write operations on unexpected memory locations that could be linked to other variables, data structures, or internal program data.
CVSS v3 base score: 9.8
Published: 2017-02-09T15:59Z
Repository: https://github.com/FFmpeg/FFmpeg
Language: C
Files changed: libavformat/http.c
Commit date: 2016-12-05 16:15:10 -0500
Commit message: http: make length/offset-related variables unsigned.

Fixes #5992, reported and found by Paul Cher <paulcher@icloud.com>.

Diff: diff --git a/libavformat/http.c b/libavformat/http.c
index d48958d8a3c..13f3be42271 100644
--- a/libavformat/http.c
+++ b/libavformat/http.c
@@ -60,12 +60,12 @@ typedef struct HTTPContext {
     URLContext *hd;
     unsigned char buffer[BUFFER_SIZE], *buf_ptr, *buf_end;
     int line_count;
     int http_code;
     /* Used if "Transfer-Encoding: chunked" otherwise -1. */
-    int64_t chunksize;
-    int64_t off, end_off, filesize;
+    uint64_t chunksize;
+    uint64_t off, end_off, filesize;
     char *location;
     HTTPAuthState auth_state;
     HTTPAuthState proxy_auth_state;
     char *http_proxy;
     char *headers;
@@ -93,13 +93,13 @@ typedef struct HTTPContext {
     char *cookies;          ///< holds newline (\n) delimited Set-Cookie header field values (without the "Set-Cookie: " field name)
     /* A dictionary containing cookies keyed by cookie name */
     AVDictionary *cookie_dict;
     int icy;
     /* how much data was read since the last ICY metadata packet */
-    int icy_data_read;
+    uint64_t icy_data_read;
     /* after how many bytes of read data a new metadata packet will be found */
-    int icy_metaint;
+    uint64_t icy_metaint;
     char *icy_metadata_headers;
     char *icy_metadata_packet;
     AVDictionary *metadata;
 #if CONFIG_ZLIB
     int compressed;
@@ -487,11 +487,11 @@ static int http_open(URLContext *h, const char *uri, int flags,
     if( s->seekable == 1 )
         h->is_streamed = 0;
     else
         h->is_streamed = 1;
 
-    s->filesize = -1;
+    s->filesize = UINT64_MAX;
     s->location = av_strdup(uri);
     if (!s->location)
         return AVERROR(ENOMEM);
     if (options)
         av_dict_copy(&s->chained_options, *options, 0);
@@ -614,13 +614,13 @@ static void parse_content_range(URLContext *h, const char *p)
     HTTPContext *s = h->priv_data;
     const char *slash;
 
     if (!strncmp(p, "bytes ", 6)) {
         p     += 6;
-        s->off = strtoll(p, NULL, 10);
+        s->off = strtoull(p, NULL, 10);
         if ((slash = strchr(p, '/')) && strlen(slash) > 0)
-            s->filesize = strtoll(slash + 1, NULL, 10);
+            s->filesize = strtoull(slash + 1, NULL, 10);
     }
     if (s->seekable == -1 && (!s->is_akamai || s->filesize != 2147483647))
         h->is_streamed = 0; /* we _can_ in fact seek */
 }
 
@@ -806,21 +806,22 @@ static int process_line(URLContext *h, char *line, int line_count,
             p++;
         if (!av_strcasecmp(tag, "Location")) {
             if ((ret = parse_location(s, p)) < 0)
                 return ret;
             *new_location = 1;
-        } else if (!av_strcasecmp(tag, "Content-Length") && s->filesize == -1) {
-            s->filesize = strtoll(p, NULL, 10);
+        } else if (!av_strcasecmp(tag, "Content-Length") &&
+                   s->filesize == UINT64_MAX) {
+            s->filesize = strtoull(p, NULL, 10);
         } else if (!av_strcasecmp(tag, "Content-Range")) {
             parse_content_range(h, p);
         } else if (!av_strcasecmp(tag, "Accept-Ranges") &&
                    !strncmp(p, "bytes", 5) &&
                    s->seekable == -1) {
             h->is_streamed = 0;
         } else if (!av_strcasecmp(tag, "Transfer-Encoding") &&
                    !av_strncasecmp(p, "chunked", 7)) {
-            s->filesize  = -1;
+            s->filesize  = UINT64_MAX;
             s->chunksize = 0;
         } else if (!av_strcasecmp(tag, "WWW-Authenticate")) {
             ff_http_auth_handle_header(&s->auth_state, tag, p);
         } else if (!av_strcasecmp(tag, "Authentication-Info")) {
             ff_http_auth_handle_header(&s->auth_state, tag, p);
@@ -840,11 +841,11 @@ static int process_line(URLContext *h, char *line, int line_count,
             s->mime_type = av_strdup(p);
         } else if (!av_strcasecmp(tag, "Set-Cookie")) {
             if (parse_cookie(s, p, &s->cookie_dict))
                 av_log(h, AV_LOG_WARNING, "Unable to parse '%s'\n", p);
         } else if (!av_strcasecmp(tag, "Icy-MetaInt")) {
-            s->icy_metaint = strtoll(p, NULL, 10);
+            s->icy_metaint = strtoull(p, NULL, 10);
         } else if (!av_strncasecmp(tag, "Icy-", 4)) {
             if ((ret = parse_icy(s, tag, p)) < 0)
                 return ret;
         } else if (!av_strcasecmp(tag, "Content-Encoding")) {
             if ((ret = parse_content_encoding(h, p)) < 0)
@@ -970,11 +971,11 @@ static int http_read_header(URLContext *h, int *new_location)
 {
     HTTPContext *s = h->priv_data;
     char line[MAX_URL_SIZE];
     int err = 0;
 
-    s->chunksize = -1;
+    s->chunksize = UINT64_MAX;
 
     for (;;) {
         if ((err = http_get_line(s, line, sizeof(line))) < 0)
             return err;
 
@@ -1004,11 +1005,11 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
 {
     HTTPContext *s = h->priv_data;
     int post, err;
     char headers[HTTP_HEADERS_SIZE] = "";
     char *authstr = NULL, *proxyauthstr = NULL;
-    int64_t off = s->off;
+    uint64_t off = s->off;
     int len = 0;
     const char *method;
     int send_expect_100 = 0;
 
     /* send http header */
@@ -1058,11 +1059,11 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
     // Note: we send this on purpose even when s->off is 0 when we're probing,
     // since it allows us to detect more reliably if a (non-conforming)
     // server supports seeking by analysing the reply headers.
     if (!has_header(s->headers, "\r\nRange: ") && !post && (s->off > 0 || s->end_off || s->seekable == -1)) {
         len += av_strlcatf(headers + len, sizeof(headers) - len,
-                           "Range: bytes=%"PRId64"-", s->off);
+                           "Range: bytes=%"PRIu64"-", s->off);
         if (s->end_off)
             len += av_strlcatf(headers + len, sizeof(headers) - len,
                                "%"PRId64, s->end_off - 1);
         len += av_strlcpy(headers + len, "\r\n",
                           sizeof(headers) - len);
@@ -1133,11 +1134,11 @@ static int http_connect(URLContext *h, const char *path, const char *local_path,
     s->buf_ptr          = s->buffer;
     s->buf_end          = s->buffer;
     s->line_count       = 0;
     s->off              = 0;
     s->icy_data_read    = 0;
-    s->filesize         = -1;
+    s->filesize         = UINT64_MAX;
     s->willclose        = 0;
     s->end_chunked_post = 0;
     s->end_header       = 0;
     if (post && !s->post_data && !send_expect_100) {
         /* Pretend that it did work. We didn't read any header yet, since
@@ -1173,19 +1174,17 @@ static int http_buf_read(URLContext *h, uint8_t *buf, int size)
         if (len > size)
             len = size;
         memcpy(buf, s->buf_ptr, len);
         s->buf_ptr += len;
     } else {
-        int64_t target_end = s->end_off ? s->end_off : s->filesize;
-        if ((!s->willclose || s->chunksize < 0) &&
-            target_end >= 0 && s->off >= target_end)
+        uint64_t target_end = s->end_off ? s->end_off : s->filesize;
+        if ((!s->willclose || s->chunksize == UINT64_MAX) && s->off >= target_end)
             return AVERROR_EOF;
         len = ffurl_read(s->hd, buf, size);
-        if (!len && (!s->willclose || s->chunksize < 0) &&
-            target_end >= 0 && s->off < target_end) {
+        if (!len && (!s->willclose || s->chunksize == UINT64_MAX) && s->off < target_end) {
             av_log(h, AV_LOG_ERROR,
-                   "Stream ends prematurely at %"PRId64", should be %"PRId64"\n",
+                   "Stream ends prematurely at %"PRIu64", should be %"PRIu64"\n",
                    s->off, target_end
                   );
             return AVERROR(EIO);
         }
     }
@@ -1245,47 +1244,53 @@ static int http_read_stream(URLContext *h, uint8_t *buf, int size)
         err = http_read_header(h, &new_location);
         if (err < 0)
             return err;
     }
 
-    if (s->chunksize >= 0) {
+    if (s->chunksize != UINT64_MAX) {
         if (!s->chunksize) {
             char line[32];
 
                 do {
                     if ((err = http_get_line(s, line, sizeof(line))) < 0)
                         return err;
                 } while (!*line);    /* skip CR LF from last chunk */
 
-                s->chunksize = strtoll(line, NULL, 16);
+                s->chunksize = strtoull(line, NULL, 16);
 
-                av_log(NULL, AV_LOG_TRACE, "Chunked encoding data size: %"PRId64"'\n",
+                av_log(h, AV_LOG_TRACE,
+                       "Chunked encoding data size: %"PRIu64"'\n",
                         s->chunksize);
 
                 if (!s->chunksize)
                     return 0;
+                else if (s->chunksize == UINT64_MAX) {
+                    av_log(h, AV_LOG_ERROR, "Invalid chunk size %"PRIu64"\n",
+                           s->chunksize);
+                    return AVERROR(EINVAL);
+                }
         }
         size = FFMIN(size, s->chunksize);
     }
 #if CONFIG_ZLIB
     if (s->compressed)
         return http_buf_read_compressed(h, buf, size);
 #endif /* CONFIG_ZLIB */
     read_ret = http_buf_read(h, buf, size);
     if (   (read_ret  < 0 && s->reconnect        && (!h->is_streamed || s->reconnect_streamed) && s->filesize > 0 && s->off < s->filesize)
         || (read_ret == 0 && s->reconnect_at_eof && (!h->is_streamed || s->reconnect_streamed))) {
-        int64_t target = h->is_streamed ? 0 : s->off;
+        uint64_t target = h->is_streamed ? 0 : s->off;
 
         if (s->reconnect_delay > s->reconnect_delay_max)
             return AVERROR(EIO);
 
-        av_log(h, AV_LOG_INFO, "Will reconnect at %"PRId64" error=%s.\n", s->off, av_err2str(read_ret));
+        av_log(h, AV_LOG_INFO, "Will reconnect at %"PRIu64" error=%s.\n", s->off, av_err2str(read_ret));
         av_usleep(1000U*1000*s->reconnect_delay);
         s->reconnect_delay = 1 + 2*s->reconnect_delay;
         seek_ret = http_seek_internal(h, target, SEEK_SET, 1);
         if (seek_ret != target) {
-            av_log(h, AV_LOG_ERROR, "Failed to reconnect at %"PRId64".\n", target);
+            av_log(h, AV_LOG_ERROR, "Failed to reconnect at %"PRIu64".\n", target);
             return read_ret;
         }
 
         read_ret = http_buf_read(h, buf, size);
     } else
@@ -1336,14 +1341,15 @@ static void update_metadata(HTTPContext *s, char *data)
 
 static int store_icy(URLContext *h, int size)
 {
     HTTPContext *s = h->priv_data;
     /* until next metadata packet */
-    int remaining = s->icy_metaint - s->icy_data_read;
+    uint64_t remaining;
 
-    if (remaining < 0)
+    if (s->icy_metaint < s->icy_data_read)
         return AVERROR_INVALIDDATA;
+    remaining = s->icy_metaint - s->icy_data_read;
 
     if (!remaining) {
         /* The metadata packet is variable sized. It has a 1 byte header
          * which sets the length of the packet (divided by 16). If it's 0,
          * the metadata doesn't change. After the packet, icy_metaint bytes
@@ -1453,22 +1459,22 @@ static int http_close(URLContext *h)
 
 static int64_t http_seek_internal(URLContext *h, int64_t off, int whence, int force_reconnect)
 {
     HTTPContext *s = h->priv_data;
     URLContext *old_hd = s->hd;
-    int64_t old_off = s->off;
+    uint64_t old_off = s->off;
     uint8_t old_buf[BUFFER_SIZE];
     int old_buf_size, ret;
     AVDictionary *options = NULL;
 
     if (whence == AVSEEK_SIZE)
         return s->filesize;
     else if (!force_reconnect &&
              ((whence == SEEK_CUR && off == 0) ||
               (whence == SEEK_SET && off == s->off)))
         return s->off;
-    else if ((s->filesize == -1 && whence == SEEK_END))
+    else if ((s->filesize == UINT64_MAX && whence == SEEK_END))
         return AVERROR(ENOSYS);
 
     if (whence == SEEK_CUR)
         off += s->off;
     else if (whence == SEEK_END)
@@ -1619,11 +1625,11 @@ redo:
         goto fail;
 
     s->buf_ptr    = s->buffer;
     s->buf_end    = s->buffer;
     s->line_count = 0;
-    s->filesize   = -1;
+    s->filesize   = UINT64_MAX;
     cur_auth_type = s->proxy_auth_state.auth_type;
 
     /* Note: This uses buffering, potentially reading more than the
      * HTTP header. If tunneling a protocol where the server starts
      * the conversation, we might buffer part of that here, too.

Vulnerable code:     int64_t chunksize;
    int64_t off, end_off, filesize;
    int icy_data_read;
    int icy_metaint;
    s->filesize = -1;
        s->off = strtoll(p, NULL, 10);
            s->filesize = strtoll(slash + 1, NULL, 10);
        } else if (!av_strcasecmp(tag, "Content-Length") && s->filesize == -1) {
            s->filesize = strtoll(p, NULL, 10);
            s->filesize  = -1;
            s->icy_metaint = strtoll(p, NULL, 10);
    s->chunksize = -1;
    int64_t off = s->off;
                           "Range: bytes=%"PRId64"-", s->off);
    s->filesize         = -1;
        int64_t target_end = s->end_off ? s->end_off : s->filesize;
        if ((!s->willclose || s->chunksize < 0) &&
            target_end >= 0 && s->off >= target_end)
        if (!len && (!s->willclose || s->chunksize < 0) &&
            target_end >= 0 && s->off < target_end) {
                   "Stream ends prematurely at %"PRId64", should be %"PRId64"\n",
    if (s->chunksize >= 0) {
                s->chunksize = strtoll(line, NULL, 16);
                av_log(NULL, AV_LOG_TRACE, "Chunked encoding data size: %"PRId64"'\n",
        int64_t target = h->is_streamed ? 0 : s->off;
        av_log(h, AV_LOG_INFO, "Will reconnect at %"PRId64" error=%s.\n", s->off, av_err2str(read_ret));
            av_log(h, AV_LOG_ERROR, "Failed to reconnect at %"PRId64".\n", target);
    int remaining = s->icy_metaint - s->icy_data_read;
    if (remaining < 0)
    int64_t old_off = s->off;
    else if ((s->filesize == -1 && whence == SEEK_END))
    s->filesize   = -1;
Fixed code:     uint64_t chunksize;
    uint64_t off, end_off, filesize;
    uint64_t icy_data_read;
    uint64_t icy_metaint;
    s->filesize = UINT64_MAX;
        s->off = strtoull(p, NULL, 10);
            s->filesize = strtoull(slash + 1, NULL, 10);
        } else if (!av_strcasecmp(tag, "Content-Length") &&
                   s->filesize == UINT64_MAX) {
            s->filesize = strtoull(p, NULL, 10);
            s->filesize  = UINT64_MAX;
            s->icy_metaint = strtoull(p, NULL, 10);
    s->chunksize = UINT64_MAX;
    uint64_t off = s->off;
                           "Range: bytes=%"PRIu64"-", s->off);
    s->filesize         = UINT64_MAX;
        uint64_t target_end = s->end_off ? s->end_off : s->filesize;
        if ((!s->willclose || s->chunksize == UINT64_MAX) && s->off >= target_end)
        if (!len && (!s->willclose || s->chunksize == UINT64_MAX) && s->off < target_end) {
                   "Stream ends prematurely at %"PRIu64", should be %"PRIu64"\n",
    if (s->chunksize != UINT64_MAX) {
                s->chunksize = strtoull(line, NULL, 16);
                av_log(h, AV_LOG_TRACE,
                       "Chunked encoding data size: %"PRIu64"'\n",
                else if (s->chunksize == UINT64_MAX) {
                    av_log(h, AV_LOG_ERROR, "Invalid chunk size %"PRIu64"\n",
                           s->chunksize);
                    return AVERROR(EINVAL);
                }
        uint64_t target = h->is_streamed ? 0 : s->off;
        av_log(h, AV_LOG_INFO, "Will reconnect at %"PRIu64" error=%s.\n", s->off, av_err2str(read_ret));
            av_log(h, AV_LOG_ERROR, "Failed to reconnect at %"PRIu64".\n", target);
    uint64_t remaining;
    if (s->icy_metaint < s->icy_data_read)
    remaining = s->icy_metaint - s->icy_data_read;
    uint64_t old_off = s->off;
    else if ((s->filesize == UINT64_MAX && whence == SEEK_END))
    s->filesize   = UINT64_MAX;