Skip to content

5 memory-safety bugs in NITF header/TRE and image decode paths #633

Description

@clarkparry

Hi, I found 5 distinct memory-safety bugs in NITRO's NITF reader and image decode path while testing with a fuzzing harness on x86-64 Ubuntu 22.04. All are still reproducible on commit 827437b.

Compiler

Ubuntu clang version 20.1.8

Build commands

git clone https://github.com/mdaus/nitro.git
cd nitro

# -Werror trips on newer clang warnings; relax that for the ASan build
sed -i 's/add_compile_options(-Werror) # warnings as errors/add_compile_options(-Wno-error)/' \
  CMakeLists.txt externals/coda-oss/CMakeLists.txt

mkdir build && cd build
cmake -G "Unix Makefiles" \
  -DCMAKE_BUILD_TYPE=Release \
  -DBUILD_SHARED_LIBS=OFF \
  -DENABLE_PYTHON=OFF \
  -DCODA_BUILD_TESTS=OFF \
  -DCODA_INSTALL_TESTS=OFF \
  -DCMAKE_C_COMPILER=clang-20 \
  -DCMAKE_CXX_COMPILER=clang++-20 \
  -DCMAKE_C_FLAGS="-O1 -g -fno-omit-frame-pointer -fsanitize=address -static-libsan -D_GNU_SOURCE -Wno-unknown-warning-option -Wno-error=unknown-warning-option" \
  -DCMAKE_CXX_FLAGS="-O1 -g -fno-omit-frame-pointer -fsanitize=address -static-libsan -D_GNU_SOURCE -Wno-unknown-warning-option -Wno-error=unknown-warning-option" \
  ..
make all -j12
cmake --install . --prefix ../install

The -D_GNU_SOURCE is required because externals/coda-oss/modules/drivers/hdf5/source/H5E.c calls vasprintf, a GNU extension that glibc's <stdio.h> only declares under _GNU_SOURCE. Without it, clang-20 fails the build.

I used a small harness based on NITRO's modules/c++/nitf/tests/test_image_loading++.cpp.

nitro_image_harness.cpp
#include <import/nitf.hpp>
#include <import/except.h>

#include <vector>
#include <cstdint>
#include <cstdlib>

static const size_t MAX_SEGMENT_BYTES = 512ull * 1024 * 1024;

static void readSegment(nitf::ImageSegment& segment,
                        nitf::Reader& reader,
                        int imageNumber)
{
    nitf::ImageReader deserializer = reader.newImageReader(imageNumber);
    nitf::ImageSubheader subheader = segment.getSubheader();

    const nitf::Uint32 nBits = subheader.getNumBitsPerPixel();
    nitf::Uint32 nBands = subheader.getNumImageBands();
    nBands += (nitf::Uint32)subheader.getNumMultispectralImageBands();
    const nitf::Uint32 nRows = subheader.getNumRows();
    const nitf::Uint32 nCols = subheader.getNumCols();

    if (nBands == 0 || nRows == 0 || nCols == 0)
        return;

    const size_t bytesPerPixel = (size_t)NITF_NBPP_TO_BYTES(nBits);
    const size_t perBand = (size_t)nRows * (size_t)nCols * bytesPerPixel;

    if (bytesPerPixel == 0 || perBand / nCols / bytesPerPixel != (size_t)nRows)
        return;
    if (perBand > MAX_SEGMENT_BYTES || perBand * nBands > MAX_SEGMENT_BYTES)
        return;

    std::vector<nitf::Uint32> bandList(nBands);
    std::vector<std::vector<nitf::Uint8> > storage(nBands);
    std::vector<nitf::Uint8*> buffer(nBands);
    for (nitf::Uint32 b = 0; b < nBands; ++b) {
        bandList[b] = b;
        storage[b].resize(perBand);
        buffer[b] = storage[b].data();
    }

    nitf::SubWindow subWindow;
    subWindow.setStartRow(0);
    subWindow.setStartCol(0);
    subWindow.setNumRows(nRows);
    subWindow.setNumCols(nCols);
    subWindow.setBandList(bandList.data());
    subWindow.setNumBands(nBands);

    int padded = 0;
    deserializer.read(subWindow, buffer.data(), &padded);  // forces decode
}

int main(int argc, char** argv)
{
    if (argc < 2)
        return 1;

    try {
        nitf::Reader reader;
        nitf::IOHandle io(argv[1]);
        nitf::Record record = reader.read(io);

        nitf::ListIterator iter = record.getImages().begin();
        nitf::ListIterator end = record.getImages().end();
        for (int count = 0; iter != end; ++iter, ++count) {
            try {
                nitf::ImageSegment segment((nitf_ImageSegment*)iter.get());
                readSegment(segment, reader, count);
            } catch (const except::Exception&) {
            } catch (const std::exception&) {
            }
        }
    } catch (const except::Exception&) {
        return 1;
    } catch (const std::exception&) {
        return 1;
    }
    return 0;
}

From the NITRO root:

clang++-20 -O1 -g -fno-omit-frame-pointer -fsanitize=address -static-libsan \
  -D_GNU_SOURCE -Wno-unknown-warning-option -Wno-error=unknown-warning-option \
  -o asan_driver nitro_image_harness.cpp \
  -I install/include -L install/lib \
  -lnitf-c++ -lj2k-c -lnitf-c -lnrt-c -lopenjpeg -lsio.lite-c++ -lio-c++ \
  -lmt-c++ -lmem-c++ -lmath-c++ -lsys-c++ -lstr-c++ -lexcept-c++ -ltypes-c++ -ldl

Commandline

From the NITRO root:

export NITF_PLUGIN_PATH="$(pwd)/install/share/nitf/plugins"
./asan_driver <input>

Bug 1 — nrt_Utils_trimString stack-buffer-over/underflow (read) (Utils.c:182)

strp = str + (strlen(str) - 1);
while (isspace(*strp) && strp != str)   // <-- line 182
    *(strp--) = '\0';
stack-buffer-overflow(read) via readDESubheader
==1569180==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7bfff590003f at pc 0x555555802496 bp 0x7ffffffd80c0 sp 0x7ffffffd80b8
READ of size 1 at 0x7bfff590003f thread T0
    #0 0x555555802495 in nrt_Utils_trimString modules/c/nrt/source/Utils.c:182:12
    #1 0x5555557d283d in readDESubheader modules/c/nitf/source/NitfReader.c:847:5
    #2 0x5555557d1c87 in nitf_Reader_readIO modules/c/nitf/source/NitfReader.c:1720:14
    #3 0x55555572c61e in nitf::Reader::readIO(nitf::IOInterface&) modules/c++/nitf/source/Reader.cpp:100:23
    #4 0x55555572c1fd in nitf::Reader::read(nitf::IOHandle&) modules/c++/nitf/source/Reader.cpp:82:12
stack-buffer-underflow(read) via readTRE/readExtras/readHeader
==1571208==ERROR: AddressSanitizer: stack-buffer-underflow on address 0x7bfff590001f at pc 0x555555802496 bp 0x7ffffffd8060 sp 0x7ffffffd8058
READ of size 1 at 0x7bfff590001f thread T0
    #0 0x555555802495 in nrt_Utils_trimString modules/c/nrt/source/Utils.c:182:12
    #1 0x5555557d5c4a in readTRE modules/c/nitf/source/NitfReader.c:1308:5
    #2 0x5555557d58b9 in readExtras modules/c/nitf/source/NitfReader.c:1231:18
    #3 0x5555557cf984 in readHeader modules/c/nitf/source/NitfReader.c:1167:5
    #4 0x5555557cf984 in nitf_Reader_readIO modules/c/nitf/source/NitfReader.c:1543:10

Files:


Bug 2 — readField negative-size-param (NitfReader.c:258)

==1567627==ERROR: AddressSanitizer: negative-size-param: (size=-4)
    #0 0x5555556b1fe8 in __asan_memset
    #1 0x5555557d5a73 in readField modules/c/nitf/source/NitfReader.c:258:5
    #2 0x5555557d31c3 in readRESubheader modules/c/nitf/source/NitfReader.c:987:14
    #3 0x5555557d1dac in nitf_Reader_readIO modules/c/nitf/source/NitfReader.c:1746:14

File: nitro_bug2.zip


Bug 3 — nitf_ImageIO_unformatSwapExtend_4 heap-buffer-overflow (ImageIO.c:8295/8302)

for (i = 0; i < count; i++)
{
    bp8 = (uint8_t *) (bp32++);
    tmp8 = bp8[0];        // <-- line 8295
    ...
    tmp32 = (uint8_t)(*bp32 << shift);   // <-- line 8302
==1577823==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c2ff6be1be8 at pc 0x5555557bde55 bp 0x7ffffffd8240 sp 0x7ffffffd8238
READ of size 4 at 0x7c2ff6be1be8 thread T0
    #0 0x5555557bde54 in nitf_ImageIO_unformatSwapExtend_4 modules/c/nitf/source/ImageIO.c:8295:16
    #1 0x5555557a8532 in nitf_ImageIO_oneRead modules/c/nitf/source/ImageIO.c:6747:9
    #2 0x5555557a8532 in nitf_ImageIO_read modules/c/nitf/source/ImageIO.c:3446:23
    #3 0x55555570ffe9 in nitf::ImageReader::read(...) modules/c++/nitf/source/ImageReader.cpp:65:25
    #4 0x5555556f9b92 in readSegment(...) nitro_image_harness.cpp:76:18
    #5 0x5555556f9b92 in main nitro_image_harness.cpp:95:17

Files:


Bug 4 — TRE field data overrun in nitf_TREUtils_parse/nitf_Field_setRawData (TREUtils.c:789/115, Field.c:130)

nitf_TREUtils_basicRead allocates a buffer sized to the TRE's own declared data length (from the subheader):

data = (char*)NITF_MALLOC(length);   /* TREUtils.c:789 */
status = nitf_Field_setRawData(field, (NITF_DATA*)(bufptr + offset), length, error);  /* TREUtils.c:115 */
...
offset += length;
==1221902==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7d1ff6be02c4 at pc 0x5555556b1e0b bp 0x7ffffffd7ef0 sp 0x7ffffffd76b0
READ of size 5 at 0x7d1ff6be02c4 thread T0
    #0 0x5555556b1e0a in __asan_memcpy
    #1 0x7bffecada4e3 in nitf_Field_setRawData modules/c/nitf/source/Field.c:130:9
    #2 0x7bffecad63b5 in nitf_TREUtils_parse modules/c/nitf/source/TREUtils.c:115:26
    #3 0x7bffecad8ad7 in nitf_TREUtils_basicRead modules/c/nitf/source/TREUtils.c:835:14
    #4 0x5555557d5f81 in handleTRE modules/c/nitf/source/NitfReader.c:1366:18
    #5 0x5555557d5cbe in readTRE modules/c/nitf/source/NitfReader.c:1318:10
    #6 0x5555557d58b9 in readExtras modules/c/nitf/source/NitfReader.c:1231:18
    #7 0x5555557cf984 in readHeader modules/c/nitf/source/NitfReader.c:1167:5
    #8 0x5555557cf984 in nitf_Reader_readIO modules/c/nitf/source/NitfReader.c:1543:10

0x7d1ff6be02c4 is located 0 bytes after 260-byte region [0x7d1ff6be01c0,0x7d1ff6be02c4)
allocated by thread T0 here:
    #0 0x5555556b40c4 in malloc
    #1 0x7bffecad892a in nitf_TREUtils_basicRead modules/c/nitf/source/TREUtils.c:789:19
    #2 0x5555557d5f81 in handleTRE modules/c/nitf/source/NitfReader.c:1366:18
    #3 0x5555557d5cbe in readTRE modules/c/nitf/source/NitfReader.c:1318:10
    #4 0x5555557d58b9 in readExtras modules/c/nitf/source/NitfReader.c:1231:18
    #5 0x5555557cf984 in readHeader modules/c/nitf/source/NitfReader.c:1167:5
    #6 0x5555557cf984 in nitf_Reader_readIO modules/c/nitf/source/NitfReader.c:1543:10

Files:


Bug 5 — blockMask heap-buffer-overflow (read): block index from file exceeds allocation (ImageIO.c:4873, 7356)

nitf_ImageIO_commonBlockInitImageIO.c:4873
blockIO->blockMask = nitf->blockMask + maskOffset;
blockIO->imageDataOffset = blockIO->blockMask[blockNumber];   /* ImageIO.c:4873 */
==1567679==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c1ff6be1f58 at pc 0x5555557b728d bp 0x7ffffffd7f60 sp 0x7ffffffd7f58
READ of size 8 at 0x7c1ff6be1f58 thread T0
    #0 0x5555557b728c in nitf_ImageIO_commonBlockInit modules/c/nitf/source/ImageIO.c:4873:32
    #1 0x5555557b7962 in nitf_ImageIO_setup_P modules/c/nitf/source/ImageIO.c:5533:13
    #2 0x5555557a9c74 in nitf_ImageIOControl_construct modules/c/nitf/source/ImageIO.c:5812:10
    #3 0x5555557a7f3a in nitf_ImageIO_read modules/c/nitf/source/ImageIO.c:3463:16

Files: nitro_bug5a.zip

nitf_ImageIO_nextRowImageIO.c:7356
blockIO->number += cntl->numberInc;
blockIO->imageDataOffset = blockIO->blockMask[blockIO->number];   /* ImageIO.c:7356 */
==1572112==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7c1ff6bf19d8 at pc 0x5555557ad8db bp 0x7ffffffd81a0 sp 0x7ffffffd8198
READ of size 8 at 0x7c1ff6bf19d8 thread T0
    #0 0x5555557ad8da in nitf_ImageIO_nextRow modules/c/nitf/source/ImageIO.c:7356:36
    #1 0x5555557ad8da in nitf_ImageIO_readRequest modules/c/nitf/source/ImageIO.c:6813:21
    #2 0x5555557a86c8 in nitf_ImageIO_read modules/c/nitf/source/ImageIO.c:3484:19
    #3 0x55555570ffe9 in nitf::ImageReader::read(...) modules/c++/nitf/source/ImageReader.cpp:65:25

Files: nitro_bug5b.zip


Let me know if you'd like these split into separate issues instead, or need anything else to reproduce.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions