Coverage Report

Created: 2026-03-07 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qpdf/fuzz/qpdf_pages_fuzzer.cc
Line
Count
Source
1
#include <qpdf/Buffer.hh>
2
#include <qpdf/BufferInputSource.hh>
3
#include <qpdf/Pl_DCT.hh>
4
#include <qpdf/Pl_Discard.hh>
5
#include <qpdf/Pl_Flate.hh>
6
#include <qpdf/Pl_PNGFilter.hh>
7
#include <qpdf/Pl_RunLength.hh>
8
#include <qpdf/Pl_TIFFPredictor.hh>
9
#include <qpdf/QPDF.hh>
10
#include <qpdf/QPDFAcroFormDocumentHelper.hh>
11
#include <qpdf/QPDFOutlineDocumentHelper.hh>
12
#include <qpdf/QPDFPageDocumentHelper.hh>
13
#include <qpdf/QPDFPageLabelDocumentHelper.hh>
14
#include <qpdf/QPDFPageObjectHelper.hh>
15
16
#include <chrono>
17
#include <cstdlib>
18
#include <iostream>
19
20
class FuzzHelper
21
{
22
  public:
23
    FuzzHelper(unsigned char const* data, size_t size);
24
    void run();
25
26
  private:
27
    std::shared_ptr<QPDF> getQpdf();
28
    void testPages();
29
    void doChecks();
30
31
    void
32
    info(std::string const& msg, int pageno = 0) const
33
307k
    {
34
307k
        const std::chrono::duration<double> elapsed{std::chrono::steady_clock::now() - start};
35
36
307k
        std::cerr << elapsed.count() << " info - " << msg;
37
307k
        if (pageno > 0) {
38
41.8k
            std::cerr << " page " << pageno;
39
41.8k
        }
40
307k
        std::cerr << '\n';
41
307k
    }
42
43
    Buffer input_buffer;
44
    Pl_Discard discard;
45
    const std::chrono::time_point<std::chrono::steady_clock> start;
46
};
47
48
FuzzHelper::FuzzHelper(unsigned char const* data, size_t size) :
49
    // We do not modify data, so it is safe to remove the const for Buffer
50
295k
    input_buffer(const_cast<unsigned char*>(data), size),
51
295k
    start(std::chrono::steady_clock::now())
52
295k
{
53
295k
}
54
55
std::shared_ptr<QPDF>
56
FuzzHelper::getQpdf()
57
259k
{
58
259k
    auto is =
59
259k
        std::shared_ptr<InputSource>(new BufferInputSource("fuzz input", &this->input_buffer));
60
259k
    auto qpdf = QPDF::create();
61
259k
    qpdf->setMaxWarnings(200);
62
259k
    qpdf->processInputSource(is);
63
259k
    return qpdf;
64
259k
}
65
66
void
67
FuzzHelper::testPages()
68
48.1k
{
69
    // Parse all content streams, and exercise some helpers that
70
    // operate on pages.
71
48.1k
    std::shared_ptr<QPDF> q = getQpdf();
72
48.1k
    info("getQpdf done");
73
48.1k
    QPDFPageDocumentHelper pdh(*q);
74
48.1k
    QPDFPageLabelDocumentHelper pldh(*q);
75
48.1k
    QPDFOutlineDocumentHelper odh(*q);
76
48.1k
    QPDFAcroFormDocumentHelper afdh(*q);
77
48.1k
    afdh.generateAppearancesIfNeeded();
78
48.1k
    info("generateAppearancesIfNeeded done");
79
48.1k
    pdh.flattenAnnotations();
80
48.1k
    info("flattenAnnotations done");
81
48.1k
    int pageno = 0;
82
48.1k
    for (auto& page: pdh.getAllPages()) {
83
41.8k
        ++pageno;
84
41.8k
        try {
85
41.8k
            info("start page", pageno);
86
41.8k
            page.coalesceContentStreams();
87
41.8k
            info("coalesceContentStreams done");
88
41.8k
            page.parseContents(nullptr);
89
41.8k
            info("parseContents done");
90
41.8k
            page.getImages();
91
41.8k
            info("getImages done");
92
41.8k
            pldh.getLabelForPage(pageno);
93
41.8k
            info("getLabelForPage done");
94
41.8k
            QPDFObjectHandle page_obj(page.getObjectHandle());
95
41.8k
            page_obj.getJSON(JSON::LATEST, true).unparse();
96
41.8k
            info("getJSON done");
97
41.8k
            odh.getOutlinesForPage(page_obj);
98
41.8k
            info("getOutlinesForPage done");
99
100
41.8k
            for (auto& aoh: afdh.getWidgetAnnotationsForPage(page)) {
101
3.30k
                afdh.getFieldForAnnotation(aoh);
102
3.30k
            }
103
41.8k
        } catch (QPDFExc& e) {
104
10.0k
            std::cerr << "page " << pageno << ": " << e.what() << '\n';
105
10.0k
        }
106
41.8k
    }
107
48.1k
}
108
109
void
110
FuzzHelper::doChecks()
111
279k
{
112
    // Limit the memory used to decompress JPEG files during fuzzing. Excessive memory use during
113
    // fuzzing is due to corrupt JPEG data which sometimes cannot be detected before
114
    // jpeg_start_decompress is called. During normal use of qpdf very large JPEGs can occasionally
115
    // occur legitimately and therefore must be allowed during normal operations.
116
279k
    Pl_DCT::setMemoryLimit(100'000'000);
117
279k
    Pl_DCT::setScanLimit(50);
118
119
279k
    Pl_PNGFilter::setMemoryLimit(1'000'000);
120
279k
    Pl_RunLength::setMemoryLimit(1'000'000);
121
279k
    Pl_TIFFPredictor::setMemoryLimit(1'000'000);
122
279k
    Pl_Flate::memory_limit(200'000);
123
124
    // Do not decompress corrupt data. This may cause extended runtime within jpeglib without
125
    // exercising additional code paths in qpdf, and potentially causing counterproductive timeouts.
126
279k
    Pl_DCT::setThrowOnCorruptData(true);
127
128
    // Get as much coverage as possible in parts of the library that
129
    // might benefit from fuzzing.
130
279k
    std::cerr << "\ninfo: starting testPages\n";
131
279k
    testPages();
132
279k
}
133
134
void
135
FuzzHelper::run()
136
259k
{
137
    // The goal here is that you should be able to throw anything at
138
    // libqpdf and it will respond without any memory errors and never
139
    // do anything worse than throwing a QPDFExc or
140
    // std::runtime_error. Throwing any other kind of exception,
141
    // segfaulting, or having a memory error (when built with
142
    // appropriate sanitizers) will all cause abnormal exit.
143
259k
    try {
144
259k
        doChecks();
145
259k
    } catch (QPDFExc const& e) {
146
172k
        std::cerr << "QPDFExc: " << e.what() << '\n';
147
172k
    } catch (std::runtime_error const& e) {
148
705
        std::cerr << "runtime_error: " << e.what() << '\n';
149
705
    }
150
259k
}
151
152
extern "C" int
153
LLVMFuzzerTestOneInput(unsigned char const* data, size_t size)
154
295k
{
155
295k
#ifndef _WIN32
156
    // Used by jpeg library to work around false positives in memory
157
    // sanitizer.
158
295k
    setenv("JSIMD_FORCENONE", "1", 1);
159
295k
#endif
160
295k
    FuzzHelper f(data, size);
161
295k
    f.run();
162
295k
    return 0;
163
295k
}