Project: libarchive (libarchive/libarchive), language: c++
Vulnerability instance: libarchive.cve-2019-11463
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/libarchive/libarchive/issues/1165
## Description:
Issue: libarchive/libarchive#1165
Title: Memory leak when decoding LZMA
State: closed
Created by: rohanpadhye
Created at: 2019-03-28 17:41:33+00:00

Issue Body:
I've encountered a memory leak while fuzzing libarchive. A 32-byte reproducer input is attached:
[input.zip](https://github.com/libarchive/libarchive/files/3019394/input.zip). Do not extract this zip file -- it is the fuzzed input itself.

To reproduce, build latest libarchive with ASAN (`clang -fsanitize=address`) and run:
```
$ ./bsdtar -xOf input.zip  # bsdtar compiled with ASAN
a: Invalid lzma data
bsdtar: Error exit delayed from previous errors.

=================================================================
==15753==ERROR: LeakSanitizer: detected memory leaks

Direct leak of 96 byte(s) in 1 object(s) allocated from:
    #0 0x7f4c0b1dfb50 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb50)
    #1 0x7f4c0a85dced  (/lib/x86_64-linux-gnu/liblzma.so.5+0x2ced)

Indirect leak of 224 byte(s) in 1 object(s) allocated from:
    #0 0x7f4c0b1dfb50 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb50)
    #1 0x7f4c0a864274  (/lib/x86_64-linux-gnu/liblzma.so.5+0x9274)

SUMMARY: AddressSanitizer: 320 byte(s) leaked in 2 allocation(s).
```

Without ASAN, this can also be reproduced using Valgrind:
```
$ valgrind --leak-check=full ./bsdtar -xOf input.zip 
a: Invalid lzma data
bsdtar: Error exit delayed from previous errors.
==619== 
==619== HEAP SUMMARY:
==619==     in use at exit: 396 bytes in 4 blocks
==619==   total heap usage: 274 allocs, 270 frees, 342,257 bytes allocated
==619== 
==619== 320 (96 direct, 224 indirect) bytes in 1 blocks are definitely lost in loss record 4 of 4
==619==    at 0x4C2FB0F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==619==    by 0x54BECED: ??? (in /lib/x86_64-linux-gnu/liblzma.so.5.2.2)
==619==    by 0x54C52E2: lzma_alone_decoder (in /lib/x86_64-linux-gnu/liblzma.so.5.2.2)
==619==    by 0x14EEAC: zipx_lzma_alone_init (archive_read_support_format_zip.c:1455)
==619==    by 0x14EEAC: zip_read_data_zipx_lzma_alone (archive_read_support_format_zip.c:1660)
==619==    by 0x14EEAC: archive_read_format_zip_read_data (archive_read_support_format_zip.c:2670)
==619==    by 0x1219BB: archive_read_data_into_fd (archive_read_data_into_fd.c:101)
==619==    by 0x114D70: read_archive (read.c:369)
==619==    by 0x1153F6: tar_mode_x (read.c:112)
==619==    by 0x113A3F: main (bsdtar.c:880)
==619== 
==619== LEAK SUMMARY:
==619==    definitely lost: 96 bytes in 1 blocks
==619==    indirectly lost: 224 bytes in 1 blocks
==619==      possibly lost: 0 bytes in 0 blocks
==619==    still reachable: 76 bytes in 2 blocks
==619==         suppressed: 0 bytes in 0 blocks
==619== Reachable blocks (those to which a pointer was found) are not shown.
``` 




Comments:
Comment by jsonn on 2019-03-28 18:33:12+00:00:
Yeah, it's not freeing the decoder on error.

---

Comment by rohanpadhye on 2019-03-28 19:01:52+00:00:
Note that calling `archive_read_free()` does not free this memory. I tried writing my own driver based on the tutorials in the wiki (see [this gist](https://gist.github.com/rohanpadhye/57eb57cd4592f5368d7f24584d4f88f4)), and the leak remains even after calling `archive_read_free()`. 

For the test driver in the gist, I see the output:

```
entry name: a
Error reading data: Invalid lzma data

=================================================================
==27645==ERROR: LeakSanitizer: detected memory leaks
...
```

---

Comment by mmatuska on 2019-04-20 22:51:56+00:00:
Thank you there is a ugly preprocessor typo in archive_read_format_zip_cleanup()!

---

Comment by rohanpadhye on 2019-04-23 17:51:32+00:00:
Thanks for the fix! This has been assigned [CVE-2019-11463](https://nvd.nist.gov/vuln/detail/CVE-2019-11463) by MITRE.

---

Comment by mmatuska on 2019-04-23 19:11:29+00:00:
@rohanpadhye This is very interesting because the zipx lzma support code was not released yet, so an affected version doesn't exist.

---

Comment by rohanpadhye on 2019-04-23 20:17:53+00:00:
Oops. This is my bad, since I filed the report. I referenced the release date of v3.3.3 rather than the commit date. I see now that the bug appears only in 3.3.4dev. I'll request to have the CVE record amended at once.

Attachments:
https://github.com/libarchive/libarchive/files/3019394/input.zip

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
./configure
make -j$(nproc) all

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool pkg-config libbz2-dev liblzo2-dev liblzma-dev liblz4-dev libz-dev libxml2-dev libssl-dev libacl1-dev libattr1-dev
RUN git clone https://github.com/libarchive/libarchive libarchive
RUN git -C libarchive checkout 5405343bfd53830b3d018b2d982845de851aba5c
WORKDIR $SRC/libarchive
COPY build.sh $SRC/

Additional files:
filename: libarchive_fuzzer.cc
content: // Copyright 2016 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
////////////////////////////////////////////////////////////////////////////////
#include <stddef.h>
#include <stdint.h>
#include <vector>

#include "archive.h"

struct Buffer {
  const uint8_t *buf;
  size_t len;
};

ssize_t reader_callback(struct archive *a, void *client_data,
                        const void **block) {
  Buffer *buffer = reinterpret_cast<Buffer *>(client_data);
  *block = buffer->buf;
  ssize_t len = buffer->len;
  buffer->len = 0;
  return len;
}

extern "C" int LLVMFuzzerTestOneInput(const uint8_t *buf, size_t len) {
  ssize_t r;
  struct archive *a = archive_read_new();

  archive_read_support_filter_all(a);
  archive_read_support_format_all(a);

  Buffer buffer = {buf, len};
  archive_read_open(a, &buffer, NULL, reader_callback, NULL);

  std::vector<uint8_t> data_buffer(getpagesize(), 0);
  struct archive_entry *entry;
  while (archive_read_next_header(a, &entry) == ARCHIVE_OK) {
    while ((r = archive_read_data(a, data_buffer.data(),
            data_buffer.size())) > 0)
      ;
    if (r == ARCHIVE_FATAL)
      break;
  }

  archive_read_free(a);
  return 0;
}