PR #25292 opened by baylesj
URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25292
Patch URL: https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/25292.patch

# Summary of changes

This PR contains a tightly focused group of fuzzer fixes in avformat's seek 
code, specifically to resolve potential integer overflow issues found by 
fuzzing FFmpeg inside of Chromium. 

The following four functions have minor changes to avoid integer overflow:

1. `reset_index_position()`: move to using saturating add when calculating 
metadata head size, so corrupt seekpoints cannot overflow `int64_t`.

2. `In flac_read_header()`, update timestamp and pos to be read as uint64 
values (matching the spec) and bounds checked against INT64_MAX to avoid 
overflow issues.

3. `ff_seek_frame_binary()`: when mutating pos_limit, strictly bind 
min_distance to $\subset [0, pos\_limit]$ to avoid underflow issues. Used 
`FFMIN` / `FFMAX` to try to match library conventions.

4. `ff_gen_search()`: the most significant changes in this CL. Several checks 
to (a) reject negative position bounds or `pos_limit` values beyond `pos_max`, 
(b) saturate timestamp deltas to `< INT64_MAX` to avoid overflow in 
`av_rescale(), and (c) modify the logic to compute the bisection midpoint to 
prevent signed integer overflows.

> [!NOTE]
> The binary midpoint calculation previously used the traditional midpoint 
> formula, $mid = (low + high) / 2$, (where `/ 2` is done by `>> 1` in FFmpeg) 
> and now uses the subtration method, generally considered the most common and 
> readable workaround that is overflow safe: $mid = low + (high - low) / 2$. 

As this is a fuzzing fix, no new FATE test samples are included.


>From c29f5f7fa90435f0eae21cb9f7245ab05bb6c4f9 Mon Sep 17 00:00:00 2001
From: Jordan Bayles <[email protected]>
Date: Wed, 21 Oct 2026 09:15:40 +0000
Subject: [PATCH 1/2] avformat/flacdec: prevent integer overflow when parsing
 seektable

In reset_index_position(), adding metadata_head_size to each seekpoint's
stream_offset (stored as int64_t in AVIndexEntry.pos) can overflow
signed 64-bit integers when a corrupt seekpoint has a large offset near
INT64_MAX. Use av_sat_add64() to saturate the addition instead of
triggering signed integer overflow UB, and read seekpoint timestamp and
offset as uint64_t, skipping entries exceeding INT64_MAX (including
0xFFFFFFFFFFFFFFFF placeholder points) when parsing
FLAC_METADATA_TYPE_SEEKTABLE.

Signed-off-by: Jordan Bayles <[email protected]>
---
 libavformat/flacdec.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/libavformat/flacdec.c b/libavformat/flacdec.c
index 12d4b7305f7..1c7d52c34f9 100644
--- a/libavformat/flacdec.c
+++ b/libavformat/flacdec.c
@@ -20,6 +20,7 @@
  */
 
 #include "libavutil/channel_layout.h"
+#include "libavutil/common.h"
 #include "libavutil/mem.h"
 #include "libavcodec/avcodec.h"
 #include "libavcodec/bytestream.h"
@@ -47,7 +48,7 @@ static void reset_index_position(int64_t metadata_head_size, 
AVStream *st)
     FFStream *const sti = ffstream(st);
     /* the real seek index offset should be the size of metadata blocks with 
the offset in the frame blocks */
     for (int i = 0; i < sti->nb_index_entries; i++)
-        sti->index_entries[i].pos += metadata_head_size;
+        sti->index_entries[i].pos = av_sat_add64(sti->index_entries[i].pos, 
metadata_head_size);
 }
 
 static const uint16_t sr_table[16] = {
@@ -178,10 +179,12 @@ static int flac_read_header(AVFormatContext *s)
             flac->found_seektable = 1;
             if ((s->flags&AVFMT_FLAG_FAST_SEEK)) {
                 for(i=0; i<seek_point_count; i++) {
-                    int64_t timestamp = bytestream_get_be64(&seekpoint);
-                    int64_t pos = bytestream_get_be64(&seekpoint);
+                    uint64_t timestamp = bytestream_get_be64(&seekpoint);
+                    uint64_t pos = bytestream_get_be64(&seekpoint);
                     /* skip number of samples */
                     bytestream_get_be16(&seekpoint);
+                    if (timestamp > INT64_MAX || pos > INT64_MAX)
+                        continue;
                     av_add_index_entry(st, pos, timestamp, 0, 0, 
AVINDEX_KEYFRAME);
                 }
             }
-- 
2.54.0


>From 6f90708db337dfb0d3cbcbfa23de2cdc853deb66 Mon Sep 17 00:00:00 2001
From: Jordan Bayles <[email protected]>
Date: Wed, 21 Oct 2026 09:15:40 +0000
Subject: [PATCH 2/2] avformat/seek: prevent signed integer overflows in
 ff_gen_search

When seeking on a stream with corrupt index entries or extreme
timestamps/positions:
1. In ff_seek_frame_binary(), subtracting e->min_distance from pos_max
   can underflow if pos_max < min_distance. Clamp the subtraction so
   pos_limit stays non-negative.
2. In ff_gen_search(), validate that pos_min, pos_max, and pos_limit are
   non-negative and pos_limit <= pos_max.
3. Computing ts_max - ts_min or target_ts - ts_min in signed int64_t
   overflows when timestamps span across negative and positive values.
   Compute the deltas in uint64_t and scale down by 1 bit if the range
   exceeds INT64_MAX before passing to av_rescale().
4. Computing (pos_min + pos_limit) >> 1 overflows int64_t when both
   positions are large (sum > INT64_MAX). Use
   pos_min + ((pos_limit - pos_min) >> 1) instead.

Signed-off-by: Jordan Bayles <[email protected]>
---
 libavformat/seek.c | 19 +++++++++++++++----
 1 file changed, 15 insertions(+), 4 deletions(-)

diff --git a/libavformat/seek.c b/libavformat/seek.c
index 31e3e6dd697..4a4459a9dd3 100644
--- a/libavformat/seek.c
+++ b/libavformat/seek.c
@@ -336,7 +336,7 @@ int ff_seek_frame_binary(AVFormatContext *s, int 
stream_index,
             av_assert1(e->timestamp >= target_ts);
             pos_max   = e->pos;
             ts_max    = e->timestamp;
-            pos_limit = pos_max - e->min_distance;
+            pos_limit = pos_max - FFMIN(pos_max, FFMAX(e->min_distance, 0));
             av_log(s, AV_LOG_TRACE, "using cached pos_max=0x%"PRIx64" 
pos_limit=0x%"PRIx64
                     " dts_max=%s\n", pos_max, pos_limit, av_ts2str(ts_max));
         }
@@ -417,6 +417,9 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, 
int64_t target_ts,
             return -1;
     }
 
+    if (pos_min < 0)
+        return -1;
+
     if (ts_min >= target_ts) {
         *ts_ret = ts_min;
         return pos_min;
@@ -428,6 +431,9 @@ int64_t ff_gen_search(AVFormatContext *s, int stream_index, 
int64_t target_ts,
         pos_limit = pos_max;
     }
 
+    if (pos_max < 0 || pos_limit < 0 || pos_limit > pos_max)
+        return -1;
+
     if (ts_max <= target_ts) {
         *ts_ret = ts_max;
         return pos_max;
@@ -444,13 +450,18 @@ int64_t ff_gen_search(AVFormatContext *s, int 
stream_index, int64_t target_ts,
 
         if (no_change == 0) {
             int64_t approximate_keyframe_distance = pos_max - pos_limit;
+            uint64_t ts_delta = (uint64_t)target_ts - (uint64_t)ts_min;
+            uint64_t ts_range = (uint64_t)ts_max - (uint64_t)ts_min;
+            if (ts_range > INT64_MAX) {
+                ts_delta >>= 1;
+                ts_range >>= 1;
+            }
             // interpolate position (better than dichotomy)
-            pos = av_rescale(target_ts - ts_min, pos_max - pos_min,
-                             ts_max - ts_min) +
+            pos = av_rescale(ts_delta, pos_max - pos_min, ts_range) +
                   pos_min - approximate_keyframe_distance;
         } else if (no_change == 1) {
             // bisection if interpolation did not change min / max pos last 
time
-            pos = (pos_min + pos_limit) >> 1;
+            pos = pos_min + ((pos_limit - pos_min) >> 1);
         } else {
             /* linear search if bisection failed, can only happen if there
              * are very few or no keyframes between min/max */
-- 
2.54.0

_______________________________________________
ffmpeg-devel mailing list -- [email protected]
To unsubscribe send an email to [email protected]

Reply via email to