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_commonBlockInit — ImageIO.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_nextRow — ImageIO.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.
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 commit827437b.Compiler
Build commands
The
-D_GNU_SOURCEis required becauseexternals/coda-oss/modules/drivers/hdf5/source/H5E.ccallsvasprintf, 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.cppFrom the NITRO root:
Commandline
From the NITRO root:
Bug 1 —
nrt_Utils_trimStringstack-buffer-over/underflow (read) (Utils.c:182)stack-buffer-overflow(read) via
readDESubheaderstack-buffer-underflow(read) via
readTRE/readExtras/readHeaderFiles:
readDESubheader: nitro_bug1a.zipreadTRE/readExtras/readHeader: nitro_bug1b.zipBug 2 —
readFieldnegative-size-param (NitfReader.c:258)File: nitro_bug2.zip
Bug 3 —
nitf_ImageIO_unformatSwapExtend_4heap-buffer-overflow (ImageIO.c:8295/8302)Files:
Bug 4 — TRE field data overrun in
nitf_TREUtils_parse/nitf_Field_setRawData(TREUtils.c:789/115,Field.c:130)nitf_TREUtils_basicReadallocates a buffer sized to the TRE's own declared data length (from the subheader):Files:
Bug 5 —
blockMaskheap-buffer-overflow (read): block index from file exceeds allocation (ImageIO.c:4873,7356)nitf_ImageIO_commonBlockInit—ImageIO.c:4873Files: nitro_bug5a.zip
nitf_ImageIO_nextRow—ImageIO.c:7356Files: nitro_bug5b.zip
Let me know if you'd like these split into separate issues instead, or need anything else to reproduce.