Skip to content
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

Heap-use-after-free in Pl_ASCII85Decoder::write #492

Closed
bin2415 opened this issue Dec 23, 2020 · 2 comments
Closed

Heap-use-after-free in Pl_ASCII85Decoder::write #492

bin2415 opened this issue Dec 23, 2020 · 2 comments

Comments

@bin2415
Copy link

bin2415 commented Dec 23, 2020

  • 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

@jberkenbilt
Copy link
Contributor

fixed

@jberkenbilt
Copy link
Contributor

Fixed in dc92574

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants