CVE-2020-15503: Improper Input Validation (CWE-20)
value: LibRaw before 0.20-RC1 lacks a thumbnail size range check. This affects decoders/unpack_thumb.cpp, postprocessing/mem_image.cpp, and utils/thumb_utils.cpp. For example, malloc(sizeof(libraw_processed_image_t)+T.tlength) occurs without validating T.tlength.
CWE description: The product receives input or data, but it does
        not validate or incorrectly validates that the input has the
        properties that are required to process the data safely and
        correctly.
CVSS v3 base score: 7.5
Published: 2020-07-02T14:15Z
Repository: https://github.com/LibRaw/LibRaw
Language: C++
Files changed: libraw/libraw_const.h, src/decoders/unpack_thumb.cpp, src/postprocessing/mem_image.cpp, src/utils/thumb_utils.cpp
Commit date: 2020-06-22 19:49:28 +0300
Commit message: Thumbnail size range check

Diff: diff --git a/libraw/libraw_const.h b/libraw/libraw_const.h
index aaeba8b1..9327d87a 100644
--- a/libraw/libraw_const.h
+++ b/libraw/libraw_const.h
@@ -18,11 +18,18 @@ it under the terms of the one of two licenses as you choose:
 #define _LIBRAW_ERRORS_H
 
 #define LIBRAW_DEFAULT_ADJUST_MAXIMUM_THRESHOLD 0.75
 #define LIBRAW_DEFAULT_AUTO_BRIGHTNESS_THRESHOLD 0.01
 /* limit allocation size, default is 2Gb */
+#ifndef LIBRAW_MAX_ALLOC_MB_DEFAULT
 #define LIBRAW_MAX_ALLOC_MB_DEFAULT 2048L
+#endif
+
+/* limit thumbnail size, default is 512Mb*/
+#ifndef LIBRAW_MAX_THUMBNAIL_MB
+#define LIBRAW_MAX_THUMBNAIL_MB 512L
+#endif
 
 /* Check if enough file space exists before tag read */
 #ifndef LIBRAW_NO_IOSPACE_CHECK
 #define LIBRAW_IOSPACE_CHECK
 #endif
diff --git a/src/decoders/unpack_thumb.cpp b/src/decoders/unpack_thumb.cpp
index fa4e2bfc..24ccff3e 100644
--- a/src/decoders/unpack_thumb.cpp
+++ b/src/decoders/unpack_thumb.cpp
@@ -31,10 +31,29 @@ static void jpegErrorExit(j_common_ptr cinfo)
 int LibRaw::unpack_thumb(void)
 {
   CHECK_ORDER_LOW(LIBRAW_PROGRESS_IDENTIFY);
   CHECK_ORDER_BIT(LIBRAW_PROGRESS_THUMB_LOAD);
 
+#define THUMB_SIZE_CHECKT(A) \
+  do { \
+    if (INT64(A) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB) throw LIBRAW_EXCEPTION_IO_CORRUPT; \
+    if (INT64(A) > 0 &&  INT64(A) < 64ULL)        throw LIBRAW_EXCEPTION_IO_CORRUPT; \
+  } while (0)
+
+#define THUMB_SIZE_CHECKTNZ(A) \
+  do { \
+    if (INT64(A) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB) throw LIBRAW_EXCEPTION_IO_CORRUPT; \
+    if (INT64(A) < 64ULL)        throw LIBRAW_EXCEPTION_IO_CORRUPT; \
+  } while (0)
+
+
+#define THUMB_SIZE_CHECKWH(W,H) \
+  do { \
+    if (INT64(W)*INT64(H) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB) throw LIBRAW_EXCEPTION_IO_CORRUPT; \
+    if (INT64(W)*INT64(H) < 64ULL)        throw LIBRAW_EXCEPTION_IO_CORRUPT; \
+  } while (0)
+
   try
   {
     if (!libraw_internal_data.internal_data.input)
       return LIBRAW_INPUT_CLOSED;
 
@@ -63,10 +82,11 @@ int LibRaw::unpack_thumb(void)
         if (tsize < 2048 || INT64(ID.toffset) + tsize < 1)
           throw LIBRAW_EXCEPTION_IO_CORRUPT;
 
         if (INT64(ID.toffset) + tsize > ID.input->size() + THUMB_READ_BEYOND)
           throw LIBRAW_EXCEPTION_IO_EOF;
+        THUMB_SIZE_CHECKT(tsize);
       }
 #else
 	if (0) {}
 #endif
       else
@@ -80,10 +100,12 @@ int LibRaw::unpack_thumb(void)
       }
 
       ID.input->seek(ID.toffset, SEEK_SET);
       if (write_thumb == &LibRaw::jpeg_thumb)
       {
+        THUMB_SIZE_CHECKTNZ(T.tlength);
+
         if (T.thumb)
           free(T.thumb);
         T.thumb = (char *)malloc(T.tlength);
         merror(T.thumb, "jpeg_thumb()");
         ID.input->read(T.thumb, 1, T.tlength);
@@ -130,10 +152,12 @@ int LibRaw::unpack_thumb(void)
       {
         int colors = libraw_internal_data.unpacker_data.thumb_misc >> 5 & 7;
         if (colors != 1 && colors != 3)
           return LIBRAW_UNSUPPORTED_THUMBNAIL;
 
+        THUMB_SIZE_CHECKWH(T.twidth, T.theight);
+
         int tlength = T.twidth * T.theight;
         if (T.thumb)
           free(T.thumb);
         T.thumb = (char *)calloc(colors, tlength);
         merror(T.thumb, "layer_thumb()");
@@ -170,10 +194,11 @@ int LibRaw::unpack_thumb(void)
         return 0;
       }
       else if (write_thumb == &LibRaw::rollei_thumb)
       {
         int i;
+        THUMB_SIZE_CHECKWH(T.twidth, T.theight);
         int tlength = T.twidth * T.theight;
         if (T.thumb)
           free(T.thumb);
         T.tcolors = 3;
         T.thumb = (char *)calloc(T.tcolors, tlength);
@@ -196,10 +221,11 @@ int LibRaw::unpack_thumb(void)
       else if (write_thumb == &LibRaw::ppm_thumb)
       {
         if (t_bytesps > 1)
           throw LIBRAW_EXCEPTION_IO_CORRUPT; // 8-bit thumb, but parsed for more
                                              // bits
+        THUMB_SIZE_CHECKWH(T.twidth, T.theight);
         int t_length = T.twidth * T.theight * t_colors;
 
         if (T.tlength &&
             (int)T.tlength < t_length) // try to find tiff ifd with needed offset
         {
@@ -217,10 +243,11 @@ int LibRaw::unpack_thumb(void)
                 T.tcolors = 3;
               else if (total_size == T.twidth * T.tlength)
                 T.tcolors = 1;
             }
             T.tlength = total_size;
+            THUMB_SIZE_CHECKTNZ(T.tlength);
             if (T.thumb)
               free(T.thumb);
             T.thumb = (char *)malloc(T.tlength);
             merror(T.thumb, "ppm_thumb()");
 
@@ -252,10 +279,12 @@ int LibRaw::unpack_thumb(void)
         if (!T.tlength)
           T.tlength = t_length;
         if (T.thumb)
           free(T.thumb);
 
+        THUMB_SIZE_CHECKTNZ(T.tlength);
+
         T.thumb = (char *)malloc(T.tlength);
         if (!T.tcolors)
           T.tcolors = t_colors;
         merror(T.thumb, "ppm_thumb()");
 
@@ -276,10 +305,14 @@ int LibRaw::unpack_thumb(void)
                         : 1;
         int o_length = T.twidth * T.theight * t_colors * o_bps;
         int i_length = T.twidth * T.theight * t_colors * 2;
         if (!T.tlength)
           T.tlength = o_length;
+        THUMB_SIZE_CHECKTNZ(o_length);
+        THUMB_SIZE_CHECKTNZ(i_length);
+        THUMB_SIZE_CHECKTNZ(T.tlength);
+
         ushort *t_thumb = (ushort *)calloc(i_length, 1);
         ID.input->read(t_thumb, 1, i_length);
         if ((libraw_internal_data.unpacker_data.order == 0x4949) ==
             (ntohs(0x1234) == 0x1234))
           swab((char *)t_thumb, (char *)t_thumb, i_length);
diff --git a/src/postprocessing/mem_image.cpp b/src/postprocessing/mem_image.cpp
index e79c9217..4d2a8781 100644
--- a/src/postprocessing/mem_image.cpp
+++ b/src/postprocessing/mem_image.cpp
@@ -31,10 +31,24 @@ libraw_processed_image_t *LibRaw::dcraw_make_mem_thumb(int *errcode)
         *errcode = LIBRAW_OUT_OF_ORDER_CALL;
     }
     return NULL;
   }
 
+  if (T.tlength < 64u)
+  {
+      if (errcode)
+          *errcode = EINVAL;
+      return NULL;
+  }
+
+  if (INT64(T.tlength) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB)
+  {
+      if (errcode)
+          *errcode = LIBRAW_TOO_BIG;
+      return NULL;
+  }
+
   if (T.tformat == LIBRAW_THUMBNAIL_BITMAP)
   {
     libraw_processed_image_t *ret = (libraw_processed_image_t *)::malloc(
         sizeof(libraw_processed_image_t) + T.tlength);
 
diff --git a/src/utils/thumb_utils.cpp b/src/utils/thumb_utils.cpp
index bf6d9bc5..418ba2de 100644
--- a/src/utils/thumb_utils.cpp
+++ b/src/utils/thumb_utils.cpp
@@ -23,10 +23,16 @@ void LibRaw::kodak_thumb_loader()
     throw LIBRAW_EXCEPTION_IO_CORRUPT;
 
   if (ID.toffset + est_datasize > ID.input->size() + THUMB_READ_BEYOND)
     throw LIBRAW_EXCEPTION_IO_EOF;
 
+  if(INT64(T.theight) * INT64(T.twidth) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB)
+      throw LIBRAW_EXCEPTION_IO_CORRUPT;
+
+  if (INT64(T.theight) * INT64(T.twidth) < 64ULL)
+      throw LIBRAW_EXCEPTION_IO_CORRUPT;
+
   // some kodak cameras
   ushort s_height = S.height, s_width = S.width, s_iwidth = S.iwidth,
          s_iheight = S.iheight;
   ushort s_flags = libraw_internal_data.unpacker_data.load_flags;
   libraw_internal_data.unpacker_data.load_flags = 12;

Fixed code: #ifndef LIBRAW_MAX_ALLOC_MB_DEFAULT
#endif

/* limit thumbnail size, default is 512Mb*/
#ifndef LIBRAW_MAX_THUMBNAIL_MB
#define LIBRAW_MAX_THUMBNAIL_MB 512L
#endif
#define THUMB_SIZE_CHECKT(A) \
  do { \
    if (INT64(A) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB) throw LIBRAW_EXCEPTION_IO_CORRUPT; \
    if (INT64(A) > 0 &&  INT64(A) < 64ULL)        throw LIBRAW_EXCEPTION_IO_CORRUPT; \
  } while (0)

#define THUMB_SIZE_CHECKTNZ(A) \
  do { \
    if (INT64(A) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB) throw LIBRAW_EXCEPTION_IO_CORRUPT; \
    if (INT64(A) < 64ULL)        throw LIBRAW_EXCEPTION_IO_CORRUPT; \
  } while (0)


#define THUMB_SIZE_CHECKWH(W,H) \
  do { \
    if (INT64(W)*INT64(H) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB) throw LIBRAW_EXCEPTION_IO_CORRUPT; \
    if (INT64(W)*INT64(H) < 64ULL)        throw LIBRAW_EXCEPTION_IO_CORRUPT; \
  } while (0)

        THUMB_SIZE_CHECKT(tsize);
        THUMB_SIZE_CHECKTNZ(T.tlength);

        THUMB_SIZE_CHECKWH(T.twidth, T.theight);

        THUMB_SIZE_CHECKWH(T.twidth, T.theight);
        THUMB_SIZE_CHECKWH(T.twidth, T.theight);
            THUMB_SIZE_CHECKTNZ(T.tlength);
        THUMB_SIZE_CHECKTNZ(T.tlength);

        THUMB_SIZE_CHECKTNZ(o_length);
        THUMB_SIZE_CHECKTNZ(i_length);
        THUMB_SIZE_CHECKTNZ(T.tlength);

  if (T.tlength < 64u)
  {
      if (errcode)
          *errcode = EINVAL;
      return NULL;
  }

  if (INT64(T.tlength) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB)
  {
      if (errcode)
          *errcode = LIBRAW_TOO_BIG;
      return NULL;
  }

  if(INT64(T.theight) * INT64(T.twidth) > 1024ULL * 1024ULL * LIBRAW_MAX_THUMBNAIL_MB)
      throw LIBRAW_EXCEPTION_IO_CORRUPT;

  if (INT64(T.theight) * INT64(T.twidth) < 64ULL)
      throw LIBRAW_EXCEPTION_IO_CORRUPT;