CVE-2023-25667: Integer Overflow or Wraparound (CWE-190)
value: TensorFlow is an open source platform for machine learning. Prior to versions 2.12.0 and 2.11.1, integer overflow occurs when `2^31 <= num_frames * height * width * channels < 2^32`, for example Full HD screencast of at least 346 frames. A fix is included in TensorFlow version 2.12.0 and version 2.11.1.

CWE description: The product performs a calculation that can
         produce an integer overflow or wraparound when the logic
         assumes that the resulting value will always be larger than
         the original value. This occurs when an integer value is
         incremented to a value that is too large to store in the
         associated representation. When this occurs, the value may
         become a very small or negative number.
CVSS v3 base score: 7.5
Published: 2023-03-25T00:15Z
Repository: https://github.com/tensorflow/tensorflow
Language: C++
Files changed: tensorflow/core/kernels/image/decode_image_op.cc, tensorflow/core/lib/gif/gif_io.cc, tensorflow/core/lib/gif/gif_io_test.cc, tensorflow/core/lib/gif/testdata/BUILD
Commit date: 2022-12-27 21:04:13 +0300
Commit message: Fix integer overflow for multiframe gifs.

Diff: diff --git a/tensorflow/core/kernels/image/decode_image_op.cc b/tensorflow/core/kernels/image/decode_image_op.cc
index 91137f8343c83..baa4407fd84dd 100644
--- a/tensorflow/core/kernels/image/decode_image_op.cc
+++ b/tensorflow/core/kernels/image/decode_image_op.cc
@@ -450,16 +450,16 @@ class DecodeImageV2Op : public OpKernel {
 
     // Decode GIF, allocating tensor if dtype is uint8, otherwise defer tensor
     // allocation til after dtype conversion is done. `gif`::Decode` supports
     // uint8 only.
     Tensor* output = nullptr;
-    int buffer_size = 0;
+    ptrdiff_t buffer_size = 0;
     string error_string;
     uint8* buffer = gif::Decode(
         input.data(), input.size(),
         [&](int num_frames, int width, int height, int channels) -> uint8* {
-          buffer_size = num_frames * height * width * channels;
+          buffer_size = ptrdiff_t(num_frames) * height * width * channels;
 
           Status status;
           // By the existing API, we support decoding GIF with `decode_jpeg` or
           // with `decode_png` if the GIF is a single-frame GIF (non-animated).
           // We need to make sure to return 3-D shapes when using in this case.
diff --git a/tensorflow/core/lib/gif/gif_io.cc b/tensorflow/core/lib/gif/gif_io.cc
index ba4aa1156db83..dddb8708c5e02 100644
--- a/tensorflow/core/lib/gif/gif_io.cc
+++ b/tensorflow/core/lib/gif/gif_io.cc
@@ -103,11 +103,11 @@ uint8* Decode(const void* srcdata, int datasize,
   if (!expand_animations) target_num_frames = 1;
 
   uint8* const dstdata =
       allocate_output(target_num_frames, width, height, channel);
   if (!dstdata) return nullptr;
-  for (int k = 0; k < target_num_frames; k++) {
+  for (ptrdiff_t k = 0; k < target_num_frames; k++) {
     uint8* this_dst = dstdata + k * width * channel * height;
 
     SavedImage* this_image = &gif_file->SavedImages[k];
     GifImageDesc* img_desc = &this_image->ImageDesc;
 
@@ -123,14 +123,14 @@ uint8* Decode(const void* srcdata, int datasize,
     int imgRight = img_desc->Left + img_desc->Width;
     int imgBottom = img_desc->Top + img_desc->Height;
 
     if (k > 0) {
       uint8* last_dst = dstdata + (k - 1) * width * channel * height;
-      for (int i = 0; i < height; ++i) {
+      for (ptrdiff_t i = 0; i < height; ++i) {
         uint8* p_dst = this_dst + i * width * channel;
         uint8* l_dst = last_dst + i * width * channel;
-        for (int j = 0; j < width; ++j) {
+        for (ptrdiff_t j = 0; j < width; ++j) {
           p_dst[j * channel + 0] = l_dst[j * channel + 0];
           p_dst[j * channel + 1] = l_dst[j * channel + 1];
           p_dst[j * channel + 2] = l_dst[j * channel + 2];
         }
       }
@@ -139,13 +139,13 @@ uint8* Decode(const void* srcdata, int datasize,
     if (img_desc->Left != 0 || img_desc->Top != 0 || img_desc->Width != width ||
         img_desc->Height != height) {
       // If the first frame does not fill the entire canvas then fill the
       // unoccupied canvas with zeros (black).
       if (k == 0) {
-        for (int i = 0; i < height; ++i) {
+        for (ptrdiff_t i = 0; i < height; ++i) {
           uint8* p_dst = this_dst + i * width * channel;
-          for (int j = 0; j < width; ++j) {
+          for (ptrdiff_t j = 0; j < width; ++j) {
             p_dst[j * channel + 0] = 0;
             p_dst[j * channel + 1] = 0;
             p_dst[j * channel + 2] = 0;
           }
         }
@@ -163,13 +163,13 @@ uint8* Decode(const void* srcdata, int datasize,
     if (color_map == nullptr) {
       *error_string = absl::StrCat("missing color map for frame ", k);
       return nullptr;
     }
 
-    for (int i = imgTop; i < imgBottom; ++i) {
+    for (ptrdiff_t i = imgTop; i < imgBottom; ++i) {
       uint8* p_dst = this_dst + i * width * channel;
-      for (int j = imgLeft; j < imgRight; ++j) {
+      for (ptrdiff_t j = imgLeft; j < imgRight; ++j) {
         GifByteType color_index =
             this_image->RasterBits[(i - img_desc->Top) * (img_desc->Width) +
                                    (j - img_desc->Left)];
 
         if (color_index >= color_map->ColorCount) {
diff --git a/tensorflow/core/lib/gif/gif_io_test.cc b/tensorflow/core/lib/gif/gif_io_test.cc
index 38c181911697c..32e6348072066 100644
--- a/tensorflow/core/lib/gif/gif_io_test.cc
+++ b/tensorflow/core/lib/gif/gif_io_test.cc
@@ -50,11 +50,11 @@ void TestDecodeGif(Env* env, DecodeGifTestCase testcase) {
       [&](int frame_cnt, int width, int height, int channels) -> uint8* {
         nframes = frame_cnt;
         w = width;
         h = height;
         c = channels;
-        return new uint8[frame_cnt * height * width * channels];
+        return new uint8[ptrdiff_t(frame_cnt) * height * width * channels];
       },
       &error_string));
   ASSERT_NE(imgdata, nullptr);
   // Make sure the decoded information matches the ground-truth image info.
   ASSERT_EQ(nframes, testcase.num_frames);
@@ -70,11 +70,12 @@ TEST(GifTest, Gif) {
       {// file_path, num_of_channels, width, height, channels
        {testdata_path + "lena.gif", 1, 51, 26, 3},
        {testdata_path + "optimized.gif", 12, 20, 40, 3},
        {testdata_path + "red_black.gif", 1, 16, 16, 3},
        {testdata_path + "scan.gif", 12, 20, 40, 3},
-       {testdata_path + "squares.gif", 2, 16, 16, 3}});
+       {testdata_path + "squares.gif", 2, 16, 16, 3},
+       {testdata_path + "3g_multiframe.gif", 519, 1920, 1080, 3}});
 
   for (const auto& tc : testcases) {
     TestDecodeGif(env, tc);
   }
 }
diff --git a/tensorflow/core/lib/gif/testdata/3g_multiframe.gif b/tensorflow/core/lib/gif/testdata/3g_multiframe.gif
new file mode 100644
index 0000000000000..f9286dc85c008
Binary files /dev/null and b/tensorflow/core/lib/gif/testdata/3g_multiframe.gif differ
diff --git a/tensorflow/core/lib/gif/testdata/BUILD b/tensorflow/core/lib/gif/testdata/BUILD
index 59459a19facab..1d7e74af90bde 100644
--- a/tensorflow/core/lib/gif/testdata/BUILD
+++ b/tensorflow/core/lib/gif/testdata/BUILD
@@ -14,10 +14,11 @@ filegroup(
         # GIF data
         "lena.gif",
         "scan.gif",
         "red_black.gif",
         "squares.gif",
+        "3g_multiframe.gif",
         "pendulum_sm.gif",
         # Add groundtruth frames for `pendulum_sm.gif`.
         # PNG format because it's lossless.
         "pendulum_sm_frame0.png",
         "pendulum_sm_frame1.png",

Vulnerable code:     int buffer_size = 0;
          buffer_size = num_frames * height * width * channels;
  for (int k = 0; k < target_num_frames; k++) {
      for (int i = 0; i < height; ++i) {
        for (int j = 0; j < width; ++j) {
        for (int i = 0; i < height; ++i) {
          for (int j = 0; j < width; ++j) {
    for (int i = imgTop; i < imgBottom; ++i) {
      for (int j = imgLeft; j < imgRight; ++j) {
        return new uint8[frame_cnt * height * width * channels];
       {testdata_path + "squares.gif", 2, 16, 16, 3}});
Fixed code:     ptrdiff_t buffer_size = 0;
          buffer_size = ptrdiff_t(num_frames) * height * width * channels;
  for (ptrdiff_t k = 0; k < target_num_frames; k++) {
      for (ptrdiff_t i = 0; i < height; ++i) {
        for (ptrdiff_t j = 0; j < width; ++j) {
        for (ptrdiff_t i = 0; i < height; ++i) {
          for (ptrdiff_t j = 0; j < width; ++j) {
    for (ptrdiff_t i = imgTop; i < imgBottom; ++i) {
      for (ptrdiff_t j = imgLeft; j < imgRight; ++j) {
        return new uint8[ptrdiff_t(frame_cnt) * height * width * channels];
       {testdata_path + "squares.gif", 2, 16, 16, 3},
       {testdata_path + "3g_multiframe.gif", 519, 1920, 1080, 3}});
        "3g_multiframe.gif",