Vulnerability: In FFmpeg 3.2 and 4.1, a denial of service in the subtitle decoder allows attackers to hog the CPU via a crafted video file in Matroska format, because ff_htmlmarkup_to_ass in libavcodec/htmlsubtitles.c has a complex format argument to sscanf.
Vulnerability ID: CVE-2019-9718
CWE: CWE-125
Severity score: 2.9
Project: https://github.com/FFmpeg/FFmpeg
Published: 2019-03-12
File: libavcodec/htmlsubtitles.c
Commit message: avcodec/htmlsubtitles: Fixes denial of service due to use of sscanf in inner loop for tag scaning

Fixes: [Semmle Security Reports #19438]
Fixes: dos_sscanf1.mkv

Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
(cherry picked from commit 1f00c97bc3475c477f3c468cf2d924d5761d0982)
Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>
Commit author: Kevin Backhouse via RT

Vulnerable version (before fix):
 static void rstrip_spaces_buf(AVBPrint *buf)             buf->str[--buf->len] = 0; }  int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in) {     char *param, buffer[128], tmp[128]; int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)         case '<':             tag_close = in[1] == '/';             len = 0;             if (sscanf(in+tag_close+1, "%127[^<>]>%n", buffer, &len) >= 1 && len > 0) {                 const char *tagname = buffer;                 while (*tagname == ' ')                     tagname++;

Fixed version (after fix):
 static void rstrip_spaces_buf(AVBPrint *buf)             buf->str[--buf->len] = 0; }  /*  * Fast code for scanning the rest of a tag. Functionally equivalent to  * this sscanf call:  *  * sscanf(in, "%127[^<>]>%n", buffer, lenp) == 2  */ static int scantag(const char* in, char* buffer, int* lenp) {     int len;      for (len = 0; len < 128; len++) {         const char c = *in++;         switch (c) {         case '\0':             return 0;         case '<':             return 0;         case '>':             buffer[len] = '\0';             *lenp = len+1;             return 1;         default:             break;         }         buffer[len] = c;     }     return 0; }  int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in) {     char *param, buffer[128], tmp[128]; int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)         case '<':             tag_close = in[1] == '/';             len = 0;             if (scantag(in+tag_close+1, buffer, &len) && len > 0) {                 const char *tagname = buffer;                 while (*tagname == ' ')                     tagname++;

Patch:
patch: @@ -51,6 +51,34 @@ static void rstrip_spaces_buf(AVBPrint *buf)
             buf->str[--buf->len] = 0;
 }
 
+/*
+ * Fast code for scanning the rest of a tag. Functionally equivalent to
+ * this sscanf call:
+ *
+ * sscanf(in, "%127[^<>]>%n", buffer, lenp) == 2
+ */
+static int scantag(const char* in, char* buffer, int* lenp) {
+    int len;
+
+    for (len = 0; len < 128; len++) {
+        const char c = *in++;
+        switch (c) {
+        case '\0':
+            return 0;
+        case '<':
+            return 0;
+        case '>':
+            buffer[len] = '\0';
+            *lenp = len+1;
+            return 1;
+        default:
+            break;
+        }
+        buffer[len] = c;
+    }
+    return 0;
+}
+
 int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)
 {
     char *param, buffer[128], tmp[128];
@@ -102,7 +130,7 @@ int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)
         case '<':
             tag_close = in[1] == '/';
             len = 0;
-            if (sscanf(in+tag_close+1, "%127[^<>]>%n", buffer, &len) >= 1 && len > 0) {
+            if (scantag(in+tag_close+1, buffer, &len) && len > 0) {
                 const char *tagname = buffer;
                 while (*tagname == ' ')
                     tagname++;
path: libavcodec/htmlsubtitles.c