Commit 857ee5f9c5 for ffmpeg

commit 857ee5f9c5e55689f480a2dca089fece89f029f7
Author: Michael Niedermayer <michael@niedermayer.cc>
Date:   Mon Oct 12 02:11:45 2026 +0200

    avformat/hls: saturate the playlist timestamp offset arithmetic

    Fixes: signed integer overflow
    Fixes: 2PuIAHF3wU0b
    Regression since: e27ad5760c
    Found during triage of the review of https://code.ffmpeg.org/FFmpeg/FFmpeg/pulls/24873
    Signed integer overflow Replicated through UnModified FFmpeg with UBSAN

diff --git a/libavformat/hls.c b/libavformat/hls.c
index 0bff339850..519d3d852f 100644
--- a/libavformat/hls.c
+++ b/libavformat/hls.c
@@ -2803,8 +2803,8 @@ static int recheck_discard_flags(AVFormatContext *s, int first)
             pls->pb.pub.eof_reached = 0;
             if (c->cur_timestamp != AV_NOPTS_VALUE) {
                 /* catch up */
-                pls->seek_timestamp = c->cur_timestamp +
-                    (pls->ts_offset != AV_NOPTS_VALUE ? pls->ts_offset : 0);
+                pls->seek_timestamp = av_sat_add64(c->cur_timestamp,
+                    pls->ts_offset != AV_NOPTS_VALUE ? pls->ts_offset : 0);
                 pls->seek_flags = AVSEEK_FLAG_ANY;
                 pls->seek_stream_index = -1;
             }
@@ -2935,7 +2935,7 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
                                               get_timebase(pls), AV_TIME_BASE_Q);

                     for (int64_t k = 0; k < seg_idx && k < pls->n_segments; k++)
-                        ts -= pls->segments[k]->duration;
+                        ts = av_sat_sub64(ts, pls->segments[k]->duration);

                     if (c->first_timestamp == AV_NOPTS_VALUE) {
                         c->first_timestamp = ts;
@@ -2946,19 +2946,19 @@ static int hls_read_packet(AVFormatContext *s, AVPacket *pkt)
                          * order, but another stream in the same segment may
                          * start earlier. Lower the estimate while packets of
                          * that segment are still arriving. */
-                        int64_t delta = c->first_timestamp - ts;
+                        int64_t delta = av_sat_sub64(c->first_timestamp, ts);

                         if (delta > 0 && delta <= pls->target_duration) {
                             c->first_timestamp = ts;
                             for (int k = 0; k < c->n_playlists; k++)
                                 if (c->playlists[k]->ts_offset != AV_NOPTS_VALUE)
-                                    c->playlists[k]->ts_offset += delta;
+                                    c->playlists[k]->ts_offset = av_sat_add64(c->playlists[k]->ts_offset, delta);
                             adjusted = 1;
                         }
                     }

                     if (pls->ts_offset == AV_NOPTS_VALUE)
-                        pls->ts_offset = ts - c->first_timestamp;
+                        pls->ts_offset = av_sat_sub64(ts, c->first_timestamp);

                     if (adjusted) {
                         for (unsigned k = 0; k < s->nb_streams; k++)
@@ -3224,7 +3224,7 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
          * have different DTS baselines, so translate the threshold onto this
          * playlist's own baseline using its captured offset. */
         if (pls->ts_offset != AV_NOPTS_VALUE)
-            pls->seek_timestamp = seek_timestamp + pls->ts_offset;
+            pls->seek_timestamp = av_sat_add64(seek_timestamp, pls->ts_offset);
         else
             pls->seek_timestamp = seek_timestamp;
         pls->seek_flags = flags;
@@ -3235,7 +3235,7 @@ static int hls_read_seek(AVFormatContext *s, int stream_index,
              * to not discard the keyframe and land one segment late. */
             int64_t idx = seq_no - pls->start_seq_no;
             if (idx >= 0 && idx < pls->n_segments)
-                pls->seek_timestamp -= pls->segments[idx]->duration;
+                pls->seek_timestamp = av_sat_sub64(pls->seek_timestamp, pls->segments[idx]->duration);
         }

         if (pls != seek_pls) {