Project: libheif (strukturag/libheif), language: c++
Vulnerability instance: libheif.cve-2023-49464
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/strukturag/libheif/issues/1044
## Description:
Issue: strukturag/libheif#1044
Title: heap-use-after-free/SEGV/heap-buffer-overflow in UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci
State: closed
Created by: fdu-sec
Created at: 2023-11-22 11:55:49+00:00

Issue Body:
### Description

heap-use-after-free/SEGV/heap-buffer-overflow `libheif/libheif/uncompressed_image.cc:537` in `UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci`

### Version

```
commit: 64ece913266609789f5dc70fe7de9eb759badd7f

heif-convert  libheif version: 1.17.5
-------------------------------------------
Usage: heif-convert [options]  <input-image> [output-image]

The program determines the output file format from the output filename suffix.
These suffixes are recognized: jpg, jpeg, png, y4m. If no output filename is specified, 'jpg' is used.

Options:
  -h, --help                     show help
  -v, --version                  show version
  -q, --quality                  quality (for JPEG output)
  -o, --output FILENAME          write output to FILENAME (optional)
  -d, --decoder ID               use a specific decoder (see --list-decoders)
      --with-aux                 also write auxiliary images (e.g. depth images)
      --with-xmp                 write XMP metadata to file (output filename with .xmp suffix)
      --with-exif                write EXIF metadata to file (output filename with .exif suffix)
      --skip-exif-offset         skip EXIF metadata offset bytes
      --no-colons                replace ':' characters in auxiliary image filenames with '_'
      --list-decoders            list all available decoders (built-in and plugins)
      --quiet                    do not output status messages to console
  -C, --chroma-upsampling ALGO   Force chroma upsampling algorithm (nn = nearest-neighbor / bilinear)
      --png-compression-level #  Set to integer between 0 (fastest) and 9 (best). Use -1 for default.
```

### Replay

```
cd libheif
mkdir build && cd build
CC="gcc -fsanitize=address" CXX="g++ -fsanitize=address" cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_UNCOMPRESSED_CODEC=ON ..
make -j
./examples/heif-convert ./poc test.png
```

### ASAN

> I just show the uaf case, you can use my poc to reproduce the others.

```
==88148==ERROR: AddressSanitizer: heap-use-after-free on address 0x60e000000910 at pc 0x7f7a88d9ab47 bp 0x7ffef17996e0 sp 0x7ffef17996d0
READ of size 2 at 0x60e000000910 thread T0
    #0 0x7f7a88d9ab46 in UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci(HeifFile const&, unsigned int) libheif/libheif/uncompressed_image.cc:537
    #1 0x7f7a88c96f95 in HeifFile::get_luma_bits_per_pixel_from_configuration(unsigned int) const libheif/libheif/file.cc:609
    #2 0x7f7a88c58d54 in HeifContext::Image::get_luma_bits_per_pixel() const libheif/libheif/context.cc:1225
    #3 0x7f7a88c1abf7 in heif_image_handle_get_luma_bits_per_pixel libheif/libheif/heif.cc:846
    #4 0x55fa40c34835 in main libheif/examples/heif_convert.cc:475
    #5 0x7f7a88694082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)
    #6 0x55fa40c2fadd in _start (libheif/build/examples/heif-convert+0xbadd)

0x60e000000910 is located 16 bytes inside of 160-byte region [0x60e000000900,0x60e0000009a0)
freed by thread T0 here:
    #0 0x7f7a88f6c0d0 in operator delete(void*) (/lib/x86_64-linux-gnu/libasan.so.4+0xe20d0)
    #1 0x7f7a88c038d9 in __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> >::deallocate(std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2>*, unsigned long) /usr/include/c++/7/ext/new_allocator.h:125
    #2 0x7f7a88bfef61 in std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> > >::deallocate(std::allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> >&, std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2>*, unsigned long) /usr/include/c++/7/bits/alloc_traits.h:462
    #3 0x7f7a88bf4553 in std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> > >::~__allocated_ptr() /usr/include/c++/7/bits/allocated_ptr.h:73
    #4 0x7f7a88c1019d in std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2>::_M_destroy() /usr/include/c++/7/bits/shared_ptr_base.h:543
    #5 0x7f7a88b94163 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/7/bits/shared_ptr_base.h:170
    #6 0x7f7a88b938c3 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/7/bits/shared_ptr_base.h:684
    #7 0x7f7a88bc23e3 in std::__shared_ptr<Box, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/7/bits/shared_ptr_base.h:1123
    #8 0x7f7a88bc24a3 in std::shared_ptr<Box>::~shared_ptr() /usr/include/c++/7/bits/shared_ptr.h:93
    #9 0x7f7a88be9854 in void std::_Destroy<std::shared_ptr<Box> >(std::shared_ptr<Box>*) /usr/include/c++/7/bits/stl_construct.h:98
    #10 0x7f7a88be4eb8 in void std::_Destroy_aux<false>::__destroy<std::shared_ptr<Box>*>(std::shared_ptr<Box>*, std::shared_ptr<Box>*) /usr/include/c++/7/bits/stl_construct.h:108
    #11 0x7f7a88bdafef in void std::_Destroy<std::shared_ptr<Box>*>(std::shared_ptr<Box>*, std::shared_ptr<Box>*) /usr/include/c++/7/bits/stl_construct.h:137
    #12 0x7f7a88bce3b8 in void std::_Destroy<std::shared_ptr<Box>*, std::shared_ptr<Box> >(std::shared_ptr<Box>*, std::shared_ptr<Box>*, std::allocator<std::shared_ptr<Box> >&) /usr/include/c++/7/bits/stl_construct.h:206
    #13 0x7f7a88bc3acf in std::vector<std::shared_ptr<Box>, std::allocator<std::shared_ptr<Box> > >::~vector() /usr/include/c++/7/bits/stl_vector.h:434
    #14 0x7f7a88bc02f8 in Box::~Box() (libheif/build/libheif/libheif.so.1+0xa22f8)
    #15 0x7f7a88bc053e in FullBox::~FullBox() (libheif/build/libheif/libheif.so.1+0xa253e)
    #16 0x7f7a88c09a8c in Box_meta::~Box_meta() (libheif/build/libheif/libheif.so.1+0xeba8c)
    #17 0x7f7a88c12579 in void __gnu_cxx::new_allocator<Box_meta>::destroy<Box_meta>(Box_meta*) /usr/include/c++/7/ext/new_allocator.h:140
    #18 0x7f7a88c111de in void std::allocator_traits<std::allocator<Box_meta> >::destroy<Box_meta>(std::allocator<Box_meta>&, Box_meta*) /usr/include/c++/7/bits/alloc_traits.h:487
    #19 0x7f7a88c10310 in std::_Sp_counted_ptr_inplace<Box_meta, std::allocator<Box_meta>, (__gnu_cxx::_Lock_policy)2>::_M_dispose() /usr/include/c++/7/bits/shared_ptr_base.h:535
    #20 0x7f7a88b940ec in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/include/c++/7/bits/shared_ptr_base.h:154
    #21 0x7f7a88b938c3 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/7/bits/shared_ptr_base.h:684
    #22 0x7f7a88bc23e3 in std::__shared_ptr<Box, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/7/bits/shared_ptr_base.h:1123
    #23 0x7f7a88bc24a3 in std::shared_ptr<Box>::~shared_ptr() /usr/include/c++/7/bits/shared_ptr.h:93
    #24 0x7f7a88b9d8b5 in Box::read(BitstreamRange&, std::shared_ptr<Box>*) libheif/libheif/box.cc:436
    #25 0x7f7a88b9edac in Box::read_children(BitstreamRange&, int) libheif/libheif/box.cc:749
    #26 0x7f7a88bab707 in Box_iprp::parse(BitstreamRange&) libheif/libheif/box.cc:1731
    #27 0x7f7a88b9d7eb in Box::read(BitstreamRange&, std::shared_ptr<Box>*) libheif/libheif/box.cc:672
    #28 0x7f7a88b9edac in Box::read_children(BitstreamRange&, int) libheif/libheif/box.cc:749
    #29 0x7f7a88ba14a0 in Box_meta::parse(BitstreamRange&) libheif/libheif/box.cc:920

previously allocated by thread T0 here:
    #0 0x7f7a88f6b258 in operator new(unsigned long) (/lib/x86_64-linux-gnu/libasan.so.4+0xe1258)
    #1 0x7f7a88c038a8 in __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) /usr/include/c++/7/ext/new_allocator.h:111
    #2 0x7f7a88bfeeb4 in std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> >&, unsigned long) /usr/include/c++/7/bits/alloc_traits.h:436
    #3 0x7f7a88bf44b9 in std::__allocated_ptr<std::allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> > > std::__allocate_guarded<std::allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> > >(std::allocator<std::_Sp_counted_ptr_inplace<Box_hdlr, std::allocator<Box_hdlr>, (__gnu_cxx::_Lock_policy)2> >&) /usr/include/c++/7/bits/allocated_ptr.h:104
    #4 0x7f7a88bea393 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<Box_hdlr, std::allocator<Box_hdlr>>(std::_Sp_make_shared_tag, Box_hdlr*, std::allocator<Box_hdlr> const&) /usr/include/c++/7/bits/shared_ptr_base.h:635
    #5 0x7f7a88be5955 in std::__shared_ptr<Box_hdlr, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<Box_hdlr>>(std::_Sp_make_shared_tag, std::allocator<Box_hdlr> const&) /usr/include/c++/7/bits/shared_ptr_base.h:1295
    #6 0x7f7a88bdc441 in std::shared_ptr<Box_hdlr>::shared_ptr<std::allocator<Box_hdlr>>(std::_Sp_make_shared_tag, std::allocator<Box_hdlr> const&) /usr/include/c++/7/bits/shared_ptr.h:344
    #7 0x7f7a88bcf8a2 in std::shared_ptr<Box_hdlr> std::allocate_shared<Box_hdlr, std::allocator<Box_hdlr>>(std::allocator<Box_hdlr> const&) /usr/include/c++/7/bits/shared_ptr.h:691
    #8 0x7f7a88bc4768 in std::shared_ptr<Box_hdlr> std::make_shared<Box_hdlr>() /usr/include/c++/7/bits/shared_ptr.h:707
    #9 0x7f7a88b9bcf3 in Box::read(BitstreamRange&, std::shared_ptr<Box>*) libheif/libheif/box.cc:448
    #10 0x7f7a88b9edac in Box::read_children(BitstreamRange&, int) libheif/libheif/box.cc:749
    #11 0x7f7a88ba14a0 in Box_meta::parse(BitstreamRange&) libheif/libheif/box.cc:920
    #12 0x7f7a88b9d7eb in Box::read(BitstreamRange&, std::shared_ptr<Box>*) libheif/libheif/box.cc:672
    #13 0x7f7a88b9edac in Box::read_children(BitstreamRange&, int) libheif/libheif/box.cc:749
    #14 0x7f7a88bab707 in Box_iprp::parse(BitstreamRange&) libheif/libheif/box.cc:1731
    #15 0x7f7a88b9d7eb in Box::read(BitstreamRange&, std::shared_ptr<Box>*) libheif/libheif/box.cc:672
    #16 0x7f7a88b9edac in Box::read_children(BitstreamRange&, int) libheif/libheif/box.cc:749
    #17 0x7f7a88ba14a0 in Box_meta::parse(BitstreamRange&) libheif/libheif/box.cc:920
    #18 0x7f7a88b9d7eb in Box::read(BitstreamRange&, std::shared_ptr<Box>*) libheif/libheif/box.cc:672
    #19 0x7f7a88b9edac in Box::read_children(BitstreamRange&, int) libheif/libheif/box.cc:749
    #20 0x7f7a88ba14a0 in Box_meta::parse(BitstreamRange&) libheif/libheif/box.cc:920
    #21 0x7f7a88b9d7eb in Box::read(BitstreamRange&, std::shared_ptr<Box>*) libheif/libheif/box.cc:672
    #22 0x7f7a88c9217e in HeifFile::parse_heif_file(BitstreamRange&) libheif/libheif/file.cc:254
    #23 0x7f7a88c8ffc6 in HeifFile::read(std::shared_ptr<StreamReader> const&) libheif/libheif/file.cc:107
    #24 0x7f7a88c8f8c6 in HeifFile::read_from_file(char const*) libheif/libheif/file.cc:88
    #25 0x7f7a88c4b3d7 in HeifContext::read_from_file(char const*) libheif/libheif/context.cc:429
    #26 0x7f7a88c163ff in heif_context_read_from_file libheif/libheif/heif.cc:451
    #27 0x55fa40c33e68 in main libheif/examples/heif_convert.cc:410
    #28 0x7f7a88694082 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x24082)

SUMMARY: AddressSanitizer: heap-use-after-free libheif/libheif/uncompressed_image.cc:537 in UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci(HeifFile const&, unsigned int)
Shadow bytes around the buggy address:
  0x0c1c7fff80d0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c1c7fff80e0: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c1c7fff80f0: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa
  0x0c1c7fff8100: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c1c7fff8110: fd fd fd fd fd fd fd fa fa fa fa fa fa fa fa fa
=>0x0c1c7fff8120: fd fd[fd]fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c1c7fff8130: fd fd fd fd fa fa fa fa fa fa fa fa fd fd fd fd
  0x0c1c7fff8140: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fa
  0x0c1c7fff8150: fa fa fa fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c1c7fff8160: fd fd fd fd fd fd fd fd fd fd fd fd fa fa fa fa
  0x0c1c7fff8170: fa fa fa fa fd fd fd fd fd fd fd fd fd fd fd fd
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
==88148==ABORTING
```

### POC

- [poc-heap-overflow](https://github.com/fdu-sec/poc/raw/main/libheif/poc3-hof)
- [poc-heap-uaf](https://github.com/fdu-sec/poc/raw/main/libheif/poc3-huaf)
- [poc-segv](https://github.com/fdu-sec/poc/raw/main/libheif/poc3-segv)

### Environment

```
Description:	Ubuntu 22.04.2 LTS
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
```

### Credit

Yuchuan Meng ([Fudan University](https://secsys.fudan.edu.cn/))


Comments:
Comment by carnil on 2023-12-07 20:31:36+00:00:
This appears to be CVE-2023-49464

Commit References:
64ece913266609789f5dc70fe7de9eb759badd7f

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
export DEPS_PATH=$SRC/deps
mkdir -p $DEPS_PATH
cd $SRC/x265/build/linux
cmake -G "Unix Makefiles" \
    -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX \
    -DCMAKE_C_FLAGS="$CFLAGS" -DCMAKE_CXX_FLAGS="$CXXFLAGS" \
    -DCMAKE_INSTALL_PREFIX="$DEPS_PATH" \
    -DENABLE_SHARED:bool=off \
    ../../source
make clean -j$(nproc)
make -j$(nproc) x265-static
make install -j$(nproc)
cd $SRC/libde265
./autogen.sh
./configure \
    --prefix="$DEPS_PATH" \
    --disable-shared \
    --enable-static \
    --disable-dec265 \
    --disable-sherlock265 \
    --disable-hdrcopy \
    --disable-enc265 \
    --disable-acceleration_speed
make -j$(nproc)
mkdir -p $SRC/aom/build/linux
cd $SRC/aom/build/linux
  -DENABLE_SHARED:bool=off -DCONFIG_PIC=1 \
  -DENABLE_EXAMPLES=0 -DENABLE_DOCS=0 -DENABLE_TESTS=0 \
  -DCONFIG_SIZE_LIMIT=1 \
  -DDECODE_HEIGHT_LIMIT=12288 -DDECODE_WIDTH_LIMIT=12288 \
  -DDO_RANGE_CHECK_CLAMP=1 \
  -DAOM_MAX_ALLOCABLE_MEMORY=536870912 \
  -DAOM_TARGET_CPU=generic \
  ../../
cd $SRC/libheif
mkdir -p build
cd build
cmake .. --preset=fuzzing \
      -DFUZZING_COMPILE_OPTIONS="" \
      -DFUZZING_LINKER_OPTIONS="$LIB_FUZZING_ENGINE" \
      -DFUZZING_C_COMPILER=$CC -DFUZZING_CXX_COMPILER=$CXX \
      -DWITH_DEFLATE_HEADER_COMPRESSION=OFF

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y autoconf automake build-essential cmake libtool make mercurial pkg-config yasm
RUN git clone https://github.com/strukturag/libheif libheif
RUN git -C libheif checkout 82219b903001305fd916874a29dd372d92927d48
WORKDIR $SRC/libheif
COPY build.sh $SRC/

Additional files: