Repository navigation
Fix use after free in producer_avformat's cache-miss path - #1330
unicorntearsproject wants to merge 2 commits into
Conversation
producer_get_frame() creates the producer's state on a cache miss, puts it in the cache, then fetches it again to take a reference. The cache holds only a few avformat states at once (4 by default), so with more producers decoding on several threads, another thread's put can evict the new entry in between. Nobody holds a reference yet, so its destructor runs and the state is freed, but the code carried on with the pointer it had put, and also stored a NULL cache item on the frame. Use the state the cache returns, with our reference, and create it again if it has already gone. Sixteen threads, each with its own avformat producer of the same clip reading 150 frames of video and audio (cache size 4): 30 of 30 runs crashed in producer_set_up_video() on master, 0 of 30 with this change. The crashing state was a freshly zeroed one belonging to another producer. Fixes mltframework#1318.
|
This is considered a critical fix and low risk change suitable for the immediate release. Thanks |
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Address the critical mutex-initialization issue and both moderate cache/re-registration issues.
Review effort: Lite
Findings: 1
Open (1)
What changed in this PR
Fixes a use-after-free race in producer_avformat cache-miss handling by reacquiring cache-owned state and retrying after eviction.
Changes:
- Recreates state when eviction occurs before reacquisition.
- Uses the cache-returned reference during frame setup.
- Adds comments describing the race window.
| File | Description and review findings |
|---|---|
src/modules/avformat/producer_avformat.c |
Updates cache-miss handling. Critical (4 votes): mutexes may be used before initialization when the state is published. Moderate (1 vote): retry loop can spin indefinitely with a zero-sized cache. Moderate (1 vote): recreated states are not re-registered with property_changed. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
|
Moving to the release after next since this requires a little more invasive change per the legitimate review comment. |
|
No worries, I'm running my own fork w/all the fixes & custom wheels
built... my new video editor is now 100% multi-threaded! :D
Tired of paying Lightworks for HD exports, so mine has 4K & 1080p exports
for free..and interface that doesn't look like it's from 1980 and all the
bells & whistles including full frei0r & lottie support.
You can check it out here:
https://rumble.com/v7g9apa-u-stu-video-editor-amazing-new-editor-for-streamers-and-content-creators.html
if you're interested. Took me about 40 hours and the bots about 150...
another 20/40 for the handful & a half of upstream patches.
Linux flat/snap/deb/rpm & Windows installers are in testing.
Thank you for all your hard work on MLT!
J
…On Sat, Oct 3, 2026 at 1:09 PM Dan Dennedy ***@***.***> wrote:
*ddennedy* left a comment (mltframework/mlt#1330)
<#1330 (comment)>
Moving to the release after next since this requires a little more
invasive change per the legitimate review comment.
—
Reply to this email directly, view it on GitHub
<#1330?email_source=notifications&email_token=ACZWVPHBAZXFUQCLAUBADJD5SEXGBA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOJXGE2DQNBQGU2KM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KYZTPN52GK4S7MNWGSY3L#issuecomment-5971484054>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/ACZWVPBAKWLA75XIWGM27HL5SEXGBAVCNFSNUABDKJSXA33TNF2G64TZHM2DAMJZGA2TEO2JONZXKZJ3GU3DKNJSG44TSOBZUF3AE>
.
Triage notifications, keep track of coding agent tasks and review pull
requests on the go with GitHub Mobile for iOS
<https://github.com/notifications/mobile/ios/ACZWVPFKHJPC4JPPQRCWV3D5SEXGBA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOJXGE2DQNBQGU2KM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2KUZTPN52GK4S7NFXXG>
and Android
<https://github.com/notifications/mobile/android/ACZWVPBOEZQU3DIX2T3AWKL5SEXGBA5CNFSNUABFM5UWIORPF5TWS5BNNB2WEL2JONZXKZKDN5WW2ZLOOQXTKOJXGE2DQNBQGU2KM4TFMFZW63VGMF2XI2DPOKSWK5TFNZ2K4ZTPN52GK4S7MFXGI4TPNFSA>.
Download it today!
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
As raised in review: on a cache miss the state was put in the cache with its mutexes still zeroed. If it was evicted before we took our reference, or used by producer_set_up_video() before producer_open() ran, a mutex was locked before it was initialised (and producer_open() then initialised it while it was locked). - Move the mutex setup into init_mutexes() (it was written out twice) and call it before the state is put in the cache. - Try the cache miss path at most 10 times, so a cache that will not keep the state (a size of 0) cannot make it loop forever; return an empty frame and an error then, as the caller expects a frame.
|
Thanks for the review. Pushed a follow-up:
Re-checked: the 16-thread test passes 30 of 30 (it crashed 30 of 30 before the fix), a normal render works, and cache sizes 0 and 1 both finish. Not changed: the recreated state is not re-registered with |
| if (!self) { | ||
| // The cache would not keep the state (a cache size of 0, or no memory). | ||
| // The caller expects a frame even on failure. | ||
| *frame = mlt_frame_init(service); |

Fixes #1318.
In
producer_get_frame(), on a cache miss the state is created,mlt_service_cache_put()into the cache, then fetched again to take a reference. The cache holds only 4 avformat states by default, so with more producers decoding on several threads another thread's put can evict the new entry in between. Nobody holds a reference yet, so its destructor runs and the state is freed, but the code carried on with the pointer it had put, and stored a NULL cache item on the frame. This now uses the state the cache returns (with our reference) and creates it again if it has already gone.My description in the issue (that an entry in use is evicted) was imprecise: held entries are protected by their reference count. The window is only between the put and the get.
Tested on master 89fbe37: repro (16 threads, each with its own avformat producer, 150 frames of video and audio, default cache size): 30 of 30 runs crashed in
producer_set_up_video()before, 0 of 30 after. clang-format clean.🤖 Generated with Claude Code