On Sun, Dec 17, 2023 at 04:38:02PM +0100, Theo Buehler wrote:
> > mpv crashes after seeking forward(->) any .opus file on obsd current
> 
> Yes, any seek crashes mpv and mplayer. It's a bug in ffmpeg's seek code
> in libavformat and the trace always looks the same:
> 
> #0  ff_seek_frame_binary (s=0xfa08e08a800, stream_index=0, 
> target_ts=<optimized out>, flags=1)
>     at src/libavformat/utils.c:2190
> #1  0x00000fa126e2475c in ogg_read_seek (s=0xfa08e08a800, stream_index=0, 
> timestamp=303408,
>     flags=1) at src/libavformat/oggdec.c:954
> #2  0x00000fa126e8c942 in seek_frame_internal (s=0xfa08e08a800, 
> stream_index=0, timestamp=303408,
>     flags=1) at src/libavformat/utils.c:2472
> #3  av_seek_frame (s=0xfa08e08a800, stream_index=<optimized out>, 
> timestamp=<optimized out>,
>     flags=1) at src/libavformat/utils.c:2504
> 
> It's been present since the switch to llvm 16, presumably some more
> aggressive optimization around undefined behavior. I haven't spotted
> the precise issue, but the below diff works around the crash:

This seems to come from the use of the av_uninit() macro:

#    define av_uninit(x) x=x

"av_uninit() to suppress false uninitialized warnings from gcc without 
deoptimizing code.
https://github.com/FFmpeg/FFmpeg/commit/51066987cf05a7cad567e965fa637e28f9d902c5

This seems a dubious idea to begin with, even 15 years ago, but with
the aggressive optimizations of modern compilers it's downright dangerous.

    int64_t av_uninit(pos_min), av_uninit(pos_max), pos, pos_limit;
...
        index = av_index_search_timestamp(st, target_ts,
                                          flags & ~AVSEEK_FLAG_BACKWARD);
        av_assert0(index < st->nb_index_entries);
        if (index >= 0) {
            e = &st->index_entries[index];
            av_assert1(e->timestamp >= target_ts);
            pos_max   = e->pos;

The crash happens in the above line, which is the only spot initializing
pos_max.

... 
    }

    pos = ff_gen_search(s, stream_index, target_ts, pos_min, pos_max, pos_limit,
                        ts_min, ts_max, flags, &ts, avif->read_timestamp);

But here pos_max is passed by value to ff_gen_search. Passing by value
implies reading the value, which implies the value must be well-defined
which in turn means that index >= 0 always. Or something like that.

In any case, the compiler elides the index >= 0 check in unmodified code
and leaves it there when disabling optimization, or alternatively when
initializing pos_max:

--- libavformat/utils.c.orig
+++ libavformat/utils.c
@@ -2146,7 +2146,7 @@ int ff_seek_frame_binary(AVFormatContext *s, int strea
                          int64_t target_ts, int flags)
 {
     const AVInputFormat *avif = s->iformat;
-    int64_t av_uninit(pos_min), av_uninit(pos_max), pos, pos_limit;
+    int64_t av_uninit(pos_min), pos_max = 0, pos, pos_limit;
     int64_t ts_min, ts_max, ts;
     int index;
     int64_t ret;

Reply via email to