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

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/strukturag/libheif/issues/1226
## Description:
Issue: strukturag/libheif#1226
Title: OOB Read/Write in `HeifPixelImage::overlay()`
State: closed
Created by: flyyee
Created at: 2024-07-07 09:16:35+00:00

Issue Body:
## Overview
Due to insufficient validation of image overlay offset values, it is possible to OOB read & write in `HeifPixelImage::overlay()`.

## Reproduction
This vulnerability exists on both master (7c9729ea8d0f319ab423f8e4e6de121fd78596d0) and develop-v1.18.0 (44a9705568a18955230ad9d2b5de4d2682dd50f9).

```
git clone https://github.com/strukturag/libheif.git
cd libheif
mkdir build && cd build
CC="gcc -fsanitize=address" CXX="g++ -fsanitize=address" cmake ..
make -j

# OOB read
examples/heif-info poc_oob_read.heic

# OOB write
examples/heif-info poc_oob_write.heic
```

Download from google drive:
[poc_oob_read.heic](https://drive.google.com/file/d/1oo69R3N4luXzCcXeJYtL-v11KGFYHq57/view?usp=sharing)
[poc_oob_write.heic](https://drive.google.com/file/d/1nnTP4GGB7K1CQJDzeLybHlWwoVpwYuT4/view?usp=sharing)

### Asan logs
OOB read:
```
==169466==ERROR: AddressSanitizer: negative-size-param: (size=-2147482208)
    #0 0x7ffff76053ff in __interceptor_memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:827
    #1 0x7ffff73b61e0 in HeifPixelImage::overlay(std::shared_ptr<HeifPixelImage>&, int, int) (clean_build/libheif/build/libheif/libheif.so.1+0x1b61e0)
    #2 0x7ffff733cabc in HeifContext::decode_overlay_image(unsigned int, std::shared_ptr<HeifPixelImage>&, std::vector<unsigned char, std::allocator<unsigned char> > const&, heif_decoding_options const&) const (clean_build/libheif/build/libheif/libheif.so.1+0x13cabc)
    #3 0x7ffff7333f60 in HeifContext::decode_image_planar(unsigned int, std::shared_ptr<HeifPixelImage>&, heif_colorspace, heif_decoding_options const&, bool) const (clean_build/libheif/build/libheif/libheif.so.1+0x133f60)
    #4 0x7ffff7331450 in HeifContext::decode_image_user(unsigned int, std::shared_ptr<HeifPixelImage>&, heif_colorspace, heif_chroma, heif_decoding_options const&) const (clean_build/libheif/build/libheif/libheif.so.1+0x131450)
    #5 0x7ffff72eb0d9 in heif_decode_image (clean_build/libheif/build/libheif/libheif.so.1+0xeb0d9)
    #6 0x5555555625f2 in main (clean_build/libheif/build/examples/heif-info+0xe5f2)
    #7 0x7ffff6ccdd8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #8 0x7ffff6ccde3f in __libc_start_main_impl ../csu/libc-start.c:392
    #9 0x55555555c304 in _start (clean_build/libheif/build/examples/heif-info+0x8304)
```

OOB write:
```
==169585==ERROR: AddressSanitizer: SEGV on unknown address 0x62e080000400 (pc 0x7ffff6d68ae3 bp 0x7fffffffbc00 sp 0x7fffffffb9b8 T0)
==169585==The signal is caused by a WRITE memory access.
    #0 0x7ffff6d68ae3  (/lib/x86_64-linux-gnu/libc.so.6+0xc4ae3)
    #1 0x7ffff73b61e0 in HeifPixelImage::overlay(std::shared_ptr<HeifPixelImage>&, int, int) (clean_build/libheif/build/libheif/libheif.so.1+0x1b61e0)
    #2 0x7ffff733cabc in HeifContext::decode_overlay_image(unsigned int, std::shared_ptr<HeifPixelImage>&, std::vector<unsigned char, std::allocator<unsigned char> > const&, heif_decoding_options const&) const (clean_build/libheif/build/libheif/libheif.so.1+0x13cabc)
    #3 0x7ffff7333f60 in HeifContext::decode_image_planar(unsigned int, std::shared_ptr<HeifPixelImage>&, heif_colorspace, heif_decoding_options const&, bool) const (clean_build/libheif/build/libheif/libheif.so.1+0x133f60)
    #4 0x7ffff7331450 in HeifContext::decode_image_user(unsigned int, std::shared_ptr<HeifPixelImage>&, heif_colorspace, heif_chroma, heif_decoding_options const&) const (clean_build/libheif/build/libheif/libheif.so.1+0x131450)
    #5 0x7ffff72eb0d9 in heif_decode_image (clean_build/libheif/build/libheif/libheif.so.1+0xeb0d9)
    #6 0x5555555625f2 in main (clean_build/libheif/build/examples/heif-info+0xe5f2)
    #7 0x7ffff6ccdd8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #8 0x7ffff6ccde3f in __libc_start_main_impl ../csu/libc-start.c:392
    #9 0x55555555c304 in _start (clean_build/libheif/build/examples/heif-info+0x8304)
```

### Environment
```
OS: Ubuntu 22.04
gcc (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
g++ (Ubuntu 11.4.0-1ubuntu1~22.04) 11.4.0
```

## Root cause
The overlay offsets specified in the iloc box are trusted by the program. While there are "sanity" checks in `HeifPixelImage::overlay()`, these checks can be bypassed with extremely large or extremely small offsets.

For instance, in `poc_oob_write.heic`, the x offset (`dx`) is `INT_MAX - 10`. The OOB write arises from an integer overflow in this [check](https://github.com/strukturag/libheif/blob/7c9729ea8d0f319ab423f8e4e6de121fd78596d0/libheif/pixelimage.cc#L786):

```c
    // overlay image extends past the right border -> cut width for copy
    if (dx + in_w > out_w) {
      in_w = out_w - dx;
    }
```

In `poc_oob_read.heic`, `dx` is `INT_MIN`. The OOB read arises from the UB in this [check](https://github.com/strukturag/libheif/blob/7c9729ea8d0f319ab423f8e4e6de121fd78596d0/libheif/pixelimage.cc#L810):

```c
    // overlay image started outside of left border
    // -> move start into the image and start at left output column
    if (dx < 0) {
      in_x0 = -dx;
      out_x0 = 0;
    }
```

## Mitigation
A simple fix would be to check that `in_y0/x0` and `out_x0/y0` are within the bounds of the respective images' dimensions before using the values at the end of the function.


Comments:
Comment by flyyee on 2024-07-07 09:39:56+00:00:
Opened a PR with the suggested fix: https://github.com/strukturag/libheif/pull/1227

---

Comment by farindk on 2024-07-08 10:27:14+00:00:
I have rewritten the whole section. Should now handle all edge cases.


---

Comment by rathann on 2025-02-07 20:43:44+00:00:
For reference, this is CVE-2024-41311 .

Commit References:
7c9729ea8d0f319ab423f8e4e6de121fd78596d0
44a9705568a18955230ad9d2b5de4d2682dd50f9

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 e382107736f987fa30faeaa06543eeec72a85dd1
WORKDIR $SRC/libheif
COPY build.sh $SRC/

Additional files: