CVE-2021-30145 (published 2021-05-18T14:15Z)

A format string vulnerability in mpv through 0.33.0 allows user-assisted remote attackers to achieve code execution via a crafted m3u playlist file.

Severity: MEDIUM
CVSS v2 base score: 6.8 (AV:N/AC:M/Au:N/C:P/I:P/A:P)
CVSS v3 base score: 7.8, HIGH (CVSS:3.1/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H)

Weakness information:
cwe_id: CWE-134
cwe_name: Use of Externally-Controlled Format String
description: The product uses a function that accepts a format string as an argument, but the format string originates from an external source.
extended_description: 
            When an attacker can modify an externally-controlled format string, this can lead to buffer overflows, denial of service, or data representation problems.
            It should be noted that in some circumstances, such as internationalization, the set of format strings is externally controlled by design. If the source of these format strings is trusted (e.g. only contained in library files that are only modifiable by the system administrator), then the external control might not itself pose a vulnerability.
         
         

Fix information:
repo_url: https://github.com/mpv-player/mpv
commit_details: repo_url: https://github.com/mpv-player/mpv
author: Avi Halachmi (:avih)
author_date: 2021-04-04 14:11:15+03:00
msg: demux_mf: improve format string processing

Before this commit, the user could specify a printf format string
which wasn't verified, and could result in:
- Undefined behavior due to missing or non-matching arguments.
- Buffer overflow due to untested result length.

The offending code was added at commit 103a9609 (2002, mplayer svn):
git-svn-id: svn://svn.mplayerhq.hu/mplayer/trunk@4566 b3059339-0415-0410-9bf9-f77b7e298cf2

It moved around but was not modified meaningfully until now.

Now we reject all conversion specifiers at the format except %%
and a simple subset of the valid specifiers. Also, we now use
snprintf to avoid buffer overflow.

The format string is provided by the user as part of mf:// URI.

Report and initial patch by Stefan Schiller.
Patch reviewed by @jeeb, @sfan5, Stefan Schiller.
repository: repo_url: https://github.com/mpv-player/mpv
repo_name: mpv-player/mpv
description: 🎥 Command line video player
repo_language: C
owner: mpv-player
file_changes: filename: demux_mf.c
change_type: ModificationType.MODIFY
diff: @@ -121,7 +121,8 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename
         goto exit_mf;
     }
 
-    char *fname = talloc_size(mf, strlen(filename) + 32);
+    size_t fname_avail = strlen(filename) + 32;
+    char *fname = talloc_size(mf, fname_avail);
 
 #if HAVE_GLOB
     if (!strchr(filename, '%')) {
@@ -148,10 +149,44 @@ static mf_t *open_mf_pattern(void *talloc_ctx, struct demuxer *d, char *filename
     }
 #endif
 
+    // We're using arbitrary user input as printf format with 1 int argument.
+    // Any format which uses exactly 1 int argument would be valid, but for
+    // simplicity we reject all conversion specifiers except %% and simple
+    // integer specifier: %[.][NUM]d where NUM is 1-3 digits (%.d is valid)
+    const char *f = filename;
+    int MAXDIGS = 3, nspec = 0, bad_spec = 0, c;
+
+    while (nspec < 2 && (c = *f++)) {
+        if (c != '%')
+            continue;
+        if (*f != '%') {
+            nspec++;  // conversion specifier which isn't %%
+            if (*f == '.')
+                f++;
+            for (int ndig = 0; mp_isdigit(*f) && ndig < MAXDIGS; ndig++, f++)
+                /* no-op */;
+            if (*f != 'd') {
+                bad_spec++;  // not int, or beyond our validation capacity
+                break;
+            }
+        }
+        // *f is '%' or 'd'
+        f++;
+    }
+
+    // nspec==0 (zero specifiers) is rejected because fname wouldn't advance.
+    if (bad_spec || nspec != 1) {
+        mp_err(log, "unsupported expr format: '%s'\n", filename);
+        goto exit_mf;
+    }
+
     mp_info(log, "search expr: %s\n", filename);
 
     while (error_count < 5) {
-        sprintf(fname, filename, count++);
+        if (snprintf(fname, fname_avail, filename, count++) >= fname_avail) {
+            mp_err(log, "format result too long: '%s'\n", filename);
+            goto exit_mf;
+        }
         if (!mp_path_exists(fname)) {
             error_count++;
             mp_verbose(log, "file not found: '%s'\n", fname);

programming_language: C