| Beschreibung | # PoDoFo Deep Recursion Use-After-Free in PDF Dictionary Parsing
## Summary
During fuzzing of the PoDoFo PDF library's podofoencrypt tool, a critical heap-use-after-free vulnerability was discovered that occurs specifically during deep recursive parsing of nested PDF dictionary structures. The vulnerability manifests when extremely nested dictionary structures (96+ levels of recursion) cause stack exhaustion leading to heap memory corruption. This vulnerability is distinct from standard dictionary parsing issues as it involves recursive depth limits and stack/heap interaction causing memory management failures in the `PdfTokenizer::ReadDictionary` function.
## Technical Details
- **Vulnerability Type**: Heap Use-After-Free (Deep Recursion Induced)
- **Affected Component**: PoDoFo PDF Library - PdfTokenizer
- **Affected Function**: `PdfTokenizer::ReadDictionary` (recursive calls)
- **Source File**: `PdfTokenizer.cpp`
- **Line Number**: 464-505 (recursive execution)
- **Signal**: SIGABRT (6)
- **Memory Access**: READ of size 4
- **Recursion Depth**: 96+ levels
- **Stack Frame Pattern**: Repeated `ReadDictionary` → `ReadNextVariant` → `ReadDictionary`
## Vulnerability Mechanism and Root Cause
This heap-use-after-free vulnerability is triggered by excessively deep recursion in PDF dictionary parsing, creating a unique failure mode distinct from normal dictionary parsing issues. The root cause involves the interaction between stack exhaustion and heap memory management.
The vulnerability sequence involves:
1. **Deep Recursion Initiation**: Malformed PDF contains deeply nested dictionary structures (<<</>>)
2. **Stack Frame Accumulation**: Each dictionary level creates new stack frames via `ReadDictionary` → `tryReadDataType` → `ReadNextVariant` → `ReadDictionary`
3. **Resource Exhaustion**: At approximately 96+ recursion levels, stack space approaches limits
4. **Heap Corruption**: Stack pressure causes heap allocator corruption or premature cleanup
5. **Use-After-Free**: `PdfName::NameData` objects are accessed after being freed due to corrupted memory management state
The extended call chain demonstrates the recursion pattern:
```
ReadDictionary() #1 → ReadDictionary() #2 → ... → ReadDictionary() #96+
```
This recursive depth creates a fundamentally different memory corruption scenario compared to normal dictionary parsing, requiring separate mitigation strategies focused on recursion limits rather than just PdfName lifetime management.
## AddressSanitizer Report
```
WARNING: Invalid number while parsing content
=================================================================
==3576531==ERROR: AddressSanitizer: heap-use-after-free on address 0x603000000498 at pc 0x7f26bed23147 bp 0x7fff2716f780 sp 0x7fff2716f778
READ of size 4 at 0x603000000498 thread T0
#0 0x7f26bed23146 in __gnu_cxx::__exchange_and_add_single(int*, int) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/ext/atomicity.h:84:29
#1 0x7f26bed23146 in __gnu_cxx::__exchange_and_add_dispatch(int*, int) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/ext/atomicity.h:99:14
#2 0x7f26bed23146 in std::_Sp_counted_base<(__gnu_cxx::_Lock_policy)2>::_M_release() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/shared_ptr_base.h:165:6
#3 0x7f26bed23146 in std::__shared_count<(__gnu_cxx::_Lock_policy)2>::~__shared_count() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/shared_ptr_base.h:705:11
#4 0x7f26bed23146 in std::__shared_ptr<PoDoFo::PdfName::NameData, (__gnu_cxx::_Lock_policy)2>::~__shared_ptr() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/shared_ptr_base.h:1154:31
#5 0x7f26bed23146 in PoDoFo::PdfName::~PdfName() /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfName.cpp:33:16
#6 0x7f26be9cdeac in std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>::~pair() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_iterator.h:2488:12
#7 0x7f26be9cdeac in void __gnu_cxx::new_allocator<std::_Rb_tree_node<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>>::destroy<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>(std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/ext/new_allocator.h:168:10
#8 0x7f26be9cdeac in void std::allocator_traits<std::allocator<std::_Rb_tree_node<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>>>::destroy<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>(std::allocator<std::_Rb_tree_node<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>>&, std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/alloc_traits.h:535:8
#9 0x7f26be9cdeac in std::_Rb_tree<PoDoFo::PdfName, std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>, std::_Select1st<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>, PoDoFo::PdfNameInequality, std::allocator<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>>::_M_destroy_node(std::_Rb_tree_node<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_tree.h:623:2
#10 0x7f26be9cdeac in std::_Rb_tree<PoDoFo::PdfName, std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>, std::_Select1st<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>, PoDoFo::PdfNameInequality, std::allocator<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>>::_M_drop_node(std::_Rb_tree_node<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_tree.h:631:2
#11 0x7f26be9cdeac in std::_Rb_tree<PoDoFo::PdfName, std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>, std::_Select1st<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>, PoDoFo::PdfNameInequality, std::allocator<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>>::_M_erase(std::_Rb_tree_node<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>*) /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_tree.h:1891:4
#12 0x7f26beec3dc0 in std::_Rb_tree<PoDoFo::PdfName, std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>, std::_Select1st<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>, PoDoFo::PdfNameInequality, std::allocator<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>>::~_Rb_tree() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_tree.h:984:9
#13 0x7f26beec3dc0 in std::map<PoDoFo::PdfName, PoDoFo::PdfObject, PoDoFo::PdfNameInequality, std::allocator<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject>>>::~map() /usr/lib/gcc/x86_64-linux-gnu/11/../../../../include/c++/11/bits/stl_map.h:302:22
#14 0x7f26beec3dc0 in PoDoFo::PdfDictionary::~PdfDictionary() /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfDictionary.h:81:18
#15 0x7f26beec3dc0 in PoDoFo::PdfVariant::~PdfVariant() /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfVariant.cpp:94:13
#16 0x7f26beebc1f9 in PoDoFo::PdfTokenizer::ReadDictionary(PoDoFo::InputStreamDevice&, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:505:1
#17 0x7f26beeba5a6 in PoDoFo::PdfTokenizer::tryReadDataType(PoDoFo::InputStreamDevice&, PoDoFo::PdfTokenizer::PdfLiteralDataType, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:416:19
#18 0x7f26beeb8952 in PoDoFo::PdfTokenizer::TryReadNextVariant(PoDoFo::InputStreamDevice&, std::basic_string_view<char, std::char_traits<char>> const&, PoDoFo::PdfTokenType, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:251:12
#19 0x7f26beeb8952 in PoDoFo::PdfTokenizer::ReadNextVariant(PoDoFo::InputStreamDevice&, std::basic_string_view<char, std::char_traits<char>> const&, PoDoFo::PdfTokenType, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:243:10
#20 0x7f26beebaf55 in PoDoFo::PdfTokenizer::ReadDictionary(PoDoFo::InputStreamDevice&, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:464:15
#21 0x7f26beeba5a6 in PoDoFo::PdfTokenizer::tryReadDataType(PoDoFo::InputStreamDevice&, PoDoFo::PdfTokenizer::PdfLiteralDataType, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:416:19
#22 0x7f26beeb8952 in PoDoFo::PdfTokenizer::TryReadNextVariant(PoDoFo::InputStreamDevice&, std::basic_string_view<char, std::char_traits<char>> const&, PoDoFo::PdfTokenType, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:251:12
#23 0x7f26beeb8952 in PoDoFo::PdfTokenizer::ReadNextVariant(PoDoFo::InputStreamDevice&, std::basic_string_view<char, std::char_traits<char>> const&, PoDoFo::PdfTokenType, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:243:10
#24 0x7f26beebaf55 in PoDoFo::PdfTokenizer::ReadDictionary(PoDoFo::InputStreamDevice&, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:464:15
#25 0x7f26beeba5a6 in PoDoFo::PdfTokenizer::tryReadDataType(PoDoFo::InputStreamDevice&, PoDoFo::PdfTokenizer::PdfLiteralDataType, PoDoFo::PdfVariant&, PoDoFo::PdfStatefulEncrypt const*) /workspace/program/podofo-053cf47-Jul30/src/podofo/main/PdfTokenizer.cpp:416:19
#26 0x7f26beeb8952 in PoDoFo::PdfTokenizer::TryReadNextVariant(PoDoFo::InputStreamDevice&, std::basic_string_view<char, std::char_traits<char>> const&, PoDoFo::PdfTok |
|---|