From 55d5b2b507b7fac3d4bf844b19f0cf473dbbefe5 Mon Sep 17 00:00:00 2001 From: Ruikai Peng Date: Mon, 15 Dec 2025 21:57:52 -0500 Subject: [PATCH] avfilter/drawvg: limit VGS parser recursion depth --- libavfilter/tests/drawvg.c | 2 +- libavfilter/vf_drawvg.c | 44 +++++++++++++++++++++++++++++--------- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/libavfilter/tests/drawvg.c b/libavfilter/tests/drawvg.c index 487b709ec5..473051bae0 100644 --- a/libavfilter/tests/drawvg.c +++ b/libavfilter/tests/drawvg.c @@ -278,7 +278,7 @@ static void check_script(int is_file, const char* source) { state.metadata = metadata; state.cairo_ctx = &cairo_ctx; - ret = vgs_eval(&state, &program); + ret = vgs_eval(&state, &program, 0); vgs_eval_state_free(&state); if (ret != 0) diff --git a/libavfilter/vf_drawvg.c b/libavfilter/vf_drawvg.c index 99280366f3..15b1e4899b 100644 --- a/libavfilter/vf_drawvg.c +++ b/libavfilter/vf_drawvg.c @@ -244,6 +244,8 @@ struct VGSParameter { // for the arguments. #define MAX_PROC_ARGS (MAX_COMMAND_PARAMS - 2) +#define VGS_MAX_RECURSION_DEPTH 100 + // Definition of each command. struct VGSCommandSpec { @@ -453,6 +455,7 @@ struct VGSParser { const char **proc_names; int proc_names_count; + int depth; // Store the variable names for the default ones (from `vgs_default_vars`) // and the variables created with `setvar`. @@ -1297,6 +1300,7 @@ static void vgs_parser_init(struct VGSParser *parser, const char *source) { parser->proc_names = NULL; parser->proc_names_count = 0; + parser->depth = 0; memset(parser->var_names, 0, sizeof(parser->var_names)); for (int i = 0; i < VAR_U0; i++) @@ -1329,11 +1333,19 @@ static int vgs_parse( int subprogram ) { struct VGSParserToken token; + int ret = 0; memset(program, 0, sizeof(*program)); + parser->depth++; + if (parser->depth > VGS_MAX_RECURSION_DEPTH) { + av_log(log_ctx, AV_LOG_ERROR, + "Exceeded maximum drawvg block nesting depth (%d)\n", + VGS_MAX_RECURSION_DEPTH); + goto fail; + } + for (;;) { - int ret; const struct VGSCommandSpec *cmd; ret = vgs_parser_next_token(log_ctx, parser, &token, 1); @@ -1351,7 +1363,7 @@ static int vgs_parse( FFSWAP(int, program->proc_names_count, parser->proc_names_count); } - return 0; + goto out; case TOKEN_WORD: // The token must be a valid command. @@ -1369,21 +1381,26 @@ static int vgs_parse( if (!subprogram) goto invalid_token; - return 0; + goto out; default: goto invalid_token; } } - return AVERROR_BUG; /* unreachable */ + ret = AVERROR_BUG; /* unreachable */ + goto out; invalid_token: vgs_log_invalid_token(log_ctx, parser, &token, "Expected command."); fail: vgs_free(program); - return AVERROR(EINVAL); + ret = AVERROR(EINVAL); + +out: + parser->depth--; + return ret; } /* @@ -1882,9 +1899,15 @@ static void hsl2rgb( /// the subprogram allocated to the block. static int vgs_eval( struct VGSEvalState *state, - const struct VGSProgram *program + const struct VGSProgram *program, + int stack_level ) { + if (stack_level >= VGS_MAX_RECURSION_DEPTH) { + av_log(state->log_ctx, AV_LOG_ERROR, "maximum recursion depth exceeded\n"); + return AVERROR(EINVAL); + } + #define ASSERT_ARGS(n) av_assert0(statement->args_count == n) // When `preserve` is used, the next call to `clip`, `fill`, or `stroke` @@ -2135,7 +2158,8 @@ static int vgs_eval( ASSERT_ARGS(2); if (isfinite(numerics[0]) && numerics[0] != 0.0) { - int ret = vgs_eval(state, statement->args[1].subprogram); + const int ret = vgs_eval(state, statement->args[1].subprogram, stack_level + 1); + if (ret != 0 || state->interrupted != 0) return ret; } @@ -2275,7 +2299,7 @@ static int vgs_eval( color_copy(&state->color_vars[color_var], &colors[i + 1]); } - const int ret = vgs_eval(state, proc->program); + const int ret = vgs_eval(state, proc->program, stack_level + 1); // Restore variable values. for (int i = 0; i < proc_args; i++) { @@ -2360,7 +2384,7 @@ static int vgs_eval( for (int i = 0, count = (int)numerics[0]; i < count; i++) { state->vars[VAR_I] = i; - const int ret = vgs_eval(state, statement->args[1].subprogram); + const int ret = vgs_eval(state, statement->args[1].subprogram, stack_level + 1); if (ret != 0) return ret; @@ -2726,7 +2750,7 @@ static int drawvg_filter_frame(AVFilterLink *inlink, AVFrame *frame) { eval_state.metadata = frame->metadata; - ret = vgs_eval(&eval_state, &drawvg_ctx->program); + ret = vgs_eval(&eval_state, &drawvg_ctx->program, 0); cairo_destroy(eval_state.cairo_ctx); cairo_surface_destroy(surface);