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)
#!/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.