From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from ffbox0-bg.ffmpeg.org (ffbox0-bg.ffmpeg.org [79.124.17.100]) by master.gitmailbox.com (Postfix) with ESMTPS id B68024EF20 for ; Thu, 19 Feb 2026 18:53:54 +0000 (UTC) Authentication-Results: ffbox; dkim=fail (body hash mismatch (got b'hQrtHcX2sSclFi+Nang3b+WyCrVmctiAQAaRDwAX7fQ=', expected b'm2C7c5hJ72oGP1diD47+KXUPNlISOY8nuhXz0Pm0Yts=')) header.d=ffmpeg.org header.i=@ffmpeg.org header.a=rsa-sha256 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1771527217; h=mime-version : to : date : message-id : reply-to : subject : list-id : list-archive : list-archive : list-help : list-owner : list-post : list-subscribe : list-unsubscribe : from : cc : content-type : content-transfer-encoding : from; bh=hQrtHcX2sSclFi+Nang3b+WyCrVmctiAQAaRDwAX7fQ=; b=O8eOT9XAB38cvu6Le6QwmU2IchQ9UMTUQAUpcqZlJuWDhng2lD0BGFo5JimG5dOyY05Iz yRgaViMfMmoDBPGQbdVhtGHN1cLRW2XA2VqAFnAmKNiKev5+Td5/SOOCjYUEEhchkTh0DsK iauuX4a4Wg1/ChfpFqvigDURPVa2ncT5/dYTJGCosFeXQTPfvKH81HRIZyqQZ3wtHguirXW yMbsNrs5lnkOcp4mIDxISY9yjQ2VMcVEps5M3RyeJybgXS6QbG/qS4idw1wZYJ4c5bhHQGl 3YNCRBN/bREUIU9V/hQ0rEH9WyRxe99LepbM6D56oR2b1LXrU/cn3sUmw8Sg== Received: from [172.18.0.3] (unknown [172.18.0.3]) by ffbox0-bg.ffmpeg.org (Postfix) with ESMTP id 86B466912A2; Thu, 19 Feb 2026 20:53:37 +0200 (EET) ARC-Seal: i=1; cv=none; a=rsa-sha256; d=ffmpeg.org; s=arc; t=1771527204; b=BgSCeLGQpLkj6PjhjpAL3Q6/eI7DoKZwZgKYi0YCfGrc1lLwdji72Seqg5/gCHryqrrC2 rPag4R5G/xaHLAz/N8AcaPbxMDInbBKWItev0ehY5iB/ftcQ3QzYMICFw96FwgsJfYCCnyK G6m/jaK2mscouitCxkmlWqY0ELgGMZAvXsTJtildX/vrQ7RTCML9ohm2lMqgmdWJL7Hd57C lnv8tjiTCiQRZDTKdeUkwVsxhxYEDhT/gHiJoINtKeZx3WmsK0eUSWbwQpix5zybcO+40di Mvhehg4OQNubSXtDjNL51Osf2nfH47K3L1RJOEtgVDeUaa57pZKSu0y3eEXA== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=ffmpeg.org; s=arc; t=1771527204; h=from : sender : reply-to : subject : date : message-id : to : cc : mime-version : content-type : content-transfer-encoding : content-id : content-description : resent-date : resent-from : resent-sender : resent-to : resent-cc : resent-message-id : in-reply-to : references : list-id : list-help : list-unsubscribe : list-subscribe : list-post : list-owner : list-archive; bh=Tcdl0NVWQQTW+nFFFpYYtlQnz5HPHPE7W8+YA0cKYNo=; b=GcUU11psq3aifumIEzB9iMl+UGSk1pi5CL0jLoqWSeqT4CVMA/3NdVNgOQZRxJxrc4Qu3 DkibMeaopJpA6wBHMym+SUmh+85pbxwIDsfN1bx9hJSYv5NfpYXJn1uqZcpIDX5azd1LOSt xzzVxbCUZnzFEesslL74vVc6ufiUucGe+eZVal03KBPlg7tKCQ4trGTuzlkl/tGjMb5JyDY b/izZ79jBUu54X9WFfpqMUyddxIxc7XipQqxsXdYPB4/ZeYVKm5kt/Md2hQtbOMx0JrHoOZ qkdLUmhZP2sXDFanBt/d8GCft1craTH1jAMtUY98cPToswUoeYibffKe2VNQ== ARC-Authentication-Results: i=1; ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none; dmarc=pass header.from=ffmpeg.org policy.dmarc=quarantine Authentication-Results: ffmpeg.org; dkim=pass header.d=ffmpeg.org header.i=@ffmpeg.org; arc=none (Message is not ARC signed); dmarc=pass (Used From Domain Record) header.from=ffmpeg.org policy.dmarc=quarantine DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=ffmpeg.org; i=@ffmpeg.org; q=dns/txt; s=mail; t=1771527198; h=content-type : mime-version : content-transfer-encoding : from : to : reply-to : subject : date : from; bh=m2C7c5hJ72oGP1diD47+KXUPNlISOY8nuhXz0Pm0Yts=; b=CgxQMvYzQLIbxaj7sWO+LFwoQZWCzVgQwJIxSHuJPgZOigieDM7GXYZ6DnePNU2et47U5 fGWHJoxklzLi2otp/AI411j58Uf3+N6V9aSRWq8aynq1mxQlG2xlrWujeTp274U6oU9hhdy yNGG85dPj4haSMZUDm5fZdheE5B+pWou6qc/7a47Nj528Ekw2CRchj6KDXdpYtSLOtnaKP9 SNB7XGBeTGLpckuxsu58XMt5KMW6n82x8QnRX58Pr36J9SxYevJE6Zv/mjtFi1wpj8oyHIq smiR3l7kr9ZA1OnXyQK6FN8xdq7ur06P+TIN34Uk4vq/laopuIiw5z9fsIvw== MIME-Version: 1.0 To: ffmpeg-devel@ffmpeg.org Date: Thu, 19 Feb 2026 18:53:18 -0000 Message-ID: <177152719869.25.8417834295992772466@29965ddac10e> Message-ID-Hash: U6PRMYQIPC4UAGH53POLRHTSDPOH7GPO X-Message-ID-Hash: U6PRMYQIPC4UAGH53POLRHTSDPOH7GPO X-MailFrom: code@ffmpeg.org X-Mailman-Rule-Hits: nonmember-moderation X-Mailman-Rule-Misses: dmarc-mitigation; no-senders; approved; loop; banned-address; header-match-ffmpeg-devel.ffmpeg.org-0; header-match-ffmpeg-devel.ffmpeg.org-1; header-match-ffmpeg-devel.ffmpeg.org-2; header-match-ffmpeg-devel.ffmpeg.org-3; emergency; member-moderation X-Mailman-Version: 3.3.10 Precedence: list Reply-To: FFmpeg development discussions and patches Subject: [FFmpeg-devel] [PR] fftools/ffmpeg_demux: Check metadata provided filename and add av_safe_filename() (PR #21803) List-Id: FFmpeg development discussions and patches Archived-At: Archived-At: List-Archive: List-Archive: List-Help: List-Owner: List-Post: List-Subscribe: List-Unsubscribe: From: michaelni via ffmpeg-devel Cc: michaelni Content-Type: text/plain; charset="us-ascii" Content-Transfer-Encoding: 7bit Archived-At: List-Archive: List-Post: PR #21803 opened by michaelni URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21803 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/21803.patch Fixes: path traversal with -dump_attachment:t Fixes: malicious.mkv Based on code from libavformat/concatdec.c This will be factored out into libavutil in the next commit, It is here for easy backport without API change Found-by: Shangzhi Xu Signed-off-by: Michael Niedermayer >>From ed66e82d4d41f1c7a4e9538fe25dba969487a73c Mon Sep 17 00:00:00 2001 From: Michael Niedermayer Date: Thu, 19 Feb 2026 18:14:28 +0100 Subject: [PATCH 1/2] fftools/ffmpeg_demux: Check metadata provided filename Fixes: path traversal with -dump_attachment:t Fixes: malicious.mkv Based on code from libavformat/concatdec.c This will be factored out into libavutil in the next commit, It is here for easy backport without API change Found-by: Shangzhi Xu Signed-off-by: Michael Niedermayer --- fftools/ffmpeg_demux.c | 46 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c index c8d8a7e044..939e55fad6 100644 --- a/fftools/ffmpeg_demux.c +++ b/fftools/ffmpeg_demux.c @@ -1748,6 +1748,45 @@ static int istg_add(const OptionsContext *o, Demuxer *d, AVStreamGroup *stg) return 0; } +static int is_windows_reserved_device_name(const char *f) +{ + char stem[6], *s; + av_strlcpy(stem, f, sizeof(stem)); + if((s=strchr(stem, '.'))) + *s = 0; + if((s=strpbrk(stem, "123456789"))) + *s = '1'; + + return !av_strcasecmp(stem, "AUX") || + !av_strcasecmp(stem, "CON") || + !av_strcasecmp(stem, "NUL") || + !av_strcasecmp(stem, "PRN") || + !av_strcasecmp(stem, "COM1") || + !av_strcasecmp(stem, "LPT1"); +} + +static int safe_filename(const char *f, int allow_subdir) +{ + const char *start = f; + + if (!*f || is_windows_reserved_device_name(f)) + return 0; + + for (; *f; f++) { + /* A-Za-z0-9_- */ + if (!((unsigned)((*f | 32) - 'a') < 26 || + (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) { + if (f == start) + return 0; + else if (allow_subdir && *f == '/') + start = f + 1; + else if (*f != '.') + return 0; + } + } + return 1; +} + static int dump_attachment(InputStream *ist, const char *filename) { AVStream *st = ist->st; @@ -1759,8 +1798,13 @@ static int dump_attachment(InputStream *ist, const char *filename) av_log(ist, AV_LOG_WARNING, "No extradata to dump.\n"); return 0; } - if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) + if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) { filename = e->value; + if (!safe_filename(filename, 0)) { + av_log(ist, AV_LOG_ERROR, "Filemame %s is unsafe\n", filename); + return AVERROR(EINVAL); + } + } if (!*filename) { av_log(ist, AV_LOG_FATAL, "No filename specified and no 'filename' tag"); return AVERROR(EINVAL); -- 2.52.0 >>From 38fd15464dbc8017d5b3e4ae132f0179cc20081b Mon Sep 17 00:00:00 2001 From: Michael Niedermayer Date: Thu, 19 Feb 2026 19:36:51 +0100 Subject: [PATCH 2/2] avutil/avstring: Factor code into av_safe_filename() Signed-off-by: Michael Niedermayer --- doc/APIchanges | 3 +++ fftools/ffmpeg_demux.c | 41 +---------------------------------------- libavformat/concatdec.c | 21 +-------------------- libavutil/avstring.c | 39 +++++++++++++++++++++++++++++++++++++++ libavutil/avstring.h | 7 +++++++ 5 files changed, 51 insertions(+), 60 deletions(-) diff --git a/doc/APIchanges b/doc/APIchanges index 2b43139b48..5beff45af8 100644 --- a/doc/APIchanges +++ b/doc/APIchanges @@ -2,6 +2,9 @@ The last version increases of all libraries were on 2025-03-28 API changes, most recent first: +2026-02-xx - xxxxxxxxxx - lavu 60.xx.100 - avstring.h + add av_safe_filename() + 2026-02-13 - xxxxxxxxxx - lavu 60.25.100 - avassert.h Deprecate av_assert0_fpu() and av_assert2_fpu() without replacement. diff --git a/fftools/ffmpeg_demux.c b/fftools/ffmpeg_demux.c index 939e55fad6..19bc4bc10a 100644 --- a/fftools/ffmpeg_demux.c +++ b/fftools/ffmpeg_demux.c @@ -1748,45 +1748,6 @@ static int istg_add(const OptionsContext *o, Demuxer *d, AVStreamGroup *stg) return 0; } -static int is_windows_reserved_device_name(const char *f) -{ - char stem[6], *s; - av_strlcpy(stem, f, sizeof(stem)); - if((s=strchr(stem, '.'))) - *s = 0; - if((s=strpbrk(stem, "123456789"))) - *s = '1'; - - return !av_strcasecmp(stem, "AUX") || - !av_strcasecmp(stem, "CON") || - !av_strcasecmp(stem, "NUL") || - !av_strcasecmp(stem, "PRN") || - !av_strcasecmp(stem, "COM1") || - !av_strcasecmp(stem, "LPT1"); -} - -static int safe_filename(const char *f, int allow_subdir) -{ - const char *start = f; - - if (!*f || is_windows_reserved_device_name(f)) - return 0; - - for (; *f; f++) { - /* A-Za-z0-9_- */ - if (!((unsigned)((*f | 32) - 'a') < 26 || - (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) { - if (f == start) - return 0; - else if (allow_subdir && *f == '/') - start = f + 1; - else if (*f != '.') - return 0; - } - } - return 1; -} - static int dump_attachment(InputStream *ist, const char *filename) { AVStream *st = ist->st; @@ -1800,7 +1761,7 @@ static int dump_attachment(InputStream *ist, const char *filename) } if (!*filename && (e = av_dict_get(st->metadata, "filename", NULL, 0))) { filename = e->value; - if (!safe_filename(filename, 0)) { + if (av_safe_filename(filename, 0) <= 0) { av_log(ist, AV_LOG_ERROR, "Filemame %s is unsafe\n", filename); return AVERROR(EINVAL); } diff --git a/libavformat/concatdec.c b/libavformat/concatdec.c index e0c2c87248..cc19c160fb 100644 --- a/libavformat/concatdec.c +++ b/libavformat/concatdec.c @@ -91,25 +91,6 @@ static char *get_keyword(uint8_t **cursor) return ret; } -static int safe_filename(const char *f) -{ - const char *start = f; - - for (; *f; f++) { - /* A-Za-z0-9_- */ - if (!((unsigned)((*f | 32) - 'a') < 26 || - (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) { - if (f == start) - return 0; - else if (*f == '/') - start = f + 1; - else if (*f != '.') - return 0; - } - } - return 1; -} - #define FAIL(retcode) do { ret = (retcode); goto fail; } while(0) static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile, @@ -123,7 +104,7 @@ static int add_file(AVFormatContext *avf, char *filename, ConcatFile **rfile, size_t url_len; int ret; - if (cat->safe && !safe_filename(filename)) { + if (cat->safe && av_safe_filename(filename, 1) <= 0) { av_log(avf, AV_LOG_ERROR, "Unsafe file name '%s'\n", filename); FAIL(AVERROR(EPERM)); } diff --git a/libavutil/avstring.c b/libavutil/avstring.c index 281c5cdc88..41c0e9751b 100644 --- a/libavutil/avstring.c +++ b/libavutil/avstring.c @@ -463,3 +463,42 @@ int av_match_list(const char *name, const char *list, char separator) return 0; } + +static int is_windows_reserved_device_name(const char *f) +{ + char stem[6], *s; + av_strlcpy(stem, f, sizeof(stem)); + if((s=strchr(stem, '.'))) + *s = 0; + if((s=strpbrk(stem, "123456789"))) + *s = '1'; + + return !av_strcasecmp(stem, "AUX") || + !av_strcasecmp(stem, "CON") || + !av_strcasecmp(stem, "NUL") || + !av_strcasecmp(stem, "PRN") || + !av_strcasecmp(stem, "COM1") || + !av_strcasecmp(stem, "LPT1"); +} + +int av_safe_filename(const char *f, int allow_subdir) +{ + const char *start = f; + + if (!*f || is_windows_reserved_device_name(f)) + return 0; + + for (; *f; f++) { + /* A-Za-z0-9_- */ + if (!((unsigned)((*f | 32) - 'a') < 26 || + (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) { + if (f == start) + return 0; + else if (allow_subdir && *f == '/') + start = f + 1; + else if (*f != '.') + return 0; + } + } + return 1; +} diff --git a/libavutil/avstring.h b/libavutil/avstring.h index 17f7b03db5..12219401e8 100644 --- a/libavutil/avstring.h +++ b/libavutil/avstring.h @@ -421,6 +421,13 @@ int av_match_list(const char *name, const char *list, char separator); */ int av_sscanf(const char *string, const char *format, ...) av_scanf_format(2, 3); +/** + * Check if the given filename is safe. + * @param allow_subdir 1 means allowing subdirectories, 0 means allowing only files in the current directory + * @returns positive if safe, 0 if unsafe, negative on error (the current implementation does not return errors, future implementations might) + */ +int av_safe_filename(const char *f, int allow_subdir); + /** * @} */ -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- ffmpeg-devel@ffmpeg.org To unsubscribe send an email to ffmpeg-devel-leave@ffmpeg.org