diff --git a/modules/imgcodecs/src/grfmt_png.cpp b/modules/imgcodecs/src/grfmt_png.cpp index 7febc3153b..c4b5a2c3a6 100644 --- a/modules/imgcodecs/src/grfmt_png.cpp +++ b/modules/imgcodecs/src/grfmt_png.cpp @@ -133,6 +133,7 @@ const uint32_t id_bKGD = 0x624B4744; // The bKGD chunk specifies a default backg const uint32_t id_tRNS = 0x74524E53; // The tRNS chunk provides transparency information const uint32_t id_tEXt = 0x74455874; // The tEXt chunk stores metadata as text in key-value pairs const uint32_t id_IEND = 0x49454E44; // end/footer chunk +const uint32_t id_CgBI = 0x43674249; // The CgBI chunk (Apple private) is not supported. APNGFrame::APNGFrame() { @@ -285,9 +286,18 @@ bool PngDecoder::readHeader() if (!readFromStreamOrBuffer(&sig, 8)) return false; + // IHDR chunk shall be first. ( https://www.w3.org/TR/png-3/#5ChunkOrdering ) id = read_chunk(m_chunkIHDR); - if (id != id_IHDR) + if (id == id_CgBI) + { + CV_LOG_ERROR(NULL, "CgBI chunk (Apple private) found as the first chunk. IHDR is expected."); return false; + } + if (id != id_IHDR) + { + CV_LOG_ERROR(NULL, "IHDR chunk shall be first. This data may be broken or malformed."); + return false; + } m_is_fcTL_loaded = false; while (true) diff --git a/modules/imgcodecs/test/test_png.cpp b/modules/imgcodecs/test/test_png.cpp index 95b0bc0793..f271950a5b 100644 --- a/modules/imgcodecs/test/test_png.cpp +++ b/modules/imgcodecs/test/test_png.cpp @@ -110,6 +110,44 @@ TEST(Imgcodecs_Png, read_color_palette_with_alpha) EXPECT_EQ(img.at(0, 1), Vec3b(255, 0, 0)); } +// IHDR shall be first. +// See https://github.com/opencv/opencv/issues/27295 +TEST(Imgcodecs_Png, decode_regression27295) +{ + vector buff; + Mat src = Mat::zeros(240, 180, CV_8UC3); + vector param; + EXPECT_NO_THROW(imencode(".png", src, buff, param)); + + Mat img; + + // If IHDR chunk found as the first chunk, output shall not be empty. + // 8 means PNG signature length. + // 4 means length field(uint32_t). + EXPECT_EQ(buff[8+4+0], 'I'); + EXPECT_EQ(buff[8+4+1], 'H'); + EXPECT_EQ(buff[8+4+2], 'D'); + EXPECT_EQ(buff[8+4+3], 'R'); + EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR)); + EXPECT_FALSE(img.empty()); + + // If Non-IHDR chunk found as the first chunk, output shall be empty. + buff[8+4+0] = 'i'; // Not 'I' + buff[8+4+1] = 'H'; + buff[8+4+2] = 'D'; + buff[8+4+3] = 'R'; + EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR)); + EXPECT_TRUE(img.empty()); + + // If CgBI chunk (Apple private) found as the first chunk, output shall be empty with special message. + buff[8+4+0] = 'C'; + buff[8+4+1] = 'g'; + buff[8+4+2] = 'B'; + buff[8+4+3] = 'I'; + EXPECT_NO_THROW(img = imdecode(buff, IMREAD_COLOR)); + EXPECT_TRUE(img.empty()); +} + typedef testing::TestWithParam Imgcodecs_Png_PngSuite; TEST_P(Imgcodecs_Png_PngSuite, decode)