mirror of https://github.com/mpv-player/mpv
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.
This commit is contained in:
parent
aa08e304c4
commit
9a861c930b
|
@ -77,7 +77,8 @@ static char *stripext(void *talloc_ctx, const char *s)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool write_screenshot(struct mp_cmd_ctx *cmd, struct mp_image *img,
|
static bool write_screenshot(struct mp_cmd_ctx *cmd, struct mp_image *img,
|
||||||
const char *filename, struct image_writer_opts *opts)
|
const char *filename, struct image_writer_opts *opts,
|
||||||
|
bool overwrite)
|
||||||
{
|
{
|
||||||
struct MPContext *mpctx = cmd->mpctx;
|
struct MPContext *mpctx = cmd->mpctx;
|
||||||
struct image_writer_opts *gopts = mpctx->opts->screenshot_image_opts;
|
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,
|
||||||
mp_core_unlock(mpctx);
|
mp_core_unlock(mpctx);
|
||||||
|
|
||||||
bool ok = img && write_image(img, &opts_copy, filename, mpctx->global,
|
bool ok = img && write_image(img, &opts_copy, filename, mpctx->global,
|
||||||
mpctx->screenshot_ctx->log);
|
mpctx->screenshot_ctx->log, overwrite);
|
||||||
|
|
||||||
mp_core_lock(mpctx);
|
mp_core_lock(mpctx);
|
||||||
|
|
||||||
|
@ -496,7 +497,7 @@ void cmd_screenshot_to_file(void *p)
|
||||||
cmd->success = false;
|
cmd->success = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
cmd->success = write_screenshot(cmd, image, filename, &opts);
|
cmd->success = write_screenshot(cmd, image, filename, &opts, true);
|
||||||
talloc_free(image);
|
talloc_free(image);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,7 +538,7 @@ void cmd_screenshot(void *p)
|
||||||
if (image) {
|
if (image) {
|
||||||
char *filename = gen_fname(cmd, image_writer_file_ext(opts));
|
char *filename = gen_fname(cmd, image_writer_file_ext(opts));
|
||||||
if (filename) {
|
if (filename) {
|
||||||
cmd->success = write_screenshot(cmd, image, filename, NULL);
|
cmd->success = write_screenshot(cmd, image, filename, NULL, false);
|
||||||
if (cmd->success) {
|
if (cmd->success) {
|
||||||
node_init(res, MPV_FORMAT_NODE_MAP, NULL);
|
node_init(res, MPV_FORMAT_NODE_MAP, NULL);
|
||||||
node_map_add_string(res, "filename", filename);
|
node_map_add_string(res, "filename", filename);
|
||||||
|
|
|
@ -60,7 +60,7 @@ static void dump_image(struct scale_test *stest, const char *name,
|
||||||
struct image_writer_opts opts = image_writer_opts_defaults;
|
struct image_writer_opts opts = image_writer_opts_defaults;
|
||||||
opts.format = AV_CODEC_ID_PNG;
|
opts.format = AV_CODEC_ID_PNG;
|
||||||
|
|
||||||
if (!write_image(img, &opts, path, NULL, NULL)) {
|
if (!write_image(img, &opts, path, NULL, NULL, true)) {
|
||||||
printf("Failed to write '%s'.\n", path);
|
printf("Failed to write '%s'.\n", path);
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "osdep/io.h"
|
#include "osdep/io.h"
|
||||||
|
#include "misc/path_utils.h"
|
||||||
|
|
||||||
#include "common/av_common.h"
|
#include "common/av_common.h"
|
||||||
#include "common/msg.h"
|
#include "common/msg.h"
|
||||||
|
@ -162,9 +163,14 @@ static void prepare_avframe(AVFrame *pic, AVCodecContext *avctx,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image, const char *filename)
|
static bool write_lavc(struct image_writer_ctx *ctx, mp_image_t *image,
|
||||||
|
const char *filename, bool overwrite)
|
||||||
{
|
{
|
||||||
FILE *fp = fopen(filename, "wb");
|
FILE *fp = fopen(filename, overwrite ? "wb" : "wbx");
|
||||||
|
if (!fp && errno == EEXIST) {
|
||||||
|
MP_ERR(ctx, "File '%s' already exists!\n", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
|
MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
|
||||||
return false;
|
return false;
|
||||||
|
@ -277,9 +283,13 @@ static void write_jpeg_error_exit(j_common_ptr cinfo)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image,
|
static bool write_jpeg(struct image_writer_ctx *ctx, mp_image_t *image,
|
||||||
const char *filename)
|
const char *filename, bool overwrite)
|
||||||
{
|
{
|
||||||
FILE *fp = fopen(filename, "wb");
|
FILE *fp = fopen(filename, overwrite ? "wb" : "wbx");
|
||||||
|
if (!fp && errno == EEXIST) {
|
||||||
|
MP_ERR(ctx, "File '%s' already exists!\n", filename);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (!fp) {
|
if (!fp) {
|
||||||
MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
|
MP_ERR(ctx, "Error opening '%s' for writing!\n", filename);
|
||||||
return false;
|
return false;
|
||||||
|
@ -361,7 +371,7 @@ static void log_side_data(struct image_writer_ctx *ctx, AVPacketSideData *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image,
|
static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image,
|
||||||
const char *filename)
|
const char *filename, bool overwrite)
|
||||||
{
|
{
|
||||||
const AVCodec *codec = NULL;
|
const AVCodec *codec = NULL;
|
||||||
const AVOutputFormat *ofmt = NULL;
|
const AVOutputFormat *ofmt = NULL;
|
||||||
|
@ -429,6 +439,11 @@ static bool write_avif(struct image_writer_ctx *ctx, mp_image_t *image,
|
||||||
goto free_data;
|
goto free_data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!overwrite && mp_path_exists(filename)) {
|
||||||
|
MP_ERR(ctx, "File '%s' already exists\n", filename);
|
||||||
|
goto free_data;
|
||||||
|
}
|
||||||
|
|
||||||
ret = avio_open(&avioctx, filename, AVIO_FLAG_WRITE);
|
ret = avio_open(&avioctx, filename, AVIO_FLAG_WRITE);
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
MP_ERR(ctx, "Could not open file '%s' for saving images\n", filename);
|
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,
|
||||||
|
|
||||||
bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
||||||
const char *filename, struct mpv_global *global,
|
const char *filename, struct mpv_global *global,
|
||||||
struct mp_log *log)
|
struct mp_log *log, bool overwrite)
|
||||||
{
|
{
|
||||||
struct image_writer_opts defs = image_writer_opts_defaults;
|
struct image_writer_opts defs = image_writer_opts_defaults;
|
||||||
if (!opts)
|
if (!opts)
|
||||||
|
@ -714,7 +729,7 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
||||||
mp_verbose(log, "input: %s\n", mp_image_params_to_str(&image->params));
|
mp_verbose(log, "input: %s\n", mp_image_params_to_str(&image->params));
|
||||||
|
|
||||||
struct image_writer_ctx ctx = { log, opts, image->fmt };
|
struct image_writer_ctx ctx = { log, opts, image->fmt };
|
||||||
bool (*write)(struct image_writer_ctx *, mp_image_t *, const char *) = write_lavc;
|
bool (*write)(struct image_writer_ctx *, mp_image_t *, const char *, bool) = write_lavc;
|
||||||
int destfmt = 0;
|
int destfmt = 0;
|
||||||
|
|
||||||
#if HAVE_JPEG
|
#if HAVE_JPEG
|
||||||
|
@ -751,7 +766,7 @@ bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
||||||
if (!dst)
|
if (!dst)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
bool success = write(&ctx, dst, filename);
|
bool success = write(&ctx, dst, filename, overwrite);
|
||||||
if (!success)
|
if (!success)
|
||||||
mp_err(log, "Error writing file '%s'!\n", filename);
|
mp_err(log, "Error writing file '%s'!\n", filename);
|
||||||
|
|
||||||
|
@ -763,5 +778,5 @@ void dump_png(struct mp_image *image, const char *filename, struct mp_log *log)
|
||||||
{
|
{
|
||||||
struct image_writer_opts opts = image_writer_opts_defaults;
|
struct image_writer_opts opts = image_writer_opts_defaults;
|
||||||
opts.format = AV_CODEC_ID_PNG;
|
opts.format = AV_CODEC_ID_PNG;
|
||||||
write_image(image, &opts, filename, NULL, log);
|
write_image(image, &opts, filename, NULL, log, true);
|
||||||
}
|
}
|
||||||
|
|
|
@ -68,7 +68,7 @@ int image_writer_format_from_ext(const char *ext);
|
||||||
*/
|
*/
|
||||||
bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
bool write_image(struct mp_image *image, const struct image_writer_opts *opts,
|
||||||
const char *filename, struct mpv_global *global,
|
const char *filename, struct mpv_global *global,
|
||||||
struct mp_log *log);
|
struct mp_log *log, bool overwrite);
|
||||||
|
|
||||||
// Debugging helper.
|
// Debugging helper.
|
||||||
void dump_png(struct mp_image *image, const char *filename, struct mp_log *log);
|
void dump_png(struct mp_image *image, const char *filename, struct mp_log *log);
|
||||||
|
|
|
@ -118,7 +118,7 @@ static void flip_page(struct vo *vo)
|
||||||
filename = mp_path_join(t, p->opts->outdir, filename);
|
filename = mp_path_join(t, p->opts->outdir, filename);
|
||||||
|
|
||||||
MP_INFO(vo, "Saving %s\n", filename);
|
MP_INFO(vo, "Saving %s\n", filename);
|
||||||
write_image(p->current, p->opts->opts, filename, vo->global, vo->log);
|
write_image(p->current, p->opts->opts, filename, vo->global, vo->log, true);
|
||||||
|
|
||||||
talloc_free(t);
|
talloc_free(t);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue