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
77}77}
78 78
79static bool write_screenshot(struct mp_cmd_ctx *cmd, struct mp_image *img,79static 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)
81{82{
82 struct MPContext *mpctx = cmd->mpctx;83 struct MPContext *mpctx = cmd->mpctx;
83 struct image_writer_opts *gopts = mpctx->opts->screenshot_image_opts;84 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
88 mp_core_unlock(mpctx);89 mp_core_unlock(mpctx);
89 90
90 bool ok = img && write_image(img, &opts_copy, filename, mpctx->global,91 bool ok = img && write_image(img, &opts_copy, filename, mpctx->global,
91 mpctx->screenshot_ctx->log);92 mpctx->screenshot_ctx->log, overwrite);
92 93
93 mp_core_lock(mpctx);94 mp_core_lock(mpctx);
94 95
@@ -496,7 +497,7 @@ void cmd_screenshot_to_file(void *p) · 401 unmodified lines
496 cmd->success = false;497 cmd->success = false;
497 return;498 return;
498 }499 }
499 cmd->success = write_screenshot(cmd, image, filename, &opts);500 cmd->success = write_screenshot(cmd, image, filename, &opts, true);
500 talloc_free(image);501 talloc_free(image);
501}502}
502 503
@@ -537,7 +538,7 @@ void cmd_screenshot(void *p) · 34 unmodified lines
537 if (image) {538 if (image) {
538 char *filename = gen_fname(cmd, image_writer_file_ext(opts));539 char *filename = gen_fname(cmd, image_writer_file_ext(opts));
539 if (filename) {540 if (filename) {
540 cmd->success = write_screenshot(cmd, image, filename, NULL);541 cmd->success = write_screenshot(cmd, image, filename, NULL, false);
541 if (cmd->success) {542 if (cmd->success) {
542 node_init(res, MPV_FORMAT_NODE_MAP, NULL);543 node_init(res, MPV_FORMAT_NODE_MAP, NULL);
543 node_map_add_string(res, "filename", filename);544 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
60 struct image_writer_opts opts = image_writer_opts_defaults;60 struct image_writer_opts opts = image_writer_opts_defaults;
61 opts.format = AV_CODEC_ID_PNG;61 opts.format = AV_CODEC_ID_PNG;
62 62
63 if (!write_image(img, &opts, path, NULL, NULL)) {63 if (!write_image(img, &opts, path, NULL, NULL, true)) {
64 printf("Failed to write '%s'.\n", path);64 printf("Failed to write '%s'.\n", path);
65 abort();65 abort();
66 }66 }
video/image_writer.c+24−9
@@ -37,6 +37,7 @@ · 36 unmodified lines
37#endif37#endif
38 38
39#include "osdep/io.h"39#include "osdep/io.h"
40#include "misc/path_utils.h"
40 41
41#include "common/av_common.h"42#include "common/av_common.h"
42#include "common/msg.h"43#include "common/msg.h"
@@ -162,9 +163,14 @@ static void prepare_avframe(AVFrame *pic, AVCodecContext *avctx, · 119 unmodified lines
162 );163 );
163}164}
164 165
165static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, const char *filename)166static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image,
167 const char *filename, bool overwrite)
166{168{
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 }
168 if (!fp) {174 if (!fp) {
169 MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);175 MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
170 return false;176 return false;
@@ -277,9 +283,13 @@ static void write_jpeg_error_exit(j_common_ptr cinfo) · 106 unmodified lines
277}283}
278 284
279static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image,285static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image,
280 const char *filename)286 const char *filename, bool overwrite)
281{287{
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 }
283 if (!fp) {293 if (!fp) {
284 MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);294 MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
285 return false;295 return false;
@@ -361,7 +371,7 @@ static void log_side_data(struct image_writer_ctx *ctx, AVPacketSideData *data, · 75 unmodified lines
361}371}
362 372
363static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image,373static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image,
364 const char *filename)374 const char *filename, bool overwrite)
365{375{
366 const AVCodec *codec = NULL;376 const AVCodec *codec = NULL;
367 const AVOutputFormat *ofmt = NULL;377 const AVOutputFormat *ofmt = NULL;
@@ -429,6 +439,11 @@ static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image, · 61 unmodified lines
429 goto free_data;439 goto free_data;
430 }440 }
431 441
442 if (!overwrite && mp_path_exists(filename)) {
443 MP_ERR(ctx, "File '%s' already exists\n", filename);
444 goto free_data;
445 }
446
432 ret = avio_open(&avioctx, filename, AVIO_FLAG_WRITE);447 ret = avio_open(&avioctx, filename, AVIO_FLAG_WRITE);
433 if (ret < 0) {448 if (ret < 0) {
434 MP_ERR(ctx, "Could not open file '%s' for saving images\n", filename);449 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
705 720
706bool write_image(struct mp_image *image, const struct image_writer_opts *opts,721bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
707 const char *filename, struct mpv_global *global,722 const char *filename, struct mpv_global *global,
708 struct mp_log *log)723 struct mp_log *log, bool overwrite)
709{724{
710 struct image_writer_opts defs = image_writer_opts_defaults;725 struct image_writer_opts defs = image_writer_opts_defaults;
711 if (!opts)726 if (!opts)
@@ -714,7 +729,7 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts, · 2 unmodified lines
714 mp_verbose(log, "input: %s\n", mp_image_params_to_str(&image->params));729 mp_verbose(log, "input: %s\n", mp_image_params_to_str(&image->params));
715 730
716 struct image_writer_ctx ctx = { log, opts, image->fmt };731 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;
718 int destfmt = 0;733 int destfmt = 0;
719 734
720#if HAVE_JPEG735#if HAVE_JPEG
@@ -751,7 +766,7 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts, · 30 unmodified lines
751 if (!dst)766 if (!dst)
752 return false;767 return false;
753 768
754 bool success = write(&ctx, dst, filename);769 bool success = write(&ctx, dst, filename, overwrite);
755 if (!success)770 if (!success)
756 mp_err(log, "Error writing file '%s'!\n", filename);771 mp_err(log, "Error writing file '%s'!\n", filename);
757 772
@@ -763,5 +778,5 @@ void dump_png(struct mp_image *image, const char *filename, struct mp_log *log) · 5 unmodified lines
763{778{
764 struct image_writer_opts opts = image_writer_opts_defaults;779 struct image_writer_opts opts = image_writer_opts_defaults;
765 opts.format = AV_CODEC_ID_PNG;780 opts.format = AV_CODEC_ID_PNG;
766 write_image(image, &opts, filename, NULL, log);781 write_image(image, &opts, filename, NULL, log, true);
767}782}
video/image_writer.h+1−1
@@ -68,7 +68,7 @@ int image_writer_format_from_ext(const char *ext); · 67 unmodified lines
68 */68 */
69bool write_image(struct mp_image *image, const struct image_writer_opts *opts,69bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
70 const char *filename, struct mpv_global *global,70 const char *filename, struct mpv_global *global,
71 struct mp_log *log);71 struct mp_log *log, bool overwrite);
72 72
73// Debugging helper.73// Debugging helper.
74void dump_png(struct mp_image *image, const char *filename, struct mp_log *log);74void 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
118 filename = mp_path_join(t, p->opts->outdir, filename);118 filename = mp_path_join(t, p->opts->outdir, filename);
119 119
120 MP_INFO(vo, "Saving %s\n", filename);120 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);
122 122
123 talloc_free(t);123 talloc_free(t);
124}124}

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