From 29475c92880d5dffa9aaff9731da3652c983dc4e Mon Sep 17 00:00:00 2001 From: BlackSpirits Date: Sun, 13 Sep 2026 22:23:34 +0200 Subject: [PATCH 1/2] Audit: combine final FFmpeg hardening stack --- .../Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs | 49 +++++++++++++++++-- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs b/src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs index 988378b17d..c5d73efe9c 100644 --- a/src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs +++ b/src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs @@ -935,6 +935,7 @@ private void VideoLoop() var swsSourceFormat = AVPixelFormat.AV_PIX_FMT_NONE; var outputWidth = 0; var outputHeight = 0; + VideoFrame? lastDropped = null; try { @@ -950,9 +951,9 @@ private void VideoLoop() : 1.0 / 25.0; var serial = -1; + var minimumReplaySerial = -1; var dropUntil = -1.0; var presentedForSerial = false; - VideoFrame? lastDropped = null; // kept so a target past the last picture still shows something while (!_closing) { @@ -961,6 +962,19 @@ private void VideoLoop() continue; } + if (minimumReplaySerial >= 0 && entry.Serial < minimumReplaySerial) + { + var stalePacket = entry.Packet; + if (stalePacket != null) + { + ffmpeg.av_packet_free(&stalePacket); + } + + continue; + } + + minimumReplaySerial = -1; + if (entry.Serial != serial) { ffmpeg.avcodec_flush_buffers(codec); @@ -985,6 +999,7 @@ private void VideoLoop() // The hardware decoder rejected the stream - retry it in software. codec = FallBackToSoftware(codec, stream, sendResult, ref hardware); serial = -1; + minimumReplaySerial = RequestHardwareFallbackReplay(); } continue; @@ -1092,7 +1107,7 @@ private void VideoLoop() // the key frame. codec = FallBackToSoftware(codec, stream, 0, ref hardware); serial = -1; - Seek(Position); + minimumReplaySerial = RequestHardwareFallbackReplay(); continue; } @@ -1124,6 +1139,8 @@ private void VideoLoop() } finally { + _videoFrames.Return(lastDropped); + if (sws != null) { ffmpeg.sws_freeContext(sws); @@ -1146,6 +1163,19 @@ private void VideoLoop() } } + private int RequestHardwareFallbackReplay() + { + var target = Position; + Seek(target); + + _videoFrames.Flush(); + _presentWake.Set(); + lock (_seekLock) + { + return _requestedSerial; + } + } + private AVCodecContext* FallBackToSoftware(AVCodecContext* codec, AVStream* stream, int error, ref bool hardware) { var reason = error < 0 ? FfmpegLibraries.ErrorText(error) : "picture transfer failed"; @@ -1296,7 +1326,7 @@ private static bool SupportsHardwareDevice(AVCodec* decoder, AVHWDeviceType devi /// is attached; the caller can tell by hw_device_ctx being set. Any hardware setup /// failure silently means software. /// - private AVCodecContext* OpenDecoder(AVStream* stream, bool hardware = false) + private AVCodecContext* OpenDecoder(AVStream* stream, bool hardware = false, int hardwareStartIndex = 0) { var decoder = ffmpeg.avcodec_find_decoder(stream->codecpar->codec_id); if (decoder == null) @@ -1320,10 +1350,12 @@ private static bool SupportsHardwareDevice(AVCodec* decoder, AVHWDeviceType devi codec->pkt_timebase = stream->time_base; codec->thread_count = 0; // auto + var selectedHardwareIndex = -1; if (hardware && stream->codecpar->codec_type == AVMediaType.AVMEDIA_TYPE_VIDEO) { - foreach (var deviceType in HardwareDeviceTypes) + for (var i = hardwareStartIndex; i < HardwareDeviceTypes.Length; i++) { + var deviceType = HardwareDeviceTypes[i]; if (!SupportsHardwareDevice(decoder, deviceType)) { continue; @@ -1346,6 +1378,7 @@ private static bool SupportsHardwareDevice(AVCodec* decoder, AVHWDeviceType devi codec->hw_device_ctx = device; // freed with the codec context codec->get_format = GetHardwareFormatDelegate; + selectedHardwareIndex = i; break; } } @@ -1353,6 +1386,14 @@ private static bool SupportsHardwareDevice(AVCodec* decoder, AVHWDeviceType devi result = ffmpeg.avcodec_open2(codec, decoder, null); if (result < 0) { + if (hardware && selectedHardwareIndex >= 0) + { + var deviceName = HardwareDeviceName(codec); + Se.LogError($"ffmpeg player: {deviceName} decoder open failed for {ffmpeg.avcodec_get_name(stream->codecpar->codec_id)} ({FfmpegLibraries.ErrorText(result)}), trying the next hardware decoder or software"); + ffmpeg.avcodec_free_context(&codec); + return OpenDecoder(stream, hardware: true, hardwareStartIndex: selectedHardwareIndex + 1); + } + ffmpeg.avcodec_free_context(&codec); throw new InvalidOperationException($"avcodec_open2: {FfmpegLibraries.ErrorText(result)}"); } From fba72f8d71189d10c3090dd1c9a8c50855f5d1f1 Mon Sep 17 00:00:00 2001 From: BlackSpirits Date: Sun, 13 Sep 2026 22:24:47 +0200 Subject: [PATCH 2/2] Audit: preserve final FFmpeg hardening rationale --- src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs b/src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs index c5d73efe9c..79f0b51e2f 100644 --- a/src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs +++ b/src/ui/Logic/VideoPlayers/Ffmpeg/FfmpegPlayer.cs @@ -935,7 +935,7 @@ private void VideoLoop() var swsSourceFormat = AVPixelFormat.AV_PIX_FMT_NONE; var outputWidth = 0; var outputHeight = 0; - VideoFrame? lastDropped = null; + VideoFrame? lastDropped = null; // detached from the queue while seeking; must be returned on every exit try { @@ -1168,6 +1168,9 @@ private int RequestHardwareFallbackReplay() var target = Position; Seek(target); + // Do not let already-demuxed packets from the failed hardware serial feed the fresh + // software decoder before the demux thread performs the seek. A user seek that races + // with this one has a higher serial and is also safe to accept. _videoFrames.Flush(); _presentWake.Set(); lock (_seekLock)