Skip to content

Fix segfault in 2 pass fallback

Background

I found this segfault when 2 pass encoding a vfr file using ffmpeg 4.4.6 on macOS. ffmpeg improperly handles the situation and the 2nd pass ends up having more frames than the 1st. (I will provide a patch to them as well.) x264 detects the mismatch between the passes prints a message and enters a fallback path, yet fails to handle it properly and segfaults. This patch allows x264 to continue gracefully via the already existing fallback.

Reproducer (ffmpeg 4.4.6)

ffmpeg_vfr_crash_repro.mov

#!/bin/bash
# Minimal reproducer for ffmpeg 2-pass VFR segfault
# ffmpeg 4.4.6 on macOS arm64
# File: ffmpeg_vfr_crash_repro.mov (8.1KB, 100x100, 5s, VFR)

set -e

INPUT="ffmpeg_vfr_crash_repro.mov"
PASSLOG="passlog_repro"

echo "=== ffmpeg version ==="
ffmpeg -version | head -1

echo ""
echo "=== Input file ==="
ls -lh "$INPUT"
ffprobe -v error -show_entries stream=width,height,r_frame_rate,avg_frame_rate -of default=noprint_wrappers=1 "$INPUT"

echo ""
echo "=== Pass 1 ==="
rm -f ${PASSLOG}*
ffmpeg -y -i "$INPUT" -c:v libx264 -b:v 50k -pass 1 -passlogfile "$PASSLOG" -an -f null /dev/null 2>&1 | tail -1

echo ""
echo "=== Pass 2 (crashes) ==="
set +e
ffmpeg -y -i "$INPUT" -c:v libx264 -b:v 50k -pass 2 -passlogfile "$PASSLOG" -an output.mp4 2>&1
EXIT_CODE=$?
set -e

echo ""
echo "=== Result ==="
echo "Exit code: $EXIT_CODE"
if [ $EXIT_CODE -eq 139 ]; then
    echo "SIGSEGV (segmentation fault) - bug reproduced!"
elif [ $EXIT_CODE -eq 0 ]; then
    echo "No crash - bug may be fixed in your version"
else
    echo "Different error occurred"
fi

Debugger Output

step=4 cplxblur=20.0 qblur=0.5 ip_ratio=1.40 aq=1:1.00
Output #0, mp4, to 'output.mp4':
  Metadata:
    major_brand     : qt
    minor_version   : 512
    compatible_brands: qt
    encoder         : Lavf58.76.100
  Stream #0:0: Video: h264 (avc1 / 0x31637661), yuv420p(tv, bt709, progressive), 100x100, q=2-31, 50 kb/s, 120 fps, 15360 tbn (default)
    Metadata:
      handler_name    : Core Media Video
      vendor_id       : FFMP
      encoder         : Lavc58.134.100 libx264
    Side data:
      cpb: bitrate max/min/avg: 0/0/50000 buffer size: 0 vbv_delay: N/A
[libx264 @ 0x9d0c4de00] 2nd pass has more frames than 1st pass (165)/A speed=   0x
[libx264 @ 0x9d0c4de00] continuing anyway, at constant QP=3
[libx264 @ 0x9d0c4de00] disabling adaptive B-frames
Process 76156 stopped
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
    frame #0: 0x000000010203bde4 libx264.165.dylib`weight_cost_init_luma + 56
libx264.165.dylib`weight_cost_init_luma:
->  0x10203bde4 <+56>: ldrh   w8, [x8]
    0x10203bde8 <+60>: mov    w9, #0x7fff ; =32767
    0x10203bdec <+64>: cmp    w8, w9
    0x10203bdf0 <+68>: b.ne   0x10203bdfc    ; <+80>
Target 0: (ffmpeg) stopped.
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x0)
  * frame #0: 0x000000010203bde4 libx264.165.dylib`weight_cost_init_luma + 56
    frame #1: 0x000000010203b10c libx264.165.dylib`x264_8_weights_analyse + 1548
    frame #2: 0x000000010203f730 libx264.165.dylib`x264_8_slicetype_decide + 1956
    frame #3: 0x000000010208700c libx264.165.dylib`x264_8_lookahead_get_frames + 232
    frame #4: 0x000000010207d200 libx264.165.dylib`x264_8_encoder_encode + 580
    frame #5: 0x0000000100849580 ffmpeg`___lldb_unnamed_symbol16472 + 1348
    frame #6: 0x00000001005d43a4 ffmpeg`___lldb_unnamed_symbol14254 + 464
    frame #7: 0x00000001005d4178 ffmpeg`avcodec_send_frame + 560
    frame #8: 0x0000000100022120 ffmpeg`___lldb_unnamed_symbol7152 + 2908
    frame #9: 0x0000000100020ec8 ffmpeg`___lldb_unnamed_symbol7150 + 396
    frame #10: 0x000000010001b058 ffmpeg`___lldb_unnamed_symbol7137 + 13444
    frame #11: 0x00000001000174a4 ffmpeg`main + 492
    frame #12: 0x00000001859edd54 dyld`start + 7184
(lldb) register read x8
      x8 = 0x0000000000000000
(lldb) frame select 1
frame #1: 0x000000010203b10c libx264.165.dylib`x264_8_weights_analyse + 1548
libx264.165.dylib`x264_8_weights_analyse:
->  0x10203b10c <+1548>: mov    x2, x0
    0x10203b110 <+1552>: ldr    x1, [sp, #0xd8]
    0x10203b114 <+1556>: mov    x0, x21
    0x10203b118 <+1560>: str    x2, [sp, #0xb8]
(lldb)

Analysis

When 2nd pass has more frames than 1st pass, x264 falls back to constant QP mode and disables features that depend on stats data (ABR, 2-pass, stat reading, adaptive B-frames, scenecut, MB-tree).

However, weighted prediction (weightp) was not disabled. With b_stat_read set to 0, x264_weights_analyse() gets called for the first time (previously gated by the stat_read check). This leads to weight_cost_init_luma() computing a ref0_distance that exceeds the lowres_mvs array bounds, dereferencing a NULL pointer and crashing.

Fix by also disabling i_weighted_pred in the fallback loop, consistent with the other features being turned off.

Merge request reports

Loading