Project: qpdf (qpdf/qpdf), language: c++
Vulnerability instance: qpdf.cve-2024-24246
Sanitizer: address

================= Bug Report (1/1) ==================
## Source: GitHub Issue
## URL: https://github.com/qpdf/qpdf/issues/1123
## Description:
Issue: qpdf/qpdf#1123
Title: qpdf11.9.0 found a heap-buffer-overflow  crash
State: closed
Created by: Crspidey
Created at: 2024-01-15 02:29:15+00:00
Labels: bug, next

Issue Body:
### Description

I found a heap-buffer-overflow crash when  I use this instruction:

```shell
/qpdf/build/qpdf/qpdf   --json-input  POC_qpdf11-9-0_heap-buffer-overflow  output_json.pdf
#Changed the name of id:000002,sig:11,src:001030+000958,time:17469012,execs:8722360,op:splice,rep:15 to POC_qpdf11-9-0_heap-buffer-overflow
```





### Version

```shell
chen@DESKTOP-9AK26R1:~/qpdf2/crashes$ /home/chen/qpdf2/qpdf/build/qpdf/qpdf --version
qpdf version 11.9.0
Run qpdf --copyright to see copyright and license information.
```



### Reference

https://github.com/qpdf/qpdf

### **Actual Behavior**

heap-buffer-overflow

### PoC

https://github.com/Crspidey/my-poc/blob/main/POC_qpdf11-9-0_heap-buffer-overflow

### Reproduction

```shell
git clone https://github.com/qpdf/qpdf
cd qpdf
./build-scripts/build-linux
#Modified the instruction to add ASAN.
#cmake -S . -B build -DCI_MODE=1 -DCMAKE_BUILD_TYPE=RelWithDebInfo \
#      -DREQUIRE_CRYPTO_OPENSSL=1 -DREQUIRE_CRYPTO_GNUTLS=1   \
#      -DCMAKE_C_FLAGS="${CMAKE_C_FLAGS} -fsanitize=address" \
#      -DCMAKE_CXX_FLAGS="${CMAKE_CXX_FLAGS} -fsanitize=address" \
```

### ASAN Log

```shell
chen@DESKTOP-9AK26R1:~/qpdf2/crashes$ /home/chen/qpdf2/qpdf/build/qpdf/qpdf --json-input id:000002,sig:11,src:001030+000958,time:17469012,execs:8722360,op:splice,rep:15 output_json.pdf
WARNING: id:000002,sig:11,src:001030+000958,time:17469012,execs:8722360,op:splice,rep:15 (obj:3 0 R, offset 738): "stream.data" must be a string
=================================================================
==3736095==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60600000d2d8 at pc 0x7f929f1e4327 bp 0x7ffc280f2ac0 sp 0x7ffc280f2ab0
READ of size 8 at 0x60600000d2d8 thread T0
    #0 0x7f929f1e4326 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/11/bits/shared_ptr_base.h:704
    #1 0x7f929f1e4326 in std::__shared_ptr<QPDFObject, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/11/bits/shared_ptr_base.h:1154
    #2 0x7f929f1e4326 in std::shared_ptr<QPDFObject>::~shared_ptr() /usr/include/c++/11/bits/shared_ptr.h:122
    #3 0x7f929f1e4326 in QPDFObjectHandle::~QPDFObjectHandle() /home/chen/qpdf2/qpdf/include/qpdf/QPDFObjectHandle.hh:64
    #4 0x7f929f1e4326 in void __gnu_cxx::new_allocator<QPDFObjectHandle>::destroy<QPDFObjectHandle>(QPDFObjectHandle*) /usr/include/c++/11/ext/new_allocator.h:168
    #5 0x7f929f1e4326 in void std::allocator_traits<std::allocator<QPDFObjectHandle> >::destroy<QPDFObjectHandle>(std::allocator<QPDFObjectHandle>&, QPDFObjectHandle*) /usr/include/c++/11/bits/alloc_traits.h:535
    #6 0x7f929f1e4326 in std::vector<QPDFObjectHandle, std::allocator<QPDFObjectHandle> >::pop_back() /usr/include/c++/11/bits/stl_vector.h:1229
    #7 0x7f929f1e4326 in QPDF::JSONReactor::containerEnd(JSON const&) /home/chen/qpdf2/qpdf/libqpdf/QPDF_json.cc:419
    #8 0x7f929ec79cf2 in handleToken /home/chen/qpdf2/qpdf/libqpdf/JSON.cc:1188
    #9 0x7f929ec7c96f in parse /home/chen/qpdf2/qpdf/libqpdf/JSON.cc:1335
    #10 0x7f929ec7d1c6 in JSON::parse(InputSource&, JSON::Reactor*) /home/chen/qpdf2/qpdf/libqpdf/JSON.cc:1352
    #11 0x7f929f1f5e32 in QPDF::importJSON(std::shared_ptr<InputSource>, bool) /home/chen/qpdf2/qpdf/libqpdf/QPDF_json.cc:806
    #12 0x7f929f1f6d34 in QPDF::createFromJSON(std::shared_ptr<InputSource>) /home/chen/qpdf2/qpdf/libqpdf/QPDF_json.cc:786
    #13 0x7f929f1fb3e3 in QPDF::createFromJSON(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /home/chen/qpdf2/qpdf/libqpdf/QPDF_json.cc:779
    #14 0x7f929ee8deba in QPDFJob::doProcessOnce(std::unique_ptr<QPDF, std::default_delete<QPDF> >&, std::function<void (QPDF*, char const*)>, char const*, bool, bool, bool) /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:1725
    #15 0x7f929ee8e73e in QPDFJob::doProcess(std::unique_ptr<QPDF, std::default_delete<QPDF> >&, std::function<void (QPDF*, char const*)>, char const*, bool, bool, bool) /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:1763
    #16 0x7f929ee90383 in QPDFJob::processFile(std::unique_ptr<QPDF, std::default_delete<QPDF> >&, char const*, char const*, bool, bool) /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:1819
    #17 0x7f929eefbcb3 in QPDFJob::createQPDF() /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:434
    #18 0x7f929ef081d9 in QPDFJob::run() /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:516
    #19 0x55dff954ba51 in realmain(int, char**) /home/chen/qpdf2/qpdf/qpdf/qpdf.cc:36
    #20 0x7f929e5cdd8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #21 0x7f929e5cde3f in __libc_start_main_impl ../csu/libc-start.c:392
    #22 0x55dff954b884 in _start (/home/chen/qpdf2/qpdf/build/qpdf/qpdf+0x2884)

0x60600000d2d8 is located 8 bytes to the left of 64-byte region [0x60600000d2e0,0x60600000d320)
allocated by thread T0 here:
    #0 0x7f929f5c91e7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99
    #1 0x7f929ed5361a in __gnu_cxx::new_allocator<QPDFObjectHandle>::allocate(unsigned long, void const*) /usr/include/c++/11/ext/new_allocator.h:127
    #2 0x7f929ed5361a in std::allocator_traits<std::allocator<QPDFObjectHandle> >::allocate(std::allocator<QPDFObjectHandle>&, unsigned long) /usr/include/c++/11/bits/alloc_traits.h:464
    #3 0x7f929ed5361a in std::_Vector_base<QPDFObjectHandle, std::allocator<QPDFObjectHandle> >::_M_allocate(unsigned long) /usr/include/c++/11/bits/stl_vector.h:346
    #4 0x7f929ed5361a in void std::vector<QPDFObjectHandle, std::allocator<QPDFObjectHandle> >::_M_realloc_insert<QPDFObjectHandle const&>(__gnu_cxx::__normal_iterator<QPDFObjectHandle*, std::vector<QPDFObjectHandle, std::allocator<QPDFObjectHandle> > >, QPDFObjectHandle const&) /usr/include/c++/11/bits/vector.tcc:440

SUMMARY: AddressSanitizer: heap-buffer-overflow /usr/include/c++/11/bits/shared_ptr_base.h:704 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count()
Shadow bytes around the buggy address:
  0x0c0c7fff9a00: 00 00 02 fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c0c7fff9a10: fa fa fa fa fd fd fd fd fd fd fd fd fa fa fa fa
  0x0c0c7fff9a20: fd fd fd fd fd fd fd fd fa fa fa fa 00 00 00 00
  0x0c0c7fff9a30: 00 00 00 00 fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c0c7fff9a40: fa fa fa fa fd fd fd fd fd fd fd fd fa fa fa fa
=>0x0c0c7fff9a50: fd fd fd fd fd fd fd fd fa fa fa[fa]00 00 00 00
  0x0c0c7fff9a60: 00 00 00 00 fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c0c7fff9a70: fa fa fa fa 00 00 00 00 00 00 00 00 fa fa fa fa
  0x0c0c7fff9a80: 00 00 00 00 00 00 00 00 fa fa fa fa 00 00 00 00
  0x0c0c7fff9a90: 00 00 00 00 fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fff9aa0: 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
  Shadow gap:              cc
==3736095==ABORTING
```

### GDB log

```shell
(gdb) set args --json-input id:000002,sig:11,src:001030+000958,time:17469012,execs:8722360,op:splice,rep:15 output_json.pdf
(gdb) run
Starting program: /home/chen/qpdf2/qpdf/build/qpdf/qpdf --json-input id:000002,sig:11,src:001030+000958,time:17469012,execs:8722360,op:splice,rep:15 output_json.pdf
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
WARNING: id:000002,sig:11,src:001030+000958,time:17469012,execs:8722360,op:splice,rep:15 (obj:3 0 R, offset 738): "stream.data" must be a string
=================================================================
==3736122==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x60600000d2d8 at pc 0x7ffff7297327 bp 0x7fffffff7740 sp 0x7fffffff7730
READ of size 8 at 0x60600000d2d8 thread T0
    #0 0x7ffff7297326 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/include/c++/11/bits/shared_ptr_base.h:704
    #1 0x7ffff7297326 in std::__shared_ptr<QPDFObject, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/include/c++/11/bits/shared_ptr_base.h:1154
    #2 0x7ffff7297326 in std::shared_ptr<QPDFObject>::~shared_ptr() /usr/include/c++/11/bits/shared_ptr.h:122
    #3 0x7ffff7297326 in QPDFObjectHandle::~QPDFObjectHandle() /home/chen/qpdf2/qpdf/include/qpdf/QPDFObjectHandle.hh:64
    #4 0x7ffff7297326 in void __gnu_cxx::new_allocator<QPDFObjectHandle>::destroy<QPDFObjectHandle>(QPDFObjectHandle*) /usr/include/c++/11/ext/new_allocator.h:168
    #5 0x7ffff7297326 in void std::allocator_traits<std::allocator<QPDFObjectHandle> >::destroy<QPDFObjectHandle>(std::allocator<QPDFObjectHandle>&, QPDFObjectHandle*) /usr/include/c++/11/bits/alloc_traits.h:535
    #6 0x7ffff7297326 in std::vector<QPDFObjectHandle, std::allocator<QPDFObjectHandle> >::pop_back() /usr/include/c++/11/bits/stl_vector.h:1229
    #7 0x7ffff7297326 in QPDF::JSONReactor::containerEnd(JSON const&) /home/chen/qpdf2/qpdf/libqpdf/QPDF_json.cc:419
    #8 0x7ffff6d2ccf2 in handleToken /home/chen/qpdf2/qpdf/libqpdf/JSON.cc:1188
    #9 0x7ffff6d2f96f in parse /home/chen/qpdf2/qpdf/libqpdf/JSON.cc:1335
    #10 0x7ffff6d301c6 in JSON::parse(InputSource&, JSON::Reactor*) /home/chen/qpdf2/qpdf/libqpdf/JSON.cc:1352
    #11 0x7ffff72a8e32 in QPDF::importJSON(std::shared_ptr<InputSource>, bool) /home/chen/qpdf2/qpdf/libqpdf/QPDF_json.cc:806
    #12 0x7ffff72a9d34 in QPDF::createFromJSON(std::shared_ptr<InputSource>) /home/chen/qpdf2/qpdf/libqpdf/QPDF_json.cc:786
    #13 0x7ffff72ae3e3 in QPDF::createFromJSON(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /home/chen/qpdf2/qpdf/libqpdf/QPDF_json.cc:779
    #14 0x7ffff6f40eba in QPDFJob::doProcessOnce(std::unique_ptr<QPDF, std::default_delete<QPDF> >&, std::function<void (QPDF*, char const*)>, char const*, bool, bool, bool) /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:1725
    #15 0x7ffff6f4173e in QPDFJob::doProcess(std::unique_ptr<QPDF, std::default_delete<QPDF> >&, std::function<void (QPDF*, char const*)>, char const*, bool, bool, bool) /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:1763
    #16 0x7ffff6f43383 in QPDFJob::processFile(std::unique_ptr<QPDF, std::default_delete<QPDF> >&, char const*, char const*, bool, bool) /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:1819
    #17 0x7ffff6faecb3 in QPDFJob::createQPDF() /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:434
    #18 0x7ffff6fbb1d9 in QPDFJob::run() /home/chen/qpdf2/qpdf/libqpdf/QPDFJob.cc:516
    #19 0x555555556a51 in realmain(int, char**) /home/chen/qpdf2/qpdf/qpdf/qpdf.cc:36
    #20 0x7ffff6680d8f in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:58
    #21 0x7ffff6680e3f in __libc_start_main_impl ../csu/libc-start.c:392
    #22 0x555555556884 in _start (/home/chen/qpdf2/qpdf/build/qpdf/qpdf+0x2884)

0x60600000d2d8 is located 8 bytes to the left of 64-byte region [0x60600000d2e0,0x60600000d320)
allocated by thread T0 here:
    #0 0x7ffff767c1e7 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:99
    #1 0x7ffff6e0661a in __gnu_cxx::new_allocator<QPDFObjectHandle>::allocate(unsigned long, void const*) /usr/include/c++/11/ext/new_allocator.h:127
    #2 0x7ffff6e0661a in std::allocator_traits<std::allocator<QPDFObjectHandle> >::allocate(std::allocator<QPDFObjectHandle>&, unsigned long) /usr/include/c++/11/bits/alloc_traits.h:464
    #3 0x7ffff6e0661a in std::_Vector_base<QPDFObjectHandle, std::allocator<QPDFObjectHandle> >::_M_allocate(unsigned long) /usr/include/c++/11/bits/stl_vector.h:346
    #4 0x7ffff6e0661a in void std::vector<QPDFObjectHandle, std::allocator<QPDFObjectHandle> >::_M_realloc_insert<QPDFObjectHandle const&>(__gnu_cxx::__normal_iterator<QPDFObjectHandle*, std::vector<QPDFObjectHandle, std::allocator<QPDFObjectHandle> > >, QPDFObjectHandle const&) /usr/include/c++/11/bits/vector.tcc:440

SUMMARY: AddressSanitizer: heap-buffer-overflow /usr/include/c++/11/bits/shared_ptr_base.h:704 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count()
Shadow bytes around the buggy address:
  0x0c0c7fff9a00: 00 00 02 fa fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c0c7fff9a10: fa fa fa fa fd fd fd fd fd fd fd fd fa fa fa fa
  0x0c0c7fff9a20: fd fd fd fd fd fd fd fd fa fa fa fa 00 00 00 00
  0x0c0c7fff9a30: 00 00 00 00 fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c0c7fff9a40: fa fa fa fa fd fd fd fd fd fd fd fd fa fa fa fa
=>0x0c0c7fff9a50: fd fd fd fd fd fd fd fd fa fa fa[fa]00 00 00 00
  0x0c0c7fff9a60: 00 00 00 00 fa fa fa fa fd fd fd fd fd fd fd fd
  0x0c0c7fff9a70: fa fa fa fa 00 00 00 00 00 00 00 00 fa fa fa fa
  0x0c0c7fff9a80: 00 00 00 00 00 00 00 00 fa fa fa fa 00 00 00 00
  0x0c0c7fff9a90: 00 00 00 00 fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0c7fff9aa0: 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
  Shadow gap:              cc
==3736122==ABORTING
[Inferior 1 (process 3736122) exited with code 01]
```



### Environment

```shell
Distributor ID: Ubuntu
Description:    Ubuntu 22.04.3 LTS
Release:        22.04
Codename:       jammy


gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)
clang version 11.0.0 (https://github.com/aflgo/aflgo fa125da5d70621daf7141c6279877c97708c8c1f)

afl-cc++4.09a

build-essential is already the newest version (12.9ubuntu3).

libjpeg-dev is already the newest version (8c-2ubuntu10).

ghostscript is already the newest version (9.55.0~dfsg1-0ubuntu5.6).

libgnutls28-dev is already the newest version (3.7.3-4ubuntu1.3).

libssl-dev is already the newest version (3.0.2-0ubuntu1.12).

zlib1g-dev is already the newest version (1:1.2.11.dfsg-2ubuntu9.2).

libtiff-tools is already the newest version (4.3.0-6ubuntu0.7).

cmake is already the newest version (3.22.1-1ubuntu1.22.04.1).

```



### Credit

Chen zhiyuan ([2507519957@qq.com](mailto:2507519957@qq.com)/[czy_edu@whut.edu.cn](mailto:czy_edu@whut.edu.cn))




Comments:
Comment by Crspidey on 2024-01-15 02:46:57+00:00:
I found this poc when I used AFL++ to fuzz  qpdf11.1.0,ASAN shows the result as segv on unknow address.I thought that the new version should not have this error,after trying to use the new version the error is heap-buffer-overflow. I am trying to analyze it further. (The commands used are the same)


---

Comment by jberkenbilt on 2024-01-15 13:12:38+00:00:
Thanks. I'll take a look. Could be deep recursion. I intend to add fuzzing tests for the JSON part of the code. It's been on my to-do list for a while, and coincidentally, I just bumped it up a few days ago.

---

Comment by Crspidey on 2024-01-15 13:28:32+00:00:
> deep recursion

Thanks for your reply, I used the command   qpdf --json input.pdf   output.json   to generate the json files I needed.After that,I used AFL++ to fuzz qpdf by using that instruction(afl-fuzz -i in -o out -x json.dict   -- /path/to/qpdf/build/qpdf/qpdf   --json-input  @@ output_json.pdf)

---

Comment by jberkenbilt on 2024-02-03 20:58:02+00:00:
I will be pushing a fix for this. I also added qpdf's JSON code to oss-fuzz.

Commit References:
fa125da5d70621daf7141c6279877c97708c8c1f

Build script:
#!/bin/bash -eu
# Minimized build script with only core build commands
set -eu
./configure --static --prefix="$WORK"
make -j$(nproc) all
make install -j$(nproc)
cmake . -DCMAKE_INSTALL_PREFIX="$WORK" -DENABLE_STATIC=1 -DENABLE_SHARED=0 -DCMAKE_POSITION_INDEPENDENT_CODE=1
make -j$(nproc)

Dockerfile:
FROM hwiwonlee/secb.base:latest
RUN apt-get update && apt-get install -y make autoconf automake libtool cmake nasm
RUN git clone https://github.com/qpdf/qpdf qpdf
RUN git -C qpdf checkout 3b97c9bd266b7c32ea36d3536e22dab77412886d
WORKDIR $SRC/qpdf
COPY build.sh $SRC/

Additional files: