[PR] avfilter/swaprect: validate expression results before integer conversion (PR #24023)
Qingzheng Li via ffmpeg-devel <[email protected]> Wed, 05 Aug 2026 17:30:42 -0000
| Newsgroups | gmane.comp.video.ffmpeg.devel |
|---|---|
| Message-ID | <178595104269.59.15293236546379835877@29965ddac10e> |
PR #24023 opened by Qingzheng Li (iSoldLeo) URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24023 Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24023.patch ## Summary - validate all six evaluated `swaprect` size and position expressions before converting them from `double` to `int` - preserve the filter's existing truncation-toward-zero semantics, then reject converted non-positive dimensions - free the owned input frame on expression-evaluation and validation failures - add FATE coverage for negative, sub-unit, finite out-of-range, and infinite values ## Background Supersedes #23088. The earlier patch by @un-tag added the initial non-positive-size check and regression test for #22866. This version is based on current master and also addresses the blocking review concerning all six unchecked `double`-to-`int` conversions. `av_expr_parse_and_eval()` rejects a final NaN result, but expressions such as `1/0` can still produce infinity and return successfully. Large finite values are also possible. Converting either class to `int` is undefined, and the same problem affects all four coordinates before they are clipped. The conversion helper first rejects non-finite values, applies `trunc()`, and then checks the truncated result against the `int` range. Applying `trunc()` first preserves the existing conversion semantics for valid fractional expressions. The positivity check intentionally applies after conversion. Values such as `w=0.5` therefore truncate to zero and are rejected as non-positive rectangle dimensions. Negative coordinates remain valid and continue to be clipped by the existing `av_clip()` calls. All expression-evaluation and validation failures use a common cleanup path because `filter_frame()` owns the input frame until it forwards that frame with `ff_filter_frame()`. ## Tests The new FATE cases require both the expected diagnostic and the exit status corresponding to `AVERROR(EINVAL)`, so a signal termination cannot satisfy the tests: - `w=-1`: original negative-width reproducer - `w=0.5`: positive expression that truncates to a zero width - `x1=1e100`: finite coordinate outside the `int` range - `x1=1/0`: reachable non-finite coordinate Validated on macOS arm64: - `fate-filter-swaprect` - `fate-filter-swaprect-invalid-size` - `fate-filter-swaprect-invalid-fractional-size` - `fate-filter-swaprect-invalid-coordinate` - `fate-filter-swaprect-nonfinite-coordinate` All tests pass. A targeted build and `git diff --check` also pass. >From 1252ac789d71181666f64007d256bbde5dc26dcb Mon Sep 17 00:00:00 2001 From: Qingzheng Li <[email protected]> Date: Thu, 6 Aug 2026 01:22:57 +0800 Subject: [PATCH 1/2] avfilter/swaprect: validate rectangle expression results swaprect evaluates its dimensions and coordinates as doubles before converting them to int. Non-finite or out-of-range results make that conversion undefined, and negative dimensions can later become invalid copy sizes. Truncate each of the six results before checking the int range, preserving the filter's existing conversion semantics. Reject non-finite and unrepresentable results, then validate the converted dimensions so values such as 0.5 cannot become zero-sized rectangles. Route expression and validation failures through a common cleanup path, since filter_frame() owns the input frame until it is forwarded. Fixes: #22866 Reported-by: cxxz16 Based-on-patch-by: un-tag <[email protected]> Signed-off-by: Qingzheng Li <[email protected]> --- libavfilter/vf_swaprect.c | 48 +++++++++++++++++++++++++++++++++------ 1 file changed, 41 insertions(+), 7 deletions(-) diff --git a/libavfilter/vf_swaprect.c b/libavfilter/vf_swaprect.c index fe007ee5e7..0c8b8cd846 100644 --- a/libavfilter/vf_swaprect.c +++ b/libavfilter/vf_swaprect.c @@ -21,6 +21,7 @@ #include "libavutil/avassert.h" #include "libavutil/eval.h" #include "libavutil/imgutils.h" +#include "libavutil/libm.h" #include "libavutil/mem.h" #include "libavutil/opt.h" @@ -71,6 +72,19 @@ static int query_formats(const AVFilterContext *ctx, static const char *const var_names[] = { "w", "h", "a", "n", "t", "sar", "dar", NULL }; enum { VAR_W, VAR_H, VAR_A, VAR_N, VAR_T, VAR_SAR, VAR_DAR, VAR_VARS_NB }; +static int trunc_double_to_int(int *dst, double value) +{ + if (!isfinite(value)) + return AVERROR(EINVAL); + + value = trunc(value); + if (value < INT_MIN || value > INT_MAX) + return AVERROR(EINVAL); + + *dst = value; + return 0; +} + static int filter_frame(AVFilterLink *inlink, AVFrame *in) { FilterLink *inl = ff_filter_link(inlink); @@ -101,44 +115,60 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) NULL, NULL, NULL, NULL, 0, 0, ctx); if (ret < 0) - return ret; + goto fail; ret = av_expr_parse_and_eval(&dh, s->h, var_names, &var_values[0], NULL, NULL, NULL, NULL, 0, 0, ctx); if (ret < 0) - return ret; + goto fail; ret = av_expr_parse_and_eval(&dx1, s->x1, var_names, &var_values[0], NULL, NULL, NULL, NULL, 0, 0, ctx); if (ret < 0) - return ret; + goto fail; ret = av_expr_parse_and_eval(&dy1, s->y1, var_names, &var_values[0], NULL, NULL, NULL, NULL, 0, 0, ctx); if (ret < 0) - return ret; + goto fail; ret = av_expr_parse_and_eval(&dx2, s->x2, var_names, &var_values[0], NULL, NULL, NULL, NULL, 0, 0, ctx); if (ret < 0) - return ret; + goto fail; ret = av_expr_parse_and_eval(&dy2, s->y2, var_names, &var_values[0], NULL, NULL, NULL, NULL, 0, 0, ctx); if (ret < 0) - return ret; + goto fail; - w = dw; h = dh; x1[0] = dx1; y1[0] = dy1; x2[0] = dx2; y2[0] = dy2; + if (trunc_double_to_int(&w, dw) < 0 || + trunc_double_to_int(&h, dh) < 0 || + trunc_double_to_int(&x1[0], dx1) < 0 || + trunc_double_to_int(&y1[0], dy1) < 0 || + trunc_double_to_int(&x2[0], dx2) < 0 || + trunc_double_to_int(&y2[0], dy2) < 0) { + av_log(ctx, AV_LOG_ERROR, + "Rectangle expression result is not representable as an integer.\n"); + ret = AVERROR(EINVAL); + goto fail; + } + + if (w <= 0 || h <= 0) { + av_log(ctx, AV_LOG_ERROR, "Rectangle dimensions must be positive.\n"); + ret = AVERROR(EINVAL); + goto fail; + } x1[0] = av_clip(x1[0], 0, inlink->w - 1); y1[0] = av_clip(y1[0], 0, inlink->h - 1); @@ -194,6 +224,10 @@ static int filter_frame(AVFilterLink *inlink, AVFrame *in) } return ff_filter_frame(outlink, in); + +fail: + av_frame_free(&in); + return ret; } static int config_input(AVFilterLink *inlink) -- 2.52.0 >From 794fc1dd0e5117728656b3a01f7b63aa75d5101e Mon Sep 17 00:00:00 2001 From: Qingzheng Li <[email protected]> Date: Thu, 6 Aug 2026 01:23:33 +0800 Subject: [PATCH 2/2] fate/filter-video: test invalid swaprect expressions Cover negative and sub-unit dimensions, as well as finite out-of-range and infinite coordinates. Require the expected EINVAL process status and diagnostic so a crash does not satisfy the rejection tests. Based-on-patch-by: un-tag <[email protected]> Signed-off-by: Qingzheng Li <[email protected]> --- tests/fate/filter-video.mak | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/fate/filter-video.mak b/tests/fate/filter-video.mak index 0eb2e7076c..18afdf45a1 100644 --- a/tests/fate/filter-video.mak +++ b/tests/fate/filter-video.mak @@ -376,6 +376,20 @@ fate-filter-swaprect: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf swaprect FATE_FILTER_VSYNTH_PGMYUV-$(CONFIG_SWAPRECT_FILTER) += $(FATE_SWAPRECT) +FATE_FILTER-$(call ALLYES, COLOR_FILTER LAVFI_INDEV SWAPRECT_FILTER WRAPPED_AVFRAME_ENCODER NULL_MUXER) += fate-filter-swaprect-invalid-size fate-filter-swaprect-invalid-fractional-size fate-filter-swaprect-invalid-coordinate fate-filter-swaprect-nonfinite-coordinate +fate-filter-swaprect-invalid-size: CMD = run $(FFMPEG) -nostdin -hide_banner -f lavfi -i "color=c=black:s=16x16:r=1" -vf "swaprect=w=-1:h=1:x1=0:y1=0:x2=1:y2=0" -frames:v 1 -f null none ; ret=$$?; test $$ret -eq 234 +fate-filter-swaprect-invalid-size: CMP = grep +fate-filter-swaprect-invalid-size: REF = Rectangle dimensions must be positive +fate-filter-swaprect-invalid-fractional-size: CMD = run $(FFMPEG) -nostdin -hide_banner -f lavfi -i "color=c=black:s=16x16:r=1" -vf "swaprect=w=0.5:h=1:x1=0:y1=0:x2=1:y2=0" -frames:v 1 -f null none ; ret=$$?; test $$ret -eq 234 +fate-filter-swaprect-invalid-fractional-size: CMP = grep +fate-filter-swaprect-invalid-fractional-size: REF = Rectangle dimensions must be positive +fate-filter-swaprect-invalid-coordinate: CMD = run $(FFMPEG) -nostdin -hide_banner -f lavfi -i "color=c=black:s=16x16:r=1" -vf "swaprect=w=1:h=1:x1=1e100:y1=0:x2=1:y2=0" -frames:v 1 -f null none ; ret=$$?; test $$ret -eq 234 +fate-filter-swaprect-invalid-coordinate: CMP = grep +fate-filter-swaprect-invalid-coordinate: REF = Rectangle expression result is not representable as an integer +fate-filter-swaprect-nonfinite-coordinate: CMD = run $(FFMPEG) -nostdin -hide_banner -f lavfi -i "color=c=black:s=16x16:r=1" -vf "swaprect=w=1:h=1:x1=1/0:y1=0:x2=1:y2=0" -frames:v 1 -f null none ; ret=$$?; test $$ret -eq 234 +fate-filter-swaprect-nonfinite-coordinate: CMP = grep +fate-filter-swaprect-nonfinite-coordinate: REF = Rectangle expression result is not representable as an integer + FATE_FILTER_VSYNTH_PGMYUV-$(CONFIG_TBLEND_FILTER) += fate-filter-tblend fate-filter-tblend: CMD = framecrc -c:v pgmyuv -i $(SRC) -vf tblend=all_mode=difference128 -- 2.52.0 _______________________________________________ ffmpeg-devel mailing list -- [email protected] To unsubscribe send an email to [email protected]