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

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/libarchive/libarchive/issues/1298
## Description:
Issue: libarchive/libarchive#1298
Title: Heap-buffer-overflow in archive_string_append_from_wcs() (archive_string.c)
State: closed
Created by: sanjeevk001
Created at: 2019-12-28 18:16:57+00:00

Issue Body:
bsdtar: An error in archive_string_append_from_wcs() (archive_string.c) triggers an out-of-bounds write in heap memory that results into a crash, via a specially crafted archive file. This bug was found using our custom fuzzer.

Basic Information:
versions of libarchive: 3.4.1dev
How you obtained it: built from source
libarchive-3.4.1dev (https://github.com/libarchive/libarchive)
Tested OS: Linux 5.0.0-37-generic x86_64 
Tested compiler version : gcc version 7.4.0

What other files were involved?
To trigger the bug, use the the crash file (unzip crash-file.zip)
[crash-file.zip](https://github.com/libarchive/libarchive/files/4007270/crash-file.zip)

===============
Compile with address address sanitizer (-fsanitize=addrsan)
Command to reproduce the bug:
$ ./bsdtar -t -f crash-file

Output (partial):
```
==2650==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x607000000298 at pc 0x5586b16c5082 bp 0x7ffc3d24a950 sp 0x7ffc3d24a940
WRITE of size 1 at 0x607000000298 thread T0
    #0 0x5586b16c5081 in archive_string_append_from_wcs libarchive/archive_string.c:831
    #1 0x5586b16c5fd7 in archive_mstring_get_mbs libarchive/archive_string.c:3913
    #2 0x5586b162200f in archive_entry_pathname libarchive/archive_entry.c:562
    #3 0x5586b16177bb in read_archive tar/read.c:275
    #4 0x5586b1618acf in tar_mode_t tar/read.c:94
    #5 0x5586b1613c3d in main tar/bsdtar.c:913
    #6 0x7fb07ba60b96 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b96)
    #7 0x5586b1615a89 in _start (libarchive/bsdtar-addrsan+0x46a89)

0x607000000298 is located 0 bytes to the right of 72-byte region [0x607000000250,0x607000000298)
allocated by thread T0 here:
    #0 0x7fb07c9b7f40 in realloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdef40)
    #1 0x5586b16bc2bd in archive_string_ensure libarchive/archive_string.c:314

SUMMARY: AddressSanitizer: heap-buffer-overflow libarchive/archive_string.c:831 in archive_string_append_from_wcs
Shadow bytes around the buggy address:
  0x0c0e7fff8000: fa fa fa fa 00 00 00 00 00 00 00 00 00 fa fa fa
  0x0c0e7fff8010: fa fa 00 00 00 00 00 00 00 00 00 00 fa fa fa fa
  0x0c0e7fff8020: 00 00 00 00 00 00 00 00 00 00 fa fa fa fa 00 00
  0x0c0e7fff8030: 00 00 00 00 00 00 00 fa fa fa fa fa fd fd fd fd
  0x0c0e7fff8040: fd fd fd fd fd fa fa fa fa fa 00 00 00 00 00 00
=>0x0c0e7fff8050: 00 00 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff80a0: 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
==2650==ABORTING
```
Crash analysis : 
--
In archive_string_append_from_wcs() (archive_string.c), upon execution of the crashing input (crash-file):

```
archive_string_append_from_wcs(){      
....

// Allocated heap memory for as->s is 72 bytes in range (0x607000000250,0x607000000298) 

p = as->s + as->length;
... 
while (*w != L'\0' && len > 0) {
		...

#if HAVE_WCRTOMB
		n = wcrtomb(p, *w++, &shift_state);   // p = 0x611000000628 --> 0x00,  wcrtomb() returns 3, n = 3
#else
		n = wctomb(p, *w++);
#endif
		if (n == -1) {
			if (errno == EILSEQ) {
		    ...
		} else
			p += n;               // p = 0x611000000628 --> 0x00
		len--;
	}
	as->length = p - as->s;      // as->length = 72 
	as->s[as->length] = '\0';    // **Heap overflow** occurs as it tries to write beyond the allocated heap memory
	return (ret_val);
}
```


Attachments:
https://github.com/libarchive/libarchive/files/4007270/crash-file.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 fbd0e40471c996c4c78fa00318f0f7e2e40a5179
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) {
  int ret;
  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(1) {
    ret = archive_read_next_header(a, &entry);
    if (ret == ARCHIVE_EOF || ret == ARCHIVE_FATAL)
      break;
    if (ret == ARCHIVE_RETRY)
      continue;
    while ((r = archive_read_data(a, data_buffer.data(),
            data_buffer.size())) > 0)
      ;
    if (r == ARCHIVE_FATAL)
      break;
  }

  archive_read_free(a);
  return 0;
}