A stack overflow vulnerability exists in Poppler PDF library versions 25.03.0 through 25.11.0 in the roleMapResolve() function. The vulnerability allows for denial of service and potential code execution through maliciously crafted PDF files containing deeply nested RoleMap chains.
CVE ID: CVE-2025-66970
Product: Poppler PDF Library
Vendor: freedesktop.org (Poppler project)
Affected Versions: 25.03.0 to 25.11.0
Fixed Version: 25.11.1
Vulnerability Type: Stack Overflow via Unbounded Recursion
Component: poppler/StructElement.cc
Function: StructElement::roleMapResolve()
Lines: 880-897
The roleMapResolve() function recursively resolves RoleMap chains without implementing a depth counter (line 890). While the function checks for immediate self-references, it allows unlimited chains of role mappings, leading to stack exhaustion.
Vulnerable Code Pattern:
// Function recursively resolves RoleMap chains without depth limit
// Only checks immediate self-references (A -> A)
// Allows unlimited chains (A -> B -> C -> D -> ... -> Z -> A0 -> ...)==195101==ERROR: AddressSanitizer: stack-overflow on address 0x7ffc8676bfe8
(pc 0x7fa32667e3e3 bp 0x7ffc8676c840 sp 0x7ffc8676bff0 T0)
#0 0x7fa32667e3e3 in strlen ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:423
#1 0x7fa325ca204e in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::compare(char const*) const /usr/include/c++/14/bits/basic_string.h:3397
#2 0x7fa325d13028 in decltype ((__char_traits_cmp_cat<std::char_traits<char> >)(0)) std::operator<=><char, std::char_traits<char>, std::allocator<char> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const*) /usr/include/c++/14/bits/basic_string.h:3808
#3 0x7fa325d14856 in decltype(auto) std::less<void>::_S_cmp<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const* const&>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const* const&, std::integral_constant<bool, false>) /usr/include/c++/14/bits/stl_function.h:605
#4 0x7fa325d14896 in decltype (((forward<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&>)({parm#1}))<((forward<char const* const&>)({parm#2}))) std::less<void>::operator()<std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const* const&>(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, char const* const&) const /usr/include/c++/14/bits/stl_function.h:590
Craft a malicious PDF with tagged structure containing deeply nested /RoleMap chains:
/A0 → /A1 → /A2 → ... → /A50000
- Email attachment
- Web hosting
- Document sharing platforms
- Server-side upload
- Opening in Poppler-based applications (Evince, Okular, GIMP, LibreOffice)
- Running
pdfinfo -structon the malicious PDF - Server-side PDF processing using Poppler library
LOW - The exploit works reliably with no special conditions needed
Severity: High
Attack Type: Context-dependent
Impacts:
- ✅ Code Execution: Potential for arbitrary code execution through stack manipulation
- ✅ Denial of Service: Crash through stack overflow
- ✅ Escalation of Privileges: Possible privilege escalation in certain contexts
- ✅ Information Disclosure: Potential memory disclosure through stack corruption
Add a depth parameter with MAX_DEPTH=100 limit to prevent unbounded recursion:
// Proposed fix
const int MAX_DEPTH = 100;
std::string roleMapResolve(const std::string& role, int depth = 0) {
if (depth > MAX_DEPTH) {
return role; // Return original role if depth exceeded
}
// Continue with existing logic, passing depth+1 to recursive calls
}- Update to Poppler 25.11.1 or later
- Implement input validation for PDF files
- Disable tagged PDF structure parsing if not required
- Run Poppler-based applications in sandboxed environments
- Discovery Date: 2025
- Vendor Notification: 2025
- Fixed Version Released: 25.11.1
- CVE Assigned: CVE-2025-66970
Discoverer: Shadowbyte
PGP Signature: SHA256