libtiff: Fix IFD loop detection

From 2c0f2ed803c84ec9244236c59dacf4dc32447ecc Mon Sep 17 00:00:00 2001
From: Even Rouault <[EMAIL REDACTED]>
Date: Tue, 13 Dec 2022 15:14:47 +0100
Subject: [PATCH] Fix IFD loop detection

---
 libtiff/tif_dir.c      |  7 ++--
 libtiff/tif_dirread.c  | 78 +++++++++++++++++++++++++++++-------------
 libtiff/tif_hash_set.c |  2 +-
 libtiff/tif_hash_set.h |  3 +-
 libtiff/tif_open.c     |  3 +-
 libtiff/tiffiop.h      |  4 +++
 6 files changed, 68 insertions(+), 29 deletions(-)

diff --git a/libtiff/tif_dir.c b/libtiff/tif_dir.c
index 97fb5eb9..901dff82 100644
--- a/libtiff/tif_dir.c
+++ b/libtiff/tif_dir.c
@@ -2071,7 +2071,7 @@ int TIFFSetSubDirectory(TIFF *tif, uint64_t diroff)
     if (diroff == 0)
     {
         /* Special case to invalidate the tif_lastdiroff member. */
-        tif->tif_curdir = UINT_MAX;
+        tif->tif_curdir = TIFF_NON_EXISTENT_DIR_NUMBER;
     }
     else
     {
@@ -2081,7 +2081,8 @@ int TIFFSetSubDirectory(TIFF *tif, uint64_t diroff)
             probablySubIFD = 1;
         }
         /* -1 because TIFFReadDirectory() will increment tif_curdir. */
-        tif->tif_curdir = curdir == 0 ? UINT_MAX : curdir - 1;
+        tif->tif_curdir =
+            curdir == 0 ? TIFF_NON_EXISTENT_DIR_NUMBER : curdir - 1;
     }
 
     tif->tif_nextdiroff = diroff;
@@ -2090,7 +2091,7 @@ int TIFFSetSubDirectory(TIFF *tif, uint64_t diroff)
      * back. */
     if (!retval)
     {
-        if (tif->tif_curdir == UINT_MAX)
+        if (tif->tif_curdir == TIFF_NON_EXISTENT_DIR_NUMBER)
             tif->tif_curdir = 0;
         else
             tif->tif_curdir++;
diff --git a/libtiff/tif_dirread.c b/libtiff/tif_dirread.c
index fc21851b..b9606f58 100644
--- a/libtiff/tif_dirread.c
+++ b/libtiff/tif_dirread.c
@@ -4076,9 +4076,12 @@ int TIFFReadDirectory(TIFF *tif)
     /* tif_curdir++ and tif_nextdiroff should only be updated after SUCCESSFUL
      * reading of the directory. Otherwise, invalid IFD offsets could corrupt
      * the IFD list. */
-    if (!_TIFFCheckDirNumberAndOffset(
-            tif, tif->tif_curdir == UINT_MAX ? 0 : tif->tif_curdir + 1,
-            nextdiroff))
+    if (!_TIFFCheckDirNumberAndOffset(tif,
+                                      tif->tif_curdir ==
+                                              TIFF_NON_EXISTENT_DIR_NUMBER
+                                          ? 0
+                                          : tif->tif_curdir + 1,
+                                      nextdiroff))
     {
         return 0; /* bad offset (IFD looping or more than TIFF_MAX_DIR_COUNT
                      IFDs) */
@@ -4094,7 +4097,7 @@ int TIFFReadDirectory(TIFF *tif)
     /* Set global values after a valid directory has been fetched.
      * tif_diroff is already set to nextdiroff in TIFFFetchDirectory() in the
      * beginning. */
-    if (tif->tif_curdir == UINT_MAX)
+    if (tif->tif_curdir == TIFF_NON_EXISTENT_DIR_NUMBER)
         tif->tif_curdir = 0;
     else
         tif->tif_curdir++;
@@ -5345,8 +5348,10 @@ int _TIFFCheckDirNumberAndOffset(TIFF *tif, tdir_t dirn, uint64_t diroff)
 
     if (tif->tif_map_dir_number_to_offset == NULL)
     {
+        /* No free callback for this map, as it shares the same items as
+         * tif->tif_map_dir_offset_to_number. */
         tif->tif_map_dir_number_to_offset = TIFFHashSetNew(
-            hashFuncNumberToOffset, equalFuncNumberToOffset, free);
+            hashFuncNumberToOffset, equalFuncNumberToOffset, NULL);
         if (tif->tif_map_dir_number_to_offset == NULL)
         {
             TIFFErrorExtR(tif, "_TIFFCheckDirNumberAndOffset",
@@ -5386,15 +5391,50 @@ int _TIFFCheckDirNumberAndOffset(TIFF *tif, tdir_t dirn, uint64_t diroff)
 
     /* Check if offset of an IFD has been changed and update offset of that IFD
      * number. */
-    entry.dirNumber = dirn;
-    entry.offset = 0; /* not used */
-
     foundEntry = (TIFFOffsetAndDirNumber *)TIFFHashSetLookup(
         tif->tif_map_dir_number_to_offset, &entry);
     if (foundEntry)
     {
-        /* tif_dirlistdirn can have IFD numbers dirn in random order */
-        foundEntry->offset = diroff;
+        if (foundEntry->offset != diroff)
+        {
+            TIFFOffsetAndDirNumber entryOld;
+            entryOld.offset = diroff;
+            entryOld.dirNumber = foundEntry->offset;
+            TIFFOffsetAndDirNumber *foundEntryOld =
+                (TIFFOffsetAndDirNumber *)TIFFHashSetLookup(
+                    tif->tif_map_dir_offset_to_number, &entryOld);
+            if (foundEntryOld)
+            {
+                TIFFHashSetRemove(tif->tif_map_dir_offset_to_number,
+                                  foundEntryOld);
+            }
+            foundEntryOld = (TIFFOffsetAndDirNumber *)TIFFHashSetLookup(
+                tif->tif_map_dir_number_to_offset, &entryOld);
+            if (foundEntryOld)
+            {
+                TIFFHashSetRemove(tif->tif_map_dir_number_to_offset,
+                                  foundEntryOld);
+            }
+
+            TIFFOffsetAndDirNumber *entryPtr = (TIFFOffsetAndDirNumber *)malloc(
+                sizeof(TIFFOffsetAndDirNumber));
+            if (entryPtr == NULL)
+            {
+                return 0;
+            }
+
+            /* Add IFD offset and dirn to IFD directory list */
+            *entryPtr = entry;
+
+            if (!TIFFHashSetInsert(tif->tif_map_dir_offset_to_number, entryPtr))
+            {
+                return 0;
+            }
+            if (!TIFFHashSetInsert(tif->tif_map_dir_number_to_offset, entryPtr))
+            {
+                return 0;
+            }
+        }
         return 1;
     }
 
@@ -5407,30 +5447,22 @@ int _TIFFCheckDirNumberAndOffset(TIFF *tif, tdir_t dirn, uint64_t diroff)
         return 0;
     }
 
-    TIFFOffsetAndDirNumber *entry1 =
-        (TIFFOffsetAndDirNumber *)malloc(sizeof(TIFFOffsetAndDirNumber));
-    TIFFOffsetAndDirNumber *entry2 =
+    TIFFOffsetAndDirNumber *entryPtr =
         (TIFFOffsetAndDirNumber *)malloc(sizeof(TIFFOffsetAndDirNumber));
-    if (entry1 == NULL || entry2 == NULL)
+    if (entryPtr == NULL)
     {
-        free(entry1);
-        free(entry2);
         return 0;
     }
 
     /* Add IFD offset and dirn to IFD directory list */
-    *entry1 = entry;
-    *entry2 = entry;
+    *entryPtr = entry;
 
-    if (!TIFFHashSetInsert(tif->tif_map_dir_offset_to_number, entry1))
+    if (!TIFFHashSetInsert(tif->tif_map_dir_offset_to_number, entryPtr))
     {
-        free(entry1);
-        free(entry2);
         return 0;
     }
-    if (!TIFFHashSetInsert(tif->tif_map_dir_number_to_offset, entry2))
+    if (!TIFFHashSetInsert(tif->tif_map_dir_number_to_offset, entryPtr))
     {
-        free(entry2);
         return 0;
     }
 
diff --git a/libtiff/tif_hash_set.c b/libtiff/tif_hash_set.c
index e2b18331..5fbfbbea 100644
--- a/libtiff/tif_hash_set.c
+++ b/libtiff/tif_hash_set.c
@@ -504,7 +504,6 @@ void *TIFFHashSetLookup(TIFFHashSet *set, const void *elt)
     return NULL;
 }
 
-#ifdef notused
 /************************************************************************/
 /*                     TIFFHashSetRemoveInternal()                      */
 /************************************************************************/
@@ -575,6 +574,7 @@ bool TIFFHashSetRemove(TIFFHashSet *set, const void *elt)
     return TIFFHashSetRemoveInternal(set, elt, false);
 }
 
+#ifdef notused
 /************************************************************************/
 /*                     TIFFHashSetRemoveDeferRehash()                   */
 /************************************************************************/
diff --git a/libtiff/tif_hash_set.h b/libtiff/tif_hash_set.h
index 5d412468..1a3f8da6 100644
--- a/libtiff/tif_hash_set.h
+++ b/libtiff/tif_hash_set.h
@@ -87,8 +87,9 @@ extern "C"
 
     void *TIFFHashSetLookup(TIFFHashSet *set, const void *elt);
 
-#ifdef notused
     bool TIFFHashSetRemove(TIFFHashSet *set, const void *elt);
+
+#ifdef notused
     bool TIFFHashSetRemoveDeferRehash(TIFFHashSet *set, const void *elt);
 #endif
 
diff --git a/libtiff/tif_open.c b/libtiff/tif_open.c
index 953d8258..8a86a269 100644
--- a/libtiff/tif_open.c
+++ b/libtiff/tif_open.c
@@ -26,6 +26,7 @@
  * TIFF Library.
  */
 #include "tiffiop.h"
+#include <limits.h>
 
 /*
  * Dummy functions to fill the omitted client procedures.
@@ -241,7 +242,7 @@ TIFF *TIFFClientOpenExt(const char *name, const char *mode,
     tif->tif_name = (char *)tif + sizeof(TIFF);
     strcpy(tif->tif_name, name);
     tif->tif_mode = m & ~(O_CREAT | O_TRUNC);
-    tif->tif_curdir = (uint16_t)-1; /* non-existent directory */
+    tif->tif_curdir = TIFF_NON_EXISTENT_DIR_NUMBER; /* non-existent directory */
     tif->tif_curoff = 0;
     tif->tif_curstrip = (uint32_t)-1; /* invalid strip */
     tif->tif_row = (uint32_t)-1;      /* read/write pre-increment */
diff --git a/libtiff/tiffiop.h b/libtiff/tiffiop.h
index fe677071..791881a3 100644
--- a/libtiff/tiffiop.h
+++ b/libtiff/tiffiop.h
@@ -51,6 +51,8 @@
 
 #include "tif_dir.h"
 
+#include <limits.h>
+
 #ifndef STRIP_SIZE_DEFAULT
 #define STRIP_SIZE_DEFAULT 8192
 #endif
@@ -59,6 +61,8 @@
 #define TIFF_MAX_DIR_COUNT 1048576
 #endif
 
+#define TIFF_NON_EXISTENT_DIR_NUMBER UINT_MAX
+
 #define streq(a, b) (strcmp(a, b) == 0)
 #define strneq(a, b, n) (strncmp(a, b, n) == 0)