Files
opencv-MIRROR/modules/imgcodecs/test/test_gdal.cpp
uwezkhan 52aa8c2961 Merge pull request #29439 from uwezkhan:gdal-write-pixel-channel-bound
gdal: skip raster band mapped to an out-of-range channel in readData - #29439

Repro: read a 3-band GDT_Byte raster (no palette) whose third band is tagged `GCI_AlphaBand`, via `imread(path, IMREAD_LOAD_GDAL)`. ASan reports a heap-buffer-overflow WRITE of size 1 at `grfmt_gdal.cpp:259`.
Cause: an alpha band makes `readData` pass `color = 3`, but the `gdalChannels == 3 && image.channels() == 3` branch of `write_pixel` indexes `Vec3b[channel]` with no bound, so `channel == 3` stores one element past the 3-lane pixel. On the last pixel that lands past the Mat buffer.
Fix (per review): `readData` checks the mapped color index against `img.channels()` in the band loop, before the pixel iteration. An out-of-range band is skipped as a whole with a `CV_LOG_WARNING` naming the band and range, instead of a silent per-pixel guard. Valid RGB rasters decode identically.

### Pull Request Readiness Checklist

See details at https://github.com/opencv/opencv/wiki/How_to_contribute#making-a-good-pull-request

- [x] I agree to contribute to the project under Apache 2 License.
- [x] To the best of my knowledge, the proposed patch is not based on a code under GPL or another license that is incompatible with OpenCV
- [x] The PR is proposed to the proper branch
- [ ] There is a reference to the original bug report and related work
- [ ] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
- [ ] The feature is well documented and sample code can be built with the project CMake
2026-08-13 16:12:22 +03:00

108 lines
5.5 KiB
C++

// This file is part of OpenCV project.
// It is subject to the license terms in the LICENSE file found in the top-level directory
// of this distribution and at http://opencv.org/license.html
#include "test_precomp.hpp"
#include "test_common.hpp"
namespace opencv_test { namespace {
#ifdef HAVE_GDAL
static void test_gdal_read(const string filename, bool required = true) {
const string path = cvtest::findDataFile(filename);
Mat img;
ASSERT_NO_THROW(img = imread(path, cv::IMREAD_LOAD_GDAL | cv::IMREAD_ANYDEPTH | cv::IMREAD_ANYCOLOR));
if(!required && img.empty())
{
throw SkipTestException("GDAL is built wihout required back-end support");
}
ASSERT_FALSE(img.empty());
EXPECT_EQ(3, img.cols);
EXPECT_EQ(5, img.rows);
EXPECT_EQ(CV_MAKETYPE(CV_32F, 7), img.type());
EXPECT_EQ(101.125, (img.at<Vec<float, 7>>(0, 0)[0]));
EXPECT_EQ(203.500, (img.at<Vec<float, 7>>(2, 1)[3]));
EXPECT_EQ(305.875, (img.at<Vec<float, 7>>(4, 2)[6]));
}
TEST(Imgcodecs_gdal, read_envi)
{
test_gdal_read("../cv/gdal/envi_test.raw");
}
TEST(Imgcodecs_gdal, read_fits)
{
// .fit test is optional because GDAL may be built wihtout CFITSIO library support
test_gdal_read("../cv/gdal/fits_test.fit", false);
}
// See https://github.com/opencv/opencv/pull/29439
// A 3-band GDT_Byte raster (no palette, so the Mat is CV_8UC3) whose third band
// is tagged GCI_AlphaBand makes readData pass channel index 3 into the
// gdalChannels == 3 && image.channels() == 3 branch of write_pixel, which indexed
// a Vec3b out of bounds. The alpha band must be dropped, leaving red and green.
TEST(Imgcodecs_gdal, read_3band_alpha_channel_bound)
{
// 4x4 GeoTIFF, bands Red/Green/Alpha = 11/22/44, band 3 tagged GCI_AlphaBand
const uchar tiff_alpha3[] = {
0x49, 0x49, 0x2a, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x03, 0x00, 0x01, 0x00,
0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x01, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00,
0x00, 0x00, 0x02, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x03, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x06, 0x01, 0x03, 0x00, 0x01, 0x00,
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x11, 0x01, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00, 0x9a, 0x01,
0x00, 0x00, 0x15, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x16, 0x01,
0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x17, 0x01, 0x04, 0x00, 0x01, 0x00,
0x00, 0x00, 0x30, 0x00, 0x00, 0x00, 0x1c, 0x01, 0x03, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
0x00, 0x00, 0x53, 0x01, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0xa4, 0x00, 0x00, 0x00, 0x80, 0xa4,
0x02, 0x00, 0xf0, 0x00, 0x00, 0x00, 0xaa, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, 0x00,
0x08, 0x00, 0x08, 0x00, 0x01, 0x00, 0x01, 0x00, 0x01, 0x00, 0x3c, 0x47, 0x44, 0x41, 0x4c, 0x4d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x49, 0x74, 0x65, 0x6d,
0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x49, 0x4e, 0x54, 0x45,
0x52, 0x50, 0x22, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3d, 0x22, 0x30, 0x22, 0x20, 0x72,
0x6f, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70,
0x22, 0x3e, 0x52, 0x65, 0x64, 0x3c, 0x2f, 0x49, 0x74, 0x65, 0x6d, 0x3e, 0x0a, 0x20, 0x20, 0x3c,
0x49, 0x74, 0x65, 0x6d, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d, 0x22, 0x43, 0x4f, 0x4c, 0x4f, 0x52,
0x49, 0x4e, 0x54, 0x45, 0x52, 0x50, 0x22, 0x20, 0x73, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3d, 0x22,
0x31, 0x22, 0x20, 0x72, 0x6f, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, 0x6f, 0x72, 0x69, 0x6e,
0x74, 0x65, 0x72, 0x70, 0x22, 0x3e, 0x47, 0x72, 0x65, 0x65, 0x6e, 0x3c, 0x2f, 0x49, 0x74, 0x65,
0x6d, 0x3e, 0x0a, 0x20, 0x20, 0x3c, 0x49, 0x74, 0x65, 0x6d, 0x20, 0x6e, 0x61, 0x6d, 0x65, 0x3d,
0x22, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x49, 0x4e, 0x54, 0x45, 0x52, 0x50, 0x22, 0x20, 0x73, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x3d, 0x22, 0x32, 0x22, 0x20, 0x72, 0x6f, 0x6c, 0x65, 0x3d, 0x22, 0x63,
0x6f, 0x6c, 0x6f, 0x72, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x70, 0x22, 0x3e, 0x41, 0x6c, 0x70, 0x68,
0x61, 0x3c, 0x2f, 0x49, 0x74, 0x65, 0x6d, 0x3e, 0x0a, 0x3c, 0x2f, 0x47, 0x44, 0x41, 0x4c, 0x4d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x3e, 0x0a, 0x00, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c,
0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b,
0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16,
0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c, 0x0b, 0x16, 0x2c,
};
const string filename = cv::tempfile(".tif");
FILE* fp = fopen(filename.c_str(), "wb");
ASSERT_TRUE(fp != NULL);
ASSERT_EQ((size_t)1, fwrite(tiff_alpha3, sizeof(tiff_alpha3), 1, fp));
fclose(fp);
Mat img;
ASSERT_NO_THROW(img = imread(filename, cv::IMREAD_LOAD_GDAL));
if (img.empty())
{
remove(filename.c_str());
throw SkipTestException("GDAL is built without GTiff back-end support");
}
EXPECT_EQ(4, img.cols);
EXPECT_EQ(4, img.rows);
EXPECT_EQ(CV_8UC3, img.type());
// the alpha band maps to channel index 3 and is dropped; red and green stay
EXPECT_EQ(11, img.at<Vec3b>(0, 0)[2]);
EXPECT_EQ(22, img.at<Vec3b>(0, 0)[1]);
EXPECT_EQ(11, img.at<Vec3b>(3, 3)[2]);
EXPECT_EQ(22, img.at<Vec3b>(3, 3)[1]);
EXPECT_EQ(0, remove(filename.c_str()));
}
#endif // HAVE_GDAL
}} // namespace