New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Segmentation Fault (stack exhaustion) on a crafted PDF file #202
Comments
|
Use CVE-2018-9918 |
|
Thanks. I'll take a look when I can. Pretty soon. |
|
How depressing. I put a lot of effort into getting rid of recursion in parsing to avoid falling for this kind of trap. Now qpdf recovers well enough that it is actually able to successfully parse the file, and then it all blows up because delete ends up recursively calling destructors, which is what is blowing out the stack. What's happening is basically the same as in this program: class A
{
public:
A(A* a)
{
this->a = a;
}
~A()
{
delete a;
}
private:
A* a;
};
int main()
{
A* a = new A(0);
for (int i = 0; i < 300000; ++i)
{
a = new A(a);
}
delete a;
return 0;
}Even though the recursive data structure is created iteratively, destruction is recursive. This happens to be an invalid file because there are dictionaries without keys that qpdf is supplying fake keys for, but you could create this exact problem using a syntactically valid PDF that just has a very deeply nested object. For example, changing each |
|
I'm going to fix it by just imposing an arbitrary limit on how deeply nested the data structure expressed in a direct object can be. This is a very easy fix. |
|
In lieu of immediately releasing 8.0.3, I am going to prepare a patch relative to 8.0.2 that fixes this. It will be this commit without the additional test case. |
Index: qpdf/ChangeLog
===================================================================
--- qpdf.orig/ChangeLog
+++ qpdf/ChangeLog
@@ -1,3 +1,8 @@
+2018-04-15 Jay Berkenbilt <ejb@ql.org>
+
+ * Arbitrarily limit the depth of data structures represented by
+ direct object. This is CVE-2018-9918. Fixes #202.
+
2018-03-06 Jay Berkenbilt <ejb@ql.org>
* 8.0.2: release
Index: qpdf/libqpdf/QPDFObjectHandle.cc
===================================================================
--- qpdf.orig/libqpdf/QPDFObjectHandle.cc
+++ qpdf/libqpdf/QPDFObjectHandle.cc
@@ -1487,12 +1487,26 @@ QPDFObjectHandle::parseInternal(PointerH
case QPDFTokenizer::tt_array_open:
case QPDFTokenizer::tt_dict_open:
- olist_stack.push_back(std::vector<QPDFObjectHandle>());
- state = st_start;
- offset_stack.push_back(input->tell());
- state_stack.push_back(
- (token.getType() == QPDFTokenizer::tt_array_open) ?
- st_array : st_dictionary);
+ if (olist_stack.size() > 500)
+ {
+ QTC::TC("qpdf", "QPDFObjectHandle too deep");
+ warn(context,
+ QPDFExc(qpdf_e_damaged_pdf, input->getName(),
+ object_description,
+ input->getLastOffset(),
+ "ignoring excessively deeply nested data structure"));
+ object = newNull();
+ state = st_top;
+ }
+ else
+ {
+ olist_stack.push_back(std::vector<QPDFObjectHandle>());
+ state = st_start;
+ offset_stack.push_back(input->tell());
+ state_stack.push_back(
+ (token.getType() == QPDFTokenizer::tt_array_open) ?
+ st_array : st_dictionary);
+ }
break;
case QPDFTokenizer::tt_bool:
Index: qpdf/qpdf/qpdf.testcov
===================================================================
--- qpdf.orig/qpdf/qpdf.testcov
+++ qpdf/qpdf/qpdf.testcov
@@ -335,3 +335,4 @@ QPDFObjectHandle numeric non-numeric 0
QPDFObjectHandle erase array bounds 0
qpdf-c called qpdf_check_pdf 0
QPDF xref loop 0
+QPDFObjectHandle too deep 0
Index: qpdf/qpdf/qtest/qpdf/issue-146.out
===================================================================
--- qpdf.orig/qpdf/qtest/qpdf/issue-146.out
+++ qpdf/qpdf/qtest/qpdf/issue-146.out
@@ -1,7 +1,5 @@
WARNING: issue-146.pdf: file is damaged
WARNING: issue-146.pdf: can't find startxref
WARNING: issue-146.pdf: Attempting to reconstruct cross-reference table
-WARNING: issue-146.pdf (trailer, offset 20728): unknown token while reading object; treating as string
-WARNING: issue-146.pdf (trailer, offset 20732): unexpected EOF
-WARNING: issue-146.pdf (trailer, offset 20732): parse error while reading object
+WARNING: issue-146.pdf (trailer, offset 695): ignoring excessively deeply nested data structure
issue-146.pdf: unable to find trailer dictionary while recovering damaged file |
|
Hello Jay, |
Fixes CVE-2018-9918: mishandle certain "expected dictionary key but found non-name object" cases, allowing remote attackers to cause a denial of service (stack exhaustion) qpdf/qpdf#202 Drop local SHA256 hash since we use upstream provided SHA512. Signed-off-by: Baruch Siach <baruch@tkos.co.il> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com>
Fixes CVE-2018-9918: mishandle certain "expected dictionary key but found non-name object" cases, allowing remote attackers to cause a denial of service (stack exhaustion) qpdf/qpdf#202 Drop local SHA256 hash since we use upstream provided SHA512. Signed-off-by: Baruch Siach <baruch@tkos.co.il> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com> (cherry picked from commit 473390a) Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
Fixes CVE-2018-9918: mishandle certain "expected dictionary key but found non-name object" cases, allowing remote attackers to cause a denial of service (stack exhaustion) qpdf/qpdf#202 Drop local SHA256 hash since we use upstream provided SHA512. Signed-off-by: Baruch Siach <baruch@tkos.co.il> Signed-off-by: Thomas Petazzoni <thomas.petazzoni@bootlin.com> (cherry picked from commit 473390a) Signed-off-by: Peter Korsgaard <peter@korsgaard.com>
A crafted pdf file causes a segmentation fault, stack-overflow reported by LLVM ASan - but it seems stack exhaustion, causing a denial of service and there is a chance of code execution.
qpdflibsegfault.zip
Details are attached inside the zip called 'gdb log'.
I have tested with libqpdf and qpdf itself, debugged and used LLVM ASan to identify the possible root cause.
A pdf POC is inside the zip.
Versions tested: 6.0.0 and 8.0.2.
c code to test:
running:
Testing qpdf 6.0.0:
testing qpdf 8.0.2
The text was updated successfully, but these errors were encountered: