sammycage / plutosvg

Tiny SVG rendering library in C
MIT License
247 stars 17 forks source link

CWE-131 (Incorrect Calculation of Buffer Size) in plutosvg_load_from_memory #7

Closed W4terf1re closed 1 month ago

W4terf1re commented 11 months ago

Summary

An integer overflow in the allocated size of calloc causes a segment fault. It might lead to heap overflow and arbitrary code execution.

Steps to reproduce

code:

#include <plutosvg.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    plutovg_surface_t* surface = plutosvg_load_from_file(argv[1], NULL, 0, 0, 96.0);
    return 0;
}

run

$ ./example ./poc
Segmentation fault

ASAN report

==16700==WARNING: AddressSanitizer failed to allocate 0xfffffffffef21b74 bytes
==16700==AddressSanitizer's allocator is terminating the process instead of returning 0
==16700==If you don't like this behavior set allocator_may_return_null=1
==16700==AddressSanitizer CHECK failed: ../../../../src/libsanitizer/sanitizer_common/sanitizer_allocator.cc:218 "((0)) != (0)" (0x0, 0x0)
    #0 0x7ffff6f01bf2  (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xe9bf2)
    #1 0x7ffff6f20575 in __sanitizer::CheckFailed(char const*, int, char const*, unsigned long long, unsigned long long) (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x108575)
    #2 0x7ffff6f07332  (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xef332)
    #3 0x7ffff6e3fe46  (/usr/lib/x86_64-linux-gnu/libasan.so.4+0x27e46)
    #4 0x7ffff6ef6b0a in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb0a)
    #5 0x555555588ba1 in plutovg_surface_create (/home/waterfire/fuzz/example/plutosvg/build/example/example+0x34ba1)
    #6 0x55555557a6aa in plutosvg_load_from_memory (/home/waterfire/fuzz/example/plutosvg/build/example/example+0x266aa)
    #7 0x55555557aa21 in plutosvg_load_from_file (/home/waterfire/fuzz/example/plutosvg/build/example/example+0x26a21)
    #8 0x55555556405a in main (/home/waterfire/fuzz/example/plutosvg/build/example/example+0x1005a)
    #9 0x7ffff66aac86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)
    #10 0x555555563f19 in _start (/home/waterfire/fuzz/example/plutosvg/build/example/example+0xff19)

Analysis

plutosvg_load_from_memory does not check the size of width and height and calls plutovg_surface_create(width, height);. In plutovg_surface_create:

plutovg_surface_t* plutovg_surface_create(int width, int height)
{
    plutovg_surface_t* surface = malloc(sizeof(plutovg_surface_t));
    surface->ref = 1;
    surface->owndata = 1;
    surface->data = calloc(1, (size_t)(width * height * 4));
    surface->width = width;
    surface->height = height;
    surface->stride = width * 4;
    return surface;
}

An integer overflow might happen when calculating width * height * 4. It might be better to check the sizes of width and height before the allocation.

PoC

poc.zip

sammycage commented 11 months ago

I'll make sure to dive into this matter as soon as my schedule permits. Thank you for bringing it to my attention!

W4terf1re commented 11 months ago

A simple way to fix the bug is adding a size check like this

if((size_t)(width * height * 4) >= 0x80000000){
    // error message
    exit(0);
}
W4terf1re commented 11 months ago

By the way, the allocation in function stbi_write_png_to_mem seems to have the same problem:

filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0;
line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; }

They can be fixed by the same way.

Steps to reproduce

code:

#include <plutosvg.h>
#include <stdlib.h>
#include <stdio.h>

int main(int argc, char* argv[])
{
    plutovg_surface_t* surface = plutosvg_load_from_file(argv[1], NULL, 0, 0, 96.0);
    if(surface == NULL)
    {
        printf("Load failed\n");
        return -1;
    }
    plutovg_surface_write_to_png(surface, "test.png");
    plutovg_surface_destroy(surface);
    return 0;
}

ASAN report

=================================================================

==32443==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x602000000191 at pc 0x555555582f20 bp 0x7fffffffd740 sp 0x7fffffffd730

READ of size 1 at 0x602000000191 thread T0

    #0 0x555555582f1f in stbiw__encode_png_line (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0x2ef1f)

    #1 0x555555583ff8 in stbi_write_png_to_mem (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0x2fff8)

    #2 0x5555555852cd in stbi_write_png (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0x312cd)

    #3 0x55555558950a in plutovg_surface_write_to_png (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0x3550a)

    #4 0x55555556408b in main (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0x1008b)

    #5 0x7ffff66aac86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)

    #6 0x555555563f19 in _start (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0xff19)

0x602000000191 is located 0 bytes to the right of 1-byte region [0x602000000190,0x602000000191)

allocated by thread T0 here:

    #0 0x7ffff6ef6b40 in __interceptor_malloc (/usr/lib/x86_64-linux-gnu/libasan.so.4+0xdeb40)

    #1 0x555555589232 in plutovg_surface_write_to_png (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0x35232)

    #2 0x55555556408b in main (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0x1008b)

    #3 0x7ffff66aac86 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21c86)

SUMMARY: AddressSanitizer: heap-buffer-overflow (/home/waterfire/fuzz/example/plutosvg/build/example/example_asan+0x2ef1f) in stbiw__encode_png_line

Shadow bytes around the buggy address:

  0x0c047fff7fe0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

  0x0c047fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

  0x0c047fff8000: fa fa fd fd fa fa fd fd fa fa 01 fa fa fa fd fd

  0x0c047fff8010: fa fa fd fd fa fa fd fa fa fa fd fa fa fa fd fa

  0x0c047fff8020: fa fa fd fd fa fa fd fa fa fa fd fa fa fa fd fa

=>0x0c047fff8030: fa fa[01]fa fa fa 00 01 fa fa 01 fa fa fa fa fa

  0x0c047fff8040: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c047fff8050: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c047fff8060: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c047fff8070: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa

  0x0c047fff8080: 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

==32443==ABORTING

The poc does not cause segment fault, but can trigger heap overflow. The allocation size of line_buffer is overflowed to 0, and the heap overflow is triggered in stbiw__encode_png_line when writing to line_buffer. poc_heap_overflow.zip

sammycage commented 1 month ago

@W4terf1re Resolved in the latest commit. Thank you for bringing this issue to our attention.