Add RecursionGuard - #9456
Conversation
1688005 to
371f9e2
Compare
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #9456 +/- ##
==========================================
- Coverage 65.61% 65.46% -0.16%
==========================================
Files 117 118 +1
Lines 21596 21679 +83
Branches 10740 10788 +48
==========================================
+ Hits 14171 14192 +21
- Misses 5133 5191 +58
- Partials 2292 2296 +4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Copilot review overview
Review tier: Lite
Findings: 7
New issues introduced by this change (12)
| Severity | Finding |
|---|---|
include/exiv2/image.hpp — These signature changes are an API break for downstream users (e.g., `ImageFactory::open(path, bool… |
|
include/exiv2/image.hpp — These signature changes are an API break for downstream users (e.g., `ImageFactory::open(path, bool… |
|
include/exiv2/image.hpp — These signature changes are an API break for downstream users (e.g., `ImageFactory::open(path, bool… |
|
include/exiv2/image.hpp — These signature changes are an API break for downstream users (e.g., `ImageFactory::open(path, bool… |
|
include/exiv2/image.hpp — These signature changes are an API break for downstream users (e.g., `ImageFactory::open(path, bool… |
|
include/exiv2/image.hpp — These signature changes are an API break for downstream users (e.g., `ImageFactory::open(path, bool… |
|
include/exiv2/recursion_guard.hpp — The macro expands to a fixed local variable name (_recursion_guard), which can cause a compile… |
|
include/exiv2/recursion_guard.hpp — Fix grammar in this new documentation comment: use 'Its constructor' (possessive) instead of 'It's… |
|
app/exiv2.cpp — If the user passes a positive value that exceeds size_t::max, the code silently falls back to 'no… |
|
app/exiv2.cpp — The PR introduces a new CLI surface (-R) and new parsing/validation paths in setRecursionLimit,… |
|
app/exiv2.cpp — The PR introduces a new CLI surface (-R) and new parsing/validation paths in setRecursionLimit,… |
|
app/exiv2.cpp — This new error message is vague and doesn’t indicate which option failed or what format is… |
What changed in this PR
[!WARNING]
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add acopilot-code-review.ymlfile specifying one with theruns-onattribute. See the docs for more details.
This PR introduces a reusable recursion-limiting mechanism (RecursionLimit + RecursionGuard) and wires it through image decoding/metadata parsing to mitigate excessively deep nesting (solution direction for #9414). It also adds a CLI flag (-R) to configure the maximum recursion depth and updates tests/fixtures accordingly.
Changes:
- Add
RecursionLimit/RecursionGuardand aRECURSION_GUARD(...)helper macro; adopt it in selected recursive parsers. - Replace several
max_recursion_depth_usages with the newRecursionLimitplumbing viaImageCtorParams/DecodeParams. - Add
-RCLI option (and update golden outputs + a regression test expectation) to configure recursion depth / disable the limit.
| File | Description |
|---|---|
| unitTests/unittest_utils.cpp | Updates helper constructors to pass new ImageCtorParams / RecursionLimit. |
| unitTests/test_ImageFactory.cpp | Updates ImageFactory tests to use new ImageCtorParams signatures. |
| tests/suite.conf | Adds expected error string key for max recursion depth. |
| tests/bugfixes/github/test_issue_ghsa_crmj_qh74_2r36.py | Updates expected stderr to new recursion-depth error. |
| test/data/test_reference_files/exiv2-test.out | Updates CLI help reference output to include -R. |
| src/xmpsidecar.cpp | Switches decode params to use recursion_limit(). |
| src/xmp.cpp | Refactors validator construction to accept RecursionLimit. |
| src/webpimage.cpp | Switches decode params to use recursion_limit(). |
| src/tiffvisitor_int.hpp | Changes decoder member from size_t to RecursionLimit. |
| src/tiffvisitor_int.cpp | Formatting-only changes to warnings/errors (line wrapping). |
| src/tiffimage.cpp | Switches decode params to use recursion_limit(). |
| src/rw2image.cpp | Switches decode params to use recursion_limit(); threads params through preview open. |
| src/rafimage.cpp | Threads ImageCtorParams into embedded JPEG parsing; switches decode params. |
| src/quicktimevideo.cpp | Replaces manual depth tracking with RECURSION_GUARD in recursive traversal. |
| src/psdimage.cpp | Switches decode params to use recursion_limit(). |
| src/preview.cpp | Threads recursion limit into preview image opens. |
| src/pngimage.cpp | Switches decode params to use recursion_limit(). |
| src/pgfimage.cpp | Adds RECURSION_GUARD and threads params into internal opens/creates. |
| src/orfimage.cpp | Switches decode params to use recursion_limit(). |
| src/mrwimage.cpp | Switches decode params to use recursion_limit(). |
| src/jpgimage.cpp | Switches decode params to use recursion_limit(). |
| src/jp2image.cpp | Switches decode params to use recursion_limit(). |
| src/image.cpp | Introduces ImageCtorParams defaults + withCreate; updates ImageFactory open/create APIs. |
| src/exif.cpp | Updates DecodeParams ctor to accept RecursionLimit. |
| src/error.cpp | Adds localized error message for kerMaxRecursionDepth. |
| src/epsimage.cpp | Switches decode params to use recursion_limit(). |
| src/cr2image.cpp | Switches decode params to use recursion_limit(). |
| src/bmffimage.cpp | Adds RECURSION_GUARD to recursion entry point; removes redundant depth check. |
| src/asfvideo.cpp | Migrates recursion limiting from manual depth to RECURSION_GUARD. |
| src/CMakeLists.txt | Exposes new public header recursion_guard.hpp. |
| samples/xmpparser-test.cpp | Updates sample to construct DecodeParams with RecursionLimit. |
| samples/xmpparse.cpp | Updates sample to construct DecodeParams with RecursionLimit. |
| samples/tiff-test.cpp | Updates sample to use RecursionLimit / default ctor params. |
| samples/remotetest.cpp | Updates sample to use new ImageFactory::open signature with ImageCtorParams. |
| include/meson.build | Exposes new public header recursion_guard.hpp for Meson builds. |
| include/exiv2/recursion_guard.hpp | Adds new RecursionLimit / RecursionGuard API + macro. |
| include/exiv2/quicktimevideo.hpp | Updates method signatures after removing explicit depth parameters. |
| include/exiv2/params.hpp | Changes DecodeParams to store RecursionLimit. |
| include/exiv2/image.hpp | Changes ImageFactory API to accept ImageCtorParams; adds recursion accessor. |
| include/exiv2/error.hpp | Adds new ErrorCode::kerMaxRecursionDepth. |
| include/exiv2/asfvideo.hpp | Updates method signatures after removing explicit depth parameters. |
| app/exiv2app.hpp | Adds max_recursion_depth_ storage + setter declaration. |
| app/exiv2.cpp | Adds -R option parsing and help text. |
| app/actions.hpp | Makes Task non-copyable; stores ImageCtorParams for consistent recursion configuration. |
| app/actions.cpp | Threads ImageCtorParams through app open/create paths; updates helper signatures. |
Suppressed comments (1)
src/tiffvisitor_int.hpp:1
- This member is now a
RecursionLimitbut is alsoconstand still namedmax_recursion_depth_. If this is intended as a numeric limit used for comparisons, storing asize_t(or renaming to something likerecursion_limit_and storing a plain 'limit' value) would better communicate intent. If it’s intended to participate inRECURSION_GUARD, it can’t beconstbecause the guard mutates the counter.
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
371f9e2 to
5a7cf48
Compare
5a7cf48 to
5dffad2
Compare
5dffad2 to
53ee020
Compare



This is my proposed solution to #9414. The idea is that we add this statement at the start of every recursive parsing function:
In this PR, I haven't added it to all the parsing functions yet. I have only added it to the parsers that already had recursion depth tracking, for example bmffimage.
It works by decrementing
Image::recursion_limit_at the beginning of the function and incrementing it back to it's original value on function exit. This is done by theRecursionGuardclass. Function exit is handled automatically byRecursionGuard's destructor. An exception is thrown if the limit is decremented all the way down to zero.The default limit is 1000. I've also added a command-line parameter so that you can change it. For example: