Bug 455386 - [win] Render animation use-after-free waiting for ffmpeg
Summary: [win] Render animation use-after-free waiting for ffmpeg
Status: RESOLVED FIXED
Alias: None
Product: krita
Classification: Applications
Component: Animation (show other bugs)
Version: nightly build (please specify the git hash!)
Platform: Microsoft Windows Microsoft Windows
: NOR normal
Target Milestone: ---
Assignee: Krita Bugs
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-06-16 08:17 UTC by Alvin Wong
Modified: 2022-07-01 20:50 UTC (History)
1 user (show)

See Also:
Latest Commit:
Version Fixed In:
Sentry Crash Report:


Attachments
crash backtrace (5.82 KB, text/plain)
2022-06-16 08:17 UTC, Alvin Wong
Details
ASAN output (18.01 KB, text/plain)
2022-06-19 16:44 UTC, Alvin Wong
Details
ASAN output with line numbers (19.04 KB, text/plain)
2022-06-19 17:28 UTC, Alvin Wong
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Alvin Wong 2022-06-16 08:17:13 UTC
Created attachment 149778 [details]
crash backtrace

Thread 1 received signal SIGSEGV, Segmentation fault.
0x00007ffa33edc1e0 in QWindowsPipeReader::waitForReadyRead ()
    at C:\Packaging\KritaDepsBuild\ext_qt\s\qtbase\src\corelib\io/qwindowspipereader.cpp:317

Happened when rendering an animation to gif.

Nightly 93e3db259c
Comment 1 Alvin Wong 2022-06-16 09:02:49 UTC
By the way this is when testing under Application Verifier.
Comment 2 Alvin Wong 2022-06-19 16:44:43 UTC
Created attachment 149928 [details]
ASAN output

Use-after free ASAN output from an ASAN+UBSAN build (mangled names, no line numbers)
Comment 3 Alvin Wong 2022-06-19 17:28:27 UTC
Created attachment 149929 [details]
ASAN output with line numbers
Comment 4 Alvin Wong 2022-06-19 18:01:10 UTC
The root of evil seems to be this QApplication::processEvents() call [1]. This will be called from signals activated inside the blocking QProcess::waitForFinished() call. Inside processEvents(), if the child ffmpeg process has died, it will deleteLater() the pipe readers of the active QProcess. The deferred delete is also processed in processEvents(). When control returns to QProcess, it is now handling a deleted pipe reader, hence the UAF.

[1]: https://invent.kde.org/graphics/krita/-/blob/0aa50565d3b1974338d9c25f39a5302a5be0a591/libs/ui/animation/KisFFMpegWrapper.cpp#L195
Comment 5 Eoin O'Neill 2022-06-21 22:14:06 UTC
Hey Alvin,

I'm not even sure why processEvents is necessary in that particular callback. I'll have to check out if removing that call has any regressions and report back my findings. If we really need to "process" events, then I think we simply want to call a non-blocking version...
Comment 6 Eoin O'Neill 2022-06-29 23:34:38 UTC
Git commit abb211e6decfbcb2f5c95a261b95754a8b5b907c by Eoin O'Neill.
Committed on 29/06/2022 at 23:31.
Pushed by eoinoneill into branch 'master'.

Hotfix: Attempt to prevent processEvents call when dealing with FFMPEG
sub-process.

This is a bandaid -- we need remove the need of "blocking" ffmpeg
processes and instead opt for a futures based approach.

M  +5    -2    libs/ui/animation/KisFFMpegWrapper.cpp

https://invent.kde.org/graphics/krita/commit/abb211e6decfbcb2f5c95a261b95754a8b5b907c
Comment 7 Eoin O'Neill 2022-06-29 23:37:29 UTC
Alvin, 

I think I may have solved this issue, but I would like you to test it on Windows to see if the problem persists for you (I don't have a windows test environment atm) and if they do, please reopen the bug.

Diving into the FFMPEGWrapper code, there are issues with how we are treading the subprocess. By having a "blocking" ffmpeg process, we are preventing the UI from updating. This is a bad design and we should be approaching this with a "Futures" based approach (I.E. schedule a task to occur after process has concluded.)

At some point I plan to redo parts of how our exporter works, which would be a good time to change this approach.
Comment 8 Alvin Wong 2022-06-30 15:17:49 UTC
Thanks for trying to fix it. Honestly I think the change may not fix the issue -- it seems QProcess::state is set to NotRunning only after QProcess has detected the process has died, so the condition may still be true if the process has just died.

But I'll need to do a new ASAN build and test it out to tell. (I could also test with Application Verifier, but I feel it is a bit less reliable than ASAN, and it's also quite slow...)

(In reply to Eoin O'Neill from comment #7)
> Diving into the FFMPEGWrapper code, there are issues with how we are
> treading the subprocess. By having a "blocking" ffmpeg process, we are
> preventing the UI from updating. This is a bad design and we should be
> approaching this with a "Futures" based approach (I.E. schedule a task to
> occur after process has concluded.)

The biggest issue is the waitForFinished call. I wonder if it can instead start a nested QEventLoop, and rely on the QProcess::finished signal, perhaps also with a timeout QTimer to exit the event loop? This may even remove the need to call processEvents to update UI.

Running it on another thread or using QFuture can also work, but that may be a bit overkill.
Comment 9 Alvin Wong 2022-07-01 15:51:07 UTC
> Honestly I think the change may not fix the issue

Ah sorry, I do take that back. Your fix does work because it sets the process state and call deleteLater, instead of deleting the pipe reader immediately. Skipping processEvents will prevent triggering the deferred delete and thus prevent the UAF. Thanks again :)
Comment 10 Eoin O'Neill 2022-07-01 20:50:21 UTC
Yeah no worries. :) I agree with you that the whole thing needs a bit of a work -- but for now I think this will have to do since 5.1 is coming soon.  I have an interest in taking another crack at the animation exporter once I get the audio branch in a nicer place, so it will be high(ish) on my todo list. We have a few bugs in the exporter that could be resolved or made streamlined in general. 

I will set a reminder in my email client to take a look at this specific bug report again in a few months just so I don't lose track of it. :) 

Cheers!
Eoin