2026-06-06 16:04:22 +08:00
|
|
|
Index: FFmpeg/fftools/ffmpeg_demux.c
|
|
|
|
|
===================================================================
|
|
|
|
|
--- FFmpeg.orig/fftools/ffmpeg_demux.c
|
|
|
|
|
+++ FFmpeg/fftools/ffmpeg_demux.c
|
2026-08-12 17:05:26 +08:00
|
|
|
@@ -1770,23 +1770,59 @@ static int is_windows_reserved_device_na
|
2026-06-29 19:18:34 +08:00
|
|
|
{
|
|
|
|
|
#if HAVE_DOS_PATHS
|
|
|
|
|
for (const char *p = f; p && *p; ) {
|
|
|
|
|
- char stem[6], *s;
|
2026-07-18 18:32:36 +08:00
|
|
|
- av_strlcpy(stem, p, sizeof(stem));
|
|
|
|
|
- if ((s = strchr(stem, '.')))
|
|
|
|
|
- *s = 0;
|
|
|
|
|
- if ((s = strpbrk(stem, "123456789")))
|
|
|
|
|
- *s = '1';
|
|
|
|
|
-
|
|
|
|
|
- if( !av_strcasecmp(stem, "AUX") ||
|
|
|
|
|
- !av_strcasecmp(stem, "CON") ||
|
|
|
|
|
- !av_strcasecmp(stem, "NUL") ||
|
|
|
|
|
- !av_strcasecmp(stem, "PRN") ||
|
|
|
|
|
- !av_strcasecmp(stem, "COM1") ||
|
|
|
|
|
- !av_strcasecmp(stem, "LPT1")
|
|
|
|
|
- )
|
|
|
|
|
+ const char *next_slash;
|
|
|
|
|
+ const char *seg_end;
|
|
|
|
|
+ const char *dot;
|
2026-06-29 19:18:34 +08:00
|
|
|
+ size_t len;
|
|
|
|
|
+
|
2026-07-18 18:32:36 +08:00
|
|
|
+ next_slash = strchr(p, '/');
|
|
|
|
|
+ seg_end = next_slash ? next_slash : p + strlen(p);
|
2026-06-29 19:18:34 +08:00
|
|
|
+
|
2026-07-18 18:32:36 +08:00
|
|
|
+ /* Trim trailing spaces and dots of the current component */
|
|
|
|
|
+ while (seg_end > p && (*(seg_end - 1) == ' ' || *(seg_end - 1) == '.'))
|
|
|
|
|
+ seg_end--;
|
2026-06-29 19:18:34 +08:00
|
|
|
+
|
2026-07-18 18:32:36 +08:00
|
|
|
+ /* Discard extension if present (Windows stops checking at first dot) */
|
|
|
|
|
+ dot = p;
|
|
|
|
|
+ while (dot < seg_end && *dot != '.')
|
|
|
|
|
+ dot++;
|
|
|
|
|
+ if (dot < seg_end)
|
|
|
|
|
+ seg_end = dot;
|
2026-06-29 19:18:34 +08:00
|
|
|
+
|
|
|
|
|
+ /* Trim again after stripping extension */
|
2026-07-18 18:32:36 +08:00
|
|
|
+ while (seg_end > p && (*(seg_end - 1) == ' ' || *(seg_end - 1) == '.'))
|
|
|
|
|
+ seg_end--;
|
2026-06-29 19:18:34 +08:00
|
|
|
+
|
2026-07-18 18:32:36 +08:00
|
|
|
+ len = seg_end - p;
|
|
|
|
|
+
|
2026-08-12 17:05:26 +08:00
|
|
|
+ /* Match 6-byte and 7-byte console device names: CONIN$, CONOUT$ */
|
|
|
|
|
+ if ((len == 6 && !av_strncasecmp(p, "CONIN$", 6)) ||
|
|
|
|
|
+ (len == 7 && !av_strncasecmp(p, "CONOUT$", 7)))
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
- p = strchr(p, '/');
|
2026-07-18 18:32:36 +08:00
|
|
|
+ /* Match 3-byte legacy device names: AUX, CON, NUL, PRN */
|
|
|
|
|
+ if (len == 3 && (!av_strncasecmp(p, "AUX", 3) ||
|
|
|
|
|
+ !av_strncasecmp(p, "CON", 3) ||
|
|
|
|
|
+ !av_strncasecmp(p, "NUL", 3) ||
|
|
|
|
|
+ !av_strncasecmp(p, "PRN", 3)))
|
2026-08-12 17:05:26 +08:00
|
|
|
+ return 1;
|
|
|
|
|
+
|
2026-07-18 18:32:36 +08:00
|
|
|
+ /* Match COM1-9 / LPT1-9 and their UTF-8 superscript aliases */
|
|
|
|
|
+ if ((len == 4 || len == 5) && (!av_strncasecmp(p, "COM", 3) ||
|
|
|
|
|
+ !av_strncasecmp(p, "LPT", 3))) {
|
|
|
|
|
+ /* Standard ASCII digits 1-9 */
|
|
|
|
|
+ if (len == 4 && *(seg_end - 1) >= '1' && *(seg_end - 1) <= '9')
|
|
|
|
|
+ return 1;
|
|
|
|
|
+
|
|
|
|
|
+ /* UTF-8 superscripts (¹, ², ³) */
|
|
|
|
|
+ if (len == 5 && (unsigned char)*(seg_end - 2) == 0xC2 &&
|
|
|
|
|
+ ((unsigned char)*(seg_end - 1) == 0xB9 ||
|
|
|
|
|
+ (unsigned char)*(seg_end - 1) == 0xB2 ||
|
|
|
|
|
+ (unsigned char)*(seg_end - 1) == 0xB3))
|
|
|
|
|
+ return 1;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ p = next_slash;
|
|
|
|
|
if (p)
|
|
|
|
|
p++;
|
|
|
|
|
}
|
2026-08-12 17:05:26 +08:00
|
|
|
@@ -1794,26 +1830,66 @@ static int is_windows_reserved_device_na
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
+static int valid_utf8_filename(const char *s)
|
|
|
|
|
+{
|
|
|
|
|
+ const uint8_t *p = (const uint8_t *)s;
|
|
|
|
|
+ const uint8_t *end = p + strlen(s);
|
|
|
|
|
+
|
|
|
|
|
+ while (p < end) {
|
|
|
|
|
+ int32_t code;
|
|
|
|
|
+
|
|
|
|
|
+ if (av_utf8_decode(&code, &p, end,
|
|
|
|
|
+ AV_UTF8_FLAG_ACCEPT_NON_CHARACTERS) < 0)
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ }
|
|
|
|
|
+ return 1;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
static int safe_filename(const char *f, int allow_subdir)
|
|
|
|
|
{
|
|
|
|
|
const char *start = f;
|
|
|
|
|
|
|
|
|
|
- if (!*f || is_windows_reserved_device_name(f))
|
|
|
|
|
+ if (!*f || !valid_utf8_filename(f) ||
|
|
|
|
|
+ is_windows_reserved_device_name(f))
|
2026-06-06 16:04:22 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
for (; *f; f++) {
|
|
|
|
|
- /* A-Za-z0-9_- */
|
|
|
|
|
- if (!((unsigned)((*f | 32) - 'a') < 26 ||
|
|
|
|
|
- (unsigned)(*f - '0') < 10 || *f == '_' || *f == '-')) {
|
|
|
|
|
- if (f == start)
|
|
|
|
|
+ /* Non-ASCII bytes cannot be '/' or '.' */
|
|
|
|
|
+ if ((unsigned char)*f > 127)
|
|
|
|
|
+ continue;
|
2026-08-12 17:05:26 +08:00
|
|
|
+
|
|
|
|
|
+#if HAVE_DOS_PATHS
|
|
|
|
|
+ /* Reject Windows reserved filename punctuation */
|
|
|
|
|
+ if (strchr("<>\"|?*", *f))
|
|
|
|
|
+ return 0;
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
2026-06-06 16:04:22 +08:00
|
|
|
+ /* Block control characters and dangerous path characters */
|
|
|
|
|
+ if ((unsigned char)*f < 32 || *f == '\\' || *f == ':')
|
|
|
|
|
+ return 0;
|
|
|
|
|
+ if (*f == '/') {
|
|
|
|
|
+ if (!allow_subdir || f == start)
|
|
|
|
|
return 0;
|
2026-06-29 19:18:34 +08:00
|
|
|
- else if (allow_subdir && *f == '/')
|
|
|
|
|
- start = f + 1;
|
|
|
|
|
- else if (*f != '.')
|
|
|
|
|
+
|
|
|
|
|
+#if HAVE_DOS_PATHS
|
|
|
|
|
+ /* Reject intermediate dirs ending with trailing spaces or dots */
|
|
|
|
|
+ if (f > start && (*(f - 1) == ' ' || *(f - 1) == '.'))
|
|
|
|
|
return 0;
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
2026-06-06 16:04:22 +08:00
|
|
|
+ start = f + 1;
|
|
|
|
|
+ } else if (*f == '.' && f == start) {
|
|
|
|
|
+ return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
- return 1;
|
2026-06-29 19:18:34 +08:00
|
|
|
+
|
|
|
|
|
+#if HAVE_DOS_PATHS
|
|
|
|
|
+ /* Reject final filenames ending with trailing spaces or dots */
|
|
|
|
|
+ if (f > start && (*(f - 1) == ' ' || *(f - 1) == '.'))
|
|
|
|
|
+ return 0;
|
|
|
|
|
+#endif
|
|
|
|
|
+
|
2026-06-06 16:04:22 +08:00
|
|
|
+ return f != start;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int dump_attachment(InputStream *ist, const char *filename)
|
2026-08-12 17:05:26 +08:00
|
|
|
@@ -1830,8 +1906,8 @@ static int dump_attachment(InputStream *
|
2026-06-06 16:04:22 +08:00
|
|
|
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, "Filename %s is unsafe\n", filename);
|
|
|
|
|
- return AVERROR(EINVAL);
|
|
|
|
|
+ av_log(ist, AV_LOG_WARNING, "Filename %s is unsafe, skipping\n", filename);
|
|
|
|
|
+ return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!*filename) {
|