Repository: exiv2/exiv2
Fuzzing engine: libfuzzer
Fuzz target: fuzz-read-print-write
Sanitizer: address
Crash type: Heap-buffer-overflow READ 1

Sanitizer report:
INFO: Running with entropic power schedule (0xFF, 100).
INFO: Seed: 3745154859
INFO: Loaded 1 modules   (56818 inline 8-bit counters): 56818 [0x555c71641960, 0x555c7164f752), 
INFO: Loaded 1 PC tables (56818 PCs): 56818 [0x555c7164f758,0x555c7172d678), 
/out/fuzz-read-print-write: Running 1 inputs 1 time(s) each.
Running: /testcase/OSV-2022-280.poc
=================================================================
==7==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x502000000094 at pc 0x555c70fc4f0f bp 0x7ffc4ff83240 sp 0x7ffc4ff83238
READ of size 1 at 0x502000000094 thread T0
SCARINESS: 12 (1-byte-read-heap-buffer-overflow)
    #0 0x555c70fc4f0e in Exiv2::getULong(unsigned char const*, Exiv2::ByteOrder) /src/exiv2/src/types.cpp:278:10
    #1 0x555c71083752 in Exiv2::Internal::isValidBoxFileType(std::__1::vector<unsigned char, std::__1::allocator<unsigned char>> const&) /src/exiv2/src/jp2image_int.cpp:20:33
    #2 0x555c70f6bc30 in Exiv2::Jp2Image::readMetadata() /src/exiv2/src/jp2image.cpp:186:14
    #3 0x555c70efa08b in LLVMFuzzerTestOneInput /src/exiv2/fuzz/fuzz-read-print-write.cpp:22:12
    #4 0x555c70dae930 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:614:13
    #5 0x555c70d99ba5 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:327:6
    #6 0x555c70d9f63f in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:862:9
    #7 0x555c70dca8e2 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #8 0x7fb000b70082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 0702430aef5fa3dda43986563e9ffcc47efbd75e)
    #9 0x555c70d91d8d in _start (/out/fuzz-read-print-write+0x3b5d8d)

DEDUP_TOKEN: Exiv2::getULong(unsigned char const*, Exiv2::ByteOrder)--Exiv2::Internal::isValidBoxFileType(std::__1::vector<unsigned char, std::__1::allocator<unsigned char>> const&)--Exiv2::Jp2Image::readMetadata()
0x502000000094 is located 0 bytes after 4-byte region [0x502000000090,0x502000000094)
allocated by thread T0 here:
    #0 0x555c70ef7acd in operator new(unsigned long) /src/llvm-project/compiler-rt/lib/asan/asan_new_delete.cpp:86:3
    #1 0x555c70f49dad in __libcpp_operator_new<unsigned long> /usr/local/bin/../include/c++/v1/new:271:10
    #2 0x555c70f49dad in __libcpp_allocate /usr/local/bin/../include/c++/v1/new:295:10
    #3 0x555c70f49dad in allocate /usr/local/bin/../include/c++/v1/__memory/allocator.h:125:32
    #4 0x555c70f49dad in __allocate_at_least<std::__1::allocator<unsigned char> > /usr/local/bin/../include/c++/v1/__memory/allocate_at_least.h:55:19
    #5 0x555c70f49dad in __vallocate /usr/local/bin/../include/c++/v1/vector:741:25
    #6 0x555c70f49dad in std::__1::vector<unsigned char, std::__1::allocator<unsigned char>>::vector(unsigned long) /usr/local/bin/../include/c++/v1/vector:1121:5
    #7 0x555c70f6bbef in Exiv2::Jp2Image::readMetadata() /src/exiv2/src/jp2image.cpp:184:27
    #8 0x555c70efa08b in LLVMFuzzerTestOneInput /src/exiv2/fuzz/fuzz-read-print-write.cpp:22:12
    #9 0x555c70dae930 in fuzzer::Fuzzer::ExecuteCallback(unsigned char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerLoop.cpp:614:13
    #10 0x555c70d99ba5 in fuzzer::RunOneTest(fuzzer::Fuzzer*, char const*, unsigned long) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:327:6
    #11 0x555c70d9f63f in fuzzer::FuzzerDriver(int*, char***, int (*)(unsigned char const*, unsigned long)) /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerDriver.cpp:862:9
    #12 0x555c70dca8e2 in main /src/llvm-project/compiler-rt/lib/fuzzer/FuzzerMain.cpp:20:10
    #13 0x7fb000b70082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082) (BuildId: 0702430aef5fa3dda43986563e9ffcc47efbd75e)

DEDUP_TOKEN: operator new(unsigned long)--__libcpp_operator_new<unsigned long>--__libcpp_allocate
SUMMARY: AddressSanitizer: heap-buffer-overflow /src/exiv2/src/types.cpp:278:10 in Exiv2::getULong(unsigned char const*, Exiv2::ByteOrder)
Shadow bytes around the buggy address:
  0x501ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x501fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x502000000000: fa fa 00 00 fa fa 00 fa fa fa 00 fa fa fa 00 fa
=>0x502000000080: fa fa[04]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x502000000300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
  Addressable:           00
  Partially addressable: 01 02 03 04 05 06 07 
  Heap left redzone:       fa
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack after return:      f5
  Stack use after scope:   f8
  Global redzone:          f9
  Global init order:       f6
  Poisoned by user:        f7
  Container overflow:      fc
  Array cookie:            ac
  Intra object redzone:    bb
  ASan internal:           fe
  Left alloca redzone:     ca
  Right alloca redzone:    cb
==7==ABORTING


Fix patch:
diff --git a/src/jp2image_int.cpp b/src/jp2image_int.cpp
index 9d67bc6cc..c4a356382 100644
--- a/src/jp2image_int.cpp
+++ b/src/jp2image_int.cpp
@@ -11,7 +11,7 @@ namespace Exiv2::Internal {
 
 bool isValidBoxFileType(const std::vector<uint8_t>& boxData) {
   // BR & MinV are obligatory (4 + 4 bytes). Afterwards we have N compatibility lists (of size 4)
-  if ((boxData.size() - 8u) % 4u != 0) {
+  if (boxData.size() < 8 || ((boxData.size() - 8u) % 4u) != 0) {
     return false;
   }
 
diff --git a/unitTests/test_jp2image_int.cpp b/unitTests/test_jp2image_int.cpp
index 6af2bb2a5..cd83c19bf 100644
--- a/unitTests/test_jp2image_int.cpp
+++ b/unitTests/test_jp2image_int.cpp
@@ -58,6 +58,11 @@ TEST(Jp2_FileTypeBox, withInvalidBoxDataSizeIsInvalid) {
   ASSERT_FALSE(isValidBoxFileType(boxData));
 }
 
+TEST(Jp2_FileTypeBox, withSmallBoxDataSizeIsInvalid) {
+  std::vector<std::uint8_t> boxData(7);  // Minimum size is 8
+  ASSERT_FALSE(isValidBoxFileType(boxData));
+}
+
 TEST(Jp2_FileTypeBox, with2CLs_lastOneWithBrandValue_isValid) {
   std::vector<std::uint8_t> boxData(16);
   // The first 4 bytes correspond to the BR (Brand). It must have the value 'jp2\040'