CVE-2017-14058: Loop with Unreachable Exit Condition ('Infinite Loop') (CWE-835)
value: In FFmpeg 2.4 and 3.3.3, the read_data function in libavformat/hls.c does not restrict reload attempts for an insufficient list, which allows remote attackers to cause a denial of service (infinite loop).
CWE description: The product contains an iteration or loop with an exit condition that cannot be reached, i.e., an infinite loop.
CVSS v3 base score: 6.5
Published: 2017-08-31T15:29Z
Repository: https://github.com/FFmpeg/FFmpeg
Language: C
Files changed: doc/demuxers.texi, libavformat/hls.c
Commit date: 2017-08-27 19:19:00 +0200
Commit message: avformat/hls: Fix DoS due to infinite loop

Fixes: loop.m3u

The default max iteration count of 1000 is arbitrary and ideas for a better solution are welcome

Found-by: Xiaohei and Wangchu from Alibaba Security Team

Previous version reviewed-by: Steven Liu <lingjiujianke@gmail.com>
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>

Diff: diff --git a/doc/demuxers.texi b/doc/demuxers.texi
index 29a23d48b258..73dc0feec194 100644
--- a/doc/demuxers.texi
+++ b/doc/demuxers.texi
@@ -298,10 +298,28 @@ ffmpeg -i input.mp4 -ignore_loop 0 -i input.gif -filter_complex overlay=shortest
 Note that in the above example the shortest option for overlay filter is
 used to end the output video at the length of the shortest input file,
 which in this case is @file{input.mp4} as the GIF in this example loops
 infinitely.
 
+@section hls
+
+HLS demuxer
+
+It accepts the following options:
+
+@table @option
+@item live_start_index
+segment index to start live streams at (negative values are from the end).
+
+@item allowed_extensions
+',' separated list of file extensions that hls is allowed to access.
+
+@item max_reload
+Maximum number of times a insufficient list is attempted to be reloaded.
+Default value is 1000.
+@end table
+
 @section image2
 
 Image file demuxer.
 
 This demuxer reads from a list of image files specified by a pattern.
diff --git a/libavformat/hls.c b/libavformat/hls.c
index 01731bd36b0c..0995345bbf2c 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -203,10 +203,11 @@ typedef struct HLSContext {
     char *headers;                       ///< holds HTTP headers set as an AVOption to the HTTP protocol context
     char *http_proxy;                    ///< holds the address of the HTTP proxy server
     AVDictionary *avio_opts;
     int strict_std_compliance;
     char *allowed_extensions;
+    int max_reload;
 } HLSContext;
 
 static int read_chomp_line(AVIOContext *s, char *buf, int maxlen)
 {
     int len = ff_get_line(s, buf, maxlen);
@@ -1261,10 +1262,11 @@ static int read_data(void *opaque, uint8_t *buf, int buf_size)
 {
     struct playlist *v = opaque;
     HLSContext *c = v->parent->priv_data;
     int ret, i;
     int just_opened = 0;
+    int reload_count = 0;
 
 restart:
     if (!v->needed)
         return AVERROR_EOF;
 
@@ -1292,10 +1294,13 @@ restart:
         /* If this is a live stream and the reload interval has elapsed since
          * the last playlist reload, reload the playlists now. */
         reload_interval = default_reload_interval(v);
 
 reload:
+        reload_count++;
+        if (reload_count > c->max_reload)
+            return AVERROR_EOF;
         if (!v->finished &&
             av_gettime_relative() - v->last_load_time >= reload_interval) {
             if ((ret = parse_playlist(c, v->url, v, NULL)) < 0) {
                 av_log(v->parent, AV_LOG_WARNING, "Failed to reload playlist %d\n",
                        v->index);
@@ -2148,10 +2153,12 @@ static const AVOption hls_options[] = {
         OFFSET(live_start_index), AV_OPT_TYPE_INT, {.i64 = -3}, INT_MIN, INT_MAX, FLAGS},
     {"allowed_extensions", "List of file extensions that hls is allowed to access",
         OFFSET(allowed_extensions), AV_OPT_TYPE_STRING,
         {.str = "3gp,aac,avi,flac,mkv,m3u8,m4a,m4s,m4v,mpg,mov,mp2,mp3,mp4,mpeg,mpegts,ogg,ogv,oga,ts,vob,wav"},
         INT_MIN, INT_MAX, FLAGS},
+    {"max_reload", "Maximum number of times a insufficient list is attempted to be reloaded",
+        OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS},
     {NULL}
 };
 
 static const AVClass hls_class = {
     .class_name = "hls,applehttp",

Fixed code: @section hls

HLS demuxer

It accepts the following options:

@table @option
@item live_start_index
segment index to start live streams at (negative values are from the end).

@item allowed_extensions
',' separated list of file extensions that hls is allowed to access.

@item max_reload
Maximum number of times a insufficient list is attempted to be reloaded.
Default value is 1000.
@end table

    int max_reload;
    int reload_count = 0;
        reload_count++;
        if (reload_count > c->max_reload)
            return AVERROR_EOF;
    {"max_reload", "Maximum number of times a insufficient list is attempted to be reloaded",
        OFFSET(max_reload), AV_OPT_TYPE_INT, {.i64 = 1000}, 0, INT_MAX, FLAGS},