PoDoFo 0.9.5 allows remote attackers to cause a denial of service(infinite loop)

PoDoFo 0.9.5 funciton PdfPagesTree::GetPageNodeFromArray in PdfPagesTree.cpp cause a denial of service.

Analyzer

code from https://sourceforge.net/p/podofo/code/HEAD/tree/podofo/trunk/ (2017-04-09)

compile:

1
2
cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX=/home/icepng/aaaa/ -DCMAKE_BUILD_TYPE=DEBUG -DCMAKE_EXE_LINKER_FLAGS="-fsanitize=address -fno-omit-frame-pointer" ../
make -j

and run it:

1
./podofo-code/podofo/trunk/build/tools/podofotxtextract/podofotxtextract ./PoC

Crash Info

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
==22649==ERROR: AddressSanitizer: stack-overflow on address 0x7ffced665ff8 (pc 0x7f648bc08749 bp 0x7ffced666870 sp 0x7ffced666000 T0)
#0 0x7f648bc08748 in memcmp (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x77748)
#1 0x7f648a73c277 in std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::compare(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) const (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0x121277)
#2 0x4d53a4 in bool std::operator< <char, std::char_traits<char>, std::allocator<char> >(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> > const&) /usr/include/c++/5/bits/basic_string.h:4989
#3 0x4f3042 in PoDoFo::PdfName::operator<(PoDoFo::PdfName const&) const /home/rg/Documents/podofo2/podofo/src/base/PdfName.h:252
#4 0x52bfca in std::less<PoDoFo::PdfName>::operator()(PoDoFo::PdfName const&, PoDoFo::PdfName const&) const /usr/include/c++/5/bits/stl_function.h:387
#5 0x52c8a3 in std::_Rb_tree<PoDoFo::PdfName, std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*>, std::_Select1st<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*> >, std::less<PoDoFo::PdfName>, std::allocator<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*> > >::_M_lower_bound(std::_Rb_tree_node<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*> > const*, std::_Rb_tree_node<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*> > const*, PoDoFo::PdfName const&) const /usr/include/c++/5/bits/stl_tree.h:1644
#6 0x52c297 in std::_Rb_tree<PoDoFo::PdfName, std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*>, std::_Select1st<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*> >, std::less<PoDoFo::PdfName>, std::allocator<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*> > >::find(PoDoFo::PdfName const&) const /usr/include/c++/5/bits/stl_tree.h:2308
#7 0x52be86 in std::map<PoDoFo::PdfName, PoDoFo::PdfObject*, std::less<PoDoFo::PdfName>, std::allocator<std::pair<PoDoFo::PdfName const, PoDoFo::PdfObject*> > >::find(PoDoFo::PdfName const&) const /usr/include/c++/5/bits/stl_map.h:871
#8 0x52b55a in PoDoFo::PdfDictionary::HasKey(PoDoFo::PdfName const&) const /home/rg/Documents/podofo2/podofo/src/base/PdfDictionary.cpp:240
#9 0x52b23b in PoDoFo::PdfDictionary::GetKey(PoDoFo::PdfName const&) const /home/rg/Documents/podofo2/podofo/src/base/PdfDictionary.cpp:162
#10 0x52b490 in PoDoFo::PdfDictionary::GetKeyAsName(PoDoFo::PdfName const&) const /home/rg/Documents/podofo2/podofo/src/base/PdfDictionary.cpp:224
#11 0x518211 in PoDoFo::PdfPagesTree::IsTypePage(PoDoFo::PdfObject const*) const /home/rg/Documents/podofo2/podofo/src/doc/PdfPagesTree.cpp:521
#12 0x517fa6 in PoDoFo::PdfPagesTree::GetPageNodeFromArray(int, PoDoFo::PdfArray const&, std::deque<PoDoFo::PdfObject*, std::allocator<PoDoFo::PdfObject*> >&) /home/rg/Documents/podofo2/podofo/src/doc/PdfPagesTree.cpp:494
#13 0x517e92 in PoDoFo::PdfPagesTree::GetPageNodeFromArray(int, PoDoFo::PdfArray const&, std::deque<PoDoFo::PdfObject*, std::allocator<PoDoFo::PdfObject*> >&) /home/rg/Documents/podofo2/podofo/src/doc/PdfPagesTree.cpp:481
...
#250 0x517e92 in PoDoFo::PdfPagesTree::GetPageNodeFromArray(int, PoDoFo::PdfArray const&, std::deque<PoDoFo::PdfObject*, std::allocator<PoDoFo::PdfObject*> >&) /home/rg/Documents/podofo2/podofo/src/doc/PdfPagesTree.cpp:481
#251 0x517e92 in PoDoFo::PdfPagesTree::GetPageNodeFromArray(int, PoDoFo::PdfArray const&, std::deque<PoDoFo::PdfObject*, std::allocator<PoDoFo::PdfObject*> >&) /home/rg/Documents/podofo2/podofo/src/doc/PdfPagesTree.cpp:481

analysis

in src/doc/PdfPagesTree.cpp:475

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
PdfVariant rVar = rKidsArray[nPageNum];
while( true )
{
if( rVar.IsArray())
{
// Fixes some broken PDFs who have trees with 1 element kids arrays
return GetPageNodeFromArray( 0, rVar.GetArray(), rLstParents ); //rg =>
}
// it's a /Pages with a single kid, so dereference and try again...
if (this->IsTypePages(pgObject) )
{
if( !pgObject->GetDictionary().HasKey( "Kids" ) )
return NULL;
rLstParents.push_back( pgObject );
rVar = *(pgObject->GetDictionary().GetKey( "Kids" ));
...
} else {
// Reference to unexpected object
PODOFO_RAISE_ERROR_INFO( ePdfError_PageNotFound, "Reference to unexpected object." );
}
}

When use podofotxtextract to extract txt info from a PDF file, and when the argument rKidsArray is a cycle linked list, this function will in an endless loop. When rKidsArray[0].GetReference()->GetDictionary().GetKey(“Kids”) is an array which the first element is the argument rKidsArray itselt, in line 506 the var = rKidsArray[0].GetReference()->GetDictionary().GetKey(“Kids”) and in the next while loop, it will enter line 481, which will recursion call GetPageNodeFromArray. Then each 2 times call GetPageNodeFromArray it will goto the same state.