[Git][debian-gis-team/lerc][master] 5 commits: New upstream version 4.2.0+ds
Antonio Valentino (@antonio.valentino)
gitlab at salsa.debian.org
Wed Jul 29 21:24:41 BST 2026
Antonio Valentino pushed to branch master at Debian GIS Project / lerc
Commits:
d285bf6e by Antonio Valentino at 2026-07-29T19:44:04+00:00
New upstream version 4.2.0+ds
- - - - -
38975ef2 by Antonio Valentino at 2026-07-29T19:44:05+00:00
Update upstream source from tag 'upstream/4.2.0+ds'
Update to upstream version '4.2.0+ds'
with Debian dir ebfacc588068ed05721e2f62b328f3b24c741922
- - - - -
fa6c6f20 by Antonio Valentino at 2026-07-29T19:45:52+00:00
New upstream release
- - - - -
b14d962b by Antonio Valentino at 2026-07-29T20:22:40+00:00
Update symbol file
- - - - -
0d3e829a by Antonio Valentino at 2026-07-29T20:23:25+00:00
Set distribution to experimental
- - - - -
19 changed files:
- .github/workflows/build_wheels.yml
- CHANGELOG.md
- CMakeLists.txt
- HOWTO-RELEASE.md
- OtherLanguages/Python/lerc/README.md
- OtherLanguages/Python/lerc/__init__.py
- OtherLanguages/Python/setup.py
- debian/changelog
- debian/liblerc4.symbols
- src/LercLib/BitMask.cpp
- src/LercLib/BitMask.h
- src/LercLib/Lerc.cpp
- src/LercLib/Lerc.h
- src/LercLib/Lerc1Decode/CntZImage.cpp
- src/LercLib/Lerc1Decode/TImage.hpp
- src/LercLib/Lerc2.cpp
- src/LercLib/Lerc_c_api_impl.cpp
- src/LercLib/include/Lerc_c_api.h
- src/LercLib/include/Lerc_types.h
Changes:
=====================================
.github/workflows/build_wheels.yml
=====================================
@@ -1,4 +1,4 @@
-name: Build Platform Wheels
+name: Build Platform Wheels and Conda Packages
on:
push:
@@ -6,18 +6,24 @@ on:
paths:
- 'OtherLanguages/Python/**'
- 'src/**'
+ - 'build/conda/**'
- '.github/workflows/build_wheels.yml'
tags:
- 'v*'
pull_request:
paths:
- 'OtherLanguages/Python/**'
+ - 'src/**'
+ - 'build/conda/**'
- '.github/workflows/build_wheels.yml'
workflow_dispatch:
+permissions:
+ contents: read
+
jobs:
build_wheels:
- name: Build on ${{ matrix.os }}
+ name: Build Wheels on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
@@ -27,11 +33,33 @@ jobs:
- name: Checkout Code
uses: actions/checkout at v4
- - name: Build C++ Binaries (CMake)
+ - name: Build C++ Binaries (Linux Container Compatibility)
+ if: matrix.os == 'ubuntu-latest'
+ uses: addnab/docker-run-action at v3
+ with:
+ image: rockylinux:8
+ options: -v ${{ github.workspace }}:/workspace
+ run: |
+ dnf install -y gcc-c++ cmake make
+ cd /workspace
+ mkdir -p cmake_build
+ cd cmake_build
+ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_RPATH="\$ORIGIN/../../..\$ORIGIN/../lib"
+ cmake --build . --config Release
+
+ - name: Build C++ Binaries (Windows & macOS Native)
+ if: matrix.os != 'ubuntu-latest'
+ shell: bash
+ env:
+ MACOSX_DEPLOYMENT_TARGET: '11.0'
run: |
mkdir cmake_build
cd cmake_build
- cmake .. -DCMAKE_BUILD_TYPE=Release
+ if [ "${{ matrix.os }}" = "macos-latest" ]; then
+ cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_OSX_ARCHITECTURES="x86_64;arm64"
+ else
+ cmake .. -DCMAKE_BUILD_TYPE=Release
+ fi
cmake --build . --config Release
- name: Stage Binaries for setup.py
@@ -45,7 +73,9 @@ jobs:
elif [ "${{ matrix.os }}" = "macos-latest" ]; then
cp cmake_build/libLerc.dylib bin/MacOS/ || cp cmake_build/Release/libLerc.dylib bin/MacOS/ || echo "DYLIB not found"
else
- cp cmake_build/libLerc.so bin/Linux/ || cp cmake_build/Release/libLerc.so bin/Linux/ || echo "SO not found"
+ cp cmake_build/libLerc.so.4 bin/Linux/ || cp cmake_build/Release/libLerc.so.4 bin/Linux/ || \
+ cp cmake_build/libLerc.so bin/Linux/libLerc.so.4 || \
+ cp cmake_build/Release/libLerc.so bin/Linux/libLerc.so.4 || echo "libLerc.so.4 not found"
fi
- name: Set up Python
@@ -54,13 +84,84 @@ jobs:
python-version: '3.11'
- name: Build Wheel
+ env:
+ MACOSX_DEPLOYMENT_TARGET: '11.0'
run: |
python -m pip install --upgrade pip setuptools wheel build
cd OtherLanguages/Python
+ python setup.py build
python -m build --wheel
- - name: Upload Artifacts
+ - name: Standardize Wheel Platform Labels
+ shell: bash
+ run: |
+ cd OtherLanguages/Python/dist/
+ if [ "${{ matrix.os }}" = "ubuntu-latest" ]; then
+ for f in *linux_x86_64.whl; do [ -e "$f" ] && mv "$f" "${f/linux_x86_64/manylinux_2_28_x86_64}"; done
+ fi
+
+ - name: Upload Platform Wheel Artifacts
uses: actions/upload-artifact at v4
with:
name: pylerc-wheels-${{ matrix.os }}
path: OtherLanguages/Python/dist/*.whl
+
+ - name: Upload Binary Snippets for Conda
+ uses: actions/upload-artifact at v4
+ with:
+ name: compiled-binaries-${{ matrix.os }}
+ path: bin/
+
+ build_conda:
+ name: Assemble Noarch Conda Package
+ needs: build_wheels
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout at v4
+
+ - name: Download Windows Binaries
+ uses: actions/download-artifact at v4
+ with:
+ name: compiled-binaries-windows-latest
+ path: bin/
+
+ - name: Download MacOS Binaries
+ uses: actions/download-artifact at v4
+ with:
+ name: compiled-binaries-macos-latest
+ path: bin/
+
+ - name: Download Linux Binaries
+ uses: actions/download-artifact at v4
+ with:
+ name: compiled-binaries-ubuntu-latest
+ path: bin/
+
+ - name: Setup Miniconda Environment
+ uses: conda-incubator/setup-miniconda at v4
+ with:
+ auto-update-conda: true
+ auto-activate: true
+ activate-environment: ""
+
+ - name: Install Conda Build Tooling
+ shell: bash -el {0}
+ run: conda install -y python=3.11 conda-build
+
+ - name: Build Noarch Conda Distribution
+ shell: bash -el {0}
+ run: |
+ cd OtherLanguages/Python
+ python setup.py build
+ cd ../..
+ mkdir -p conda-out
+ conda build build/conda/lerc/ --output-folder conda-out/
+
+ - name: Upload Conda Distribution Artifacts
+ uses: actions/upload-artifact at v4
+ with:
+ name: pylerc-conda-package
+ path: conda-out/noarch/*.conda
+
+
=====================================
CHANGELOG.md
=====================================
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased][unreleased]
+## [4.2.0](https://github.com/Esri/lerc/releases/tag/v4.2.0) - 2026-07-23
+
+* Added explicit size checks for the input data volume and the output compressed binary Lerc blob. The maximum data volume to encode is 2 GB per band. The maximum size of a compressed binary Lerc blob is set also to 2 GB per band, and 4 GB over all bands. The data volume over all bands is not limited as long as it can be compressed into 4 GB or less.
+
+* Coverity fixes and overflow checks.
+
## [4.1.1](https://github.com/Esri/lerc/releases/tag/v4.1.1) - 2026-07-02
### Fixed
=====================================
CMakeLists.txt
=====================================
@@ -3,7 +3,7 @@ cmake_minimum_required(VERSION 3.12)
project(Lerc
DESCRIPTION "Limited Error Raster Compression"
HOMEPAGE_URL "https://github.com/Esri/lerc"
- VERSION 4.1.1) # Keep in sync with Lerc_c_api.h
+ VERSION 4.2.0) # Keep in sync with Lerc_c_api.h
include(GNUInstallDirs)
=====================================
HOWTO-RELEASE.md
=====================================
@@ -4,7 +4,7 @@ Update version numbers in src/LercLib/include/Lerc_c_api.h and CMakeLists.txt
### npm release
-- Config build environment. For 4.1.2 release: Node.js v24.11.1 LTS, npm 11.6.2
+- Config build environment. For 4.2.0 release: Node.js v24.11.1 LTS, npm 11.6.2
- Update the following files in OtherLanguages/js
- Run npm version xxx to update version numbers in package.json
- Update CHANGELOG.md
=====================================
OtherLanguages/Python/lerc/README.md
=====================================
@@ -2,6 +2,13 @@
LERC is an open-source raster format which supports rapid encoding and decoding for any pixel type, with user-set maximum compression error per pixel.
+# What's new in Lerc 4.2?
+
+Lerc 4.2 adds stricter size limits for improved safety:
+- Input data to encode is limited to 2 GB per band.
+- Compressed Lerc blob size is limited to 2 GB per band.
+- Total compressed Lerc blob size is limited to 4 GB across all bands.
+
# What's new in Lerc 4.0?
## Option 1, uses numpy masked array
=====================================
OtherLanguages/Python/lerc/__init__.py
=====================================
@@ -1,3 +1,3 @@
-__version__ = "4.1.1"
+__version__ = "4.2.0"
from ._lerc import *
=====================================
OtherLanguages/Python/setup.py
=====================================
@@ -40,7 +40,7 @@ for platform in PLATFORMS:
setuptools.setup(
name="pylerc",
- version="4.1.1",
+ version="4.2.0",
author="esri",
author_email="python at esri.com",
description="Limited Error Raster Compression",
=====================================
debian/changelog
=====================================
@@ -1,3 +1,10 @@
+lerc (4.2.0+ds-1~exp1) experimental; urgency=medium
+
+ * New upstream release.
+ * Update symbol file.
+
+ -- Antonio Valentino <antonio.valentino at tiscali.it> Wed, 29 Jul 2026 20:22:50 +0000
+
lerc (4.1.1+ds-2) unstable; urgency=medium
* Update symbol file.
=====================================
debian/liblerc4.symbols
=====================================
@@ -1,4 +1,4 @@
-# SymbolsHelper-Confirmed: 4.1.1 alpha amd64 arm64 armhf hurd-amd64 hurd-i386 i386 loong64 ppc64el riscv64 sh4
+# SymbolsHelper-Confirmed: 4.2.0 amd64
libLerc.so.4 #PACKAGE# #MINVER#
* Build-Depends-Package: liblerc-dev
_Z11lerc_assertb at Base 4.1.0
@@ -87,6 +87,7 @@ libLerc.so.4 #PACKAGE# #MINVER#
(arch=!armhf !hurd-i386 !i386 !sh4)_ZN6LercNS4Lerc11GetLercInfoEPKhjRNS0_8LercInfoEPdS5_m at Base 4.1.0
(arch=!alpha !amd64 !arm64 !hurd-amd64 !ia64 !loong64 !mips64el !ppc64el !riscv64)_ZN6LercNS4Lerc11MasksDifferEPKhS2_j at Base 3.0
(arch=alpha amd64 arm64 hurd-amd64 ia64 loong64 mips64el ppc64el riscv64)_ZN6LercNS4Lerc11MasksDifferEPKhS2_m at Base 4.1.1
+ _ZN6LercNS4Lerc15CheckDimensionsEiiim at Base 4.2.0
(arch=!alpha !amd64 !arm64 !hurd-amd64 !ia64 !loong64 !mips64el !ppc64el !riscv64)_ZN6LercNS4Lerc15ConvertToDoubleEPKvNS0_8DataTypeEjPd at Base 3.0
(arch=alpha amd64 arm64 hurd-amd64 ia64 loong64 mips64el ppc64el riscv64)_ZN6LercNS4Lerc15ConvertToDoubleEPKvNS0_8DataTypeEmPd at Base 4.1.1
_ZN6LercNS4Lerc21ComputeCompressedSizeEPKviNS0_8DataTypeEiiiiiPKhdRjS5_PKd at Base 4.0.0
=====================================
src/LercLib/BitMask.cpp
=====================================
@@ -77,7 +77,7 @@ bool BitMask::SetSize(int nCols, int nRows)
try
{
- size_t nPix = (size_t)nCols * (size_t)nRows;
+ size_t nPix = (size_t)nCols * nRows;
m_pBits = new Byte[(nPix + 7) >> 3];
}
catch (...)
@@ -97,11 +97,11 @@ bool BitMask::SetSize(int nCols, int nRows)
// -------------------------------------------------------------------------- ;
-int BitMask::CountValidBits() const
+int64_t BitMask::CountValidBits() const
{
const Byte numBitsHB[16] = {0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4};
const Byte* ptr = m_pBits;
- int sum = 0;
+ int64_t sum = 0;
size_t i = Size();
while (i--)
{
@@ -110,7 +110,8 @@ int BitMask::CountValidBits() const
}
// subtract undefined bits potentially contained in the last byte
- for (int k = m_nCols * m_nRows; k < Size() * 8; k++)
+ int64_t sizeX8 = (int64_t)(Size() * 8);
+ for (int64_t k = (int64_t)m_nCols * m_nRows; k < sizeX8; k++)
if (IsValid(k))
sum--;
=====================================
src/LercLib/BitMask.h
=====================================
@@ -26,6 +26,7 @@ Contributors: Thomas Maurer
#include "Defines.h"
#include <cstddef>
+#include <cstdint>
NAMESPACE_LERC_START
@@ -39,19 +40,19 @@ public:
BitMask() : m_pBits(nullptr), m_nCols(0), m_nRows(0) {}
BitMask(int nCols, int nRows) : m_pBits(nullptr), m_nCols(0), m_nRows(0) { SetSize(nCols, nRows); }
BitMask(const BitMask& src);
- ~BitMask() { Clear(); }
+ ~BitMask() { Clear(); }
BitMask& operator= (const BitMask& src);
// 1: valid, 0: not valid
- Byte IsValid(int k) const { return (m_pBits[k >> 3] & Bit(k)) > 0; }
- Byte IsValid(int row, int col) const { return IsValid(row * m_nCols + col); }
+ Byte IsValid(int64_t k) const { return (m_pBits[k >> 3] & Bit(k)) > 0; }
+ Byte IsValid(int row, int col) const { return IsValid((int64_t)row * m_nCols + col); }
- void SetValid(int k) const { m_pBits[k >> 3] |= Bit(k); }
- void SetValid(int row, int col) const { SetValid(row * m_nCols + col); }
+ void SetValid(int64_t k) const { m_pBits[k >> 3] |= Bit(k); }
+ void SetValid(int row, int col) const { SetValid((int64_t)row * m_nCols + col); }
- void SetInvalid(int k) const { m_pBits[k >> 3] &= ~Bit(k); }
- void SetInvalid(int row, int col) const { SetInvalid(row * m_nCols + col); }
+ void SetInvalid(int64_t k) const { m_pBits[k >> 3] &= ~Bit(k); }
+ void SetInvalid(int row, int col) const { SetInvalid((int64_t)row * m_nCols + col); }
void SetAllValid() const;
void SetAllInvalid() const;
@@ -60,12 +61,12 @@ public:
int GetWidth() const { return m_nCols; }
int GetHeight() const { return m_nRows; }
- size_t Size() const { return ((size_t)m_nCols * m_nRows + 7) >> 3; }
+ size_t Size() const { int64_t n = ((int64_t)m_nCols * m_nRows + 7) >> 3; return n >= 0 ? (size_t)n : 0; }
const Byte* Bits() const { return m_pBits; }
Byte* Bits() { return m_pBits; }
- static Byte Bit(int k) { return (1 << 7) >> (k & 7); }
+ static Byte Bit(int64_t k) { return (1 << 7) >> (k & 7); }
- int CountValidBits() const;
+ int64_t CountValidBits() const;
void Clear();
private:
=====================================
src/LercLib/Lerc.cpp
=====================================
@@ -100,12 +100,15 @@ ErrCode Lerc::GetLercInfo(const Byte* pLercBlob, unsigned int numBytesBlob, stru
if (Lerc2::GetHeaderInfo(pLercBlob, numBytesBlob, lerc2Info, bHasMask))
{
+ if (lerc2Info.blobSize < 0)
+ return ErrCode::Failed;
+
lercInfo.version = lerc2Info.version;
lercInfo.nDepth = lerc2Info.nDepth;
lercInfo.nCols = lerc2Info.nCols;
lercInfo.nRows = lerc2Info.nRows;
lercInfo.numValidPixel = lerc2Info.numValidPixel; // for 1st band
- lercInfo.blobSize = lerc2Info.blobSize;
+ lercInfo.blobSize = (unsigned int)lerc2Info.blobSize; // blob size per band is in [0 .. 2 GB]
lercInfo.dt = (DataType)lerc2Info.dt;
lercInfo.zMin = lerc2Info.zMin;
lercInfo.zMax = lerc2Info.zMax;
@@ -126,16 +129,17 @@ ErrCode Lerc::GetLercInfo(const Byte* pLercBlob, unsigned int numBytesBlob, stru
lercInfo.nBands = 1;
- if (lercInfo.blobSize > (int)numBytesBlob) // truncated blob, we won't be able to read this band
- return ErrCode::BufferTooSmall;
+ if (lercInfo.blobSize > numBytesBlob) // truncated blob, we won't be able to read this band
+ return ErrCode::Failed;
struct Lerc2::HeaderInfo hdInfo;
while (bTryNextBlob && Lerc2::GetHeaderInfo(pLercBlob + lercInfo.blobSize, numBytesBlob - lercInfo.blobSize, hdInfo, bHasMask))
{
if (hdInfo.nDepth != lercInfo.nDepth
- || hdInfo.nCols != lercInfo.nCols
- || hdInfo.nRows != lercInfo.nRows
- || (int)hdInfo.dt != (int)lercInfo.dt)
+ || hdInfo.nCols != lercInfo.nCols
+ || hdInfo.nRows != lercInfo.nRows
+ || (int)hdInfo.dt != (int)lercInfo.dt
+ || hdInfo.blobSize < 0)
{
return ErrCode::Failed;
}
@@ -148,11 +152,11 @@ ErrCode Lerc::GetLercInfo(const Byte* pLercBlob, unsigned int numBytesBlob, stru
if (bHasMask || hdInfo.numValidPixel != lercInfo.numValidPixel) // support mask per band
nMasks = 2;
- if (lercInfo.blobSize > std::numeric_limits<int>::max() - hdInfo.blobSize) // guard against overflow
+ if ((size_t)lercInfo.blobSize > (size_t)UINT_MAX - hdInfo.blobSize) // guard against overflow
return ErrCode::Failed;
- if (lercInfo.blobSize + hdInfo.blobSize > (int)numBytesBlob) // truncated blob, we won't be able to read this band
- return ErrCode::BufferTooSmall;
+ if ((size_t)lercInfo.blobSize + hdInfo.blobSize > (size_t)numBytesBlob) // truncated blob, we won't be able to read this band
+ return ErrCode::Failed;
lercInfo.zMin = min(lercInfo.zMin, hdInfo.zMin);
lercInfo.zMax = max(lercInfo.zMax, hdInfo.zMax);
@@ -208,6 +212,9 @@ ErrCode Lerc::GetLercInfo(const Byte* pLercBlob, unsigned int numBytesBlob, stru
if (height < 0 || width < 0 || height > 40000 || width > 40000) // guard against bogus numbers; size limitation for old Lerc1
return ErrCode::Failed;
+ if (sizeof(CntZ) * height * width > (size_t)INT_MAX)
+ return ErrCode::Failed;
+
lercInfo.nDepth = 1;
lercInfo.nCols = width;
lercInfo.nRows = height;
@@ -324,6 +331,9 @@ ErrCode Lerc::ComputeCompressedSizeTempl(const T* pData, int version, int nDepth
if (!(nMasks == 0 || nMasks == 1 || nMasks == nBands) || (nMasks > 0 && !pValidBytes))
return ErrCode::WrongParam;
+ if (!CheckDimensions(nDepth, nCols, nRows, sizeof(T)))
+ return ErrCode::DimensionsTooLarge;
+
unsigned int numBytesWritten = 0;
if (version >= 0 && version <= 5)
@@ -358,6 +368,9 @@ ErrCode Lerc::EncodeTempl(const T* pData, int version, int nDepth, int nCols, in
if (!(nMasks == 0 || nMasks == 1 || nMasks == nBands) || (nMasks > 0 && !pValidBytes))
return ErrCode::WrongParam;
+ if (!CheckDimensions(nDepth, nCols, nRows, sizeof(T)))
+ return ErrCode::DimensionsTooLarge;
+
memset(pBuffer, 0, (size_t)numBytesBuffer);
unsigned int numBytesNeeded = 0;
@@ -392,6 +405,9 @@ ErrCode Lerc::DecodeTempl(T* pData, const Byte* pLercBlob, unsigned int numBytes
if (!(nMasks == 0 || nMasks == 1 || nMasks == nBands) || (nMasks > 0 && !pValidBytes))
return ErrCode::WrongParam;
+ if (!CheckDimensions(nDepth, nCols, nRows, sizeof(T)))
+ return ErrCode::DimensionsTooLarge;
+
const Byte* pByte = pLercBlob;
Lerc2::HeaderInfo hdInfo;
bool bHasMask = false;
@@ -436,11 +452,11 @@ ErrCode Lerc::DecodeTempl(T* pData, const Byte* pLercBlob, unsigned int numBytes
{
if (((size_t)(pByte - pLercBlob) < numBytesBlob) && Lerc2::GetHeaderInfo(pByte, nBytesRemaining, hdInfo, bHasMask))
{
- if (hdInfo.nDepth != nDepth || hdInfo.nCols != nCols || hdInfo.nRows != nRows)
+ if (hdInfo.nDepth != nDepth || hdInfo.nCols != nCols || hdInfo.nRows != nRows || hdInfo.blobSize < 0)
return ErrCode::Failed;
- if ((pByte - pLercBlob) + (size_t)hdInfo.blobSize > numBytesBlob)
- return ErrCode::BufferTooSmall;
+ if ((pByte - pLercBlob) + (size_t)hdInfo.blobSize > numBytesBlob) // corrupted blob
+ return ErrCode::Failed;
size_t nPix = (size_t)iBand * nRows * nCols;
T* arr = pData + nPix * nDepth;
@@ -479,8 +495,8 @@ ErrCode Lerc::DecodeTempl(T* pData, const Byte* pLercBlob, unsigned int numBytes
for (int iBand = 0; iBand < nBands; iBand++)
{
unsigned int numBytesHeader = iBand == 0 ? numBytesHeaderBand0 : numBytesHeaderBand1;
- if ((size_t)(pByte1 - pLercBlob) + numBytesHeader > numBytesBlob)
- return ErrCode::BufferTooSmall;
+ if ((size_t)(pByte1 - pLercBlob) + numBytesHeader > numBytesBlob) // corrupted blob or wrong nBands
+ return ErrCode::Failed;
bool onlyZPart = iBand > 0;
if (!zImg.read(&pByte1, pLercBlob + numBytesBlob, 1e12, false, onlyZPart))
@@ -588,6 +604,9 @@ ErrCode Lerc::EncodeInternal_v5(const T* pData, int version, int nDepth, int nCo
if (nBytes <= 0)
return ErrCode::Failed;
+ if ((size_t)numBytesNeeded + nBytes > (size_t)UINT_MAX) // keep total blob size (over all bands) <= 4 GB
+ return ErrCode::DimensionsTooLarge;
+
numBytesNeeded += nBytes;
if (pBuffer)
@@ -735,6 +754,9 @@ ErrCode Lerc::EncodeInternal(const T* pData, int version, int nDepth, int nCols,
if (nBytes <= 0)
return ErrCode::Failed;
+ if ((size_t)numBytesNeeded + nBytes > (size_t)UINT_MAX) // keep total blob size (over all bands) <= 4 GB
+ return ErrCode::DimensionsTooLarge;
+
numBytesNeeded += nBytes;
if (pBuffer)
@@ -1248,7 +1270,7 @@ ErrCode Lerc::FilterNoData(std::vector<T>& dataBuffer, std::vector<Byte>& maskBu
// check for noData in valid pixels
for (int k = 0, i = 0; i < nRows; i++)
{
- T* rowArr = &(dataBuffer[i * nCols * nDepth]);
+ T* rowArr = &(dataBuffer[(size_t)i * nCols * nDepth]);
for (int n = 0, j = 0; j < nCols; j++, k++, n += nDepth)
if (maskBuffer[k])
@@ -1332,7 +1354,7 @@ ErrCode Lerc::FilterNoData(std::vector<T>& dataBuffer, std::vector<Byte>& maskBu
{
for (int k = 0, i = 0; i < nRows; i++)
{
- T* rowArr = &(dataBuffer[i * nCols * nDepth]);
+ T* rowArr = &(dataBuffer[(size_t)i * nCols * nDepth]);
for (int n = 0, j = 0; j < nCols; j++, k++, n += nDepth)
if (maskBuffer[k])
@@ -1398,7 +1420,7 @@ ErrCode Lerc::FilterNoDataAndNaN(std::vector<T>& dataBuffer, std::vector<Byte>&
// check for NaN or noData in valid pixels
for (int k = 0, i = 0; i < nRows; i++)
{
- T* rowArr = &(dataBuffer[i * nCols * nDepth]);
+ T* rowArr = &(dataBuffer[(size_t)i * nCols * nDepth]);
for (int n = 0, j = 0; j < nCols; j++, k++, n += nDepth)
if (maskBuffer[k])
@@ -1505,7 +1527,7 @@ ErrCode Lerc::FilterNoDataAndNaN(std::vector<T>& dataBuffer, std::vector<Byte>&
{
for (int k = 0, i = 0; i < nRows; i++)
{
- T* rowArr = &(dataBuffer[i * nCols * nDepth]);
+ T* rowArr = &(dataBuffer[(size_t)i * nCols * nDepth]);
for (int n = 0, j = 0; j < nCols; j++, k++, n += nDepth)
if (maskBuffer[k])
@@ -1597,3 +1619,23 @@ bool Lerc::FindNewNoDataBelowValidMin(double minVal, double maxZErr, bool bAllIn
// -------------------------------------------------------------------------- ;
+bool Lerc::CheckDimensions(int nDepth, int nCols, int nRows, size_t sizeOfDataElement)
+{
+ // here we guard against too large input dimensions that might cause a possible int32 overflow later;
+ // we limit the size of input data per band to 2 GB, so any product [height * width * depth * sizeof(data element)] <= INT_MAX;
+ // (see the corresponding size and dimension checks in Lerc2::ReadHeader(...));
+
+ if (nDepth <= 0 || nCols <= 0 || nRows <= 0)
+ return false;
+
+ const uint64_t numPixel = (uint64_t)nRows * nCols;
+ const uint64_t maxint32 = (uint64_t)INT_MAX;
+ const uint64_t nbpp = sizeOfDataElement;
+
+ if (numPixel > maxint32 || nbpp > maxint32 || nbpp * nDepth > maxint32 || nbpp * nDepth * numPixel > maxint32)
+ return false;
+
+ return true;
+}
+
+// -------------------------------------------------------------------------- ;
=====================================
src/LercLib/Lerc.h
=====================================
@@ -103,9 +103,9 @@ NAMESPACE_LERC_START
nCols, // number of columns
nRows, // number of rows
numValidPixel, // number of valid pixels
- nBands, // number of bands
- blobSize, // total blob size in bytes
- nMasks, // number of masks (0, 1, or nBands)
+ nBands; // number of bands
+ unsigned int blobSize; // total blob size in bytes, <= 4 GB
+ int nMasks, // number of masks (0, 1, or nBands)
nUsesNoDataValue; // 0 - no noData value used, nBands - noData value used in 1 or more bands (only possible for nDepth > 1)
DataType dt; // data type (float only for old Lerc1)
double zMin, // min pixel value, over all data values
@@ -282,5 +282,7 @@ NAMESPACE_LERC_START
template<class T>
static bool FindNewNoDataBelowValidMin(double minVal, double maxZErr, bool bAllInt, double lowIntLimit, T& newNoDataVal);
+
+ static bool CheckDimensions(int nDepth, int nCols, int nRows, size_t sizeOfDataElement);
};
NAMESPACE_LERC_END
=====================================
src/LercLib/Lerc1Decode/CntZImage.cpp
=====================================
@@ -21,6 +21,7 @@ http://github.com/Esri/lerc/
Contributors: Thomas Maurer
*/
+#include <climits>
#include <cstring>
#include <algorithm>
#include "CntZImage.h"
@@ -48,7 +49,7 @@ bool CntZImage::resizeFill0(int width, int height)
if (!resize(width, height))
return false;
- memset(getData(), 0, width * height * sizeof(CntZ));
+ memset(getData(), 0, sizeof(CntZ) * width * height);
return true;
}
@@ -114,6 +115,9 @@ bool CntZImage::read(const Byte** ppByte, const Byte* bArr_end, double maxZError
if (height < 0 || width < 0 || height > 40000 || width > 40000) // guard against bogus numbers; size limitation for old Lerc1
return false;
+ if (sizeof(CntZ) * height * width > (size_t)INT_MAX)
+ return false;
+
if (maxZErrorInFile > maxZError)
return false;
@@ -177,7 +181,7 @@ bool CntZImage::read(const Byte** ppByte, const Byte* bArr_end, double maxZError
// decompress to bit mask
BitMask bitMask(width_, height_);
RLE rle;
- if (!rle.decompress(bArr, width_ * height_ * 2, (Byte*)bitMask.Bits(), bitMask.Size()))
+ if (!rle.decompress(bArr, (size_t)width_ * height_ * 2, (Byte*)bitMask.Bits(), bitMask.Size()))
return false;
CntZ* dstPtr = getData();
=====================================
src/LercLib/Lerc1Decode/TImage.hpp
=====================================
@@ -88,7 +88,7 @@ bool TImage< Element >::resize(int width, int height)
width_ = 0;
height_ = 0;
- data_ = (Element*)malloc(width * height * sizeof(Element));
+ data_ = (Element*)malloc(sizeof(Element) * width * height);
if (!data_)
return false;
=====================================
src/LercLib/Lerc2.cpp
=====================================
@@ -91,7 +91,12 @@ bool Lerc2::Set(int nDepth, int nCols, int nRows, const Byte* pMaskBits)
if (pMaskBits)
{
memcpy(m_bitMask.Bits(), pMaskBits, m_bitMask.Size());
- m_headerInfo.numValidPixel = m_bitMask.CountValidBits();
+
+ int64_t numValid = m_bitMask.CountValidBits();
+ if (numValid < 0 || numValid > (int64_t)INT_MAX)
+ return false;
+
+ m_headerInfo.numValidPixel = (int)numValid;
}
else
{
@@ -256,7 +261,14 @@ unsigned int Lerc2::ComputeNumBytesNeededToWrite(const T* arr, double maxZError,
{
// add the min max ranges behind the mask and before the main data;
// so we do not write it if no valid pixel or all same value const
- m_headerInfo.blobSize += 2 * nDepth * sizeof(T);
+
+ size_t currBlobSize = m_headerInfo.blobSize;
+ currBlobSize += sizeof(T) * nDepth * 2;
+
+ if (currBlobSize > (size_t)INT_MAX) // limit Lerc blob size per band to 2 GB
+ return 0;
+
+ m_headerInfo.blobSize = (int)currBlobSize;
bool minMaxEqual = false;
if (!CheckMinMaxRanges(minMaxEqual))
@@ -267,7 +279,7 @@ unsigned int Lerc2::ComputeNumBytesNeededToWrite(const T* arr, double maxZError,
}
// data
- if (!WriteTiles(arr, &ptr, nBytesTiling))
+ if (!WriteTiles(arr, &ptr, nBytesTiling) || nBytesTiling < 0)
return 0;
m_imageEncodeMode = IEM_Tiling;
@@ -279,6 +291,9 @@ unsigned int Lerc2::ComputeNumBytesNeededToWrite(const T* arr, double maxZError,
ImageEncodeMode huffmanEncMode;
ComputeHuffmanCodes(arr, nBytesHuffman, huffmanEncMode, m_huffmanCodes); // save Huffman codes for later use
+ if (nBytesHuffman < 0)
+ nBytesHuffman = INT_MAX;
+
if (!m_huffmanCodes.empty() && nBytesHuffman < nBytesTiling)
{
m_imageEncodeMode = huffmanEncMode;
@@ -294,13 +309,16 @@ unsigned int Lerc2::ComputeNumBytesNeededToWrite(const T* arr, double maxZError,
bool rv = m_lfpc.ComputeHuffmanCodesFlt(arr, (m_headerInfo.dt == DT_Double),
m_headerInfo.nCols, m_headerInfo.nRows, m_headerInfo.nDepth);
- if (!rv) // remove this check before next release to fall back to regular Lerc instead of fail
+ if (!rv)
return 0;
if (rv)
{
nBytesHuffman = m_lfpc.compressedLength();
+ if (nBytesHuffman < 0)
+ nBytesHuffman = INT_MAX;
+
if (nBytesHuffman < nBytesTiling * 0.9) // demand at least 10% better than not Huffman
{
nBytesData = nBytesHuffman;
@@ -310,19 +328,19 @@ unsigned int Lerc2::ComputeNumBytesNeededToWrite(const T* arr, double maxZError,
}
m_writeDataOneSweep = false;
- int nBytesDataOneSweep = (int)(numValid * nDepth * sizeof(T));
+ size_t nBytesDataOneSweep = sizeof(T) * nDepth * numValid;
{
// try with double block size to reduce block header overhead, if
if (((size_t)nBytesTiling * 8 < (size_t)numTotal * nDepth * 1.5) // resulting bit rate < x (2 bpp)
- && (nBytesTiling < 4 * nBytesDataOneSweep) // bit stuffing is effective
- && (nBytesHuffman == 0 || nBytesTiling < 2 * nBytesHuffman) // not much worse than huffman (otherwise huffman wins anyway)
+ && ((size_t)nBytesTiling < 4 * nBytesDataOneSweep) // bit stuffing is effective
+ && (nBytesHuffman == 0 || (size_t)nBytesTiling < (size_t)2 * nBytesHuffman) // not much worse than huffman (otherwise huffman wins anyway)
&& (m_headerInfo.nRows > m_microBlockSize || m_headerInfo.nCols > m_microBlockSize))
{
m_headerInfo.microBlockSize = m_microBlockSize * 2;
int nBytes2 = 0;
- if (!WriteTiles(arr, &ptr, nBytes2)) // no huffman in here anymore
+ if (!WriteTiles(arr, &ptr, nBytes2) || nBytes2 < 0) // no huffman in here anymore
return 0;
if (nBytes2 <= nBytesData)
@@ -341,17 +359,24 @@ unsigned int Lerc2::ComputeNumBytesNeededToWrite(const T* arr, double maxZError,
if (m_headerInfo.TryHuffmanInt() || m_headerInfo.TryHuffmanFlt())
nBytesData += 1; // flag for image encode mode
- if (nBytesDataOneSweep <= nBytesData)
+ size_t totalBlobSize = m_headerInfo.blobSize;
+
+ if (nBytesDataOneSweep <= (size_t)nBytesData)
{
m_writeDataOneSweep = true; // fallback: write data binary uncompressed in one sweep
- m_headerInfo.blobSize += 1 + nBytesDataOneSweep; // header, mask, min max ranges, flag, data one sweep
+ totalBlobSize += 1 + nBytesDataOneSweep; // header, mask, min max ranges, flag, data one sweep
}
else
{
m_writeDataOneSweep = false;
- m_headerInfo.blobSize += 1 + nBytesData; // header, mask, min max ranges, flag(s), data
+ totalBlobSize += 1 + nBytesData; // header, mask, min max ranges, flag(s), data
}
+ if (totalBlobSize > (size_t)INT_MAX) // limit Lerc blob size per band to 2 GB
+ return 0;
+
+ m_headerInfo.blobSize = (int)totalBlobSize;
+
return m_headerInfo.blobSize;
}
@@ -442,7 +467,7 @@ bool Lerc2::Encode(const T* arr, Byte** ppByte)
}
int numBytes = 0;
- if (!WriteTiles(arr, ppByte, numBytes))
+ if (!WriteTiles(arr, ppByte, numBytes) || numBytes < 0)
return false;
}
else
@@ -476,7 +501,7 @@ bool Lerc2::GetHeaderInfo(const Byte* pByte, size_t nBytesRemaining, struct Head
return false;
int numBytesMask(0);
- if (nBytesRemaining < sizeof(int) || !memcpy(&numBytesMask, pByte, sizeof(int)))
+ if (nBytesRemaining < sizeof(int) || !memcpy(&numBytesMask, pByte, sizeof(int)) || numBytesMask < 0)
return false;
bHasMask = numBytesMask > 0;
@@ -648,8 +673,6 @@ bool Lerc2::Decode(const Byte** ppByte, size_t& nBytesRemaining, T* arr, Byte* p
}
else if (m_headerInfo.TryHuffmanFlt() && m_imageEncodeMode == IEM_DeltaDeltaHuffman)
{
- //return DecodeHuffmanFlt(ppByte, nBytesRemaining, arr); // done.
- // return false; // not impl yet
return LosslessFPCompression::DecodeHuffmanFlt(ppByte, nBytesRemaining, arr,
(m_headerInfo.dt == DT_Double), m_headerInfo.nCols, m_headerInfo.nRows, m_headerInfo.nDepth);
}
@@ -871,9 +894,20 @@ bool Lerc2::ReadHeader(const Byte** ppByte, size_t& nBytesRemainingInOut, struct
hd.noDataVal = (hd.version >= 6) ? dblVec[i++] : 0;
hd.noDataValOrig = (hd.version >= 6) ? dblVec[i++] : 0;
- size_t numPixel = (size_t)hd.nRows * hd.nCols;
+ // here we guard against bogus input parameters and dimensions:
+ // we limit the size of input data per band to 2 GB, so any product [height * width * depth * sizeof(data element)] <= INT_MAX;
+ // the size of a compressed binary Lerc blob per band is <= 2 GB;
+ // the total size of a compressed binary Lerc blob over all bands is <= 4 GB (current Lerc API limitation);
+ // (see the corresponding size and dimension checks in Lerc::CheckDimensions(...));
- if (numPixel > (size_t)INT_MAX || (size_t)hd.numValidPixel > numPixel)
+ const uint64_t numPixel = (uint64_t)hd.nRows * hd.nCols;
+ const uint64_t maxint32 = (uint64_t)INT_MAX;
+ const uint64_t nbpp = GetDataTypeSize(hd.dt);
+
+ if (numPixel > maxint32 || (uint64_t)hd.numValidPixel > numPixel)
+ return false;
+
+ if (hd.microBlockSize > 32 || nbpp * hd.nDepth > maxint32 || nbpp * hd.nDepth * numPixel > maxint32)
return false;
*ppByte = ptr;
@@ -937,7 +971,7 @@ bool Lerc2::ReadMask(const Byte** ppByte, size_t& nBytesRemainingInOut)
size_t nBytesRemaining = nBytesRemainingInOut;
int numBytesMask;
- if (nBytesRemaining < sizeof(int) || !memcpy(&numBytesMask, ptr, sizeof(int)))
+ if (nBytesRemaining < sizeof(int) || !memcpy(&numBytesMask, ptr, sizeof(int)) || numBytesMask < 0)
return false;
ptr += sizeof(int);
@@ -1050,7 +1084,7 @@ bool Lerc2::TryBitPlaneCompression(const T* data, double eps, double& newMaxZErr
if (hd.numValidPixel < minCnt) // not enough data for good stats
return false;
- std::vector<int> cntDiffVec(nDepth * maxShift, 0);
+ std::vector<int> cntDiffVec((size_t)nDepth * maxShift, 0);
int cnt = 0;
if (nDepth == 1 && hd.numValidPixel == hd.nCols * hd.nRows) // special but common case
@@ -1340,9 +1374,13 @@ bool Lerc2::ReadDataOneSweep(const Byte** ppByte, size_t& nBytesRemaining, T* da
const Byte* ptr = (*ppByte);
const HeaderInfo& hd = m_headerInfo;
int nDepth = hd.nDepth;
- int len = nDepth * sizeof(T);
+ size_t len = sizeof(T) * nDepth;
+
+ int64_t numValid = m_bitMask.CountValidBits();
+ if (numValid < 0 || numValid >(int64_t)INT_MAX)
+ return false;
- size_t nValidPix = (size_t)m_bitMask.CountValidBits();
+ size_t nValidPix = (size_t)numValid;
if (nBytesRemaining < nValidPix * len)
return false;
@@ -1449,7 +1487,7 @@ bool Lerc2::WriteTiles(const T* data, Byte** ppByte, int& numBytes) const
int mbSize = hd.microBlockSize;
int nDepth = hd.nDepth;
- std::vector<T> dataVec(mbSize * mbSize, 0);
+ std::vector<T> dataVec((size_t)mbSize * mbSize, 0);
T* dataBuf = &dataVec[0];
const bool bDtInt = (hd.dt < DT_Float);
@@ -1643,7 +1681,7 @@ bool Lerc2::ReadTiles(const Byte** ppByte, size_t& nBytesRemaining, T* data) con
int mbSize = hd.microBlockSize;
int nDepth = hd.nDepth;
- if (mbSize > 32) // fail gracefully in case of corrupted blob for old version <= 2 which had no checksum
+ if (mbSize > 32)
return false;
int numTilesVert = (hd.nRows + mbSize - 1) / mbSize;
@@ -2100,7 +2138,7 @@ bool Lerc2::ReadTile(const Byte** ppByte, size_t& nBytesRemainingInOut, T* data,
}
else
{
- size_t maxElementCount = (i1 - i0) * (j1 - j0);
+ size_t maxElementCount = size_t(i1 - i0) * (j1 - j0);
if (!m_bitStuffer2.Decode(&ptr, nBytesRemaining, bufferVec, maxElementCount, hd.version))
return false;
=====================================
src/LercLib/Lerc_c_api_impl.cpp
=====================================
@@ -269,7 +269,10 @@ lerc_status lerc_decodeToDouble_4D(const unsigned char* pLercBlob, unsigned int
return (lerc_status)errCode;
Lerc::DataType dt = lercInfo.dt;
- if (dt > Lerc::DT_Double)
+ if (dt < 0 || dt > Lerc::DT_Double)
+ return (lerc_status)ErrCode::Failed;
+
+ if (lercInfo.nDepth != nDepth || lercInfo.nCols != nCols || lercInfo.nRows != nRows || lercInfo.nBands != nBands)
return (lerc_status)ErrCode::Failed;
if (dt == Lerc::DT_Double)
@@ -284,7 +287,7 @@ lerc_status lerc_decodeToDouble_4D(const unsigned char* pLercBlob, unsigned int
{
// use the buffer passed for in place decode and convert
int sizeofDt[] = { 1, 1, 2, 2, 4, 4, 4, 8 };
- size_t nDataValues = nDepth * nCols * nRows * nBands;
+ size_t nDataValues = (size_t)nDepth * nCols * nRows * nBands; // can be > 4 GB
void* ptrDec = (Byte*)pData + nDataValues * (sizeof(double) - sizeofDt[dt]);
if ((errCode = Lerc::Decode(pLercBlob, blobSize, nMasks, pValidBytes,
=====================================
src/LercLib/include/Lerc_c_api.h
=====================================
@@ -37,8 +37,8 @@ extern "C" {
/* LERC version numbers and related macros added in 3.0.0 */
#define LERC_VERSION_MAJOR 4
-#define LERC_VERSION_MINOR 1
-#define LERC_VERSION_PATCH 1
+#define LERC_VERSION_MINOR 2
+#define LERC_VERSION_PATCH 0
/* Macro to compute a LERC version number from its components */
#define LERC_COMPUTE_VERSION(maj,min,patch) ((maj)*10000+(min)*100+(patch))
@@ -68,6 +68,25 @@ extern "C" {
//! C-API for LERC library
+ //! Added in version 4.2:
+ //!
+ //! Lerc is mainly used as an image tile compression format, usually not to compress an entire large
+ //! image into one single compressed binary file. While this is possible, it would not be optimal in
+ //! both compression and read / write access.
+ //!
+ //! Now we put some well defined size limitations in place. We don't expect this to have any
+ //! implications for typical usage scenarios when encoding image tiles. The main goal is improved
+ //! security and protection, both against accidental integer overflows thanks to wrong or too large
+ //! dimensions as well as bogus dimensions or parameters planted by an attacker into the binary Lerc
+ //! header of the compressed Lerc blob.
+ //!
+ //! - 1) The size of input data (to be encoded) is limited to 2 GB per band.
+ //! - 2) The size of the compressed binary Lerc blob is also limited to 2 GB per band.
+ //! - 3) The size of the entire compressed binary Lerc blob (over all bands) is limited to 4 GB.
+ //! - 4) The size of the entire input data (to be encoded, over all bands) is NOT limited, as long
+ //! as the above conditions are met.
+
+
//! Added in version 4.0:
//!
//! - 1) better support 3D and 4D data, allow for lossy encoding even if a noData value is used
=====================================
src/LercLib/include/Lerc_types.h
=====================================
@@ -15,7 +15,8 @@ namespace LercNS
WrongParam,
BufferTooSmall,
NaN,
- HasNoData
+ HasNoData,
+ DimensionsTooLarge
};
enum class DataType : int
View it on GitLab: https://salsa.debian.org/debian-gis-team/lerc/-/compare/c08afb4ae4a566858d6e161e3bacbd42b115ef2d...0d3e829a239a603eae4f1f05fee507a367032753
--
View it on GitLab: https://salsa.debian.org/debian-gis-team/lerc/-/compare/c08afb4ae4a566858d6e161e3bacbd42b115ef2d...0d3e829a239a603eae4f1f05fee507a367032753
You're receiving this email because of your account on salsa.debian.org. Manage all notifications: https://salsa.debian.org/-/profile/notifications | Help: https://salsa.debian.org/help
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://alioth-lists.debian.net/pipermail/pkg-grass-devel/attachments/20260729/9db00d0a/attachment-0001.htm>
More information about the Pkg-grass-devel
mailing list