[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