libtiff: Merge branch 'TIFFOpenOptionsSetMaxCumulatedMemAlloc' into 'master'

From f53258e4bbf22cf4c33aa2e31d3dd2c30801120e Mon Sep 17 00:00:00 2001
From: Even Rouault <[EMAIL REDACTED]>
Date: Mon, 27 Nov 2023 00:07:46 +0000
Subject: [PATCH] Add TIFFOpenOptionsSetMaxCumulatedMemAlloc()

This function complements ``TIFFOpenOptionsSetMaxSingleMemAlloc()`` to define the maximum cumulated memory allocations in byte, for a given TIFF handle, that libtiff internal memory allocation functions are allowed.
---
 doc/functions/TIFFOpenOptions.rst |  17 +++-
 doc/functions/libtiff.rst         |   2 +
 doc/libtiff.rst                   |   5 ++
 libtiff/CMakeLists.txt            |   7 ++
 libtiff/libtiff.def               |   1 +
 libtiff/libtiff.map               |   4 +
 libtiff/tif_close.c               |   8 ++
 libtiff/tif_open.c                | 134 ++++++++++++++++++++++++++++--
 libtiff/tif_unix.c                |   4 +
 libtiff/tif_win32.c               |   4 +
 libtiff/tiffio.h                  |   8 +-
 libtiff/tiffiop.h                 |   5 +-
 test/test_open_options.c          |  66 +++++++++++++++
 13 files changed, 254 insertions(+), 11 deletions(-)

diff --git a/doc/functions/TIFFOpenOptions.rst b/doc/functions/TIFFOpenOptions.rst
index 23f29753..01df7c88 100644
--- a/doc/functions/TIFFOpenOptions.rst
+++ b/doc/functions/TIFFOpenOptions.rst
@@ -18,6 +18,8 @@ Synopsis
 
 .. c:function:: void TIFFOpenOptionsSetMaxSingleMemAlloc(TIFFOpenOptions* opts, tmsize_t max_single_mem_alloc)
 
+.. c:function:: void TIFFOpenOptionsSetMaxCumulatedMemAlloc(TIFFOpenOptions* opts, tmsize_t max_cumulated_mem_alloc)
+
 .. c:function:: void TIFFOpenOptionsSetErrorHandlerExtR(TIFFOpenOptions* opts, TIFFErrorHandlerExtR handler, void* errorhandler_user_data)
 
 .. c:function:: void TIFFOpenOptionsSetWarningHandlerExtR(TIFFOpenOptions* opts, TIFFErrorHandlerExtR handler, void* warnhandler_user_data)
@@ -40,15 +42,24 @@ opaque structure and returns a :c:type:`TIFFOpenOptions` pointer.
 can be released straight after successful execution of the related
 TIFFOpen"Ext" functions like :c:func:`TIFFOpenExt`.
 
-:c:func:`TIFFOpenOptionsSetMaxSingleMemAlloc` sets parameter for the
-maximum single memory limit in byte that ``libtiff`` internal memory allocation
-functions are allowed to request per call.
+:c:func:`TIFFOpenOptionsSetMaxSingleMemAlloc` (added in libtiff 4.5.0) sets
+the value for the maximum single memory limit in byte that ``libtiff`` internal
+memory allocation functions are allowed to request per call.
 
 .. note::
     However, the ``libtiff`` external functions :c:func:`_TIFFmalloc`
     and :c:func:`_TIFFrealloc` **do not apply** this internal memory
     allocation limit set by :c:func:`TIFFOpenOptionsSetMaxSingleMemAlloc`!
 
+:c:func:`TIFFOpenOptionsSetMaxCumulatedMemAlloc` (added in libtiff 4.6.1) sets
+the maximum cumulated memory allocations in byte, for a given TIFF handle,
+that ``libtiff`` internal memory allocation functions are allowed.
+
+.. note::
+    However, the ``libtiff`` external functions :c:func:`_TIFFmalloc`
+    and :c:func:`_TIFFrealloc` **do not apply** this internal memory
+    allocation limit set by :c:func:`TIFFOpenOptionsSetMaxCumulatedMemAlloc`!
+
 :c:func:`TIFFOpenOptionsSetErrorHandlerExtR` sets the function pointer to
 an application-specific and per-TIFF handle (re-entrant) error handler.
 Furthermore, a pointer to a **custom defined data structure** *errorhandler_user_data* 
diff --git a/doc/functions/libtiff.rst b/doc/functions/libtiff.rst
index 2229ef32..bdf87973 100644
--- a/doc/functions/libtiff.rst
+++ b/doc/functions/libtiff.rst
@@ -329,6 +329,8 @@ will work.
       - releases the allocated memory for :c:type:`TIFFOpenOptions`
     * - :c:func:`TIFFOpenOptionsSetMaxSingleMemAlloc`
       - limits the maximum single memory allocation within ``libtiff``
+    * - :c:func:`TIFFOpenOptionsSetMaxCumulatedMemAlloc`
+      - limits the maximum cumulated memory allocation within ``libtiff``
     * - :c:func:`TIFFOpenOptionsSetErrorHandlerExtR`
       - setup of a user-specific and per-TIFF handle (re-entrant) error handler
     * - :c:func:`TIFFOpenOptionsSetWarningHandlerExtR`
diff --git a/doc/libtiff.rst b/doc/libtiff.rst
index 4fedc3ed..f61f6c2c 100644
--- a/doc/libtiff.rst
+++ b/doc/libtiff.rst
@@ -99,6 +99,11 @@ With ``libtiff`` 4.5 a method was introduced to limit the internal
 memory allocation that functions are allowed to request per call
 (see  :c:func:`TIFFOpenOptionsSetMaxSingleMemAlloc` and :c:func:`TIFFOpenExt`).
 
+With ``libtiff`` 4.6.1 a method was introduced to limit the internal
+cumulated memory allocation that functions are allowed to request for a given
+TIFF handle
+(see  :c:func:`TIFFOpenOptionsSetMaxCumulatedMemAlloc` and :c:func:`TIFFOpenExt`).
+
 Error Handling
 --------------
 
diff --git a/libtiff/CMakeLists.txt b/libtiff/CMakeLists.txt
index 1f36360f..a8aa0c32 100755
--- a/libtiff/CMakeLists.txt
+++ b/libtiff/CMakeLists.txt
@@ -102,10 +102,17 @@ if (CMAKE_VERSION VERSION_GREATER_EQUAL 3.16)
   set_property(SOURCE tif_jpeg.c tif_jpeg12.c PROPERTY SKIP_UNITY_BUILD_INCLUSION ON)
 endif ()
 
+# For all files (but tif_open.c, tif_unix.c and tif_win32.c), forbid the use
+# of _TIFFmalloc/_TIFFfree and require the use of their "Ext" versions
+target_compile_definitions(tiff PRIVATE TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS)
+set_property(SOURCE tif_open.c APPEND PROPERTY COMPILE_DEFINITIONS ALLOW_TIFF_NON_EXT_ALLOC_FUNCTIONS)
+
 if(USE_WIN32_FILEIO)
   target_sources(tiff PRIVATE tif_win32.c)
+  set_property(SOURCE tif_win32.c APPEND PROPERTY COMPILE_DEFINITIONS ALLOW_TIFF_NON_EXT_ALLOC_FUNCTIONS)
 else()
   target_sources(tiff PRIVATE tif_unix.c)
+  set_property(SOURCE tif_unix.c APPEND PROPERTY COMPILE_DEFINITIONS ALLOW_TIFF_NON_EXT_ALLOC_FUNCTIONS)
 endif()
 
 target_include_directories(tiff
diff --git a/libtiff/libtiff.def b/libtiff/libtiff.def
index 22cbb1aa..35d1d108 100644
--- a/libtiff/libtiff.def
+++ b/libtiff/libtiff.def
@@ -85,6 +85,7 @@ EXPORTS	TIFFAccessTagMethods
 	TIFFOpenWExt
 	TIFFOpenOptionsAlloc
 	TIFFOpenOptionsFree
+	TIFFOpenOptionsSetMaxCumulatedMemAlloc
 	TIFFOpenOptionsSetMaxSingleMemAlloc
 	TIFFOpenOptionsSetErrorHandlerExtR
 	TIFFOpenOptionsSetWarningHandlerExtR
diff --git a/libtiff/libtiff.map b/libtiff/libtiff.map
index 05da6d7e..3d15d9c7 100644
--- a/libtiff/libtiff.map
+++ b/libtiff/libtiff.map
@@ -214,3 +214,7 @@ LIBTIFF_4.5 {
     TIFFOpenOptionsSetErrorHandlerExtR;
     TIFFOpenOptionsSetWarningHandlerExtR;
 } LIBTIFF_4.4;
+
+LIBTIFF_4.6.1 {
+    TIFFOpenOptionsSetMaxCumulatedMemAlloc;
+} LIBTIFF_4.5;
diff --git a/libtiff/tif_close.c b/libtiff/tif_close.c
index 907d7f13..d6bb7f1d 100644
--- a/libtiff/tif_close.c
+++ b/libtiff/tif_close.c
@@ -110,6 +110,14 @@ void TIFFCleanup(TIFF *tif)
         _TIFFfreeExt(tif, tif->tif_fieldscompat);
     }
 
+    if (tif->tif_cur_cumulated_mem_alloc != 0)
+    {
+        TIFFErrorExtR(tif, "TIFFCleanup",
+                      "tif_cur_cumulated_mem_alloc = %" PRIu64 " whereas it "
+                      "should be 0",
+                      (uint64_t)tif->tif_cur_cumulated_mem_alloc);
+    }
+
     _TIFFfreeExt(NULL, tif);
 }
 
diff --git a/libtiff/tif_open.c b/libtiff/tif_open.c
index 23fcf81c..6ae178e1 100644
--- a/libtiff/tif_open.c
+++ b/libtiff/tif_open.c
@@ -25,7 +25,13 @@
 /*
  * TIFF Library.
  */
+
+#ifdef TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS
+#undef TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS
+#endif
+
 #include "tiffiop.h"
+#include <assert.h>
 #include <limits.h>
 
 /*
@@ -81,8 +87,9 @@ TIFFOpenOptions *TIFFOpenOptionsAlloc()
 void TIFFOpenOptionsFree(TIFFOpenOptions *opts) { _TIFFfree(opts); }
 
 /** Define a limit in bytes for a single memory allocation done by libtiff.
- *  If max_single_mem_alloc is set to 0, no other limit that the underlying
- *  _TIFFmalloc() will be applied, which is the default.
+ *  If max_single_mem_alloc is set to 0, which is the default, no other limit
+ *  that the underlying _TIFFmalloc() or
+ *  TIFFOpenOptionsSetMaxCumulatedMemAlloc() will be applied.
  */
 void TIFFOpenOptionsSetMaxSingleMemAlloc(TIFFOpenOptions *opts,
                                          tmsize_t max_single_mem_alloc)
@@ -90,6 +97,18 @@ void TIFFOpenOptionsSetMaxSingleMemAlloc(TIFFOpenOptions *opts,
     opts->max_single_mem_alloc = max_single_mem_alloc;
 }
 
+/** Define a limit in bytes for the cumulated memory allocations done by libtiff
+ *  on a given TIFF handle.
+ *  If max_cumulated_mem_alloc is set to 0, which is the default, no other limit
+ *  that the underlying _TIFFmalloc() or
+ *  TIFFOpenOptionsSetMaxSingleMemAlloc() will be applied.
+ */
+void TIFFOpenOptionsSetMaxCumulatedMemAlloc(TIFFOpenOptions *opts,
+                                            tmsize_t max_cumulated_mem_alloc)
+{
+    opts->max_cumulated_mem_alloc = max_cumulated_mem_alloc;
+}
+
 void TIFFOpenOptionsSetErrorHandlerExtR(TIFFOpenOptions *opts,
                                         TIFFErrorHandlerExtR handler,
                                         void *errorhandler_user_data)
@@ -117,6 +136,30 @@ static void _TIFFEmitErrorAboveMaxSingleMemAlloc(TIFF *tif,
                   (uint64_t)s, (uint64_t)tif->tif_max_single_mem_alloc);
 }
 
+static void _TIFFEmitErrorAboveMaxCumulatedMemAlloc(TIFF *tif,
+                                                    const char *pszFunction,
+                                                    tmsize_t s)
+{
+    TIFFErrorExtR(tif, pszFunction,
+                  "Cumulated memory allocation of %" PRIu64 " + %" PRIu64
+                  " bytes is beyond the %" PRIu64
+                  " cumulated byte limit defined in open options",
+                  (uint64_t)tif->tif_cur_cumulated_mem_alloc, (uint64_t)s,
+                  (uint64_t)tif->tif_max_cumulated_mem_alloc);
+}
+
+/* When allocating memory, we write at the beginning of the buffer it size.
+ * This allows us to keep track of the total memory allocated when we
+ * malloc/calloc/realloc and free. In theory we need just SIZEOF_SIZE_T bytes
+ * for that, but on x86_64, allocations of more than 16 bytes are aligned on
+ * 16 bytes. Hence using 2 * SIZEOF_SIZE_T.
+ * It is critical that _TIFFmallocExt/_TIFFcallocExt/_TIFFreallocExt are
+ * paired with _TIFFfreeExt.
+ * CMakeLists.txt defines TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS, which in
+ * turn disables the definition of the non Ext version in tiffio.h
+ */
+#define LEADING_AREA_TO_STORE_ALLOC_SIZE (2 * SIZEOF_SIZE_T)
+
 /** malloc() version that takes into account memory-specific open options */
 void *_TIFFmallocExt(TIFF *tif, tmsize_t s)
 {
@@ -126,16 +169,32 @@ void *_TIFFmallocExt(TIFF *tif, tmsize_t s)
         _TIFFEmitErrorAboveMaxSingleMemAlloc(tif, "_TIFFmallocExt", s);
         return NULL;
     }
+    if (tif != NULL && tif->tif_max_cumulated_mem_alloc > 0)
+    {
+        if (s > tif->tif_max_cumulated_mem_alloc -
+                    tif->tif_cur_cumulated_mem_alloc ||
+            s > TIFF_TMSIZE_T_MAX - LEADING_AREA_TO_STORE_ALLOC_SIZE)
+        {
+            _TIFFEmitErrorAboveMaxCumulatedMemAlloc(tif, "_TIFFmallocExt", s);
+            return NULL;
+        }
+        void *ptr = _TIFFmalloc(LEADING_AREA_TO_STORE_ALLOC_SIZE + s);
+        if (!ptr)
+            return NULL;
+        tif->tif_cur_cumulated_mem_alloc += s;
+        memcpy(ptr, &s, sizeof(s));
+        return (char *)ptr + LEADING_AREA_TO_STORE_ALLOC_SIZE;
+    }
     return _TIFFmalloc(s);
 }
 
 /** calloc() version that takes into account memory-specific open options */
 void *_TIFFcallocExt(TIFF *tif, tmsize_t nmemb, tmsize_t siz)
 {
+    if (nmemb <= 0 || siz <= 0 || nmemb > TIFF_TMSIZE_T_MAX / siz)
+        return NULL;
     if (tif != NULL && tif->tif_max_single_mem_alloc > 0)
     {
-        if (nmemb <= 0 || siz <= 0 || nmemb > TIFF_TMSIZE_T_MAX / siz)
-            return NULL;
         if (nmemb * siz > tif->tif_max_single_mem_alloc)
         {
             _TIFFEmitErrorAboveMaxSingleMemAlloc(tif, "_TIFFcallocExt",
@@ -143,6 +202,23 @@ void *_TIFFcallocExt(TIFF *tif, tmsize_t nmemb, tmsize_t siz)
             return NULL;
         }
     }
+    if (tif != NULL && tif->tif_max_cumulated_mem_alloc > 0)
+    {
+        const tmsize_t s = nmemb * siz;
+        if (s > tif->tif_max_cumulated_mem_alloc -
+                    tif->tif_cur_cumulated_mem_alloc ||
+            s > TIFF_TMSIZE_T_MAX - LEADING_AREA_TO_STORE_ALLOC_SIZE)
+        {
+            _TIFFEmitErrorAboveMaxCumulatedMemAlloc(tif, "_TIFFcallocExt", s);
+            return NULL;
+        }
+        void *ptr = _TIFFcalloc(LEADING_AREA_TO_STORE_ALLOC_SIZE + s, 1);
+        if (!ptr)
+            return NULL;
+        tif->tif_cur_cumulated_mem_alloc += s;
+        memcpy(ptr, &s, sizeof(s));
+        return (char *)ptr + LEADING_AREA_TO_STORE_ALLOC_SIZE;
+    }
     return _TIFFcalloc(nmemb, siz);
 }
 
@@ -155,13 +231,49 @@ void *_TIFFreallocExt(TIFF *tif, void *p, tmsize_t s)
         _TIFFEmitErrorAboveMaxSingleMemAlloc(tif, "_TIFFreallocExt", s);
         return NULL;
     }
+    if (tif != NULL && tif->tif_max_cumulated_mem_alloc > 0)
+    {
+        void *oldPtr = p;
+        tmsize_t oldSize = 0;
+        if (p)
+        {
+            oldPtr = (char *)p - LEADING_AREA_TO_STORE_ALLOC_SIZE;
+            memcpy(&oldSize, oldPtr, sizeof(oldSize));
+            assert(oldSize <= tif->tif_cur_cumulated_mem_alloc);
+        }
+        if (s > oldSize &&
+            (s > tif->tif_max_cumulated_mem_alloc -
+                     (tif->tif_cur_cumulated_mem_alloc - oldSize) ||
+             s > TIFF_TMSIZE_T_MAX - LEADING_AREA_TO_STORE_ALLOC_SIZE))
+        {
+            _TIFFEmitErrorAboveMaxCumulatedMemAlloc(tif, "_TIFFreallocExt",
+                                                    s - oldSize);
+            return NULL;
+        }
+        void *newPtr =
+            _TIFFrealloc(oldPtr, LEADING_AREA_TO_STORE_ALLOC_SIZE + s);
+        if (newPtr == NULL)
+            return NULL;
+        tif->tif_cur_cumulated_mem_alloc -= oldSize;
+        tif->tif_cur_cumulated_mem_alloc += s;
+        memcpy(newPtr, &s, sizeof(s));
+        return (char *)newPtr + LEADING_AREA_TO_STORE_ALLOC_SIZE;
+    }
     return _TIFFrealloc(p, s);
 }
 
 /** free() version that takes into account memory-specific open options */
 void _TIFFfreeExt(TIFF *tif, void *p)
 {
-    (void)tif;
+    if (p != NULL && tif != NULL && tif->tif_max_cumulated_mem_alloc > 0)
+    {
+        void *oldPtr = (char *)p - LEADING_AREA_TO_STORE_ALLOC_SIZE;
+        tmsize_t oldSize;
+        memcpy(&oldSize, oldPtr, sizeof(oldSize));
+        assert(oldSize <= tif->tif_cur_cumulated_mem_alloc);
+        tif->tif_cur_cumulated_mem_alloc -= oldSize;
+        p = oldPtr;
+    }
     _TIFFfree(p);
 }
 
@@ -231,6 +343,17 @@ TIFF *TIFFClientOpenExt(const char *name, const char *mode,
                         (uint64_t)opts->max_single_mem_alloc);
         goto bad2;
     }
+    if (opts && opts->max_cumulated_mem_alloc > 0 &&
+        size_to_alloc > opts->max_cumulated_mem_alloc)
+    {
+        _TIFFErrorEarly(opts, clientdata, module,
+                        "%s: Memory allocation of %" PRIu64
+                        " bytes is beyond the %" PRIu64
+                        " cumulated byte limit defined in open options",
+                        name, (uint64_t)size_to_alloc,
+                        (uint64_t)opts->max_cumulated_mem_alloc);
+        goto bad2;
+    }
     tif = (TIFF *)_TIFFmallocExt(NULL, size_to_alloc);
     if (tif == NULL)
     {
@@ -261,6 +384,7 @@ TIFF *TIFFClientOpenExt(const char *name, const char *mode,
         tif->tif_warnhandler = opts->warnhandler;
         tif->tif_warnhandler_user_data = opts->warnhandler_user_data;
         tif->tif_max_single_mem_alloc = opts->max_single_mem_alloc;
+        tif->tif_max_cumulated_mem_alloc = opts->max_cumulated_mem_alloc;
     }
 
     if (!readproc || !writeproc || !seekproc || !closeproc || !sizeproc)
diff --git a/libtiff/tif_unix.c b/libtiff/tif_unix.c
index 34dd53b9..2cfbbe8d 100644
--- a/libtiff/tif_unix.c
+++ b/libtiff/tif_unix.c
@@ -27,6 +27,10 @@
  * Windows Common RunTime Library.
  */
 
+#ifdef TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS
+#undef TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS
+#endif
+
 #include "tif_config.h"
 
 #ifdef HAVE_SYS_TYPES_H
diff --git a/libtiff/tif_win32.c b/libtiff/tif_win32.c
index 1a6b86df..d64ba49f 100644
--- a/libtiff/tif_win32.c
+++ b/libtiff/tif_win32.c
@@ -27,6 +27,10 @@
  * Scott Wagner (wagner@itek.com), Itek Graphix, Rochester, NY USA
  */
 
+#ifdef TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS
+#undef TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS
+#endif
+
 #include "tiffiop.h"
 #include <stdlib.h>
 
diff --git a/libtiff/tiffio.h b/libtiff/tiffio.h
index 20460542..26e1d089 100644
--- a/libtiff/tiffio.h
+++ b/libtiff/tiffio.h
@@ -311,14 +311,15 @@ extern "C"
     /*
      * Auxiliary functions.
      */
-
+#ifndef TIFF_DO_NOT_USE_NON_EXT_ALLOC_FUNCTIONS
     extern void *_TIFFmalloc(tmsize_t s);
     extern void *_TIFFcalloc(tmsize_t nmemb, tmsize_t siz);
     extern void *_TIFFrealloc(void *p, tmsize_t s);
+    extern void _TIFFfree(void *p);
+#endif
     extern void _TIFFmemset(void *p, int v, tmsize_t c);
     extern void _TIFFmemcpy(void *d, const void *s, tmsize_t c);
     extern int _TIFFmemcmp(const void *p1, const void *p2, tmsize_t c);
-    extern void _TIFFfree(void *p);
 
     /*
     ** Stuff, related to tag handling and creating custom tags.
@@ -508,6 +509,9 @@ extern int TIFFReadRGBAImageOriented(TIFF *, uint32_t, uint32_t, uint32_t *,
     TIFFOpenOptionsSetMaxSingleMemAlloc(TIFFOpenOptions *opts,
                                         tmsize_t max_single_mem_alloc);
     extern void
+    TIFFOpenOptionsSetMaxCumulatedMemAlloc(TIFFOpenOptions *opts,
+                                           tmsize_t max_cumulated_mem_alloc);
+    extern void
     TIFFOpenOptionsSetErrorHandlerExtR(TIFFOpenOptions *opts,
                                        TIFFErrorHandlerExtR handler,
                                        void *errorhandler_user_data);
diff --git a/libtiff/tiffiop.h b/libtiff/tiffiop.h
index fbf7b070..da6a0f91 100644
--- a/libtiff/tiffiop.h
+++ b/libtiff/tiffiop.h
@@ -233,7 +233,9 @@ struct tiff
     void *tif_errorhandler_user_data;
     TIFFErrorHandlerExtR tif_warnhandler;
     void *tif_warnhandler_user_data;
-    tmsize_t tif_max_single_mem_alloc; /* in bytes. 0 for unlimited */
+    tmsize_t tif_max_single_mem_alloc;    /* in bytes. 0 for unlimited */
+    tmsize_t tif_max_cumulated_mem_alloc; /* in bytes. 0 for unlimited */
+    tmsize_t tif_cur_cumulated_mem_alloc; /* in bytes */
 };
 
 struct TIFFOpenOptions
@@ -243,6 +245,7 @@ struct TIFFOpenOptions
     TIFFErrorHandlerExtR warnhandler;  /* may be NULL */
     void *warnhandler_user_data;       /* may be NULL */
     tmsize_t max_single_mem_alloc;     /* in bytes. 0 for unlimited */
+    tmsize_t max_cumulated_mem_alloc;  /* in bytes. 0 for unlimited */
 };
 
 #define isPseudoTag(t) (t > 0xffff) /* is tag value normal or pseudo */
diff --git a/test/test_open_options.c b/test/test_open_options.c
index e7608a19..4a3e0a22 100644
--- a/test/test_open_options.c
+++ b/test/test_open_options.c
@@ -214,6 +214,63 @@ static int test_TIFFOpenOptionsSetMaxSingleMemAlloc(
     return ret;
 }
 
+static int test_TIFFOpenOptionsSetMaxCumulatedMemAlloc(
+    tmsize_t limit, int expected_to_fail_in_open,
+    int expected_to_fail_in_write_directory)
+{
+    int ret = 0;
+    TIFFOpenOptions *opts = TIFFOpenOptionsAlloc();
+    assert(opts);
+    TIFFOpenOptionsSetMaxCumulatedMemAlloc(opts, limit);
+    TIFF *tif = TIFFOpenExt("test_error_handler.tif", "w", opts);
+    TIFFOpenOptionsFree(opts);
+    if (expected_to_fail_in_open)
+    {
+        if (tif != NULL)
+        {
+            fprintf(
+                stderr,
+                "Expected TIFFOpenExt() to fail due to memory limitation\n");
+            ret = 1;
+            TIFFClose(tif);
+        }
+    }
+    else
+    {
+        if (tif == NULL)
+        {
+            fprintf(stderr, "Expected TIFFOpenExt() to succeed\n");
+            ret = 1;
+        }
+        else
+        {
+#define VALUE_SAMPLESPERPIXEL 10000
+            TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, VALUE_SAMPLESPERPIXEL);
+            TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, 8);
+            if (TIFFWriteDirectory(tif) == 0)
+            {
+                if (!expected_to_fail_in_write_directory)
+                {
+                    fprintf(stderr,
+                            "Expected TIFFWriteDirectory() to succeed\n");
+                    ret = 1;
+                }
+            }
+            else
+            {
+                if (expected_to_fail_in_write_directory)
+                {
+                    fprintf(stderr, "Expected TIFFWriteDirectory() to fail\n");
+                    ret = 1;
+                }
+            }
+            TIFFClose(tif);
+        }
+    }
+    unlink("test_error_handler.tif");
+    return ret;
+}
+
 int main()
 {
     int ret = 0;
@@ -234,5 +291,14 @@ int main()
         sizeof(TIFF) + strlen("test_error_handler.tif") + 1, FALSE, TRUE, TRUE);
     ret += test_TIFFOpenOptionsSetMaxSingleMemAlloc(
         VALUE_SAMPLESPERPIXEL * sizeof(short), FALSE, FALSE, TRUE);
+
+    fprintf(stderr, "---- test_TIFFOpenOptionsSetMaxCumulatedMemAlloc ---- \n");
+    ret += test_TIFFOpenOptionsSetMaxCumulatedMemAlloc(1, TRUE, -1);
+    ret += test_TIFFOpenOptionsSetMaxCumulatedMemAlloc(
+        sizeof(TIFF) + strlen("test_error_handler.tif") + 1, FALSE, TRUE);
+    ret += test_TIFFOpenOptionsSetMaxCumulatedMemAlloc(
+        sizeof(TIFF) + strlen("test_error_handler.tif") + 1 + 30000, FALSE,
+        FALSE);
+
     return ret;
 }