Security
Headlines
HeadlinesLatestCVEs

Headline

CVE-2021-25786: Heap-use-after-free in `Pl_ASCII85Decoder::write` · Issue #492 · qpdf/qpdf

An issue was discovered in QPDF version 10.0.4, allows remote attackers to execute arbitrary code via crafted .pdf file to Pl_ASCII85Decoder::write parameter in libqpdf.

CVE
#linux#js#c++#pdf
  • version: 10.0.4
  • commit: 78b9d6b
  • How to reproduce: qpdf_afl_fuzzer ./poc

The log from ASAN:

==28751==ERROR: AddressSanitizer: heap-use-after-free on address 0x6070000076c5 at pc 0x0000008799bc bp 0x7fffffff6a30 sp 0x7fffffff6a28
WRITE of size 1 at 0x6070000076c5 thread T0
    #0 0x8799bb in Pl_ASCII85Decoder::write(unsigned char*, unsigned long) /src/qpdf/libqpdf/Pl_ASCII85Decoder.cc:85:32
    #1 0x877725 in Pl_AES_PDF::flush(bool) /src/qpdf/libqpdf/Pl_AES_PDF.cc:241:16
    #2 0x877c75 in Pl_AES_PDF::finish() /src/qpdf/libqpdf/Pl_AES_PDF.cc
    #3 0x5996ab in QPDF::pipeStreamData(PointerHolder<QPDF::EncryptionParameters>, PointerHolder<InputSource>, QPDF&, int, int, long long, unsigned long, QPDFObjectHandle, bool, Pipeline*, bool, bool) /src/qpdf/libqpdf/QPDF.cc:2899:23
    #4 0x599c9d in QPDF::pipeStreamData(int, int, long long, unsigned long, QPDFObjectHandle, Pipeline*, bool, bool) /src/qpdf/libqpdf/QPDF.cc:2920:12
    #5 0x77e4c7 in QPDF::Pipe::pipeStreamData(QPDF*, int, int, long long, unsigned long, QPDFObjectHandle, Pipeline*, bool, bool) /src/qpdf/include/qpdf/QPDF.hh:718:19
    #6 0x76e02e in QPDF_Stream::pipeStreamData(Pipeline*, bool*, int, qpdf_stream_decode_level_e, bool, bool) /src/qpdf/libqpdf/QPDF_Stream.cc:700:8
    #7 0x637e27 in QPDFObjectHandle::pipeStreamData(Pipeline*, int, qpdf_stream_decode_level_e, bool, bool) /src/qpdf/libqpdf/QPDFObjectHandle.cc:1228:51
    #8 0x700fbf in QPDFWriter::unparseObject(QPDFObjectHandle, int, int, unsigned long, bool) /src/qpdf/libqpdf/QPDFWriter.cc:1826:24
    #9 0x71f286 in QPDFWriter::writeObject(QPDFObjectHandle, int) /src/qpdf/libqpdf/QPDFWriter.cc:2169:2
    #10 0x737528 in QPDFWriter::writeStandard() /src/qpdf/libqpdf/QPDFWriter.cc:3676:2
    #11 0x72aae7 in QPDFWriter::write() /src/qpdf/libqpdf/QPDFWriter.cc:2745:2
    #12 0x51bb07 in FuzzHelper::doWrite(PointerHolder<QPDFWriter>) /src/qpdf_afl_fuzzer.cc:68:12
    #13 0x51cb03 in FuzzHelper::testWrite() /src/qpdf_afl_fuzzer.cc:92:5
    #14 0x5219a6 in FuzzHelper::doChecks() /src/qpdf_afl_fuzzer.cc:194:5
    #15 0x5219a6 in FuzzHelper::run() /src/qpdf_afl_fuzzer.cc:210
    #16 0x5221b7 in main /src/qpdf_afl_fuzzer.cc:228:7
    #17 0x7ffff6a99bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)
    #18 0x41eb89 in _start (/src/qpdf_afl_fuzzer+0x41eb89)

0x6070000076c5 is located 21 bytes inside of 78-byte region [0x6070000076b0,0x6070000076fe)
freed by thread T0 here:
    #0 0x517d68 in operator delete(void*) (/src/qpdf_afl_fuzzer+0x517d68)
    #1 0x7ffff7af21a9 in std::runtime_error::~runtime_error() (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xa81a9)
    #2 0x599c9d in QPDF::pipeStreamData(int, int, long long, unsigned long, QPDFObjectHandle, Pipeline*, bool, bool) /src/qpdf/libqpdf/QPDF.cc:2920:12
    #3 0x77e4c7 in QPDF::Pipe::pipeStreamData(QPDF*, int, int, long long, unsigned long, QPDFObjectHandle, Pipeline*, bool, bool) /src/qpdf/include/qpdf/QPDF.hh:718:19
    #4 0x76e02e in QPDF_Stream::pipeStreamData(Pipeline*, bool*, int, qpdf_stream_decode_level_e, bool, bool) /src/qpdf/libqpdf/QPDF_Stream.cc:700:8
    #5 0x637e27 in QPDFObjectHandle::pipeStreamData(Pipeline*, int, qpdf_stream_decode_level_e, bool, bool) /src/qpdf/libqpdf/QPDFObjectHandle.cc:1228:51
    #6 0x700fbf in QPDFWriter::unparseObject(QPDFObjectHandle, int, int, unsigned long, bool) /src/qpdf/libqpdf/QPDFWriter.cc:1826:24
    #7 0x71f286 in QPDFWriter::writeObject(QPDFObjectHandle, int) /src/qpdf/libqpdf/QPDFWriter.cc:2169:2
    #8 0x737528 in QPDFWriter::writeStandard() /src/qpdf/libqpdf/QPDFWriter.cc:3676:2
    #9 0x72aae7 in QPDFWriter::write() /src/qpdf/libqpdf/QPDFWriter.cc:2745:2
    #10 0x51bb07 in FuzzHelper::doWrite(PointerHolder<QPDFWriter>) /src/qpdf_afl_fuzzer.cc:68:12
    #11 0x5219a6 in FuzzHelper::doChecks() /src/qpdf_afl_fuzzer.cc:194:5
    #12 0x5219a6 in FuzzHelper::run() /src/qpdf_afl_fuzzer.cc:210
    #13 0x7ffff6a99bf6 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21bf6)

previously allocated by thread T0 here:
    #0 0x516ff0 in operator new(unsigned long) (/src/qpdf_afl_fuzzer+0x516ff0)
    #1 0x7ffff7b1dc28 in std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (/usr/lib/x86_64-linux-gnu/libstdc++.so.6+0xd3c28)
    #2 0x883ce8 in Pl_Flate::handleData(unsigned char*, unsigned long, int) /src/qpdf/libqpdf/Pl_Flate.cc:197:12
    #3 0x8833d5 in Pl_Flate::write(unsigned char*, unsigned long) /src/qpdf/libqpdf/Pl_Flate.cc:92:9
    #4 0x87a0a7 in Pl_ASCII85Decoder::flush() /src/qpdf/libqpdf/Pl_ASCII85Decoder.cc:122:16
    #5 0x87997b in Pl_ASCII85Decoder::write(unsigned char*, unsigned long) /src/qpdf/libqpdf/Pl_ASCII85Decoder.cc:88:4
    #6 0x877725 in Pl_AES_PDF::flush(bool) /src/qpdf/libqpdf/Pl_AES_PDF.cc:241:16

SUMMARY: AddressSanitizer: heap-use-after-free /src/qpdf/libqpdf/Pl_ASCII85Decoder.cc:85:32 in Pl_ASCII85Decoder::write(unsigned char*, unsigned long)
Shadow bytes around the buggy address:
  0x0c0e7fff8e80: fa fa fd fd fd fd fd fd fd fd fd fa fa fa fa fa
  0x0c0e7fff8e90: 00 00 00 00 00 00 00 00 01 fa fa fa fa fa fd fd
  0x0c0e7fff8ea0: fd fd fd fd fd fd fd fa fa fa fa fa fd fd fd fd
  0x0c0e7fff8eb0: fd fd fd fd fd fd fa fa fa fa 00 00 00 00 00 00
  0x0c0e7fff8ec0: 00 00 00 fa fa fa fa fa 00 00 00 00 00 00 00 00
=>0x0c0e7fff8ed0: 00 fa fa fa fa fa fd fd[fd]fd fd fd fd fd fd fd
  0x0c0e7fff8ee0: fa fa fa fa fd fd fd fd fd fd fd fd fd fa fa fa
  0x0c0e7fff8ef0: fa fa fd fd fd fd fd fd fd fd fd fa fa fa fa fa
  0x0c0e7fff8f00: 00 00 00 00 00 00 00 00 01 fa fa fa fa fa fa fa
  0x0c0e7fff8f10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
  0x0c0e7fff8f20: 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
==28751==ABORTING

The test program is a wrapper that I modified based on oss-fuzz wrapper:

// file name is qpdf_afl_fuzzer.c #include <qpdf/QPDF.hh> #include <qpdf/QPDFWriter.hh> #include <qpdf/QUtil.hh> #include <qpdf/BufferInputSource.hh> #include <qpdf/Buffer.hh> #include <qpdf/Pl_Discard.hh> #include <qpdf/QPDFPageDocumentHelper.hh> #include <qpdf/QPDFPageObjectHelper.hh> #include <qpdf/QPDFPageLabelDocumentHelper.hh> #include <qpdf/QPDFOutlineDocumentHelper.hh> #include <qpdf/QPDFAcroFormDocumentHelper.hh> #include <cstdlib>

class DiscardContents: public QPDFObjectHandle::ParserCallbacks { public: virtual ~DiscardContents() {} virtual void handleObject(QPDFObjectHandle) {} virtual void handleEOF() {} };

class FuzzHelper { public: FuzzHelper(char* data); void run();

private: PointerHolder<QPDF> getQpdf(); PointerHolder<QPDFWriter> getWriter(PointerHolder<QPDF>); void doWrite(PointerHolder<QPDFWriter> w); void testWrite(); void testPages(); void testOutlines(); void doChecks(); char const* input_f; Pl_Discard discard; };

FuzzHelper::FuzzHelper(char* _input) : // We do not modify data, so it is safe to remove the const for Buffer input_f(_input) { }

PointerHolder<QPDF> FuzzHelper::getQpdf() { PointerHolder<QPDF> qpdf = new QPDF(); qpdf->processFile(input_f); return qpdf; }

PointerHolder<QPDFWriter> FuzzHelper::getWriter(PointerHolder<QPDF> qpdf) { PointerHolder<QPDFWriter> w = new QPDFWriter(*qpdf); w->setOutputPipeline(&this->discard); w->setDecodeLevel(qpdf_dl_all); return w; }

void FuzzHelper::doWrite(PointerHolder<QPDFWriter> w) { try { w->write(); } catch (QPDFExc const& e) { std::cerr << e.what() << std::endl; } catch (std::runtime_error const& e) { std::cerr << e.what() << std::endl; } }

void FuzzHelper::testWrite() { // Write in various ways to exercise QPDFWriter

PointerHolder<QPDF\> q;
PointerHolder<QPDFWriter\> w;

q \= getQpdf();
w \= getWriter(q);
w\->setDeterministicID(true);
w\->setQDFMode(true);
doWrite(w);

q \= getQpdf();
w \= getWriter(q);
w\->setStaticID(true);
w\->setLinearization(true);
w\->setR6EncryptionParameters(
    "u", "o", true, true, true, true, true, true, qpdf\_r3p\_full, true);
doWrite(w);

q \= getQpdf();
w \= getWriter(q);
w\->setStaticID(true);
w\->setObjectStreamMode(qpdf\_o\_disable);
w\->setR3EncryptionParameters(
"u", "o", true, true, qpdf\_r3p\_full, qpdf\_r3m\_all);
doWrite(w);

q \= getQpdf();
w \= getWriter(q);
w\->setDeterministicID(true);
w\->setObjectStreamMode(qpdf\_o\_generate);
w\->setLinearization(true);
doWrite(w);

}

void FuzzHelper::testPages() { // Parse all content streams, and exercise some helpers that // operate on pages. PointerHolder<QPDF> q = getQpdf(); QPDFPageDocumentHelper pdh(*q); QPDFPageLabelDocumentHelper pldh(*q); QPDFOutlineDocumentHelper odh(*q); QPDFAcroFormDocumentHelper afdh(*q); afdh.generateAppearancesIfNeeded(); pdh.flattenAnnotations(); std::vector<QPDFPageObjectHelper> pages = pdh.getAllPages(); DiscardContents discard_contents; int pageno = 0; for (std::vector<QPDFPageObjectHelper>::iterator iter = pages.begin(); iter != pages.end(); ++iter) { QPDFPageObjectHelper& page(*iter); ++pageno; try { page.coalesceContentStreams(); page.parsePageContents(&discard_contents); page.getPageImages(); pldh.getLabelForPage(pageno); QPDFObjectHandle page_obj(page.getObjectHandle()); page_obj.getJSON(true).unparse(); odh.getOutlinesForPage(page_obj.getObjGen());

        std::vector<QPDFAnnotationObjectHelper\> annotations \=
            afdh.getWidgetAnnotationsForPage(page);
        for (std::vector<QPDFAnnotationObjectHelper\>::iterator annot\_iter \=
                 annotations.begin();
             annot\_iter != annotations.end(); ++annot\_iter)
        {
            QPDFAnnotationObjectHelper& aoh \= \*annot\_iter;
            afdh.getFieldForAnnotation(aoh);
        }
    }
    catch (QPDFExc& e)
    {
        std::cerr << "page " << pageno << ": "
                  << e.what() << std::endl;
    }
}

}

void FuzzHelper::testOutlines() { PointerHolder<QPDF> q = getQpdf(); std::list<std::vector<QPDFOutlineObjectHelper> > queue; QPDFOutlineDocumentHelper odh(*q); queue.push_back(odh.getTopLevelOutlines()); while (! queue.empty()) { std::vector<QPDFOutlineObjectHelper>& outlines = *(queue.begin()); for (std::vector<QPDFOutlineObjectHelper>::iterator iter = outlines.begin(); iter != outlines.end(); ++iter) { QPDFOutlineObjectHelper& ol = *iter; ol.getDestPage(); queue.push_back(ol.getKids()); } queue.pop_front(); } }

void FuzzHelper::doChecks() { // Get as much coverage as possible in parts of the library that // might benefit from fuzzing. testWrite(); testPages(); testOutlines(); }

void FuzzHelper::run() { // The goal here is that you should be able to throw anything at // libqpdf and it will respond without any memory errors and never // do anything worse than throwing a QPDFExc or // std::runtime_error. Throwing any other kind of exception, // segfaulting, or having a memory error (when built with // appropriate sanitizers) will all cause abnormal exit. try { doChecks(); } catch (QPDFExc const& e) { std::cerr << "QPDFExc: " << e.what() << std::endl; } catch (std::runtime_error const& e) { std::cerr << "runtime_error: " << e.what() << std::endl; } }

int main(int argc, char** argv){ if (argc < 2){ std::cerr << "Please input the processed file!\n"; } FuzzHelper f(argv[1]); f.run(); return 0; }

The PoC file is attached:
poc.zip

CVE: Latest News

CVE-2023-50976: Transactions API Authorization by oleiman · Pull Request #14969 · redpanda-data/redpanda
CVE-2023-6905
CVE-2023-6903
CVE-2023-6904
CVE-2023-3907