Project: exiv2 (exiv2/exiv2), language: c++
Vulnerability instance: exiv2.cve-2018-10958
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/Exiv2/exiv2/issues/302
## Description:
Issue: Exiv2/exiv2#302
Title: SIGABRT in types.cpp Exiv2::DataBuf::alloc function
State: closed
Created by: legend-issue
Created at: 2018-05-09 13:38:53+00:00

Issue Body:
```jsx
$rax   : 0x0000000000000000
$rbx   : 0x00007fffffffd360  →  0x0000000000000000
$rcx   : 0x0000000000ec0fed  →  0x6e69676522000001
$rdx   : 0x00007fffffffd360  →  0x0000000000000000
$rsp   : 0x00007fffffffd200  →  0x0000000000000000
$rbp   : 0x00000000ffffffea
$rsi   : 0x00000000ffffffea
$rdi   : 0x0000000000000000
$rip   : 0x00000000007685c7  →  <Exiv2::DataBuf::alloc(long)+279> mov rdi, rbp
$r8    : 0xffffffffffffe310
$r9    : 0xffffffffffffe300
$r10   : 0xffffffffffffe2f0
$r11   : 0x000000000000047a
$r12   : 0x0000000000ec146b  →  0xbcaea80000000000
$r13   : 0x000000000000001c
$r14   : 0x0000000000000000
$r15   : 0x0000000000000000
$eflags: [carry PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification]
$es: 0x0000  $gs: 0x0000  $ds: 0x0000  $ss: 0x002b  $fs: 0x0000  $cs: 0x0033  
───────────────────────────────────────────────────────────────────[ stack ]────
0x00007fffffffd200│+0x00: 0x0000000000000000	 ← $rsp
0x00007fffffffd208│+0x08: 0x00007fffffffd360  →  0x0000000000000000
0x00007fffffffd210│+0x10: 0x00000000fffffff5
0x00007fffffffd218│+0x18: 0x0000000000813738  →  <Exiv2::Internal::PngChunk::zlibUncompress(unsigned+0> mov rdi, QWORD PTR [rbx]
0x00007fffffffd220│+0x20: 0x00000000ffffffea
0x00007fffffffd228│+0x28: 0xa65c0e3e1fcb3000
0x00007fffffffd230│+0x30: 0x0000000000ec0fd0  →  0x2e6d6f633a4c4d58
0x00007fffffffd238│+0x38: 0x00007fffffffd360  →  0x0000000000000000
────────────────────────────────────────────────────────[ code:i386:x86-64 ]────
     0x7685b0 <Exiv2::DataBuf::alloc(long)+256> lea    rsp, [rsp+0x98]
     0x7685b8 <Exiv2::DataBuf::alloc(long)+264> mov    QWORD PTR [rbx], 0x0
     0x7685bf <Exiv2::DataBuf::alloc(long)+271> mov    QWORD PTR [rbx+0x8], 0x0
 →   0x7685c7 <Exiv2::DataBuf::alloc(long)+279> mov    rdi, rbp
     0x7685ca <Exiv2::DataBuf::alloc(long)+282> call   0x405ac0 <_Znam@plt>
     0x7685cf <Exiv2::DataBuf::alloc(long)+287> mov    QWORD PTR [rbx+0x8], rbp
     0x7685d3 <Exiv2::DataBuf::alloc(long)+291> mov    QWORD PTR [rbx], rax
     0x7685d6 <Exiv2::DataBuf::alloc(long)+294> add    rsp, 0x8
     0x7685da <Exiv2::DataBuf::alloc(long)+298> pop    rbx
────────────────────────────────────────────────────[ source:types.cpp+153 ]────
    148	     {
    149	         if (size > size_) {
    150	             delete[] pData_;
    151	             pData_ = 0;
    152	             size_ = 0;
		// size=0xffffffea
 →  153	             pData_ = new byte[size];
    154	             size_ = size;
    155	         }
    156	     }
    157	 
    158	     std::pair<byte*, long> DataBuf::release()
─────────────────────────────────────────────────────────────────[ threads ]────
[#0] Id 1, Name: "exiv2", stopped, reason: SINGLE STEP
───────────────────────────────────────────────────────────────────[ trace ]────
[#0] 0x7685c7 → Name: Exiv2::DataBuf::alloc(this=0x7fffffffd360, size=0xffffffea)
[#1] 0x813738 → Name: Exiv2::Internal::PngChunk::zlibUncompress(compressedText=0xec146b "", compressedTextSize=0xfffffff5, arr=@0x7fffffffd360)
[#2] 0x814330 → Name: Exiv2::Internal::PngChunk::parseTXTChunk(data=@0x7fffffffd3f0, keysize=0x1c, type=Exiv2::Internal::PngChunk::iTXt_Chunk)
[#3] 0x8153fe → Name: Exiv2::Internal::PngChunk::decodeTXTChunk(pImage=0xec0a90, data=@0x7fffffffd3f0, type=Exiv2::Internal::PngChunk::iTXt_Chunk)
[#4] 0x804342 → Name: Exiv2::PngImage::readMetadata(this=0xec0a90)
[#5] 0x480362 → Name: Action::Print::printSummary(this=0xec1c10)
[#6] 0x486d68 → Name: Action::Print::run(this=0xec1c10, path="id:000004,sig:06,src:000036,op:havoc,rep:128")
[#7] 0x40772d → Name: main(argc=0x2, argv=0x7fffffffdf38)
[#8] 0x7fffbe826830 → Name: __libc_start_main(main=0x4073d0 <main(int, char* const*)>, argc=0x2, argv=0x7fffffffdf38, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdf28)
[#9] 0x4277c9 → Name: _start()
────────────────────────────────────────────────────────────────────────────────
gef➤  
terminate called after throwing an instance of 'std::bad_alloc'
  what():  std::bad_alloc

Program received signal SIGABRT, Aborted.
```
I find this when I set ‘ulimit -v 1048576(1G)’.
The command is "exiv2 -et [poc]"
https://github.com/legend-issue/pocs/blob/master/exiv2/id:000004%2Csig:06%2Csrc:000036%2Cop:havoc%2Crep:128


Comments:
Comment by kbabioch on 2018-05-11 14:33:37+00:00:
The reproducer is not accessible, could you make it downloadable please?

---

Comment by legend-issue on 2018-05-14 00:19:20+00:00:
I think it's OK now!

---

Comment by fgeek on 2018-05-15 01:30:43+00:00:
Reproducer SHA1: e43c1eb7134d2fc4c5548253477fd09d6b2fac79
In bb2019149ae2b6f226e8d6be5f5828898b455a03 with ASan:
```
=================================================================
==11430==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x61a00001f710 at pc 0x7f1599a44063 bp 0x7fff2e000d70 sp 0x7fff2e000520
READ of size 1137 at 0x61a00001f710 thread T0
    #0 0x7f1599a44062  (/usr/lib/x86_64-linux-gnu/libasan.so.3+0x3c062)
    #1 0x7f1598e71aec in std::char_traits<char>::length(char const*) /usr/include/c++/6/bits/char_traits.h:267
    #2 0x7f1598e71aec in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::basic_string(char const*, std::allocator<char> const&) /usr/include/c++/6/bits/basic_string.h:456
    #3 0x7f1598e71aec in Exiv2::Internal::PngChunk::parseTXTChunk(Exiv2::DataBuf const&, int, Exiv2::Internal::PngChunk::TxtChunkType) /home/hsalo/src/exiv2/src/pngchunk_int.cpp:175
    #4 0x7f1598e72b54 in Exiv2::Internal::PngChunk::decodeTXTChunk(Exiv2::Image*, Exiv2::DataBuf const&, Exiv2::Internal::PngChunk::TxtChunkType) /home/hsalo/src/exiv2/src/pngchunk_int.cpp:81
    #5 0x7f1598e5fdc3 in Exiv2::PngImage::readMetadata() /home/hsalo/src/exiv2/src/pngimage.cpp:461
    #6 0x562ef0f1fc07 in Action::Extract::writeThumbnail() const /home/hsalo/src/exiv2/src/actions.cpp:1119
    #7 0x562ef0f3c8df in Action::Extract::run(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /home/hsalo/src/exiv2/src/actions.cpp:1077
    #8 0x562ef0ec12b8 in main /home/hsalo/src/exiv2/src/exiv2.cpp:166
    #9 0x7f1597afc2e0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202e0)
    #10 0x562ef0ec20d9 in _start (/home/hsalo/builds/exiv2/bb2019149ae2b6f226e8d6be5f5828898b455a03/bin/exiv2+0x130d9)

0x61a00001f710 is located 0 bytes to the right of 1168-byte region [0x61a00001f280,0x61a00001f710)
allocated by thread T0 here:
    #0 0x7f1599acad70 in operator new[](unsigned long) (/usr/lib/x86_64-linux-gnu/libasan.so.3+0xc2d70)
    #1 0x7f1598e5f3d7 in Exiv2::DataBuf::DataBuf(long) ../include/exiv2/types.hpp:206
    #2 0x7f1598e5f3d7 in Exiv2::PngImage::readMetadata() /home/hsalo/src/exiv2/src/pngimage.cpp:420
    #3 0x562ef0f1fc07 in Action::Extract::writeThumbnail() const /home/hsalo/src/exiv2/src/actions.cpp:1119
    #4 0x562ef0f3c8df in Action::Extract::run(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /home/hsalo/src/exiv2/src/actions.cpp:1077
    #5 0x562ef0ec12b8 in main /home/hsalo/src/exiv2/src/exiv2.cpp:166
    #6 0x7f1597afc2e0 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x202e0)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/usr/lib/x86_64-linux-gnu/libasan.so.3+0x3c062)
Shadow bytes around the buggy address:
  0x0c347fffbe90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c347fffbea0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c347fffbeb0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c347fffbec0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c347fffbed0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x0c347fffbee0: 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fffbef0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fffbf00: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fffbf10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fffbf20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c347fffbf30: 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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==11430==ABORTING
```
Minimized sample (SHA1: 878078e9db9a8bdb875297d1d1131037dc996e8b) [exiv2-issue-302.png.zip](https://github.com/Exiv2/exiv2/files/2003126/exiv2-issue-302.png.zip) with afl-tmin:
```
File size reduced by : 98.72% (to 1125 bytes)
Characters simplified : 7757.60%
Number of execs done : 828
Fruitless execs : path=637 crash=0 hang=0
```

---

Comment by piponazo on 2018-05-23 09:01:01+00:00:
This issue was also solved by #316 

---

Comment by rcsanchez97 on 2018-06-26 02:52:33+00:00:
@piponazo I am trying to backport the fix for CVE-2018-10958/CVE-2018-10999 to older versions of exiv2 in Debian (0.25, 0.24, and 0.23).  I have prepared two patches based on commits 2fb00c8 and 3ad0050.  In particular, I tried to avoid introducing the new enforce mechanism and instead tried to accomplish the same effect with if statements.  I would appreciate it if you could review my work and provide any feedback on any adjustments that I might need to make.

Patch 1:
``` diff
--- exiv2.git.orig/src/pngchunk.cpp
+++ exiv2.git/src/pngchunk.cpp
@@ -60,6 +60,7 @@
 #include <iostream>
 #include <cassert>
 #include <cstdio>
+#include <algorithm>
 
 /*
 
@@ -166,6 +167,9 @@
         }
         else if(type == iTXt_Chunk)
         {
+            const int nullSeparators = std::count(&data.pData_[keysize+3], &data.pData_[data.size_-1], '\0');
+            if (nullSeparators < 2) throw Error(58);
+
             // Extract a deflate compressed or uncompressed UTF-8 text chunk
 
             // we get the compression flag after the key
--- exiv2.git.orig/src/error.cpp
+++ exiv2.git/src/error.cpp
@@ -105,7 +105,8 @@
         { 49, N_("TIFF directory %1 has too many entries") }, // %1=TIFF directory name
         { 50, N_("Multiple TIFF array element tags %1 in one directory") }, // %1=tag number
         { 51, N_("TIFF array element tag %1 has wrong type") }, // %1=tag number
-        { 52, N_("%1 has invalid XMP value type `%2'") } // %1=key, %2=value type
+        { 52, N_("%1 has invalid XMP value type `%2'") }, // %1=key, %2=value type
+        { 58, N_("corrupted image metadata") }
     };
 
 }
```

Patch 2:
``` diff
--- exiv2.git.orig/src/pngchunk.cpp
+++ exiv2.git/src/pngchunk.cpp
@@ -168,14 +168,24 @@
         else if(type == iTXt_Chunk)
         {
             const int nullSeparators = std::count(&data.pData_[keysize+3], &data.pData_[data.size_-1], '\0');
-            if (nullSeparators < 2) throw Error(58);
+            if (nullSeparators < 2) throw Error(58, "iTXt chunk: not enough null separators");
 
             // Extract a deflate compressed or uncompressed UTF-8 text chunk
 
             // we get the compression flag after the key
-            const byte* compressionFlag   = data.pData_ + keysize + 1;
+            const byte compressionFlag   = data.pData_[keysize + 1];
             // we get the compression method after the compression flag
-            const byte* compressionMethod = data.pData_ + keysize + 2;
+            const byte compressionMethod = data.pData_[keysize + 2];
+
+            if (compressionFlag != 0x00 && compressionFlag != 0x01)
+            {
+                    throw Error(58, "iTXt chunk: not valid value in compressionFlag");
+            }
+            if (compressionMethod != 0x00)
+            {
+                    throw Error(58, "iTXt chunk: not valid value in compressionMethod");
+            }
+
             // language description string after the compression technique spec
             std::string languageText((const char*)(data.pData_ + keysize + 3));
             unsigned int languageTextSize = static_cast<unsigned int>(languageText.size());
@@ -183,7 +193,7 @@
             std::string translatedKeyText((const char*)(data.pData_ + keysize + 3 + languageTextSize +1));
             unsigned int translatedKeyTextSize = static_cast<unsigned int>(translatedKeyText.size());

-            if ( compressionFlag[0] == 0x00 )
+            if ( compressionFlag == 0x00 )
             {
                 // then it's an uncompressed iTXt chunk
 #ifdef DEBUG
@@ -197,7 +207,7 @@
                 arr.alloc(textsize);
                 arr = DataBuf(text, textsize);
             }
-            else if ( compressionFlag[0] == 0x01 && compressionMethod[0] == 0x00 )
+            else if ( compressionFlag == 0x01 && compressionMethod == 0x00 )
             {
                 // then it's a zlib compressed iTXt chunk
 #ifdef DEBUG
```

---

Comment by D4N on 2018-07-28 23:18:31+00:00:
@rcsanchez97 Sorry for not getting back to you earlier, but your first patch has a small issue, instead of: 
```
+            const int nullSeparators = std::count(&data.pData_[keysize+3], &data.pData_[data.size_-1], '\0');
```
it should be:
```
+            const int nullSeparators = std::count(&data.pData_[keysize+3], &data.pData_[data.size_], '\0');
```
(the `-1` was wrong, see the discussion of the associated pull request).

The rest looks good to me. You can drop the strings from the throws in the second patch where you throw an error 58, as the string will be ignored in the exception message anyway.

However, please test that your patches fix the issue by running exiv2 with asan.

---

Comment by piponazo on 2018-07-29 19:53:18+00:00:
Hi @rcsanchez97 . I am also sorry for not replying before. I double checked the changes and the only issue I see is what @D4N pointed out already. 

Attachments:
https://github.com/Exiv2/exiv2/files/2003126/exiv2-issue-302.png.zip

Commit References:
878078e9db9a8bdb875297d1d1131037dc996e8b
bb2019149ae2b6f226e8d6be5f5828898b455a03
e43c1eb7134d2fc4c5548253477fd09d6b2fac79

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
mkdir -p build
cd build
cmake -DEXIV2_ENABLE_PNG=ON -DEXIV2_ENABLE_WEBREADY=ON -DEXIV2_ENABLE_CURL=OFF -DEXIV2_ENABLE_BMFF=ON -DEXIV2_TEAM_WARNINGS_AS_ERRORS=ON -DBUILD_SHARED_LIBS=OFF -DCMAKE_CXX_COMPILER="${CXX}" -DCMAKE_CXX_FLAGS="${CXXFLAGS}" -DEXIV2_BUILD_FUZZ_TESTS=ON -DEXIV2_TEAM_OSS_FUZZ=ON -DLIB_FUZZING_ENGINE="${LIB_FUZZING_ENGINE}" ..
make -j $(nproc)

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y cmake make ccache python3 libexpat1-dev zlib1g-dev libssh-dev libcurl4-openssl-dev libxml2-utils
RUN git clone https://github.com/exiv2/exiv2 exiv2
RUN git -C exiv2 checkout fa449a4d2c58d63f0d75ff259f25683a98a44630
WORKDIR $SRC/exiv2
COPY build.sh $SRC/

Additional files: