Vulnerability: A denial of service in the subtitle decoder in FFmpeg 3.2 and 4.1 allows attackers to hog the CPU via a crafted video file in Matroska format, because handle_open_brace in libavcodec/htmlsubtitles.c has a complex format argument to sscanf.
Vulnerability ID: CVE-2019-9721
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 handling braces

Fixes: [Semmle Security Reports #19439]
Fixes: dos_sscanf2.mkv

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

Vulnerable version (before fix):
 #include "libavutil/common.h" #include "libavutil/parseutils.h" #include "htmlsubtitles.h"  static int html_color_parse(void *log_ctx, const char *str) { 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: int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)             break;         case '{':    /* skip all {\xxx} substrings except for {\an%d}                         and all microdvd like styles such as {Y:xxx} */             len = 0;             an += sscanf(in, "{\\an%*1u}%n", &len) >= 0 && len > 0;              if (!closing_brace_missing) {                 if (   (an != 1 && in[1] == '\\')                     || (in[1] && strchr("CcFfoPSsYy", in[1]) && in[2] == ':')) {

Fixed version (after fix):
 #include "libavutil/common.h" #include "libavutil/parseutils.h" #include "htmlsubtitles.h" #include <ctype.h>  static int html_color_parse(void *log_ctx, const char *str) { static void rstrip_spaces_buf(AVBPrint *buf)             buf->str[--buf->len] = 0; }  /*  * Fast code for scanning text enclosed in braces. Functionally  * equivalent to this sscanf call:  *  * sscanf(in, "{\\an%*1u}%n", &len) >= 0 && len > 0  */ static int scanbraces(const char* in) {     if (strncmp(in, "{\\an", 4) != 0) {         return 0;     }     if (!isdigit(in[4])) {         return 0;     }     if (in[5] != '}') {         return 0;     }     return 1; }  /*  * Fast code for scanning the rest of a tag. Functionally equivalent to  * this sscanf call: int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)             break;         case '{':    /* skip all {\xxx} substrings except for {\an%d}                         and all microdvd like styles such as {Y:xxx} */             an += scanbraces(in);             if (!closing_brace_missing) {                 if (   (an != 1 && in[1] == '\\')                     || (in[1] && strchr("CcFfoPSsYy", in[1]) && in[2] == ':')) {

Patch:
patch: @@ -22,6 +22,7 @@
 #include "libavutil/common.h"
 #include "libavutil/parseutils.h"
 #include "htmlsubtitles.h"
+#include <ctype.h>
 
 static int html_color_parse(void *log_ctx, const char *str)
 {
@@ -51,6 +52,25 @@ static void rstrip_spaces_buf(AVBPrint *buf)
             buf->str[--buf->len] = 0;
 }
 
+/*
+ * Fast code for scanning text enclosed in braces. Functionally
+ * equivalent to this sscanf call:
+ *
+ * sscanf(in, "{\\an%*1u}%n", &len) >= 0 && len > 0
+ */
+static int scanbraces(const char* in) {
+    if (strncmp(in, "{\\an", 4) != 0) {
+        return 0;
+    }
+    if (!isdigit(in[4])) {
+        return 0;
+    }
+    if (in[5] != '}') {
+        return 0;
+    }
+    return 1;
+}
+
 /*
  * Fast code for scanning the rest of a tag. Functionally equivalent to
  * this sscanf call:
@@ -110,9 +130,7 @@ int ff_htmlmarkup_to_ass(void *log_ctx, AVBPrint *dst, const char *in)
             break;
         case '{':    /* skip all {\xxx} substrings except for {\an%d}
                         and all microdvd like styles such as {Y:xxx} */
-            len = 0;
-            an += sscanf(in, "{\\an%*1u}%n", &len) >= 0 && len > 0;
-
+            an += scanbraces(in);
             if (!closing_brace_missing) {
                 if (   (an != 1 && in[1] == '\\')
                     || (in[1] && strchr("CcFfoPSsYy", in[1]) && in[2] == ':')) {
path: libavcodec/htmlsubtitles.c