From b48e8e93791d3601caab2c3d913eaedade576150 Mon Sep 17 00:00:00 2001 From: Luis Diaz Date: Sat, 12 Mar 2022 18:49:37 +0100 Subject: [PATCH 1/5] Add unit tests for Jp2Image revealing bugs (see #2147) --- src/jp2image.cpp | 6 -- unitTests/CMakeLists.txt | 1 + unitTests/test_basicio.cpp | 44 ++++++++++++++ unitTests/test_jp2image.cpp | 117 ++++++++++++++++++++++++++++++++++++ 4 files changed, 162 insertions(+), 6 deletions(-) create mode 100644 unitTests/test_jp2image.cpp diff --git a/src/jp2image.cpp b/src/jp2image.cpp index 556e67ff..a52b0860 100644 --- a/src/jp2image.cpp +++ b/src/jp2image.cpp @@ -174,10 +174,7 @@ void Jp2Image::readMetadata() { throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); } IoCloser closer(*io_); - // Ensure that this is the correct image type if (!isJp2Type(*io_, true)) { - if (io_->error() || io_->eof()) - throw Error(ErrorCode::kerFailedToReadImageData); throw Error(ErrorCode::kerNotAnImage, "JPEG-2000"); } @@ -411,10 +408,7 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in if (io_->open() != 0) throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); - // Ensure that this is the correct image type if (!isJp2Type(*io_, false)) { - if (io_->error() || io_->eof()) - throw Error(ErrorCode::kerFailedToReadImageData); throw Error(ErrorCode::kerNotAJpeg); } diff --git a/unitTests/CMakeLists.txt b/unitTests/CMakeLists.txt index b6e0178b..df7cb100 100644 --- a/unitTests/CMakeLists.txt +++ b/unitTests/CMakeLists.txt @@ -14,6 +14,7 @@ add_executable(unit_tests test_helper_functions.cpp test_image_int.cpp test_ImageFactory.cpp + test_jp2image.cpp test_IptcKey.cpp test_LangAltValueRead.cpp test_pngimage.cpp diff --git a/unitTests/test_basicio.cpp b/unitTests/test_basicio.cpp index 76071a87..92672596 100644 --- a/unitTests/test_basicio.cpp +++ b/unitTests/test_basicio.cpp @@ -7,6 +7,50 @@ using namespace Exiv2; +TEST(MemIo_Default, readEReturns0) { + std::array buf; + MemIo io; + ASSERT_EQ(0, io.read(buf.data(), buf.size())); +} + +TEST(MemIo_Default, isNotAtEof) { + MemIo io; + ASSERT_FALSE(io.eof()); +} + +TEST(MemIo_Default, seekBeyondBufferSizeReturns1AndSetsEofToTrue) { + MemIo io; + ASSERT_EQ(1, io.seek(1, BasicIo::beg)); + ASSERT_TRUE(io.eof()); +} + +TEST(MemIo_Default, seekBefore0Returns1ButItDoesNotSetEofToTrue) { + MemIo io; + ASSERT_EQ(1, io.seek(-1, BasicIo::beg)); + ASSERT_FALSE(io.eof()); +} + +TEST(MemIo_Default, seekToEndPosition_doesNotTriggerEof) { + MemIo io; + ASSERT_EQ(0, io.tell()); + ASSERT_EQ(0, io.seek(0, BasicIo::end)); + ASSERT_EQ(0, io.tell()); + ASSERT_FALSE(io.eof()); +} + +TEST(MemIo_Default, seekToEndPositionAndReadTriggersEof) { + MemIo io; + ASSERT_EQ(0, io.seek(0, BasicIo::end)); + ASSERT_EQ(0, io.tell()); + + std::array buf2; + buf2.fill(0); + ASSERT_EQ(0, io.read(buf2.data(), 1)); // Note that we cannot even read 1 byte being at the end + ASSERT_TRUE(io.eof()); +} + +// ------------------------- + TEST(MemIo, isNotAtEofInitially) { std::array buf; buf.fill(0); diff --git a/unitTests/test_jp2image.cpp b/unitTests/test_jp2image.cpp new file mode 100644 index 00000000..faacb64b --- /dev/null +++ b/unitTests/test_jp2image.cpp @@ -0,0 +1,117 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include + +using namespace Exiv2; + +TEST(Jp2Image, canBeCreatedFromScratch) { + auto memIo = std::make_unique(); + const bool create{true}; + ASSERT_NO_THROW(Jp2Image image(std::move(memIo), create)); +} + +TEST(Jp2Image, canBeOpenedEvenWithAnEmptyMemIo) { + auto memIo = std::make_unique(); + const bool create{false}; + ASSERT_NO_THROW(Jp2Image image(std::move(memIo), create)); +} + +TEST(Jp2Image, mimeTypeIsPng) { + auto memIo = std::make_unique(); + const bool create{true}; + Jp2Image image(std::move(memIo), create); + + ASSERT_EQ("image/jp2", image.mimeType()); +} + +TEST(Jp2Image, printStructurePrintsNothingWithKpsNone) { + auto memIo = std::make_unique(); + const bool create{true}; + Jp2Image image(std::move(memIo), create); + + std::ostringstream stream; + image.printStructure(stream, Exiv2::kpsNone, 1); + + ASSERT_TRUE(stream.str().empty()); +} + +TEST(Jp2Image, printStructurePrintsDataWithKpsBasic) { + auto memIo = std::make_unique(); + const bool create{true}; + Jp2Image image(std::move(memIo), create); + + std::ostringstream stream; + image.printStructure(stream, Exiv2::kpsBasic, 1); + + ASSERT_FALSE(stream.str().empty()); +} + +TEST(Jp2Image, cannotReadMetadataFromEmptyIo) { + auto memIo = std::make_unique(); + const bool create{false}; + Jp2Image image(std::move(memIo), create); + + try { + image.readMetadata(); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(ErrorCode::kerNotAnImage, e.code()); + ASSERT_STREQ("This does not look like a JPEG-2000 image", e.what()); + } +} + +TEST(Jp2Image, cannotReadMetadataFromIoWhichCannotBeOpened) { + auto memIo = std::make_unique("NonExistingPath.jp2"); + const bool create{false}; + Jp2Image image(std::move(memIo), create); + + try { + image.readMetadata(); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(ErrorCode::kerDataSourceOpenFailed, e.code()); + } +} + +TEST(Jp2Image, cannotWriteMetadataToEmptyIo) { + auto memIo = std::make_unique(); + const bool create{false}; + Jp2Image image(std::move(memIo), create); + + try { + image.writeMetadata(); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(ErrorCode::kerNoImageInInputData, e.code()); + } +} + +TEST(Jp2Image, canWriteMetadataFromCreatedJp2Image) { + auto memIo = std::make_unique(); + const bool create{true}; + Jp2Image image(std::move(memIo), create); + ASSERT_NO_THROW(image.writeMetadata()); +} + +TEST(Jp2Image, cannotWriteMetadataToIoWhichCannotBeOpened) { + auto memIo = std::make_unique("NonExistingPath.jp2"); + const bool create{false}; + Jp2Image image(std::move(memIo), create); + + try { + image.readMetadata(); + FAIL(); + } catch (const Exiv2::Error& e) { + ASSERT_EQ(ErrorCode::kerDataSourceOpenFailed, e.code()); + } +} + +TEST(Jp2Image, canWriteMetadataAndReadAfterwards) { + auto memIo = std::make_unique(); + const bool create{true}; + Jp2Image image(std::move(memIo), create); + ASSERT_NO_THROW(image.writeMetadata()); + ASSERT_NO_THROW(image.readMetadata()); +} From e7478f744d91f6fe59166271b4c9e3629cc1a93a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Fri, 18 Mar 2022 14:03:48 +0100 Subject: [PATCH 2/5] Jp2Image: Implement more checks from JP2 standard + fix bug - add debug info when parsing Signature box - Move definitions & static stuff to anonymous namespace - cleanup while studying code - Make exceptions more similar to other formats --- src/jp2image.cpp | 372 +++++++++++--------- test/data/test_reference_files/icc-test.out | 6 +- 2 files changed, 200 insertions(+), 178 deletions(-) diff --git a/src/jp2image.cpp b/src/jp2image.cpp index a52b0860..75f7edcf 100644 --- a/src/jp2image.cpp +++ b/src/jp2image.cpp @@ -17,53 +17,34 @@ #include #include +namespace Exiv2 { +namespace { // JPEG-2000 box types -static constexpr uint32_t kJp2BoxTypeJp2Header = 0x6a703268; // 'jp2h' -static constexpr uint32_t kJp2BoxTypeImageHeader = 0x69686472; // 'ihdr' -static constexpr uint32_t kJp2BoxTypeColorHeader = 0x636f6c72; // 'colr' -static constexpr uint32_t kJp2BoxTypeUuid = 0x75756964; // 'uuid' -static constexpr uint32_t kJp2BoxTypeClose = 0x6a703263; // 'jp2c' - -// from openjpeg-2.1.2/src/lib/openjp2/jp2.h -/*#define JPIP_JPIP 0x6a706970*/ - -#define JP2_JP 0x6a502020 /**< JPEG 2000 signature box */ -#define JP2_FTYP 0x66747970 /**< File type box */ -#define JP2_JP2H 0x6a703268 /**< JP2 header box (super-box) */ -#define JP2_IHDR 0x69686472 /**< Image header box */ -#define JP2_COLR 0x636f6c72 /**< Colour specification box */ -#define JP2_JP2C 0x6a703263 /**< Contiguous codestream box */ -#define JP2_URL 0x75726c20 /**< Data entry URL box */ -#define JP2_PCLR 0x70636c72 /**< Palette box */ -#define JP2_CMAP 0x636d6170 /**< Component Mapping box */ -#define JP2_CDEF 0x63646566 /**< Channel Definition box */ -#define JP2_DTBL 0x6474626c /**< Data Reference box */ -#define JP2_BPCC 0x62706363 /**< Bits per component box */ -#define JP2_JP2 0x6a703220 /**< File type fields */ - -/* For the future */ -/* #define JP2_RES 0x72657320 */ /**< Resolution box (super-box) */ -/* #define JP2_JP2I 0x6a703269 */ /**< Intellectual property box */ -/* #define JP2_XML 0x786d6c20 */ /**< XML box */ -/* #define JP2_UUID 0x75756994 */ /**< UUID box */ -/* #define JP2_UINF 0x75696e66 */ /**< UUID info box (super-box) */ -/* #define JP2_ULST 0x756c7374 */ /**< UUID list box */ +constexpr uint32_t kJp2BoxTypeSignature = 0x6a502020; // signature box, required, +constexpr uint32_t kJp2BoxTypeFileTypeBox = 0x66747970; // File type box, required +constexpr uint32_t kJp2BoxTypeHeader = 0x6a703268; // Jp2 Header Box, required, Superbox +constexpr uint32_t kJp2BoxTypeImageHeader = 0x69686472; // Image Header Box ('ihdr'), required, +constexpr uint32_t kJp2BoxTypeColorSpec = 0x636f6c72; // Color Specification box ('colr'), required +constexpr uint32_t kJp2BoxTypeUuid = 0x75756964; // 'uuid' +constexpr uint32_t kJp2BoxTypeClose = 0x6a703263; // 'jp2c' + +const uint32_t brandJp2 = 0x6a703220; // JPEG-2000 UUIDs for embedded metadata // // See http://www.jpeg.org/public/wg1n2600.doc for information about embedding IPTC-NAA data in JPEG-2000 files // See http://www.adobe.com/devnet/xmp/pdfs/xmp_specification.pdf for information about embedding XMP data in JPEG-2000 // files -static constexpr unsigned char kJp2UuidExif[] = "JpgTiffExif->JP2"; -static constexpr unsigned char kJp2UuidIptc[] = "\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38"; -static constexpr unsigned char kJp2UuidXmp[] = "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac"; +constexpr unsigned char kJp2UuidExif[] = "JpgTiffExif->JP2"; +constexpr unsigned char kJp2UuidIptc[] = "\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\xba\xf1\xa3\xe0\x97\xad\x38"; +constexpr unsigned char kJp2UuidXmp[] = "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac"; // See section B.1.1 (JPEG 2000 Signature box) of JPEG-2000 specification -static constexpr unsigned char Jp2Signature[] = { +constexpr unsigned char Jp2Signature[] = { 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, }; -static constexpr unsigned char Jp2Blank[] = { +constexpr unsigned char Jp2Blank[] = { 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, 0x00, 0x00, 0x00, 0x14, 0x66, 0x74, 0x79, 0x70, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x2d, 0x6a, 0x70, 0x32, 0x68, 0x00, 0x00, 0x00, 0x16, 0x69, 0x68, 0x64, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, @@ -80,7 +61,6 @@ static constexpr unsigned char Jp2Blank[] = { 0x00, 0x00, 0xff, 0x93, 0xcf, 0xb4, 0x04, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xff, 0xd9, }; -//! @cond IGNORE struct Jp2BoxHeader { uint32_t length; uint32_t type; @@ -90,47 +70,19 @@ struct Jp2ImageHeaderBox { uint32_t imageHeight; uint32_t imageWidth; uint16_t componentCount; - uint8_t bitsPerComponent; - uint8_t compressionType; - uint8_t colorspaceIsUnknown; - uint8_t intellectualPropertyFlag; - uint16_t compressionTypeProfile; + uint8_t bpc; //open() == 0) { -#ifdef EXIV2_DEBUG_MESSAGES - std::cerr << "Exiv2::Jp2Image:: Creating JPEG2000 image to memory" << std::endl; -#endif - IoCloser closer(*io_); - if (io_->write(Jp2Blank, sizeof(Jp2Blank)) != sizeof(Jp2Blank)) { -#ifdef EXIV2_DEBUG_MESSAGES - std::cerr << "Exiv2::Jp2Image:: Failed to create JPEG2000 image on memory" << std::endl; -#endif - } - } - } -} // Jp2Image::Jp2Image - -std::string Jp2Image::mimeType() const { - return "image/jp2"; -} -void Jp2Image::setComment(std::string_view /*comment*/) { - // Todo: implement me! - throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "JP2")); -} +const size_t boxHSize = sizeof(Jp2BoxHeader); -static void lf(std::ostream& out, bool& bLF) { +void lf(std::ostream& out, bool& bLF) { if (bLF) { out << std::endl; out.flush(); @@ -138,7 +90,7 @@ static void lf(std::ostream& out, bool& bLF) { } } -static bool isBigEndian() { +bool isBigEndian() { union { uint32_t i; char c[4]; @@ -147,7 +99,8 @@ static bool isBigEndian() { return e.c[0] != 0; } -static std::string toAscii(long n) { +// Obtains the ascii version from the box.type +std::string toAscii(long n) { const auto p = reinterpret_cast(&n); std::string result; bool bBigEndian = isBigEndian(); @@ -157,7 +110,7 @@ static std::string toAscii(long n) { return result; } -static void boxes_check(size_t b, size_t m) { +void boxes_check(size_t b, size_t m) { if (b > m) { #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::Jp2Image::readMetadata box maximum exceeded" << std::endl; @@ -166,6 +119,32 @@ static void boxes_check(size_t b, size_t m) { } } +} // namespace + +Jp2Image::Jp2Image(BasicIo::UniquePtr io, bool create) : Image(ImageType::jp2, mdExif | mdIptc | mdXmp, std::move(io)) { + if (create) { + if (io_->open() == 0) { +#ifdef EXIV2_DEBUG_MESSAGES + std::cerr << "Exiv2::Jp2Image:: Creating JPEG2000 image to memory" << std::endl; +#endif + IoCloser closer(*io_); + if (io_->write(Jp2Blank, sizeof(Jp2Blank)) != sizeof(Jp2Blank)) { +#ifdef EXIV2_DEBUG_MESSAGES + std::cerr << "Exiv2::Jp2Image:: Failed to create JPEG2000 image on memory" << std::endl; +#endif + } + } + } +} + +std::string Jp2Image::mimeType() const { + return "image/jp2"; +} + +void Jp2Image::setComment(std::string_view /*comment*/) { + throw(Error(ErrorCode::kerInvalidSettingForImage, "Image comment", "JP2")); +} + void Jp2Image::readMetadata() { #ifdef EXIV2_DEBUG_MESSAGES std::cerr << "Exiv2::Jp2Image::readMetadata: Reading JPEG-2000 file " << io_->path() << std::endl; @@ -180,13 +159,13 @@ void Jp2Image::readMetadata() { Jp2BoxHeader box = {0, 0}; Jp2BoxHeader subBox = {0, 0}; - Jp2ImageHeaderBox ihdr = {0, 0, 0, 0, 0, 0, 0, 0}; + Jp2ImageHeaderBox ihdr = {0, 0, 0, 0, 0, 0, 0}; Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; - size_t boxes = 0; + size_t boxesCount = 0; size_t boxem = 1000; // boxes max - while (io_->read(reinterpret_cast(&box), sizeof(box)) == sizeof(box)) { - boxes_check(boxes++, boxem); + while (io_->read(reinterpret_cast(&box), boxHSize) == boxHSize) { + boxes_check(boxesCount++, boxem); long position = io_->tell(); box.length = getLong(reinterpret_cast(&box.length), bigEndian); box.type = getLong(reinterpret_cast(&box.type), bigEndian); @@ -195,24 +174,47 @@ void Jp2Image::readMetadata() { << "Position: " << position << " box type: " << toAscii(box.type) << " length: " << box.length << std::endl; #endif - enforce(box.length <= sizeof(box) + io_->size() - io_->tell(), Exiv2::ErrorCode::kerCorruptedMetadata); + enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata); if (box.length == 0) return; if (box.length == 1) { - // FIXME. Special case. the real box size is given in another place. + /// \todo In this case, the real box size is given in bytes XLBox (bytes 8-15) } switch (box.type) { - case kJp2BoxTypeJp2Header: { + case kJp2BoxTypeSignature: { +#ifdef EXIV2_DEBUG_MESSAGES + std::cout << "Exiv2::Jp2Image::readMetadata: JPEG 2000 Signature box found" << std::endl; +#endif + break; + } + case kJp2BoxTypeFileTypeBox: { + // This box shall immediately follow the JPEG 2000 Signature box + /// \todo All files shall contain one and only one File Type box. + assert(box.length >= 20); // 8 (box) + 4 (BR) + 4(MinV) + >=4 (CLn) + DataBuf data(box.length - boxHSize); + io_->read(data.data(), data.size()); + const uint32_t brand = data.read_uint32(0, bigEndian); + const uint32_t minorVersion = data.read_uint32(4, bigEndian); + const uint32_t compatibilityList = data.read_uint32(8, bigEndian); + // const size_t clCount = (data.size() - 8) / 4; + // for(size_t i = 0; i < clCount; i++) { + // uint32_t compatibilityList = data.read_uint32(8 + i*4, bigEndian); + // } + if (brand != brandJp2 || minorVersion != 0 || compatibilityList != brandJp2) + throw Error(ErrorCode::kerCorruptedMetadata); + break; + } + case kJp2BoxTypeHeader: { #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::Jp2Image::readMetadata: JP2Header box found" << std::endl; #endif long restore = io_->tell(); - while (io_->read(reinterpret_cast(&subBox), sizeof(subBox)) == sizeof(subBox) && subBox.length) { - boxes_check(boxes++, boxem); + while (io_->read(reinterpret_cast(&subBox), boxHSize) == boxHSize && subBox.length) { + boxes_check(boxesCount++, boxem); subBox.length = getLong(reinterpret_cast(&subBox.length), bigEndian); subBox.type = getLong(reinterpret_cast(&subBox.type), bigEndian); if (subBox.length > io_->size()) { @@ -222,7 +224,7 @@ void Jp2Image::readMetadata() { std::cout << "Exiv2::Jp2Image::readMetadata: " << "subBox = " << toAscii(subBox.type) << " length = " << subBox.length << std::endl; #endif - if (subBox.type == kJp2BoxTypeColorHeader && subBox.length != 15) { + if (subBox.type == kJp2BoxTypeColorSpec && subBox.length != 15) { #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::Jp2Image::readMetadata: " << "Color data found" << std::endl; @@ -265,14 +267,14 @@ void Jp2Image::readMetadata() { ihdr.imageHeight = getLong(reinterpret_cast(&ihdr.imageHeight), bigEndian); ihdr.imageWidth = getLong(reinterpret_cast(&ihdr.imageWidth), bigEndian); ihdr.componentCount = getShort(reinterpret_cast(&ihdr.componentCount), bigEndian); - ihdr.compressionTypeProfile = getShort(reinterpret_cast(&ihdr.compressionTypeProfile), bigEndian); + enforce(ihdr.c == 7, ErrorCode::kerCorruptedMetadata); pixelWidth_ = ihdr.imageWidth; pixelHeight_ = ihdr.imageHeight; } io_->seek(restore, BasicIo::beg); - if (io_->seek(subBox.length, Exiv2::BasicIo::cur) != 0) { + if (io_->seek(subBox.length, BasicIo::cur) != 0) { throw Error(ErrorCode::kerCorruptedMetadata); } restore = io_->tell(); @@ -296,8 +298,8 @@ void Jp2Image::readMetadata() { #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::Jp2Image::readMetadata: Exif data found" << std::endl; #endif - enforce(box.length >= sizeof(box) + sizeof(uuid), ErrorCode::kerCorruptedMetadata); - rawData.alloc(box.length - (sizeof(box) + sizeof(uuid))); + enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata); + rawData.alloc(box.length - (boxHSize + sizeof(uuid))); bufRead = io_->read(rawData.data(), rawData.size()); if (io_->error()) throw Error(ErrorCode::kerFailedToReadImageData); @@ -343,8 +345,8 @@ void Jp2Image::readMetadata() { #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::Jp2Image::readMetadata: Iptc data found" << std::endl; #endif - enforce(box.length >= sizeof(box) + sizeof(uuid), ErrorCode::kerCorruptedMetadata); - rawData.alloc(box.length - (sizeof(box) + sizeof(uuid))); + enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata); + rawData.alloc(box.length - (boxHSize + sizeof(uuid))); bufRead = io_->read(rawData.data(), rawData.size()); if (io_->error()) throw Error(ErrorCode::kerFailedToReadImageData); @@ -363,8 +365,8 @@ void Jp2Image::readMetadata() { #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::Jp2Image::readMetadata: Xmp data found" << std::endl; #endif - enforce(box.length >= sizeof(box) + sizeof(uuid), ErrorCode::kerCorruptedMetadata); - rawData.alloc(box.length - (sizeof(box) + sizeof(uuid))); + enforce(box.length >= boxHSize + sizeof(uuid), ErrorCode::kerCorruptedMetadata); + rawData.alloc(box.length - (boxHSize + sizeof(uuid))); bufRead = io_->read(rawData.data(), rawData.size()); if (io_->error()) throw Error(ErrorCode::kerFailedToReadImageData); @@ -397,7 +399,7 @@ void Jp2Image::readMetadata() { } // Move to the next box. - io_->seek(static_cast(position - sizeof(box) + box.length), BasicIo::beg); + io_->seek(static_cast(position - boxHSize + box.length), BasicIo::beg); if (io_->error()) throw Error(ErrorCode::kerFailedToReadImageData); } @@ -412,6 +414,9 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in throw Error(ErrorCode::kerNotAJpeg); } + // According to the JP2 standard: The start of the first box shall be the first byte of the file, and the + // last byte of the last box shall be the last byte of the file. + bool bPrint = option == kpsBasic || option == kpsRecursive; bool bRecursive = option == kpsRecursive; bool bICC = option == kpsIccProfile; @@ -430,14 +435,14 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in bool bLF = false; while (box.length && box.type != kJp2BoxTypeClose && - io_->read(reinterpret_cast(&box), sizeof(box)) == sizeof(box)) { + io_->read(reinterpret_cast(&box), boxHSize) == boxHSize) { long position = io_->tell(); box.length = getLong(reinterpret_cast(&box.length), bigEndian); box.type = getLong(reinterpret_cast(&box.type), bigEndian); - enforce(box.length <= sizeof(box) + io_->size() - io_->tell(), Exiv2::ErrorCode::kerCorruptedMetadata); + enforce(box.length <= boxHSize + io_->size() - io_->tell(), ErrorCode::kerCorruptedMetadata); if (bPrint) { - out << Internal::stringFormat("%8ld | %8ld | ", position - sizeof(box), static_cast(box.length)) + out << Internal::stringFormat("%8ld | %8ld | ", position - boxHSize, static_cast(box.length)) << toAscii(box.type) << " | "; bLF = true; if (box.type == kJp2BoxTypeClose) @@ -447,21 +452,44 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in break; switch (box.type) { - case kJp2BoxTypeJp2Header: { + case kJp2BoxTypeSignature: { + /// \todo we should make sure that only 1 of this boxes is found + assert(box.length == 12); + DataBuf data(4); + io_->read(data.data(), data.size()); + if (data.read_uint32(0, bigEndian) != 0x0D0A870A) { + throw Error(ErrorCode::kerCorruptedMetadata); + } + break; + } + case kJp2BoxTypeFileTypeBox: { + // This box shall immediately follow the JPEG 2000 Signature box + /// \todo All files shall contain one and only one File Type box. + DataBuf data(12); + io_->read(data.data(), data.size()); + uint32_t brand = data.read_uint32(0, bigEndian); + uint32_t minorVersion = data.read_uint32(4, bigEndian); + uint32_t compatibilityList = data.read_uint32(8, bigEndian); + if (brand != brandJp2 || minorVersion != 0 || compatibilityList != brandJp2) + throw Error(ErrorCode::kerCorruptedMetadata); + break; + } + case kJp2BoxTypeHeader: { lf(out, bLF); + /// \todo All files shall contain one and only one Header box. - while (io_->read(reinterpret_cast(&subBox), sizeof(subBox)) == sizeof(subBox) && + while (io_->read(reinterpret_cast(&subBox), boxHSize) == boxHSize && io_->tell() < position + static_cast(box.length)) // don't read beyond the box! { - const size_t address = io_->tell() - sizeof(subBox); + const size_t address = io_->tell() - boxHSize; subBox.length = getLong(reinterpret_cast(&subBox.length), bigEndian); subBox.type = getLong(reinterpret_cast(&subBox.type), bigEndian); - if (subBox.length < sizeof(box) || subBox.length > io_->size() - io_->tell()) { + if (subBox.length < boxHSize || subBox.length > io_->size() - io_->tell()) { throw Error(ErrorCode::kerCorruptedMetadata); } - DataBuf data(subBox.length - sizeof(box)); + DataBuf data(subBox.length - boxHSize); io_->read(data.data(), data.size()); if (bPrint) { out << Internal::stringFormat("%8ld | %8ld | sub:", address, subBox.length) << toAscii(subBox.type) @@ -470,25 +498,40 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in bLF = true; } - if (subBox.type == kJp2BoxTypeColorHeader) { + if (subBox.type == kJp2BoxTypeImageHeader) { + assert(subBox.length == 22); + // height (4), width (4), componentsCount (2), bpc (1) + auto compressionType = data.read_uint8(11); + auto unkC = data.read_uint8(12); + auto ipr = data.read_uint8(13); + if (compressionType != 7 || unkC > 1 || ipr > 1) { + throw Error(ErrorCode::kerCorruptedMetadata); + } + } else if (subBox.type == kJp2BoxTypeColorSpec) { const size_t pad = 3; // don't know why there are 3 padding bytes // Bounds-check for the `getULong()` below, which reads 4 bytes, starting at `pad`. enforce(data.size() >= pad + 4, ErrorCode::kerCorruptedMetadata); - if (bPrint) { - out << " | pad:"; - for (int i = 0; i < 3; i++) - out << " " << static_cast(data.read_uint8(i)); - } - - const size_t iccLength = data.read_uint32(pad, bigEndian); - if (bPrint) { - out << " | iccLength:" << iccLength; - } - enforce(iccLength <= data.size() - pad, ErrorCode::kerCorruptedMetadata); - if (bICC) { - out.write(data.c_str(pad), iccLength); + /// \todo A conforming JP2 reader shall ignore all Colour Specification boxes after the first. + auto METH = data.read_uint8(0); + // auto PREC = data.read_uint8(1); + // auto APPROX = data.read_uint8(2); + if (METH == 1) { // Enumerated Colourspace + auto enumCS = data.read_uint32(3, bigEndian); + if (enumCS != 16 && enumCS != 17) { + throw Error(ErrorCode::kerCorruptedMetadata); + } + } else { // Restricted ICC Profile + // see the ICC Profile Format Specification, version ICC.1:1998-09 + const size_t iccLength = data.read_uint32(pad, bigEndian); + if (bPrint) { + out << " | iccLength:" << iccLength; + } + enforce(iccLength <= data.size() - pad, ErrorCode::kerCorruptedMetadata); + if (bICC) { + out.write(data.c_str(pad), iccLength); + } } } lf(out, bLF); @@ -515,8 +558,8 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in } DataBuf rawData; - enforce(box.length >= sizeof(uuid) + sizeof(box), ErrorCode::kerCorruptedMetadata); - rawData.alloc(box.length - sizeof(uuid) - sizeof(box)); + enforce(box.length >= sizeof(uuid) + boxHSize, ErrorCode::kerCorruptedMetadata); + rawData.alloc(box.length - sizeof(uuid) - boxHSize); const size_t bufRead = io_->read(rawData.data(), rawData.size()); if (io_->error()) throw Error(ErrorCode::kerFailedToReadImageData); @@ -553,14 +596,14 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in } // Move to the next box. - io_->seek(static_cast(position - sizeof(box) + box.length), BasicIo::beg); + io_->seek(static_cast(position - boxHSize + box.length), BasicIo::beg); if (io_->error()) throw Error(ErrorCode::kerFailedToReadImageData); if (bPrint) lf(out, bLF); } } -} // JpegBase::printStructure +} void Jp2Image::writeMetadata() { if (io_->open() != 0) { @@ -583,23 +626,23 @@ void Jp2Image::writeMetadata() { void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { DataBuf output(boxBuf.size() + iccProfile_.size() + 100); // allocate sufficient space - size_t outlen = sizeof(Jp2BoxHeader); // now many bytes have we written to output? - long inlen = sizeof(Jp2BoxHeader); // how many bytes have we read from boxBuf? - enforce(sizeof(Jp2BoxHeader) <= output.size(), Exiv2::ErrorCode::kerCorruptedMetadata); + size_t outlen = boxHSize; // now many bytes have we written to output? + long inlen = boxHSize; // how many bytes have we read from boxBuf? + enforce(boxHSize <= output.size(), ErrorCode::kerCorruptedMetadata); auto pBox = reinterpret_cast(boxBuf.c_data()); uint32_t length = getLong(reinterpret_cast(&pBox->length), bigEndian); - enforce(length <= output.size(), Exiv2::ErrorCode::kerCorruptedMetadata); - uint32_t count = sizeof(Jp2BoxHeader); + enforce(length <= output.size(), ErrorCode::kerCorruptedMetadata); + uint32_t count = boxHSize; auto p = boxBuf.c_str(); bool bWroteColor = false; while (count < length && !bWroteColor) { - enforce(sizeof(Jp2BoxHeader) <= length - count, Exiv2::ErrorCode::kerCorruptedMetadata); + enforce(boxHSize <= length - count, ErrorCode::kerCorruptedMetadata); auto pSubBox = reinterpret_cast(p + count); // copy data. pointer could be into a memory mapped file which we will decode! Jp2BoxHeader subBox; - memcpy(&subBox, pSubBox, sizeof(subBox)); + memcpy(&subBox, pSubBox, boxHSize); Jp2BoxHeader newBox = subBox; if (count < length) { @@ -609,24 +652,23 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { std::cout << "Jp2Image::encodeJp2Header subbox: " << toAscii(subBox.type) << " length = " << subBox.length << std::endl; #endif - enforce(subBox.length > 0, Exiv2::ErrorCode::kerCorruptedMetadata); - enforce(subBox.length <= length - count, Exiv2::ErrorCode::kerCorruptedMetadata); + enforce(subBox.length > 0, ErrorCode::kerCorruptedMetadata); + enforce(subBox.length <= length - count, ErrorCode::kerCorruptedMetadata); count += subBox.length; newBox.type = subBox.type; } else { subBox.length = 0; - newBox.type = kJp2BoxTypeColorHeader; + newBox.type = kJp2BoxTypeColorSpec; count = length; } size_t newlen = subBox.length; - if (newBox.type == kJp2BoxTypeColorHeader) { + if (newBox.type == kJp2BoxTypeColorSpec) { bWroteColor = true; if (!iccProfileDefined()) { const char* pad = "\x01\x00\x00\x00\x00\x00\x10\x00\x00\x05\x1cuuid"; uint32_t psize = 15; - newlen = sizeof(newBox) + psize; - enforce(newlen <= output.size() - outlen, Exiv2::ErrorCode::kerCorruptedMetadata); + enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata); ul2Data(reinterpret_cast(&newBox.length), psize, bigEndian); ul2Data(reinterpret_cast(&newBox.type), newBox.type, bigEndian); output.copyBytes(outlen, &newBox, sizeof(newBox)); @@ -635,7 +677,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { const char* pad = "\x02\x00\x00"; uint32_t psize = 3; newlen = sizeof(newBox) + psize + iccProfile_.size(); - enforce(newlen <= static_cast(output.size() - outlen), Exiv2::ErrorCode::kerCorruptedMetadata); + enforce(newlen <= static_cast(output.size() - outlen), ErrorCode::kerCorruptedMetadata); ul2Data(reinterpret_cast(&newBox.length), static_cast(newlen), bigEndian); ul2Data(reinterpret_cast(&newBox.type), newBox.type, bigEndian); output.copyBytes(outlen, &newBox, sizeof(newBox)); @@ -643,7 +685,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { output.copyBytes(outlen + sizeof(newBox) + psize, iccProfile_.c_data(), iccProfile_.size()); } } else { - enforce(newlen <= static_cast(output.size() - outlen), Exiv2::ErrorCode::kerCorruptedMetadata); + enforce(newlen <= static_cast(output.size() - outlen), ErrorCode::kerCorruptedMetadata); output.copyBytes(outlen, boxBuf.c_data(inlen), subBox.length); } @@ -655,7 +697,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { outBuf.alloc(outlen); outBuf.copyBytes(0, output.c_data(), outlen); auto oBox = reinterpret_cast(outBuf.data()); - ul2Data(reinterpret_cast(&oBox->type), kJp2BoxTypeJp2Header, bigEndian); + ul2Data(reinterpret_cast(&oBox->type), kJp2BoxTypeHeader, bigEndian); ul2Data(reinterpret_cast(&oBox->length), static_cast(outlen), bigEndian); } @@ -676,23 +718,22 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) { // Ensure that this is the correct image type if (!isJp2Type(*io_, true)) { - if (io_->error() || io_->eof()) - throw Error(ErrorCode::kerInputDataReadFailed); throw Error(ErrorCode::kerNoImageInInputData); } - // Write JPEG2000 Signature. + // Write JPEG2000 Signature (This is the 1st box) if (outIo.write(Jp2Signature, 12) != 12) throw Error(ErrorCode::kerImageWriteFailed); +#ifdef EXIV2_DEBUG_MESSAGES + std::cout << "Jp2Image::doWriteMetadata: JPEG 2000 Signature box written" << std::endl; +#endif + Jp2BoxHeader box = {0, 0}; byte boxDataSize[4]; byte boxUUIDtype[4]; - DataBuf bheaderBuf(8); // Box header : 4 bytes (data size) + 4 bytes (box type). - - // FIXME: Andreas, why the loop do not stop when EOF is taken from _io. The loop go out by an exception - // generated by a zero size data read. + DataBuf bheaderBuf(8); while (io_->tell() < static_cast(io_->size())) { #ifdef EXIV2_DEBUG_MESSAGES @@ -700,14 +741,9 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) { #endif // Read chunk header. - size_t bufRead = io_->read(bheaderBuf.data(), bheaderBuf.size()); - if (io_->error()) - throw Error(ErrorCode::kerFailedToReadImageData); - if (bufRead != bheaderBuf.size()) - throw Error(ErrorCode::kerInputDataReadFailed); + io_->readOrThrow(bheaderBuf.data(), bheaderBuf.size(), ErrorCode::kerInputDataReadFailed); // Decode box header. - box.length = bheaderBuf.read_uint32(0, bigEndian); box.type = bheaderBuf.read_uint32(4, bigEndian); @@ -733,26 +769,12 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) { enforce(box.length - 8 <= static_cast(io_->size() - io_->tell()), ErrorCode::kerCorruptedMetadata); // Read whole box : Box header + Box data (not fixed size - can be null). - DataBuf boxBuf(box.length); // Box header (8 bytes) + box data. - boxBuf.copyBytes(0, bheaderBuf.c_data(), 8); // Copy header. - bufRead = io_->read(boxBuf.data(8), box.length - 8); // Extract box data. - if (io_->error()) { -#ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::Jp2Image::doWriteMetadata: Error reading source file" << std::endl; -#endif - - throw Error(ErrorCode::kerFailedToReadImageData); - } - - if (bufRead != (box.length - 8)) { -#ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::Jp2Image::doWriteMetadata: Cannot read source file data" << std::endl; -#endif - throw Error(ErrorCode::kerInputDataReadFailed); - } + DataBuf boxBuf(box.length); // Box header (8 bytes) + box data. + boxBuf.copyBytes(0, bheaderBuf.c_data(), 8); // Copy header. + io_->readOrThrow(boxBuf.data(8), box.length - 8, ErrorCode::kerInputDataReadFailed); // Extract box data. switch (box.type) { - case kJp2BoxTypeJp2Header: { + case kJp2BoxTypeHeader: { DataBuf newBuf; encodeJp2Header(boxBuf, newBuf); #ifdef EXIV2_DEBUG_MESSAGES @@ -774,8 +796,8 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) { rawExif.copyBytes(0, &blob[0], blob.size()); DataBuf boxData(8 + 16 + rawExif.size()); - ul2Data(boxDataSize, static_cast(boxData.size()), Exiv2::bigEndian); - ul2Data(boxUUIDtype, kJp2BoxTypeUuid, Exiv2::bigEndian); + ul2Data(boxDataSize, static_cast(boxData.size()), bigEndian); + ul2Data(boxUUIDtype, kJp2BoxTypeUuid, bigEndian); boxData.copyBytes(0, boxDataSize, 4); boxData.copyBytes(4, boxUUIDtype, 4); boxData.copyBytes(8, kJp2UuidExif, 16); @@ -796,8 +818,8 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) { DataBuf rawIptc = IptcParser::encode(iptcData_); if (!rawIptc.empty()) { DataBuf boxData(8 + 16 + rawIptc.size()); - ul2Data(boxDataSize, static_cast(boxData.size()), Exiv2::bigEndian); - ul2Data(boxUUIDtype, kJp2BoxTypeUuid, Exiv2::bigEndian); + ul2Data(boxDataSize, static_cast(boxData.size()), bigEndian); + ul2Data(boxUUIDtype, kJp2BoxTypeUuid, bigEndian); boxData.copyBytes(0, boxDataSize, 4); boxData.copyBytes(4, boxUUIDtype, 4); boxData.copyBytes(8, kJp2UuidIptc, 16); @@ -824,8 +846,8 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) { DataBuf xmp(reinterpret_cast(xmpPacket_.data()), xmpPacket_.size()); DataBuf boxData(8 + 16 + xmp.size()); - ul2Data(boxDataSize, static_cast(boxData.size()), Exiv2::bigEndian); - ul2Data(boxUUIDtype, kJp2BoxTypeUuid, Exiv2::bigEndian); + ul2Data(boxDataSize, static_cast(boxData.size()), bigEndian); + ul2Data(boxUUIDtype, kJp2BoxTypeUuid, bigEndian); boxData.copyBytes(0, boxDataSize, 4); boxData.copyBytes(4, boxUUIDtype, 4); boxData.copyBytes(8, kJp2UuidXmp, 16); @@ -843,7 +865,7 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) { } case kJp2BoxTypeUuid: { - enforce(boxBuf.size() >= 24, Exiv2::ErrorCode::kerCorruptedMetadata); + enforce(boxBuf.size() >= 24, ErrorCode::kerCorruptedMetadata); if (boxBuf.cmpBytes(8, kJp2UuidExif, 16) == 0) { #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Exiv2::Jp2Image::doWriteMetadata: strip Exif Uuid box" << std::endl; @@ -897,13 +919,13 @@ Image::UniquePtr newJp2Instance(BasicIo::UniquePtr io, bool create) { bool isJp2Type(BasicIo& iIo, bool advance) { const int32_t len = 12; byte buf[len]; - iIo.read(buf, len); - if (iIo.error() || iIo.eof()) { + const size_t bytesRead = iIo.read(buf, len); + if (iIo.error() || iIo.eof() || bytesRead != len) { return false; } bool matched = (memcmp(buf, Jp2Signature, len) == 0); if (!advance || !matched) { - iIo.seek(-len, BasicIo::cur); + iIo.seek(-len, BasicIo::cur); // Return to original position } return matched; } diff --git a/test/data/test_reference_files/icc-test.out b/test/data/test_reference_files/icc-test.out index 7f8aa048..928709bb 100644 --- a/test/data/test_reference_files/icc-test.out +++ b/test/data/test_reference_files/icc-test.out @@ -735,7 +735,7 @@ STRUCTURE OF JPEG2000 FILE: Reagan2.jp2 12 | 20 | ftyp | 32 | 3185 | jp2h | 40 | 22 | sub:ihdr | ............. - 62 | 3155 | sub:colr | ......HLino....mntrRGB XYZ .. | pad: 2 0 0 | iccLength:3144 + 62 | 3155 | sub:colr | ......HLino....mntrRGB XYZ .. | iccLength:3144 3217 | 0 | jp2c | STRUCTURE OF JPEG2000 FILE: Reagan2.jp2 address | length | box | data @@ -743,7 +743,7 @@ STRUCTURE OF JPEG2000 FILE: Reagan2.jp2 12 | 20 | ftyp | 32 | 1613641 | jp2h | 40 | 22 | sub:ihdr | ............. - 62 | 1613611 | sub:colr | ...... APPL....prtrRGB Lab .. | pad: 2 0 0 | iccLength:1613600 + 62 | 1613611 | sub:colr | ...... APPL....prtrRGB Lab .. | iccLength:1613600 1613673 | 0 | jp2c | STRUCTURE OF JPEG2000 FILE: Reagan2.jp2 address | length | box | data @@ -751,7 +751,7 @@ STRUCTURE OF JPEG2000 FILE: Reagan2.jp2 12 | 20 | ftyp | 32 | 601 | jp2h | 40 | 22 | sub:ihdr | ............. - 62 | 571 | sub:colr | ......0ADBE....mntrRGB XYZ .. | pad: 2 0 0 | iccLength:560 + 62 | 571 | sub:colr | ......0ADBE....mntrRGB XYZ .. | iccLength:560 633 | 0 | jp2c | 1d3fda2edb4a89ab60a23c5f7c7d81dd 1d3fda2edb4a89ab60a23c5f7c7d81dd From 2b9f6ccf6bbe8e0d1443a30845a88dadeb9331b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Fri, 18 Mar 2022 14:35:49 +0100 Subject: [PATCH 3/5] Adapt tests to changes in JP2 --- tests/bugfixes/github/test_issue_1845.py | 7 +++++-- tests/bugfixes/github/test_issue_ghsa_583f_w9pm_99r2.py | 2 +- tests/bugfixes/github/test_issue_ghsa_mxw9_qx4c_6m8v.py | 8 +++++--- tests/regression_tests/test_regression_allfiles.py | 1 + tests/suite.conf | 2 ++ 5 files changed, 14 insertions(+), 6 deletions(-) diff --git a/tests/bugfixes/github/test_issue_1845.py b/tests/bugfixes/github/test_issue_1845.py index 6332ab18..4c5c9976 100644 --- a/tests/bugfixes/github/test_issue_1845.py +++ b/tests/bugfixes/github/test_issue_1845.py @@ -12,6 +12,9 @@ class TiffDirectoryWriteDirEntryAssert(metaclass=CaseMeta): filename = path("$tmp_path/issue_1845_poc.jp2") commands = ["$exiv2 -q -D +1 ad $filename"] - stderr = [""] + stderr = [ + """$exception_in_adjust """ + filename + """: +$kerCorruptedMetadata +"""] stdout = [""] - retval = [0] + retval = [1] diff --git a/tests/bugfixes/github/test_issue_ghsa_583f_w9pm_99r2.py b/tests/bugfixes/github/test_issue_ghsa_583f_w9pm_99r2.py index 808916ae..71a975cf 100644 --- a/tests/bugfixes/github/test_issue_ghsa_583f_w9pm_99r2.py +++ b/tests/bugfixes/github/test_issue_ghsa_583f_w9pm_99r2.py @@ -12,7 +12,7 @@ class Jp2ImagePrintStructureICC(metaclass=CaseMeta): filename = path("$data_path/issue_ghsa_583f_w9pm_99r2_poc.jp2") commands = ["$exiv2 -p C $filename"] stdout = [""] - stderr = ["""Exiv2 exception in print action for file $filename: + stderr = ["""$exiv2_exception_message """ + filename + """: $kerCorruptedMetadata """] retval = [1] diff --git a/tests/bugfixes/github/test_issue_ghsa_mxw9_qx4c_6m8v.py b/tests/bugfixes/github/test_issue_ghsa_mxw9_qx4c_6m8v.py index 8f8b6676..76e16eca 100644 --- a/tests/bugfixes/github/test_issue_ghsa_mxw9_qx4c_6m8v.py +++ b/tests/bugfixes/github/test_issue_ghsa_mxw9_qx4c_6m8v.py @@ -13,6 +13,8 @@ class Jp2ImageEncodeJp2HeaderOutOfBoundsRead2(metaclass=CaseMeta): filename = path("$tmp_path/issue_ghsa_mxw9_qx4c_6m8v_poc.jp2") commands = ["$exiv2 rm $filename"] stdout = [""] - retval = [0] - - compare_stderr = check_no_ASAN_UBSAN_errors + stderr = [ + """$exception_in_erase """ + filename + """: +$kerCorruptedMetadata +"""] + retval = [1] diff --git a/tests/regression_tests/test_regression_allfiles.py b/tests/regression_tests/test_regression_allfiles.py index 344476b1..3355615c 100644 --- a/tests/regression_tests/test_regression_allfiles.py +++ b/tests/regression_tests/test_regression_allfiles.py @@ -90,6 +90,7 @@ def get_valid_files(data_dir): "issue_960.poc.webp", "issue_ghsa_583f_w9pm_99r2_poc.jp2", "issue_ghsa_7569_phvm_vwc2_poc.jp2", + "issue_ghsa_mxw9_qx4c_6m8v_poc.jp2", "pocIssue283.jpg", "poc_1522.jp2", "xmpsdk.xmp", diff --git a/tests/suite.conf b/tests/suite.conf index 2cc34103..be31d327 100644 --- a/tests/suite.conf +++ b/tests/suite.conf @@ -50,5 +50,7 @@ addition_overflow_message: Overflow in addition exiv2_exception_message: Exiv2 exception in print action for file exiv2_overflow_exception_message: std::overflow_error exception in print action for file exception_in_extract: Exiv2 exception in extract action for file +exception_in_adjust: Exiv2 exception in adjust action for file +exception_in_erase: Exiv2 exception in erase action for file uncaught_exception: Uncaught exception: no_exif_data_found_retval: 253 From 1545a1bc4aa4323ad15607d007918f3483c71dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Fri, 18 Mar 2022 14:54:42 +0100 Subject: [PATCH 4/5] JP2 - stronger checks on Signatuer and FileType boxes --- src/CMakeLists.txt | 1 + src/jp2image.cpp | 125 +++++++++++++------------------- src/jp2image_int.cpp | 32 ++++++++ src/jp2image_int.hpp | 36 +++++++++ unitTests/CMakeLists.txt | 1 + unitTests/test_jp2image_int.cpp | 78 ++++++++++++++++++++ 6 files changed, 197 insertions(+), 76 deletions(-) create mode 100644 src/jp2image_int.cpp create mode 100644 src/jp2image_int.hpp create mode 100644 unitTests/test_jp2image_int.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b61d09ff..01bfe5df 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -17,6 +17,7 @@ add_library( exiv2lib_int OBJECT fujimn_int.cpp fujimn_int.hpp helper_functions.cpp helper_functions.hpp image_int.cpp image_int.hpp + jp2image_int.cpp jp2image_int.hpp makernote_int.cpp makernote_int.hpp minoltamn_int.cpp minoltamn_int.hpp nikonmn_int.cpp nikonmn_int.hpp diff --git a/src/jp2image.cpp b/src/jp2image.cpp index 75f7edcf..704882c5 100644 --- a/src/jp2image.cpp +++ b/src/jp2image.cpp @@ -1,6 +1,8 @@ // SPDX-License-Identifier: GPL-2.0-or-later // included header files +#include "jp2image.hpp" + #include "config.h" #include "basicio.hpp" @@ -9,7 +11,7 @@ #include "futils.hpp" #include "image.hpp" #include "image_int.hpp" -#include "jp2image.hpp" +#include "jp2image_int.hpp" #include "safe_op.hpp" #include "tiffimage.hpp" #include "types.hpp" @@ -28,8 +30,6 @@ constexpr uint32_t kJp2BoxTypeColorSpec = 0x636f6c72; // Color Specification constexpr uint32_t kJp2BoxTypeUuid = 0x75756964; // 'uuid' constexpr uint32_t kJp2BoxTypeClose = 0x6a703263; // 'jp2c' -const uint32_t brandJp2 = 0x6a703220; - // JPEG-2000 UUIDs for embedded metadata // // See http://www.jpeg.org/public/wg1n2600.doc for information about embedding IPTC-NAA data in JPEG-2000 files @@ -40,11 +40,11 @@ constexpr unsigned char kJp2UuidIptc[] = "\x33\xc7\xa4\xd2\xb8\x1d\x47\x23\xa0\x constexpr unsigned char kJp2UuidXmp[] = "\xbe\x7a\xcf\xcb\x97\xa9\x42\xe8\x9c\x71\x99\x94\x91\xe3\xaf\xac"; // See section B.1.1 (JPEG 2000 Signature box) of JPEG-2000 specification -constexpr unsigned char Jp2Signature[] = { +constexpr std::array Jp2Signature{ 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, }; -constexpr unsigned char Jp2Blank[] = { +constexpr std::array Jp2Blank{ 0x00, 0x00, 0x00, 0x0c, 0x6a, 0x50, 0x20, 0x20, 0x0d, 0x0a, 0x87, 0x0a, 0x00, 0x00, 0x00, 0x14, 0x66, 0x74, 0x79, 0x70, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x00, 0x6a, 0x70, 0x32, 0x20, 0x00, 0x00, 0x00, 0x2d, 0x6a, 0x70, 0x32, 0x68, 0x00, 0x00, 0x00, 0x16, 0x69, 0x68, 0x64, 0x72, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, @@ -61,26 +61,7 @@ constexpr unsigned char Jp2Blank[] = { 0x00, 0x00, 0xff, 0x93, 0xcf, 0xb4, 0x04, 0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0xff, 0xd9, }; -struct Jp2BoxHeader { - uint32_t length; - uint32_t type; -}; - -struct Jp2ImageHeaderBox { - uint32_t imageHeight; - uint32_t imageWidth; - uint16_t componentCount; - uint8_t bpc; //write(Jp2Blank, sizeof(Jp2Blank)) != sizeof(Jp2Blank)) { + if (io_->write(Jp2Blank.data(), Jp2Blank.size()) != Jp2Blank.size()) { #ifdef EXIV2_DEBUG_MESSAGES std::cerr << "Exiv2::Jp2Image:: Failed to create JPEG2000 image on memory" << std::endl; #endif @@ -153,16 +134,19 @@ void Jp2Image::readMetadata() { throw Error(ErrorCode::kerDataSourceOpenFailed, io_->path(), strError()); } IoCloser closer(*io_); - if (!isJp2Type(*io_, true)) { + if (!isJp2Type(*io_, false)) { throw Error(ErrorCode::kerNotAnImage, "JPEG-2000"); } - Jp2BoxHeader box = {0, 0}; - Jp2BoxHeader subBox = {0, 0}; - Jp2ImageHeaderBox ihdr = {0, 0, 0, 0, 0, 0, 0}; - Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; + Internal::Jp2BoxHeader box = {0, 0}; + Internal::Jp2BoxHeader subBox = {0, 0}; + Internal::Jp2ImageHeaderBox ihdr = {0, 0, 0, 0, 0, 0, 0}; + Internal::Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; size_t boxesCount = 0; - size_t boxem = 1000; // boxes max + const size_t boxem = 1000; // boxes max + uint32_t lastBoxTypeRead = 0; + bool boxSignatureFound = false; + bool boxFileTypeFound = false; while (io_->read(reinterpret_cast(&box), boxHSize) == boxHSize) { boxes_check(boxesCount++, boxem); @@ -180,30 +164,25 @@ void Jp2Image::readMetadata() { return; if (box.length == 1) { - /// \todo In this case, the real box size is given in bytes XLBox (bytes 8-15) + /// \todo In this case, the real box size is given in XLBox (bytes 8-15) } switch (box.type) { case kJp2BoxTypeSignature: { -#ifdef EXIV2_DEBUG_MESSAGES - std::cout << "Exiv2::Jp2Image::readMetadata: JPEG 2000 Signature box found" << std::endl; -#endif + if (boxSignatureFound) // Only one is allowed + throw Error(ErrorCode::kerCorruptedMetadata); + boxSignatureFound = true; break; } case kJp2BoxTypeFileTypeBox: { // This box shall immediately follow the JPEG 2000 Signature box - /// \todo All files shall contain one and only one File Type box. - assert(box.length >= 20); // 8 (box) + 4 (BR) + 4(MinV) + >=4 (CLn) - DataBuf data(box.length - boxHSize); - io_->read(data.data(), data.size()); - const uint32_t brand = data.read_uint32(0, bigEndian); - const uint32_t minorVersion = data.read_uint32(4, bigEndian); - const uint32_t compatibilityList = data.read_uint32(8, bigEndian); - // const size_t clCount = (data.size() - 8) / 4; - // for(size_t i = 0; i < clCount; i++) { - // uint32_t compatibilityList = data.read_uint32(8 + i*4, bigEndian); - // } - if (brand != brandJp2 || minorVersion != 0 || compatibilityList != brandJp2) + if (boxFileTypeFound || lastBoxTypeRead != kJp2BoxTypeSignature) { // Only one is allowed + throw Error(ErrorCode::kerCorruptedMetadata); + } + boxFileTypeFound = true; + std::vector boxData(box.length - boxHSize); + io_->readOrThrow(boxData.data(), boxData.size(), ErrorCode::kerCorruptedMetadata); + if (!Internal::isValidBoxFileType(boxData)) throw Error(ErrorCode::kerCorruptedMetadata); break; } @@ -397,6 +376,7 @@ void Jp2Image::readMetadata() { break; } } + lastBoxTypeRead = box.type; // Move to the next box. io_->seek(static_cast(position - boxHSize + box.length), BasicIo::beg); @@ -422,6 +402,7 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in bool bICC = option == kpsIccProfile; bool bXMP = option == kpsXMP; bool bIPTCErase = option == kpsIptcErase; + bool boxSignatureFound = false; if (bPrint) { out << "STRUCTURE OF JPEG2000 FILE: " << io_->path() << std::endl; @@ -429,9 +410,9 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in } if (bPrint || bXMP || bICC || bIPTCErase) { - Jp2BoxHeader box = {1, 1}; - Jp2BoxHeader subBox = {1, 1}; - Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; + Internal::Jp2BoxHeader box = {1, 1}; + Internal::Jp2BoxHeader subBox = {1, 1}; + Internal::Jp2UuidBox uuid = {{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}; bool bLF = false; while (box.length && box.type != kJp2BoxTypeClose && @@ -453,24 +434,17 @@ void Jp2Image::printStructure(std::ostream& out, PrintStructureOption option, in switch (box.type) { case kJp2BoxTypeSignature: { - /// \todo we should make sure that only 1 of this boxes is found - assert(box.length == 12); - DataBuf data(4); - io_->read(data.data(), data.size()); - if (data.read_uint32(0, bigEndian) != 0x0D0A870A) { + if (boxSignatureFound) // Only one is allowed throw Error(ErrorCode::kerCorruptedMetadata); - } + boxSignatureFound = true; break; } case kJp2BoxTypeFileTypeBox: { // This box shall immediately follow the JPEG 2000 Signature box /// \todo All files shall contain one and only one File Type box. - DataBuf data(12); - io_->read(data.data(), data.size()); - uint32_t brand = data.read_uint32(0, bigEndian); - uint32_t minorVersion = data.read_uint32(4, bigEndian); - uint32_t compatibilityList = data.read_uint32(8, bigEndian); - if (brand != brandJp2 || minorVersion != 0 || compatibilityList != brandJp2) + std::vector boxData(box.length - boxHSize); + io_->readOrThrow(boxData.data(), boxData.size(), ErrorCode::kerCorruptedMetadata); + if (!Internal::isValidBoxFileType(boxData)) throw Error(ErrorCode::kerCorruptedMetadata); break; } @@ -629,7 +603,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { size_t outlen = boxHSize; // now many bytes have we written to output? long inlen = boxHSize; // how many bytes have we read from boxBuf? enforce(boxHSize <= output.size(), ErrorCode::kerCorruptedMetadata); - auto pBox = reinterpret_cast(boxBuf.c_data()); + auto pBox = reinterpret_cast(boxBuf.c_data()); uint32_t length = getLong(reinterpret_cast(&pBox->length), bigEndian); enforce(length <= output.size(), ErrorCode::kerCorruptedMetadata); uint32_t count = boxHSize; @@ -638,12 +612,12 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { while (count < length && !bWroteColor) { enforce(boxHSize <= length - count, ErrorCode::kerCorruptedMetadata); - auto pSubBox = reinterpret_cast(p + count); + auto pSubBox = reinterpret_cast(p + count); // copy data. pointer could be into a memory mapped file which we will decode! - Jp2BoxHeader subBox; + Internal::Jp2BoxHeader subBox; memcpy(&subBox, pSubBox, boxHSize); - Jp2BoxHeader newBox = subBox; + Internal::Jp2BoxHeader newBox = subBox; if (count < length) { subBox.length = getLong(reinterpret_cast(&subBox.length), bigEndian); @@ -696,7 +670,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { // allocate the correct number of bytes, copy the data and update the box header outBuf.alloc(outlen); outBuf.copyBytes(0, output.c_data(), outlen); - auto oBox = reinterpret_cast(outBuf.data()); + auto oBox = reinterpret_cast(outBuf.data()); ul2Data(reinterpret_cast(&oBox->type), kJp2BoxTypeHeader, bigEndian); ul2Data(reinterpret_cast(&oBox->length), static_cast(outlen), bigEndian); } @@ -722,14 +696,14 @@ void Jp2Image::doWriteMetadata(BasicIo& outIo) { } // Write JPEG2000 Signature (This is the 1st box) - if (outIo.write(Jp2Signature, 12) != 12) + if (outIo.write(Jp2Signature.data(), Jp2Signature.size()) != 12) throw Error(ErrorCode::kerImageWriteFailed); #ifdef EXIV2_DEBUG_MESSAGES std::cout << "Jp2Image::doWriteMetadata: JPEG 2000 Signature box written" << std::endl; #endif - Jp2BoxHeader box = {0, 0}; + Internal::Jp2BoxHeader box = {0, 0}; byte boxDataSize[4]; byte boxUUIDtype[4]; @@ -917,15 +891,14 @@ Image::UniquePtr newJp2Instance(BasicIo::UniquePtr io, bool create) { } bool isJp2Type(BasicIo& iIo, bool advance) { - const int32_t len = 12; - byte buf[len]; - const size_t bytesRead = iIo.read(buf, len); - if (iIo.error() || iIo.eof() || bytesRead != len) { + byte buf[Jp2Signature.size()]; + const size_t bytesRead = iIo.read(buf, Jp2Signature.size()); + if (iIo.error() || iIo.eof() || bytesRead != Jp2Signature.size()) { return false; } - bool matched = (memcmp(buf, Jp2Signature, len) == 0); + bool matched = (memcmp(buf, Jp2Signature.data(), Jp2Signature.size()) == 0); if (!advance || !matched) { - iIo.seek(-len, BasicIo::cur); // Return to original position + iIo.seek(-Jp2Signature.size(), BasicIo::cur); // Return to original position } return matched; } diff --git a/src/jp2image_int.cpp b/src/jp2image_int.cpp new file mode 100644 index 00000000..bd66be82 --- /dev/null +++ b/src/jp2image_int.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "jp2image_int.hpp" + +#include "error.hpp" +#include "types.hpp" + +#include + +namespace Exiv2::Internal { + +bool isValidBoxFileType(const std::vector &boxData) { + // BR & MinV are obligatory (4 + 4 bytes). Afterwards we have N compatibility lists (of size 4) + if ((boxData.size() - 8u) % 4u != 0) { + return false; + } + + const size_t N = (boxData.size() - 8u) / 4u; + const uint32_t brand = getULong(boxData.data(), bigEndian); + const uint32_t minorVersion = getULong(boxData.data() + 4, bigEndian); + + bool clWithRightBrand = false; + for (size_t i = 0; i < N; i++) { + uint32_t compatibilityList = getULong(boxData.data() + 8 + i * 4, bigEndian); + if (compatibilityList == brandJp2) { + clWithRightBrand = true; + break; + } + } + return (brand == brandJp2 && minorVersion == 0 && clWithRightBrand); +} +} // namespace Exiv2::Internal diff --git a/src/jp2image_int.hpp b/src/jp2image_int.hpp new file mode 100644 index 00000000..1804771b --- /dev/null +++ b/src/jp2image_int.hpp @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#ifndef JP2IMAGE_INT_HPP +#define JP2IMAGE_INT_HPP + +#include +#include + +namespace Exiv2::Internal { + +struct Jp2BoxHeader { + uint32_t length; + uint32_t type; +}; + +struct Jp2ImageHeaderBox { + uint32_t imageHeight; + uint32_t imageWidth; + uint16_t componentCount; + uint8_t bpc; //& boxData); +} // namespace Exiv2::Internal + +#endif // JP2IMAGE_INT_HPP diff --git a/unitTests/CMakeLists.txt b/unitTests/CMakeLists.txt index df7cb100..4a766429 100644 --- a/unitTests/CMakeLists.txt +++ b/unitTests/CMakeLists.txt @@ -15,6 +15,7 @@ add_executable(unit_tests test_image_int.cpp test_ImageFactory.cpp test_jp2image.cpp + test_jp2image_int.cpp test_IptcKey.cpp test_LangAltValueRead.cpp test_pngimage.cpp diff --git a/unitTests/test_jp2image_int.cpp b/unitTests/test_jp2image_int.cpp new file mode 100644 index 00000000..6af2bb2a --- /dev/null +++ b/unitTests/test_jp2image_int.cpp @@ -0,0 +1,78 @@ +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "jp2image_int.hpp" // Internals of JPEG-2000 standard + +#include + +using namespace Exiv2::Internal; + +namespace { +void setValidValues(std::vector& boxData) { + // The first 4 bytes correspond to the BR (Brand). It must have the value 'jp2\040' + boxData[0] = 'j'; + boxData[1] = 'p'; + boxData[2] = '2'; + boxData[3] = '\040'; + + // The next 4 bytes correspond to the MinV (Minor version). It is a 4-byte unsigned int with value 0 + + // The only available Compatibility list also has the value 'jp2\040' + boxData[8] = 'j'; + boxData[9] = 'p'; + boxData[10] = '2'; + boxData[11] = '\040'; +} +} // namespace + +TEST(Jp2_FileTypeBox, isNotValidWithoutProperValuesSet) { + const std::vector boxData(12); + ASSERT_FALSE(isValidBoxFileType(boxData)); +} + +TEST(Jp2_FileTypeBox, isValidWithMinimumPossibleSizeAndValidValues) { + std::vector boxData(12); + setValidValues(boxData); + ASSERT_TRUE(isValidBoxFileType(boxData)); +} + +TEST(Jp2_FileTypeBox, isNotValidWithMinimumPossibleSizeButInvalidBrand) { + std::vector boxData(12); + setValidValues(boxData); + boxData[2] = '3'; // Change byte in the brand field + + ASSERT_FALSE(isValidBoxFileType(boxData)); +} + +TEST(Jp2_FileTypeBox, isNotValidWithMinimumPossibleSizeButInvalidCL1) { + std::vector boxData(12); + setValidValues(boxData); + boxData[10] = '3'; // Change byte in the CL1 + + ASSERT_FALSE(isValidBoxFileType(boxData)); +} + +// ---------------------------------------------------------- + +TEST(Jp2_FileTypeBox, withInvalidBoxDataSizeIsInvalid) { + std::vector boxData(13); // 12 + 1 (the extra byte causes problems) + ASSERT_FALSE(isValidBoxFileType(boxData)); +} + +TEST(Jp2_FileTypeBox, with2CLs_lastOneWithBrandValue_isValid) { + std::vector boxData(16); + // The first 4 bytes correspond to the BR (Brand). It must have the value 'jp2\040' + boxData[0] = 'j'; + boxData[1] = 'p'; + boxData[2] = '2'; + boxData[3] = '\040'; + + // The next 4 bytes correspond to the MinV (Minor version). It is a 4-byte unsigned int with value 0 + + // The 2nd Compatibility list has the value 'jp2\040' + boxData[12] = 'j'; + boxData[13] = 'p'; + boxData[14] = '2'; + boxData[15] = '\040'; + + ASSERT_TRUE(isValidBoxFileType(boxData)); +} From 1ad9e52f3ce5c53b344ddfbfe66d2f993150e49e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20D=C3=ADaz=20M=C3=A1s?= Date: Fri, 18 Mar 2022 15:07:45 +0100 Subject: [PATCH 5/5] Fix windows build --- src/jp2image.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/jp2image.cpp b/src/jp2image.cpp index 704882c5..7c977fc8 100644 --- a/src/jp2image.cpp +++ b/src/jp2image.cpp @@ -601,7 +601,7 @@ void Jp2Image::writeMetadata() { void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { DataBuf output(boxBuf.size() + iccProfile_.size() + 100); // allocate sufficient space size_t outlen = boxHSize; // now many bytes have we written to output? - long inlen = boxHSize; // how many bytes have we read from boxBuf? + size_t inlen = boxHSize; // how many bytes have we read from boxBuf? enforce(boxHSize <= output.size(), ErrorCode::kerCorruptedMetadata); auto pBox = reinterpret_cast(boxBuf.c_data()); uint32_t length = getLong(reinterpret_cast(&pBox->length), bigEndian); @@ -651,7 +651,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { const char* pad = "\x02\x00\x00"; uint32_t psize = 3; newlen = sizeof(newBox) + psize + iccProfile_.size(); - enforce(newlen <= static_cast(output.size() - outlen), ErrorCode::kerCorruptedMetadata); + enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata); ul2Data(reinterpret_cast(&newBox.length), static_cast(newlen), bigEndian); ul2Data(reinterpret_cast(&newBox.type), newBox.type, bigEndian); output.copyBytes(outlen, &newBox, sizeof(newBox)); @@ -659,7 +659,7 @@ void Jp2Image::encodeJp2Header(const DataBuf& boxBuf, DataBuf& outBuf) { output.copyBytes(outlen + sizeof(newBox) + psize, iccProfile_.c_data(), iccProfile_.size()); } } else { - enforce(newlen <= static_cast(output.size() - outlen), ErrorCode::kerCorruptedMetadata); + enforce(newlen <= output.size() - outlen, ErrorCode::kerCorruptedMetadata); output.copyBytes(outlen, boxBuf.c_data(inlen), subBox.length); } @@ -898,7 +898,7 @@ bool isJp2Type(BasicIo& iIo, bool advance) { } bool matched = (memcmp(buf, Jp2Signature.data(), Jp2Signature.size()) == 0); if (!advance || !matched) { - iIo.seek(-Jp2Signature.size(), BasicIo::cur); // Return to original position + iIo.seek(-static_cast(Jp2Signature.size()), BasicIo::cur); // Return to original position } return matched; }