Raise instead of crashing when decoding a stream with no codec context#2345
Closed
justinrmiller wants to merge 1 commit into
Closed
Raise instead of crashing when decoding a stream with no codec context#2345justinrmiller wants to merge 1 commit into
justinrmiller wants to merge 1 commit into
Conversation
container.decode()/Stream.decode() on a video or audio stream whose codec_context is None (the container could not establish codec parameters from a truncated/corrupt bitstream) crashed the whole process with a fatal memory-access fault (SIGSEGV/SIGBUS): FFmpeg 8.1's slice-multithreaded decoder does an out-of-bounds read on such input, which no try/except can recover. Guard VideoStream.decode and AudioStream.decode to raise a clear ValueError instead, and add a regression test plus a CHANGELOG entry.
justinrmiller
force-pushed
the
fix/threaded-decode-crash-truncated-input
branch
from
July 21, 2026 04:20
7879cb6 to
7ed413c
Compare
Member
|
slop |
Author
|
Hey, sorry but this isn’t slop. I’m writing a video processing pipeline that will be processing a lot of uploaded videos and this situation came up if the user uploaded an invalid file. I have successfully reproduced this on an M4 Pro MBP. |
Member
|
I can repo too, but your diagnosis is wrong and reads like an non-frontier LLM wrote it. |
Author
|
Alright, looks like you have a PR with the proper fix. Sorry for the hassle. |
Member
|
My fix is better because it correctly identifies the real cause and fixes related problems you didn't mention. #2346 |
Author
|
Thanks. Looks interesting, I’m not an expert in this space. Appreciate the quick fix, I’ll rerun our tests when the new version comes out. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Raise instead of crashing when decoding a stream with no codec context
Fixes #2344.
Summary
Calling
container.decode()/Stream.decode()on a video (or audio) streamwhose
codec_contextisNone— which happens when the container cannotestablish codec parameters from a truncated or corrupt bitstream — currently
crashes the entire process with a fatal memory-access fault (
SIGSEGVorSIGBUS;EXC_BAD_ACCESSon macOS) rather than raising a catchable Pythonexception. Because the fault is a native memory access inside FFmpeg's
multithreaded decoder, no
try/exceptcan recover it — one bad input takesdown the whole interpreter.
This PR adds a guard to
VideoStream.decodeandAudioStream.decode: if thestream has no codec context, raise a clear
ValueErrorinstead of handing thepacket to a decoder that will run FFmpeg's multithreaded decode over
unvalidated state.
Reproducer
Exit status is
138(128 + SIGBUS). A Pythontry/exceptaround the loop doesnot help — the process is gone before any exception is raised.
Root cause
Traced with a fault report (maceOS
.ips), the faulting frame is in PyAV's owncompiled decode path, dereferencing a wild/misaligned pointer:
The decode context PyAV creates for container streams uses FFmpeg's default
threading (
thread_count = 0→ auto; here 4 threads,thread_type = SLICE).FFmpeg 8.1's slice-multithreaded H.264 decoder performs an out-of-bounds /
misaligned read when handed a stream truncated inside its first coded frame.
Three independent observations confirm the threading provenance:
ffmpegCLI (same libav 62.x)container.decode()thread_count = 1/thread_type = "NONE"It is a general class of inputs, not one file
Sweeping truncation offsets across four synthetic H.264/mp4 variants (different
durations, GOP sizes, total sizes), decoding each prefix in an isolated
subprocess so a crash only kills the child:
The crashing cuts fall in a contiguous band at the same structural byte
offsets across every variant (truncations landing inside the first coded
frame), i.e. ~3–4% of arbitrary truncation points. Every crashing cut has
stream.codec_context is None; single-threaded decode is immune to all of them.The fix
VideoStream.decode/AudioStream.decodeare thin passthroughs toself.codec_context.decode(packet). Whencodec_context is Nonethis shouldnever proceed to a native decode. The guard converts the process-killing crash
into a clear, catchable error:
This is not a perf change for valid streams (they have a codec context and
are unaffected — default multithreaded decode is preserved). It only affects the
previously-crashing
codec_context is Nonepath.Note on scope
The underlying out-of-bounds read is arguably an FFmpeg bug in the
slice-threaded H.264 decoder; it should also be reported upstream. This PR is
the PyAV-side containment: a library should raise, not
SIGBUS, on input italready knows it cannot decode (
codec_context is None). It does not attempt tomake the multithreaded decoder itself safe on corrupt input.
Tests
Adds a regression test (
tests/test_decode.py::TestDecode::test_decode_truncated_no_codec_context_raises)that encodes a short faststart H.264/mp4, finds a truncation prefix that opens
but yields
codec_context is None, and assertsdecode()raisesValueError(rather than crashing the test process).
Verification
Built from source against FFmpeg 8.1.2 (
libavcodec 62.28.102, matching thereleased wheel) and confirmed both directions:
(
pytestexits 139); a directcontainer.decode()on the crashing bytes diesthe same way.
tests/test_decode.pysuite is green (14 passed, 4 skipped).