Commit c69aff7454 for ffmpeg

commit c69aff745431c73e546bdad988ec23f74c79979c
Author: Michael Niedermayer <michael@niedermayer.cc>
Date:   Sat Oct 10 05:23:52 2026 +0200

    avcodec/exif: stop following an IFD chain that loops

    Stop at the first offset that was already parsed.

    Timeout Replicated through API

    Fixes: Timeout
    Fixes: 561208791/clusterfuzz-testcase-minimized-ffmpeg_AV_CODEC_ID_SMVJPEG_fuzzer-5447460856856576
    Fixes: PrLc2prh3sxt
    Regression since: 784aa09fa8222f6704d953b5035f8d0f0bec1623

    Found-by: continuous fuzzing process https://github.com/google/oss-fuzz/tree/master/projects/ffmpeg
    Signed-off-by: Michael Niedermayer <michael@niedermayer.cc>

diff --git a/libavcodec/exif.c b/libavcodec/exif.c
index f5cb3330af..8169c4f40d 100644
--- a/libavcodec/exif.c
+++ b/libavcodec/exif.c
@@ -932,6 +932,8 @@ int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size,
      * parse IFD0 here. If the return value is positive that tells us
      * there is subimage metadata, but we don't parse that IFD here
      */
+    int ifd_offsets[17] = { bytestream2_tell(&gbytes) };
+    int nb_ifds = 1;
     ret = exif_parse_ifd_list(logctx, &gbytes, le, 0, ifd, 0);
     if (ret < 0) {
         av_log(logctx, AV_LOG_ERROR, "error decoding EXIF data: %s\n", av_err2str(ret));
@@ -945,6 +947,13 @@ int av_exif_parse_buffer(void *logctx, const uint8_t *buf, size_t size,
     /* cap at 16 extra IFDs for sanity/parse security */
     for (int extra_tag = 0xFFFCu; extra_tag > 0xFFECu; extra_tag--) {
         AVExifMetadata extra_ifd = { 0 };
+        for (int i = 0; i < nb_ifds; i++) {
+            if (ifd_offsets[i] == next) {
+                av_log(logctx, AV_LOG_WARNING, "EXIF IFD chain loops back to offset %d\n", next);
+                goto finish;
+            }
+        }
+        ifd_offsets[nb_ifds++] = next;
         ret = exif_parse_ifd_list(logctx, &gbytes, le, 0, &extra_ifd, 1);
         if (ret < 0) {
             av_exif_free(&extra_ifd);