History

image_writer: fix TOCTOU in screenshot filename generation

The screenshot command is documented to not overwrite existing files. However, there is a race window between the filename is generated with gen_fname and when the file is open to write. Specifically, the convert_image function in this window can be very time consuming depending on video and screenshot image format and size. This results in existing file being overwritten because the file writing functions don't check for the existance of file. Fix this be opening the file in exclusive mode. Add overwrite parameter to write_image for other operations that are documented to overwrite existing files, like screenshot-to-file. Note that for write_avif, checking existance is used instead because avio_open does not support exclusive open mode.

nanahi committed 2 years ago

Commit 9a861c91 parent aa08e305 files · +32 −16Browse files

Unified · Split

5 files
  1. player/screenshot.c +5 −4
  2. test/scale_test.c +1 −1
  3. video/image_writer.c +24 −9
  4. video/image_writer.h +1 −1
  5. video/out/vo_image.c +1 −1
player/screenshot.c+5−4
@@ -77,7 +77,8 @@ static char *stripext(void *talloc_ctx, const char *s) · 76 unmodified lines
7777 }
7878
7979 static bool write_screenshot(struct mp_cmd_ctx *cmd, struct mp_image *img,
80− const char *filename, struct image_writer_opts *opts)
80+ const char *filename, struct image_writer_opts *opts,
81+ bool overwrite)
8182 {
8283 struct MPContext *mpctx = cmd->mpctx;
8384 struct image_writer_opts *gopts = mpctx->opts->screenshot_image_opts;
@@ -88,7 +89,7 @@ static bool write_screenshot(struct mp_cmd_ctx *cmd, struct mp_image *img, · 4 unmodified lines
8889 mp_core_unlock(mpctx);
8990
9091 bool ok = img && write_image(img, &opts_copy, filename, mpctx->global,
91− mpctx->screenshot_ctx->log);
92+ mpctx->screenshot_ctx->log, overwrite);
9293
9394 mp_core_lock(mpctx);
9495
@@ -496,7 +497,7 @@ void cmd_screenshot_to_file(void *p) · 401 unmodified lines
496497 cmd->success = false;
497498 return;
498499 }
499− cmd->success = write_screenshot(cmd, image, filename, &opts);
500+ cmd->success = write_screenshot(cmd, image, filename, &opts, true);
500501 talloc_free(image);
501502 }
502503
@@ -537,7 +538,7 @@ void cmd_screenshot(void *p) · 34 unmodified lines
537538 if (image) {
538539 char *filename = gen_fname(cmd, image_writer_file_ext(opts));
539540 if (filename) {
540− cmd->success = write_screenshot(cmd, image, filename, NULL);
541+ cmd->success = write_screenshot(cmd, image, filename, NULL, false);
541542 if (cmd->success) {
542543 node_init(res, MPV_FORMAT_NODE_MAP, NULL);
543544 node_map_add_string(res, "filename", filename);
test/scale_test.c+1−1
@@ -60,7 +60,7 @@ static void dump_image(struct scale_test *stest, const char *name, · 59 unmodified lines
6060 struct image_writer_opts opts = image_writer_opts_defaults;
6161 opts.format = AV_CODEC_ID_PNG;
6262
63− if (!write_image(img, &opts, path, NULL, NULL)) {
63+ if (!write_image(img, &opts, path, NULL, NULL, true)) {
6464 printf("Failed to write '%s'.\n", path);
6565 abort();
6666 }
video/image_writer.c+24−9
@@ -37,6 +37,7 @@ · 36 unmodified lines
3737 #endif
3838
3939 #include "osdep/io.h"
40+#include "misc/path_utils.h"
4041
4142 #include "common/av_common.h"
4243 #include "common/msg.h"
@@ -162,9 +163,14 @@ static void prepare_avframe(AVFrame *pic, AVCodecContext *avctx, · 119 unmodified lines
162163 );
163164 }
164165
165−static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, const char *filename)
166+static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image,
167+ const char *filename, bool overwrite)
166168 {
167− FILE *fp = fopen(filename, "wb");
169+ FILE *fp = fopen(filename, overwrite ? "wb" : "wbx");
170+ if (!fp && errno == EEXIST) {
171+ MP_ERR(ctx, "File '%s' already exists!\n", filename);
172+ return false;
173+ }
168174 if (!fp) {
169175 MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
170176 return false;
@@ -277,9 +283,13 @@ static void write_jpeg_error_exit(j_common_ptr cinfo) · 106 unmodified lines
277283 }
278284
279285 static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image,
280− const char *filename)
286+ const char *filename, bool overwrite)
281287 {
282− FILE *fp = fopen(filename, "wb");
288+ FILE *fp = fopen(filename, overwrite ? "wb" : "wbx");
289+ if (!fp && errno == EEXIST) {
290+ MP_ERR(ctx, "File '%s' already exists!\n", filename);
291+ return false;
292+ }
283293 if (!fp) {
284294 MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
285295 return false;
@@ -361,7 +371,7 @@ static void log_side_data(struct image_writer_ctx *ctx, AVPacketSideData *data, · 75 unmodified lines
361371 }
362372
363373 static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image,
364− const char *filename)
374+ const char *filename, bool overwrite)
365375 {
366376 const AVCodec *codec = NULL;
367377 const AVOutputFormat *ofmt = NULL;
@@ -429,6 +439,11 @@ static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image, · 61 unmodified lines
429439 goto free_data;
430440 }
431441
442+ if (!overwrite && mp_path_exists(filename)) {
443+ MP_ERR(ctx, "File '%s' already exists\n", filename);
444+ goto free_data;
445+ }
446+
432447 ret = avio_open(&avioctx, filename, AVIO_FLAG_WRITE);
433448 if (ret < 0) {
434449 MP_ERR(ctx, "Could not open file '%s' for saving images\n", filename);
@@ -705,7 +720,7 @@ static struct mp_image *convert_image(struct mp_image *image, int destfmt, · 270 unmodified lines
705720
706721 bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
707722 const char *filename, struct mpv_global *global,
708− struct mp_log *log)
723+ struct mp_log *log, bool overwrite)
709724 {
710725 struct image_writer_opts defs = image_writer_opts_defaults;
711726 if (!opts)
@@ -714,7 +729,7 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts, · 2 unmodified lines
714729 mp_verbose(log, "input: %s\n", mp_image_params_to_str(&image->params));
715730
716731 struct image_writer_ctx ctx = { log, opts, image->fmt };
717− bool (*write)(struct image_writer_ctx *, mp_image_t *, const char *) = write_lavc;
732+ bool (*write)(struct image_writer_ctx *, mp_image_t *, const char *, bool) = write_lavc;
718733 int destfmt = 0;
719734
720735 #if HAVE_JPEG
@@ -751,7 +766,7 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts, · 30 unmodified lines
751766 if (!dst)
752767 return false;
753768
754− bool success = write(&ctx, dst, filename);
769+ bool success = write(&ctx, dst, filename, overwrite);
755770 if (!success)
756771 mp_err(log, "Error writing file '%s'!\n", filename);
757772
@@ -763,5 +778,5 @@ void dump_png(struct mp_image *image, const char *filename, struct mp_log *log) · 5 unmodified lines
763778 {
764779 struct image_writer_opts opts = image_writer_opts_defaults;
765780 opts.format = AV_CODEC_ID_PNG;
766− write_image(image, &opts, filename, NULL, log);
781+ write_image(image, &opts, filename, NULL, log, true);
767782 }
video/image_writer.h+1−1
@@ -68,7 +68,7 @@ int image_writer_format_from_ext(const char *ext); · 67 unmodified lines
6868 */
6969 bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
7070 const char *filename, struct mpv_global *global,
71− struct mp_log *log);
71+ struct mp_log *log, bool overwrite);
7272
7373 // Debugging helper.
7474 void dump_png(struct mp_image *image, const char *filename, struct mp_log *log);
video/out/vo_image.c+1−1
@@ -118,7 +118,7 @@ static void flip_page(struct vo *vo) · 117 unmodified lines
118118 filename = mp_path_join(t, p->opts->outdir, filename);
119119
120120 MP_INFO(vo, "Saving %s\n", filename);
121− write_image(p->current, p->opts->opts, filename, vo->global, vo->log);
121+ write_image(p->current, p->opts->opts, filename, vo->global, vo->log, true);
122122
123123 talloc_free(t);
124124 }

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t