[CVE-2017-6196] Ghostscript 9.21 – Use-after-free

Słowo wstępu

Przez prawie 1,5 miesiąca nie pojawił się na blogu żaden post dt. podatności – zdecydowanie nadszedł czas, aby to zmienić.

W tym poście opiszę bardzo ciekawe use-after-free w projekcie Ghostscript – interpreterze języka PostScript oraz plików PDF.

Oprogramowanie to jest często wykorzystywane jako core generatorów plików PDF lub demon zajmujący się obsługą plików rastrowych w drukarkach komputerowych. Często można z niego korzystać nawet nie wiedząc o jego istnieniu 🙂

Opis podatności

Luka występowała w wersji 9.21 (ostatnia stabilna, Git HEAD: 4d2b25f06c25e6e3e2b1bf319481a7442d42af8a).

Clue problemu było zarządzanie cyklem życia “obiektu” enumeratora używanego w funkcjach gx_begin_image1(), gx_begin_image4() oraz gx_image_enum_begin() (funkcja ta była wywoływana przez dwie pierwsze).

Pamięć dla enumetora alokowana była w funkcjach gx_begin_image[1,4]() i zwalniana w momencie zgłoszenia błędu przez gx_image_enum_begin().

Do tego momentu wszystko jest okej. Jednak ktoś wpadł na pomysł aby również w funkcji gx_image_enum_begin() zwalniać pamięć enumeratora przy okazji wystąpenia błędu.

Dodatkowo żeby dolać oliwy do ognia: nie zawsze pamięć w funkcji gx_image_enum_begin() była zwalniana. Z racji rozmiaru funkcji (prawie 750 LOC), kończy ona swoje wykonywanie w 9 miejscach – tylko 4 “wyjścia z funkcji” kończyły się zwolnieniem pamięci.

Następnie funkcja i_free_object() powołana do właściwego sprzątania po obiektach, sprawdzając typ obiektu – korzystała ze zwolnionej pamięci i triggerowała use-after-free.

Krótkie podsumowanie dla czytelników, którzy “złapali” overflow:

  1. Funkcja gx_begin_image[1,4]() alokuje enumerator i wywołuje funkcję gx_image_enum_begin()
  2. W przypadku wystąpienia błędu w gx_image_enum_begin(), funkcja gx_begin_image[1,4]() zwalnia pamięć tego obiektu
  3. Przy pewnych warunkach funkcja gx_image_enum_begin() również zwalnia pamięć tego obiektu
  4. Funkcja gx_begin_image[1,4]() “sprząta” po wszystkim za pomocą m.in. i_free_object()
  5. Do zadań i_free_object() należy sprawdzenie typu obiektu, który jest następnie zwalniany – w tym momencie dochodzi do odczytu zwolnionej pamięci

Załatanie tego błędu było banalnie proste i sprowadziło się do usunięcia czterech wywołań funkcji gs_free_object() w gx_image_enum_begin() – według zasady: “nie alokujesz pamięci, to też jej nie zwalniasz” 🙂

Log z ASAN:

==20050==ERROR: AddressSanitizer: heap-use-after-free on address 0x62b000000258 at pc 0x0000013f34f7 bp 0x7ffd6a96d7b0 sp 0x7ffd6a96d7a8
READ of size 8 at 0x62b000000258 thread T0
    #0 0x13f34f6 in i_free_object XYZ/ghostpdl/./base/gsalloc.c:1418:18
    #1 0x17fed3f in gx_begin_image1 XYZ/ghostpdl/./base/gximage1.c:99:9
    #2 0x18a7e16 in gx_default_begin_typed_image XYZ/ghostpdl/./base/gdevddrw.c:1059:12
    #3 0x18a79f8 in gx_default_begin_image XYZ/ghostpdl/./base/gdevddrw.c:1024:12
    #4 0x18a7d93 in gx_default_begin_typed_image XYZ/ghostpdl/./base/gdevddrw.c:1051:24
    #5 0x1476174 in gs_image_begin_typed XYZ/ghostpdl/./base/gsimage.c:252:12
    #6 0x1b344e5 in zimage_setup XYZ/ghostpdl/./psi/zimage.c:184:9
    #7 0x1b35cff in image1_setup XYZ/ghostpdl/./psi/zimage.c:246:12
    #8 0x1a22d37 in interp XYZ/ghostpdl/./psi/interp.c:1578:40
    #9 0x1a22d37 in gs_call_interp XYZ/ghostpdl/./psi/interp.c:511
    #10 0x1a22d37 in gs_interpret XYZ/ghostpdl/./psi/interp.c:468
    #11 0x19f5ab2 in gs_main_interpret XYZ/ghostpdl/./psi/imain.c:238:12
    #12 0x19f5ab2 in gs_main_run_string_end XYZ/ghostpdl/./psi/imain.c:656
    #13 0x19f5ab2 in gs_main_run_string_with_length XYZ/ghostpdl/./psi/imain.c:614
    #14 0x1a019ee in run_string XYZ/ghostpdl/./psi/imainarg.c:977:16
    #15 0x1a019ee in runarg XYZ/ghostpdl/./psi/imainarg.c:967
    #16 0x1a00e48 in argproc XYZ/ghostpdl/./psi/imainarg.c:900:16
    #17 0x19f9963 in gs_main_init_with_args XYZ/ghostpdl/./psi/imainarg.c:238:24
    #18 0x5475d8 in main XYZ/ghostpdl/./psi/gs.c:96:16
    #19 0x7f9f5c0d582f in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2082f)
    #20 0x47ba08 in _start (/usr/local/bin/gs+0x47ba08)

0x62b000000258 is located 88 bytes inside of 24928-byte region [0x62b000000200,0x62b000006360)
freed by thread T0 here:
    #0 0x519ecb in __interceptor_free /home/llvm/clang-3.9/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:47:3
    #1 0x148af3f in gs_heap_free_object XYZ/ghostpdl/./base/gsmalloc.c:354:5

previously allocated by thread T0 here:
    #0 0x51a21c in malloc /home/llvm/clang-3.9/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:64:3
    #1 0x148a4ff in gs_heap_alloc_bytes XYZ/ghostpdl/./base/gsmalloc.c:189:34

SUMMARY: AddressSanitizer: heap-use-after-free XYZ/ghostpdl/./base/gsalloc.c:1418:18 in i_free_object
Shadow bytes around the buggy address:
  0x0c567fff7ff0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
  0x0c567fff8000: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c567fff8010: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c567fff8020: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c567fff8030: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
=>0x0c567fff8040: fd fd fd fd fd fd fd fd fd fd fd[fd]fd fd fd fd
  0x0c567fff8050: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c567fff8060: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c567fff8070: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c567fff8080: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd
  0x0c567fff8090: fd fd fd fd 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
  Heap right redzone:      fb
  Freed heap region:       fd
  Stack left redzone:      f1
  Stack mid redzone:       f2
  Stack right redzone:     f3
  Stack partial redzone:   f4
  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
==20050==ABORTING

Bug #697596 w Bugzilli

Commit naprawiający: ecceafe3abba2714ef9b432035fe0739d9b1a283

CVE: CVE-2017-6196