Merge pull request #27811 from Kumataro:fix27789

imgcodecs: bmp: relax decoding size limit to over 1GiB #27811

Close https://github.com/opencv/opencv/issues/27789
Close https://github.com/opencv/opencv/issues/23233

### 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
- [x] There is a reference to the original bug report and related work
- [x] There is accuracy test, performance test and test data in opencv_extra repository, if applicable
      Patch to opencv_extra has the same branch name.
- [ ] The feature is well documented and sample code can be built with the project CMake
This commit is contained in:
Kumataro 2025-09-22 17:49:47 +09:00 committed by GitHub
parent 744d5ecd14
commit 3c3a26b6ab
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 95 additions and 19 deletions

View File

@ -59,7 +59,17 @@ void RBaseStream::readBlock()
throw RBS_THROW_EOS;
}
#ifdef _WIN32
// See https://learn.microsoft.com/en-us/cpp/c-runtime-library/reference/fseek-fseeki64?view=msvc-170
// See https://en.wikipedia.org/wiki/64-bit_computing#64-bit_data_models
// - Windows uses LLP64 data model, sizeof long int = 32.
// - Linux uses LP64 data model, sizeof long int = 64.
// So for Windows, we have to use _fseeki64() instead of fseek().
_fseeki64( m_file, m_block_pos, SEEK_SET );
#else
fseek( m_file, m_block_pos, SEEK_SET );
#endif
size_t readed = fread( m_start, 1, m_block_size, m_file );
m_end = m_start + readed;
@ -120,7 +130,7 @@ void RBaseStream::release()
}
void RBaseStream::setPos( int pos )
void RBaseStream::setPos( int64_t pos )
{
CV_Assert(isOpened() && pos >= 0);
@ -132,7 +142,7 @@ void RBaseStream::setPos( int pos )
}
int offset = pos % m_block_size;
int old_block_pos = m_block_pos;
int64_t old_block_pos = m_block_pos;
m_block_pos = pos - offset;
m_current = m_start + offset;
if (old_block_pos != m_block_pos)
@ -140,16 +150,16 @@ void RBaseStream::setPos( int pos )
}
int RBaseStream::getPos()
int64_t RBaseStream::getPos()
{
CV_Assert(isOpened());
int pos = validateToInt((m_current - m_start) + m_block_pos);
int64_t pos = validateToInt64((m_current - m_start) + m_block_pos);
CV_Assert(pos >= m_block_pos); // overflow check
CV_Assert(pos >= 0); // overflow check
return pos;
}
void RBaseStream::skip( int bytes )
void RBaseStream::skip( int64_t bytes )
{
CV_Assert(bytes >= 0);
uchar* old = m_current;

View File

@ -45,9 +45,9 @@ public:
virtual bool open( const Mat& buf );
virtual void close();
bool isOpened();
void setPos( int pos );
int getPos();
void skip( int bytes );
void setPos( int64_t pos );
int64_t getPos();
void skip( int64_t bytes );
protected:
@ -57,7 +57,7 @@ protected:
uchar* m_current;
FILE* m_file;
int m_block_size;
int m_block_pos;
int64_t m_block_pos;
bool m_is_opened;
virtual void readBlock();

View File

@ -237,9 +237,6 @@ bool BmpDecoder::readData( Mat& img )
int nch = color ? 3 : 1;
int y, width3 = m_width*nch;
// FIXIT: use safe pointer arithmetic (avoid 'int'), use size_t, intptr_t, etc
CV_Assert(((uint64)m_height * m_width * nch < (CV_BIG_UINT(1) << 30)) && "BMP reader implementation doesn't support large images >= 1Gb");
if( m_offset < 0 || !m_strm.isOpened())
return false;

View File

@ -87,7 +87,7 @@ protected:
PaletteEntry m_palette[256];
Origin m_origin;
int m_bpp;
int m_offset;
int64_t m_offset;
BmpCompression m_rle_code;
uint m_rgba_mask[4];
int m_rgba_bit_offset[4];

View File

@ -78,8 +78,8 @@ public:
protected:
RLByteStream m_strm;
int m_maxval, m_channels, m_sampledepth, m_offset,
selected_fmt;
int64_t m_offset;
int m_maxval, m_channels, m_sampledepth, selected_fmt;
bool bit_mode;
};

View File

@ -79,7 +79,7 @@ protected:
RLByteStream m_strm;
PaletteEntry m_palette[256];
int m_bpp;
int m_offset;
int64_t m_offset;
bool m_binary;
int m_maxval;
};

View File

@ -94,7 +94,7 @@ bool SunRasterDecoder::readHeader()
m_type = IsColorPalette( m_palette, m_bpp ) ? CV_8UC3 : CV_8UC1;
m_offset = m_strm.getPos();
CV_Assert(m_offset == 32 + m_maplength);
CV_Assert(m_offset == static_cast<int64_t>(32 + m_maplength));
result = true;
}
}
@ -107,7 +107,7 @@ bool SunRasterDecoder::readHeader()
m_offset = m_strm.getPos();
CV_Assert(m_offset == 32 + m_maplength);
CV_Assert(m_offset == static_cast<int64_t>(32 + m_maplength));
result = true;
}
}

View File

@ -84,7 +84,7 @@ protected:
RMByteStream m_strm;
PaletteEntry m_palette[256];
int m_bpp;
int m_offset;
int64_t m_offset;
SunRasType m_encoding;
SunRasMapType m_maptype;
int m_maplength;

View File

@ -51,6 +51,13 @@ int validateToInt(size_t sz)
return valueInt;
}
int64_t validateToInt64(size_t sz)
{
int64_t valueInt = static_cast<int64_t>(sz);
CV_Assert((size_t)valueInt == sz);
return valueInt;
}
#define SCALE 14
#define cR (int)(0.299*(1 << SCALE) + 0.5)
#define cG (int)(0.587*(1 << SCALE) + 0.5)

View File

@ -45,6 +45,7 @@
namespace cv {
int validateToInt(size_t step);
int64_t validateToInt64(size_t step);
template <typename _Tp> static inline
size_t safeCastToSizeT(const _Tp v_origin, const char* msg)

View File

@ -0,0 +1,61 @@
// 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"
#include <vector>
namespace opencv_test { namespace {
// See https://github.com/opencv/opencv/issues/27789
// See https://github.com/opencv/opencv/issues/23233
TEST(Imgcodecs_BMP, encode_decode_over1GB_regression27789)
{
applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_LONG );
// Create large Mat over 1GB
// 20000 px * 18000 px * 24 bpp(3ch) = 1,080,000,000 bytes
// 1 GiB = 1,073,741,824 bytes
cv::Mat src(20000, 18000, CV_8UC3, cv::Scalar(0,0,0));
// Encode large BMP file.
std::vector<uint8_t> buf;
bool ret = false;
ASSERT_NO_THROW(ret = cv::imencode(".bmp", src, buf, {}));
ASSERT_TRUE(ret);
src.release(); // To reduce usage memory, it is needed.
// Decode large BMP file.
cv::Mat dst;
ASSERT_NO_THROW(dst = cv::imdecode(buf, cv::IMREAD_COLOR));
ASSERT_FALSE(dst.empty());
}
TEST(Imgcodecs_BMP, write_read_over1GB_regression27789)
{
// tag CV_TEST_TAG_VERYLONG applied to skip on CI. The test writes ~1GB file.
applyTestTag( CV_TEST_TAG_MEMORY_2GB, CV_TEST_TAG_VERYLONG );
string bmpFilename = cv::tempfile(".bmp"); // To remove it, test must use EXPECT_* instead of ASSERT_*.
// Create large Mat over 1GB
// 20000 px * 18000 px * 24 bpp(3ch) = 1,080,000,000 bytes
// 1 GiB = 1,073,741,824 bytes
cv::Mat src(20000, 18000, CV_8UC3, cv::Scalar(0,0,0));
// Write large BMP file.
bool ret = false;
EXPECT_NO_THROW(ret = cv::imwrite(bmpFilename, src, {}));
EXPECT_TRUE(ret);
// Read large BMP file.
cv::Mat dst;
EXPECT_NO_THROW(dst = cv::imread(bmpFilename, cv::IMREAD_COLOR));
EXPECT_FALSE(dst.empty());
remove(bmpFilename.c_str());
}
}} // namespace