Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport release/3.8] BMP: fix reading images larger than 4GB #9179

Merged
merged 1 commit into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions autotest/gcore/bmp_read.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,13 @@
# Boston, MA 02111-1307, USA.
###############################################################################


import gdaltest
import pytest

from osgeo import gdal

pytestmark = pytest.mark.require_driver("HDF4")

init_list = [
("1bit.bmp", 200),
("4bit_pal.bmp", 2587),
Expand All @@ -43,12 +46,19 @@
init_list,
ids=[tup[0].split(".")[0] for tup in init_list],
)
@pytest.mark.require_driver("BMP")
def test_bmp_open(filename, checksum):
ut = gdaltest.GDALTest("BMP", filename, 1, checksum)
ut.testOpen()


def test_bmp_read_more_than_4GB():

ds = gdal.Open("/vsisparse/data/bmp/huge_sparse.xml")
assert ds.RasterXSize == 65536
assert ds.RasterYSize == 65536
assert ds.GetRasterBand(1).ReadRaster(65535, 65535, 1, 1) == b"\0"


def test_bmp_online_1():

gdaltest.download_or_skip(
Expand Down
Binary file added autotest/gcore/data/bmp/huge_header.bmp.bin
Binary file not shown.
9 changes: 9 additions & 0 deletions autotest/gcore/data/bmp/huge_sparse.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<VSISparseFile>
<Length>12884901942</Length>
<SubfileRegion>
<Filename relative="1">huge_header.bmp.bin</Filename>
<DestinationOffset>0</DestinationOffset>
<SourceOffset>0</SourceOffset>
<RegionLength>128</RegionLength>
</SubfileRegion>
</VSISparseFile>
30 changes: 16 additions & 14 deletions frmts/bmp/bmpdataset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ class BMPDataset final : public GDALPamDataset
double adfGeoTransform[6];
int bGeoTransformValid;
bool m_bNewFile = false;
vsi_l_offset m_nLargeFileSize = 0;
vsi_l_offset m_nFileSize = 0;

char *pszFilename;
VSILFILE *fp;
Expand Down Expand Up @@ -719,16 +719,17 @@ BMPComprRasterBand::BMPComprRasterBand(BMPDataset *poDSIn, int nBandIn)
return;
}

if (poDSIn->sFileHeader.iSize <= poDSIn->sFileHeader.iOffBits ||
poDSIn->sFileHeader.iSize - poDSIn->sFileHeader.iOffBits > knIntMax)
if (poDSIn->m_nFileSize <= poDSIn->sFileHeader.iOffBits ||
poDSIn->m_nFileSize - poDSIn->sFileHeader.iOffBits > knIntMax)
{
CPLError(CE_Failure, CPLE_NotSupported, "Invalid header");
return;
}

GUInt32 iComprSize =
poDSIn->sFileHeader.iSize - poDSIn->sFileHeader.iOffBits;
GUInt32 iUncomprSize = poDS->GetRasterXSize() * poDS->GetRasterYSize();
const GUInt32 iComprSize = static_cast<GUInt32>(
poDSIn->m_nFileSize - poDSIn->sFileHeader.iOffBits);
const GUInt32 iUncomprSize =
poDS->GetRasterXSize() * poDS->GetRasterYSize();

#ifdef DEBUG
CPLDebug("BMP", "RLE compression detected.");
Expand Down Expand Up @@ -973,9 +974,9 @@ BMPDataset::~BMPDataset()
// Extend the file with zeroes if needed
VSIFSeekL(fp, 0, SEEK_END);

if (VSIFTellL(fp) < m_nLargeFileSize)
if (VSIFTellL(fp) < m_nFileSize)
{
VSIFTruncateL(fp, m_nLargeFileSize);
VSIFTruncateL(fp, m_nFileSize);
}
}

Expand Down Expand Up @@ -1122,17 +1123,18 @@ GDALDataset *BMPDataset::Open(GDALOpenInfo *poOpenInfo)
#ifdef CPL_MSB
CPL_SWAP32PTR(&poDS->sFileHeader.iOffBits);
#endif
poDS->sFileHeader.iSize = (GUInt32)sStat.st_size;
poDS->m_nFileSize = sStat.st_size;

#ifdef BMP_DEBUG
CPLDebug("BMP", "File size %d bytes.", poDS->sFileHeader.iSize);
CPLDebug("BMP", "File size " CPL_FRMT_GUIB " bytes.",
static_cast<GUIntBig>(poDS->m_nFileSize));
CPLDebug("BMP", "Image offset 0x%x bytes from file start.",
poDS->sFileHeader.iOffBits);
#endif

// Validatate iOffBits
if (poDS->sFileHeader.iOffBits <= BFH_SIZE + SIZE_OF_INFOHEADER_SIZE ||
poDS->sFileHeader.iOffBits >= poDS->sFileHeader.iSize)
poDS->sFileHeader.iOffBits >= poDS->m_nFileSize)
{
delete poDS;
return nullptr;
Expand Down Expand Up @@ -1544,7 +1546,7 @@ GDALDataset *BMPDataset::Create(const char *pszFilename, int nXSize, int nYSize,

const vsi_l_offset nLargeImageSize =
static_cast<vsi_l_offset>(nScanSize) * poDS->sInfoHeader.iHeight;
poDS->m_nLargeFileSize = poDS->sFileHeader.iOffBits + nLargeImageSize;
poDS->m_nFileSize = poDS->sFileHeader.iOffBits + nLargeImageSize;
if (nLargeImageSize > std::numeric_limits<uint32_t>::max())
{
CPLError(CE_Warning, CPLE_AppDefined,
Expand All @@ -1554,7 +1556,7 @@ GDALDataset *BMPDataset::Create(const char *pszFilename, int nXSize, int nYSize,
poDS->sFileHeader.iSize = std::numeric_limits<uint32_t>::max();
poDS->sInfoHeader.iSizeImage = std::numeric_limits<uint32_t>::max();
}
else if (poDS->m_nLargeFileSize > std::numeric_limits<uint32_t>::max())
else if (poDS->m_nFileSize > std::numeric_limits<uint32_t>::max())
{
CPLError(CE_Warning, CPLE_AppDefined,
"File too big for its size to fit in a 32 bit integer! "
Expand All @@ -1565,7 +1567,7 @@ GDALDataset *BMPDataset::Create(const char *pszFilename, int nXSize, int nYSize,
}
else
{
poDS->sFileHeader.iSize = static_cast<uint32_t>(poDS->m_nLargeFileSize);
poDS->sFileHeader.iSize = static_cast<uint32_t>(poDS->m_nFileSize);
poDS->sInfoHeader.iSizeImage = static_cast<uint32_t>(nLargeImageSize);
}

Expand Down
Loading