Project: openexr (academysoftwarefoundation/openexr), language: c++
Vulnerability instance: openexr.cve-2020-16589
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/AcademySoftwareFoundation/openexr/issues/494
## Description:
Issue: AcademySoftwareFoundation/openexr#494
Title: SEGV exrmakepreview in ImfTiledOutputFile.cpp:458
State: closed
Created by: strongcourage
Created at: 2019-07-24 15:48:18+00:00
Labels: Bug

Issue Body:
Hi,

I found a crash due to a heap buffer overflow bug on exrmakepreview (the latest commit 9410823 on master).

PoC: https://github.com/strongcourage/PoCs/blob/master/openexr_9410823/PoC_hbo_writeTileData
Command: exrmakepreview -v $PoC /dev/null

ASAN says:
~~~
==22567==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60400000e178 at pc 0x7f2f375c522a bp 0x7ffe478e5550 sp 0x7ffe478e5540
READ of size 8 at 0x60400000e178 thread T0
    #0 0x7f2f375c5229 in Imf_2_3::TileOffsets::operator()(int, int, int, int) (/home/dungnguyen/gueb-testing/openexr/obj-asan/OpenEXR/IlmImf/libIlmImf-2_3.so.24+0x13e229)
    #1 0x7f2f375a8eac in writeTileData /home/dungnguyen/gueb-testing/openexr/OpenEXR/IlmImf/ImfTiledOutputFile.cpp:458
    #2 0x7f2f375ae164 in Imf_2_3::TiledOutputFile::copyPixels(Imf_2_3::TiledInputFile&) /home/dungnguyen/gueb-testing/openexr/OpenEXR/IlmImf/ImfTiledOutputFile.cpp:1534
    #3 0x40307b in makePreview(char const*, char const*, int, float, bool) /home/dungnguyen/gueb-testing/openexr/OpenEXR/exrmakepreview/makePreview.cpp:176
    #4 0x402187 in main /home/dungnguyen/gueb-testing/openexr/OpenEXR/exrmakepreview/main.cpp:185
    #5 0x7f2f3659582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #6 0x402428 in _start (/home/dungnguyen/PoCs/openexr_9410823/exrmakepreview-asan+0x402428)
~~~
Thanks,
Manh Dung


Comments:
Comment by strongcourage on 2019-07-24 16:27:56+00:00:
This bug also causes exrstdattr crash.

---

Comment by kdt3rd on 2019-07-24 20:51:25+00:00:
I am able to reproduce. thank you for the report

---

Comment by kdt3rd on 2019-07-25 09:24:06+00:00:
This should be fixed and merged to master

---

Comment by carnil on 2020-12-10 20:56:12+00:00:
CVE-2020-16589 seems to have been assigned for this issue.

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
cmake $SRC/openexr ${CMAKE_SETTINGS[@]}
make -j$(nproc)

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool zlib1g-dev
RUN git clone https://github.com/academysoftwarefoundation/openexr openexr
RUN git -C openexr checkout c04673810a86ba050d809da42339aeb7129fc910
WORKDIR $SRC/openexr
COPY build.sh $SRC/

Additional files:
filename: openexr_deepscanlines_fuzzer.cc
content: // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <vector>

#include <ImfArray.h>
#include <ImfChannelList.h>
#include <ImfDeepFrameBuffer.h>
#include <ImfDeepScanLineInputPart.h>
#include <ImfMultiPartInputFile.h>
#include <ImfNamespace.h>

namespace IMF = OPENEXR_IMF_NAMESPACE;
using namespace IMF;
using IMATH_NAMESPACE::Box2i;
using IMATH_NAMESPACE::V2i;

namespace {

const int width = 90;
const int height = 80;
const int minX = 10;
const int minY = 11;
const Box2i dataWindow(V2i(minX, minY),
                       V2i(minX + width - 1, minY + height - 1));
const Box2i displayWindow(V2i(0, 0), V2i(minX + width * 2, minY + height * 2));

template <typename T>
static void readFile(T *inpart) {
  const Header &fileHeader = inpart->header();

  int channelCount = 0;
  for (ChannelList::ConstIterator i = fileHeader.channels().begin();
       i != fileHeader.channels().end(); ++i, ++channelCount) {
  }

  Array2D<unsigned int> localSampleCount;
  localSampleCount.resizeErase(height, width);
  Array<Array2D<void *> > data(channelCount);

  for (int i = 0; i < channelCount; i++) data[i].resizeErase(height, width);

  DeepFrameBuffer frameBuffer;

  frameBuffer.insertSampleCountSlice(
      Slice(IMF::UINT,
            (char *)(&localSampleCount[0][0] - dataWindow.min.x -
                     dataWindow.min.y * width),
            sizeof(unsigned int) * 1, sizeof(unsigned int) * width));

  std::vector<int> read_channel(channelCount);

  for (int i = 0; i < channelCount; i++) {
    PixelType type = IMF::FLOAT;

    std::stringstream ss;
    ss << i;
    std::string str = ss.str();

    int sampleSize = sizeof(float);

    int pointerSize = sizeof(char *);

    frameBuffer.insert(
        str, DeepSlice(type,
                       (char *)(&data[i][0][0] - dataWindow.min.x -
                                dataWindow.min.y * width),
                       pointerSize * 1, pointerSize * width, sampleSize));
  }

  inpart->setFrameBuffer(frameBuffer);
  inpart->readPixelSampleCounts(dataWindow.min.y, dataWindow.max.y);
  for (int i = 0; i < dataWindow.max.y - dataWindow.min.y + 1; i++) {
    int y = i + dataWindow.min.y;

    for (int j = 0; j < width; j++) {
      for (int k = 0; k < channelCount; k++) {
        data[k][i][j] = new float[localSampleCount[i][j]];
      }
    }
  }
  try {
    inpart->readPixels(dataWindow.min.y, dataWindow.max.y);
  } catch (...) {
  }

  for (int i = 0; i < height; i++) {
    for (int j = 0; j < width; j++) {
      for (int k = 0; k < channelCount; k++) {
        delete[](float *) data[k][i][j];
      }
    }
  }
}

static void readFileSingle(const char filename[]) {
  DeepScanLineInputFile *file = NULL;
  try {
    file = new DeepScanLineInputFile(filename, 0);
  } catch (...) {
    return;
  }

  try {
    readFile(file);
  } catch (std::exception &e) {
  }

  delete file;
}

static void readFileMulti(const char filename[]) {
  MultiPartInputFile *file = NULL;
  try {
    file = new MultiPartInputFile(filename, 0);
  } catch (...) {
    return;
  }

  for (int p = 0; p < file->parts(); p++) {
    DeepScanLineInputPart *inpart = NULL;
    try {
      inpart = new DeepScanLineInputPart(*file, p);
    } catch (...) {
      continue;
    }
    try {
      readFile(inpart);
    } catch (std::exception &e) {
    }
    delete inpart;
  }

  delete file;
}

}  // namespace

// from cl/164883104
static char *buf_to_file(const char *buf, size_t size) {
  char *name = strdup("/dev/shm/fuzz-XXXXXX");
  int fd = mkstemp(name);
  if (fd < 0) {
    perror("open");
    exit(1);
  }
  size_t pos = 0;
  while (pos < size) {
    int nbytes = write(fd, &buf[pos], size - pos);
    if (nbytes <= 0) {
      perror("write");
      exit(1);
    }
    pos += nbytes;
  }
  if (close(fd) != 0) {
    perror("close");
    exit(1);
  }
  return name;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  char *file = buf_to_file((const char *)data, size);
  readFileSingle(file);
  readFileMulti(file);
  unlink(file);
  free(file);
  return 0;
}

filename: openexr_deeptiles_fuzzer.cc
content: // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <ImfArray.h>
#include <ImfChannelList.h>
#include <ImfDeepTiledInputFile.h>
#include <ImfDeepTiledInputPart.h>
#include <ImfTiledInputPart.h>

namespace IMF = OPENEXR_IMF_NAMESPACE;
using namespace IMF;
using IMATH_NAMESPACE::Box2i;

namespace {

template <typename T>
static void readFile(T *part) {
  const Header &fileHeader = part->header();

  Array2D<unsigned int> localSampleCount;

  Box2i dataWindow = fileHeader.dataWindow();

  int height = dataWindow.size().y + 1;
  int width = dataWindow.size().x + 1;

  localSampleCount.resizeErase(height, width);

  int channelCount = 0;
  for (ChannelList::ConstIterator i = fileHeader.channels().begin();
       i != fileHeader.channels().end(); ++i, channelCount++) {
  }

  Array<Array2D<void *> > data(channelCount);

  for (int i = 0; i < channelCount; i++) {
    data[i].resizeErase(height, width);
  }

  DeepFrameBuffer frameBuffer;

  int memOffset = dataWindow.min.x + dataWindow.min.y * width;
  frameBuffer.insertSampleCountSlice(
      Slice(IMF::UINT, (char *)(&localSampleCount[0][0] - memOffset),
            sizeof(unsigned int) * 1, sizeof(unsigned int) * width));

  for (int i = 0; i < channelCount; i++) {
    std::stringstream ss;
    ss << i;
    std::string str = ss.str();

    int sampleSize = sizeof(float);

    int pointerSize = sizeof(char *);

    frameBuffer.insert(
        str, DeepSlice(IMF::FLOAT, (char *)(&data[i][0][0] - memOffset),
                       pointerSize * 1, pointerSize * width, sampleSize));
  }

  part->setFrameBuffer(frameBuffer);
  for (int ly = 0; ly < part->numYLevels(); ly++) {
    for (int lx = 0; lx < part->numXLevels(); lx++) {
      Box2i dataWindowL = part->dataWindowForLevel(lx, ly);

      part->readPixelSampleCounts(0, part->numXTiles(lx) - 1, 0,
                                  part->numYTiles(ly) - 1, lx, ly);

      for (int i = 0; i < part->numYTiles(ly); i++) {
        for (int j = 0; j < part->numXTiles(lx); j++) {
          Box2i box = part->dataWindowForTile(j, i, lx, ly);
          for (int y = box.min.y; y <= box.max.y; y++)
            for (int x = box.min.x; x <= box.max.x; x++) {
              int dwy = y - dataWindowL.min.y;
              int dwx = x - dataWindowL.min.x;

              for (int k = 0; k < channelCount; k++) {
                data[k][dwy][dwx] = new float[localSampleCount[dwy][dwx]];
              }
            }
        }
      }

      try {
        part->readTiles(0, part->numXTiles(lx) - 1, 0, part->numYTiles(ly) - 1,
                        lx, ly);
      } catch (...) {
      }

      for (int i = 0; i < part->levelHeight(ly); i++) {
        for (int j = 0; j < part->levelWidth(lx); j++) {
          for (int k = 0; k < channelCount; k++) {
            delete[](float *) data[k][i][j];
          }
        }
      }
    }
  }
}

static void readFileSingle(const char filename[]) {
  DeepTiledInputFile *file;
  try {
    file = new DeepTiledInputFile(filename, 8);
  } catch (...) {
    return;
  }

  try {
    readFile(file);
  } catch (std::exception &e) {
  }

  delete file;
}

static void readFileMulti(const char filename[]) {
  MultiPartInputFile *file;

  try {
    file = new MultiPartInputFile(filename, 8);
  } catch (...) {
    return;
  }

  for (int p = 0; p < file->parts(); p++) {
    DeepTiledInputPart *part;
    try {
      part = new DeepTiledInputPart(*file, p);
    } catch (...) {
      continue;
    }

    try {
      readFile(part);
    } catch (...) {
    }

    delete part;
  }

  delete file;
}

}  // namespace

// from cl/164883104
static char *buf_to_file(const char *buf, size_t size) {
  char *name = strdup("/dev/shm/fuzz-XXXXXX");
  int fd = mkstemp(name);
  if (fd < 0) {
    perror("open");
    exit(1);
  }
  size_t pos = 0;
  while (pos < size) {
    int nbytes = write(fd, &buf[pos], size - pos);
    if (nbytes <= 0) {
      perror("write");
      exit(1);
    }
    pos += nbytes;
  }
  if (close(fd) != 0) {
    perror("close");
    exit(1);
  }
  return name;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  char *file = buf_to_file((const char *)data, size);
  Header::setMaxImageSize(10000, 10000);
  readFileSingle(file);
  readFileMulti(file);
  unlink(file);
  free(file);
  return 0;
}

filename: openexr_scanlines_fuzzer.cc
content: // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <ImfArray.h>
#include <ImfInputPart.h>
#include <ImfMultiPartInputFile.h>
#include <ImfRgbaFile.h>

using namespace OPENEXR_IMF_INTERNAL_NAMESPACE;
using IMATH_NAMESPACE::Box2i;

namespace {

static void readSingle(const char fileName[]) {
  RgbaInputFile *in = NULL;
  try {
    in = new RgbaInputFile(fileName);
  } catch (...) {
    return;
  }

  try {
    const Box2i &dw = in->dataWindow();

    int w = dw.max.x - dw.min.x + 1;
    int dx = dw.min.x;

    if (w > (1 << 24)) return;

    Array<Rgba> pixels(w);
    in->setFrameBuffer(&pixels[-dx], 1, 0);

    for (int y = dw.min.y; y <= dw.max.y; ++y) in->readPixels(y);
  } catch (...) {
  }

  delete in;
}

static void readMulti(const char fileName[]) {
  MultiPartInputFile *file;
  try {
    file = new MultiPartInputFile(fileName);
  } catch (...) {
    return;
  }

  for (int p = 0; p < file->parts(); p++) {
    InputPart *in;
    try {
      in = new InputPart(*file, p);
    } catch (...) {
      continue;
    }

    try {
      const Box2i &dw = in->header().dataWindow();

      int w = dw.max.x - dw.min.x + 1;
      int dx = dw.min.x;

      if (w > (1 << 24)) return;

      Array<Rgba> pixels(w);
      FrameBuffer i;
      i.insert("R", Slice(HALF, (char *)&(pixels[-dx].r), sizeof(Rgba), 0));
      i.insert("G", Slice(HALF, (char *)&(pixels[-dx].g), sizeof(Rgba), 0));
      i.insert("B", Slice(HALF, (char *)&(pixels[-dx].b), sizeof(Rgba), 0));
      i.insert("A", Slice(HALF, (char *)&(pixels[-dx].a), sizeof(Rgba), 0));

      in->setFrameBuffer(i);
      for (int y = dw.min.y; y <= dw.max.y; ++y) in->readPixels(y);
    } catch (...) {
    }

    delete in;
  }

  delete file;
}

}  // namespace

// from cl/164883104
static char *buf_to_file(const char *buf, size_t size) {
  char *name = strdup("/dev/shm/fuzz-XXXXXX");
  int fd = mkstemp(name);
  if (fd < 0) {
    perror("open");
    exit(1);
  }
  size_t pos = 0;
  while (pos < size) {
    int nbytes = write(fd, &buf[pos], size - pos);
    if (nbytes <= 0) {
      perror("write");
      exit(1);
    }
    pos += nbytes;
  }
  if (close(fd) != 0) {
    perror("close");
    exit(1);
  }
  return name;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  char *file = buf_to_file((const char *)data, size);
  readSingle(file);
  readMulti(file);
  unlink(file);
  free(file);
  return 0;
}

filename: openexr_tiles_fuzzer.cc
content: // Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <ImfArray.h>
#include <ImfTiledRgbaFile.h>

// Handle the case when the custom namespace is not exposed
#include <ImfChannelList.h>
#include <ImfMultiPartInputFile.h>
#include <ImfMultiPartOutputFile.h>
#include <ImfPartType.h>
#include <ImfTiledInputPart.h>
#include <ImfTiledOutputPart.h>
#include <OpenEXRConfig.h>

using namespace OPENEXR_IMF_INTERNAL_NAMESPACE;
using IMATH_NAMESPACE::Box2i;

namespace {

void readImageONE(TiledRgbaInputFile *in, int dwx, int dwy) {
  try {
    const Box2i &dw = in->dataWindow();

    int w = dw.max.x - dw.min.x + 1;
    int h = dw.max.y - dw.min.y + 1;

    Array2D<Rgba> pixels(h, w);
    in->setFrameBuffer(&pixels[-dwy][-dwx], 1, w);
    in->readTiles(0, in->numXTiles() - 1, 0, in->numYTiles() - 1);
  } catch (...) {
  }
}

void readImageONE2(const char fileName[]) {
  MultiPartInputFile *in;
  try {
    in = new MultiPartInputFile(fileName);
  } catch (...) {
    return;
  }

  TiledInputPart *inpart;
  try {
    for (int p = 0; p < in->parts(); p++) {
      try {
        inpart = new TiledInputPart(*in, p);
      } catch (...) {
        inpart = NULL;
        continue;
      }

      const Box2i &dw = inpart->header().dataWindow();

      int w = dw.max.x - dw.min.x + 1;
      int h = dw.max.y - dw.min.y + 1;
      int dwx = dw.min.x;
      int dwy = dw.min.y;

      Array2D<Rgba> pixels(h, w);
      FrameBuffer i;
      i.insert("R", Slice(HALF, (char *)&(pixels[-dwy][-dwx].r), sizeof(Rgba),
                          w * sizeof(Rgba)));
      i.insert("G", Slice(HALF, (char *)&(pixels[-dwy][-dwx].g), sizeof(Rgba),
                          w * sizeof(Rgba)));
      i.insert("B", Slice(HALF, (char *)&(pixels[-dwy][-dwx].b), sizeof(Rgba),
                          w * sizeof(Rgba)));
      i.insert("A", Slice(HALF, (char *)&(pixels[-dwy][-dwx].a), sizeof(Rgba),
                          w * sizeof(Rgba)));

      inpart->setFrameBuffer(i);
      inpart->readTiles(0, inpart->numXTiles() - 1, 0, inpart->numYTiles() - 1);

      delete inpart;
      inpart = NULL;
    }
  } catch (...) {
    delete inpart;
  }

  delete in;
}

void readImageMIP(TiledRgbaInputFile *in, int dwx, int dwy) {
  try {
    int numLevels = in->numLevels();
    Array<Array2D<Rgba> > levels2(numLevels);

    for (int level = 0; level < numLevels; ++level) {
      int levelWidth = in->levelWidth(level);
      int levelHeight = in->levelHeight(level);
      levels2[level].resizeErase(levelHeight, levelWidth);

      in->setFrameBuffer(&(levels2[level])[-dwy][-dwx], 1, levelWidth);
      in->readTiles(0, in->numXTiles(level) - 1, 0, in->numYTiles(level) - 1,
                    level);
    }
  } catch (...) {
  }
}

void readImageRIP(TiledRgbaInputFile *in, int dwx, int dwy) {
  try {
    int numXLevels = in->numXLevels();
    int numYLevels = in->numYLevels();
    Array2D<Array2D<Rgba> > levels2(numYLevels, numXLevels);

    for (int ylevel = 0; ylevel < numYLevels; ++ylevel) {
      for (int xlevel = 0; xlevel < numXLevels; ++xlevel) {
        int levelWidth = in->levelWidth(xlevel);
        int levelHeight = in->levelHeight(ylevel);
        levels2[ylevel][xlevel].resizeErase(levelHeight, levelWidth);
        in->setFrameBuffer(&(levels2[ylevel][xlevel])[-dwy][-dwx], 1,
                           levelWidth);

        in->readTiles(0, in->numXTiles(xlevel) - 1, 0,
                      in->numYTiles(ylevel) - 1, xlevel, ylevel);
      }
    }
  } catch (...) {
  }
}

}  // namespace

static void fuzzImage(const char filename[]) {
  Header::setMaxImageSize(10000, 10000);
  Header::setMaxTileSize(10000, 10000);

  TiledRgbaInputFile *in;
  try {
    in = new TiledRgbaInputFile(filename);
  } catch (...) {
    return;
  }

  const Box2i &dw = in->dataWindow();
  int dwx = dw.min.x;
  int dwy = dw.min.y;

  readImageMIP(in, dwx, dwy);
  readImageRIP(in, dwx, dwy);
  readImageONE(in, dwx, dwy);
  readImageONE2(filename);

  delete in;
}

// from cl/164883104
static char *buf_to_file(const char *buf, size_t size) {
  char *name = strdup("/dev/shm/fuzz-XXXXXX");
  int fd = mkstemp(name);
  if (fd < 0) {
    perror("open");
    exit(1);
  }
  size_t pos = 0;
  while (pos < size) {
    int nbytes = write(fd, &buf[pos], size - pos);
    if (nbytes <= 0) {
      perror("write");
      exit(1);
    }
    pos += nbytes;
  }
  if (close(fd) != 0) {
    perror("close");
    exit(1);
  }
  return name;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
  char *file = buf_to_file((const char *)data, size);
  fuzzImage(file);
  unlink(file);
  free(file);
  return 0;
}