Coverage Report

Created: 2025-07-01 06:10

/src/qpdf/libqpdf/Pl_DCT.cc
Line
Count
Source (jump to first uncovered line)
1
#include <qpdf/Pl_DCT.hh>
2
3
#include <qpdf/QIntC.hh>
4
#include <qpdf/QTC.hh>
5
6
#include <csetjmp>
7
#include <stdexcept>
8
#include <string>
9
10
#if BITS_IN_JSAMPLE != 8
11
# error "qpdf does not support libjpeg built with BITS_IN_JSAMPLE != 8"
12
#endif
13
14
namespace
15
{
16
    class FunctionCallbackConfig: public Pl_DCT::CompressConfig
17
    {
18
      public:
19
        explicit FunctionCallbackConfig(std::function<void(jpeg_compress_struct*)> f) :
20
0
            f(std::move(f))
21
0
        {
22
0
        }
23
0
        ~FunctionCallbackConfig() override = default;
24
        void
25
        apply(jpeg_compress_struct* config) override
26
0
        {
27
0
            f(config);
28
0
        };
29
30
        std::function<void(jpeg_compress_struct*)> f;
31
    };
32
33
    struct qpdf_jpeg_error_mgr
34
    {
35
        struct jpeg_error_mgr pub;
36
        jmp_buf jmpbuf;
37
        std::string msg;
38
    };
39
40
    long memory_limit{0};
41
    int scan_limit{0};
42
    bool throw_on_corrupt_data{true};
43
} // namespace
44
45
static void
46
error_handler(j_common_ptr cinfo)
47
0
{
48
0
    auto* jerr = reinterpret_cast<qpdf_jpeg_error_mgr*>(cinfo->err);
49
0
    char buf[JMSG_LENGTH_MAX];
50
0
    (*cinfo->err->format_message)(cinfo, buf);
51
0
    jerr->msg = buf;
52
0
    longjmp(jerr->jmpbuf, 1);
53
0
}
54
55
static void
56
emit_message(j_common_ptr cinfo, int msg_level)
57
0
{
58
0
    if (msg_level == -1) {
59
0
        auto* jerr = reinterpret_cast<qpdf_jpeg_error_mgr*>(cinfo->err);
60
0
        jerr->msg = "Pl_DCT::decompress: JPEG data is corrupt";
61
0
        longjmp(jerr->jmpbuf, 1);
62
0
    }
63
0
}
64
65
static void
66
progress_monitor(j_common_ptr cinfo)
67
0
{
68
0
    if (cinfo->is_decompressor &&
69
0
        reinterpret_cast<jpeg_decompress_struct*>(cinfo)->input_scan_number > scan_limit) {
70
0
        auto* jerr = reinterpret_cast<qpdf_jpeg_error_mgr*>(cinfo->err);
71
0
        jerr->msg = "Pl_DCT::decompress: JPEG data has too many scans";
72
0
        longjmp(jerr->jmpbuf, 1);
73
0
    }
74
0
}
75
76
class Pl_DCT::Members
77
{
78
  public:
79
    // For compression
80
    Members(
81
        JDIMENSION image_width,
82
        JDIMENSION image_height,
83
        int components,
84
        J_COLOR_SPACE color_space,
85
        CompressConfig* config_callback) :
86
0
        action(a_compress),
87
0
        image_width(image_width),
88
0
        image_height(image_height),
89
0
        components(components),
90
0
        color_space(color_space),
91
0
        config_callback(config_callback)
92
0
    {
93
0
    }
94
95
    // For decompression
96
    Members() :
97
0
        action(a_decompress)
98
0
    {
99
0
    }
100
101
    Members(Members const&) = delete;
102
103
0
    ~Members() = default;
104
105
    action_e action;
106
    std::string buf;
107
108
    // Used for compression
109
    JDIMENSION image_width{0};
110
    JDIMENSION image_height{0};
111
    int components{1};
112
    J_COLOR_SPACE color_space{JCS_GRAYSCALE};
113
114
    CompressConfig* config_callback{nullptr};
115
};
116
117
Pl_DCT::Pl_DCT(char const* identifier, Pipeline* next) :
118
0
    Pipeline(identifier, next),
119
0
    m(std::make_unique<Members>())
120
0
{
121
0
    if (!next) {
122
0
        throw std::logic_error("Attempt to create Pl_DCT with nullptr as next");
123
0
    }
124
0
}
125
126
void
127
Pl_DCT::setMemoryLimit(long limit)
128
0
{
129
0
    memory_limit = limit;
130
0
}
131
132
void
133
Pl_DCT::setScanLimit(int limit)
134
0
{
135
0
    scan_limit = limit;
136
0
}
137
138
void
139
Pl_DCT::setThrowOnCorruptData(bool treat_as_error)
140
0
{
141
0
    throw_on_corrupt_data = treat_as_error;
142
0
}
143
144
Pl_DCT::Pl_DCT(
145
    char const* identifier,
146
    Pipeline* next,
147
    JDIMENSION image_width,
148
    JDIMENSION image_height,
149
    int components,
150
    J_COLOR_SPACE color_space,
151
    CompressConfig* compress_callback) :
152
0
    Pipeline(identifier, next),
153
0
    m(std::make_unique<Members>(
154
0
        image_width, image_height, components, color_space, compress_callback))
155
0
{
156
0
}
157
158
// Must be explicit and not inline -- see QPDF_DLL_CLASS in README-maintainer
159
0
Pl_DCT::~Pl_DCT() = default;
160
161
void
162
Pl_DCT::write(unsigned char const* data, size_t len)
163
0
{
164
0
    if (len > 0) {
165
0
        m->buf.append(reinterpret_cast<char const*>(data), len);
166
0
    }
167
0
}
168
169
void
170
Pl_DCT::finish()
171
0
{
172
0
    if (m->buf.empty()) {
173
        // Special case: empty data will never succeed and probably means we're calling finish a
174
        // second time from an exception handler.
175
0
        next()->finish();
176
0
        return;
177
0
    }
178
179
0
    struct jpeg_compress_struct cinfo_compress;
180
0
    struct jpeg_decompress_struct cinfo_decompress;
181
0
    struct qpdf_jpeg_error_mgr jerr;
182
183
0
    cinfo_compress.err = jpeg_std_error(&(jerr.pub));
184
0
    cinfo_decompress.err = jpeg_std_error(&(jerr.pub));
185
0
    jerr.pub.error_exit = error_handler;
186
0
    if (m->action == a_decompress && throw_on_corrupt_data) {
187
0
        jerr.pub.emit_message = emit_message;
188
0
    }
189
190
0
    bool error = false;
191
    // The jpeg library is a "C" library, so we use setjmp and longjmp for exception handling.
192
0
    if (setjmp(jerr.jmpbuf) == 0) {
193
0
        try {
194
0
            if (m->action == a_compress) {
195
0
                compress(reinterpret_cast<void*>(&cinfo_compress));
196
0
            } else {
197
0
                decompress(reinterpret_cast<void*>(&cinfo_decompress));
198
0
            }
199
0
        } catch (std::exception& e) {
200
            // Convert an exception back to a longjmp so we can ensure that the right cleanup
201
            // happens. This will get converted back to an exception.
202
0
            jerr.msg = e.what();
203
0
            longjmp(jerr.jmpbuf, 1);
204
0
        }
205
0
    } else {
206
0
        error = true;
207
0
    }
208
209
0
    if (m->action == a_compress) {
210
0
        jpeg_destroy_compress(&cinfo_compress);
211
0
    }
212
0
    if (m->action == a_decompress) {
213
0
        jpeg_destroy_decompress(&cinfo_decompress);
214
0
    }
215
0
    if (error) {
216
0
        throw std::runtime_error(jerr.msg);
217
0
    }
218
0
}
219
220
namespace
221
{
222
    struct dct_pipeline_dest
223
    {
224
        struct jpeg_destination_mgr pub; /* public fields */
225
        unsigned char* buffer;
226
        size_t size;
227
        Pipeline* next;
228
    };
229
} // namespace
230
231
static void
232
init_pipeline_destination(j_compress_ptr)
233
0
{
234
0
}
235
236
static boolean
237
empty_pipeline_output_buffer(j_compress_ptr cinfo)
238
0
{
239
0
    QTC::TC("libtests", "Pl_DCT empty_pipeline_output_buffer");
240
0
    auto* dest = reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
241
0
    dest->next->write(dest->buffer, dest->size);
242
0
    dest->pub.next_output_byte = dest->buffer;
243
0
    dest->pub.free_in_buffer = dest->size;
244
0
    return TRUE;
245
0
}
246
247
static void
248
term_pipeline_destination(j_compress_ptr cinfo)
249
0
{
250
0
    QTC::TC("libtests", "Pl_DCT term_pipeline_destination");
251
0
    auto* dest = reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
252
0
    dest->next->write(dest->buffer, dest->size - dest->pub.free_in_buffer);
253
0
}
254
255
static void
256
jpeg_pipeline_dest(j_compress_ptr cinfo, unsigned char* outbuffer, size_t size, Pipeline* next)
257
0
{
258
0
    cinfo->dest = static_cast<struct jpeg_destination_mgr*>(
259
        // line-break
260
0
        (*cinfo->mem->alloc_small)(
261
0
            reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(dct_pipeline_dest)));
262
0
    auto* dest = reinterpret_cast<dct_pipeline_dest*>(cinfo->dest);
263
0
    dest->pub.init_destination = init_pipeline_destination;
264
0
    dest->pub.empty_output_buffer = empty_pipeline_output_buffer;
265
0
    dest->pub.term_destination = term_pipeline_destination;
266
0
    dest->pub.next_output_byte = dest->buffer = outbuffer;
267
0
    dest->pub.free_in_buffer = dest->size = size;
268
0
    dest->next = next;
269
0
}
270
271
static void
272
init_buffer_source(j_decompress_ptr)
273
0
{
274
0
}
275
276
static boolean
277
fill_buffer_input_buffer(j_decompress_ptr)
278
0
{
279
    // The whole JPEG data is expected to reside in the supplied memory buffer, so any request for
280
    // more data beyond the given buffer size is treated as an error.
281
0
    throw std::runtime_error("invalid jpeg data reading from buffer");
282
0
    return TRUE;
283
0
}
284
285
static void
286
skip_buffer_input_data(j_decompress_ptr cinfo, long num_bytes)
287
0
{
288
0
    if (num_bytes < 0) {
289
0
        throw std::runtime_error(
290
0
            "reading jpeg: jpeg library requested skipping a negative number of bytes");
291
0
    }
292
0
    size_t to_skip = QIntC::to_size(num_bytes);
293
0
    if ((to_skip > 0) && (to_skip <= cinfo->src->bytes_in_buffer)) {
294
0
        cinfo->src->next_input_byte += to_skip;
295
0
        cinfo->src->bytes_in_buffer -= to_skip;
296
0
    } else if (to_skip != 0) {
297
0
        cinfo->src->next_input_byte += cinfo->src->bytes_in_buffer;
298
0
        cinfo->src->bytes_in_buffer = 0;
299
0
    }
300
0
}
301
302
static void
303
term_buffer_source(j_decompress_ptr)
304
0
{
305
0
}
306
307
static void
308
jpeg_buffer_src(j_decompress_ptr cinfo, std::string& buffer)
309
0
{
310
0
    cinfo->src = reinterpret_cast<jpeg_source_mgr*>(
311
        // line-break
312
0
        (*cinfo->mem->alloc_small)(
313
0
            reinterpret_cast<j_common_ptr>(cinfo), JPOOL_PERMANENT, sizeof(jpeg_source_mgr)));
314
315
0
    jpeg_source_mgr* src = cinfo->src;
316
0
    src->init_source = init_buffer_source;
317
0
    src->fill_input_buffer = fill_buffer_input_buffer;
318
0
    src->skip_input_data = skip_buffer_input_data;
319
0
    src->resync_to_restart = jpeg_resync_to_restart; /* use default method */
320
0
    src->term_source = term_buffer_source;
321
0
    src->bytes_in_buffer = buffer.size();
322
0
    src->next_input_byte = reinterpret_cast<unsigned char*>(buffer.data());
323
0
}
324
325
void
326
Pl_DCT::compress(void* cinfo_p)
327
0
{
328
0
    auto* cinfo = reinterpret_cast<jpeg_compress_struct*>(cinfo_p);
329
330
0
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || defined(__clang__))
331
0
# pragma GCC diagnostic push
332
0
# pragma GCC diagnostic ignored "-Wold-style-cast"
333
0
#endif
334
0
    jpeg_create_compress(cinfo);
335
0
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || defined(__clang__))
336
0
# pragma GCC diagnostic pop
337
0
#endif
338
0
    static int const BUF_SIZE = 65536;
339
0
    auto outbuffer_ph = std::make_unique<unsigned char[]>(BUF_SIZE);
340
0
    unsigned char* outbuffer = outbuffer_ph.get();
341
0
    jpeg_pipeline_dest(cinfo, outbuffer, BUF_SIZE, next());
342
343
0
    cinfo->image_width = m->image_width;
344
0
    cinfo->image_height = m->image_height;
345
0
    cinfo->input_components = m->components;
346
0
    cinfo->in_color_space = m->color_space;
347
0
    jpeg_set_defaults(cinfo);
348
0
    if (m->config_callback) {
349
0
        m->config_callback->apply(cinfo);
350
0
    }
351
352
0
    jpeg_start_compress(cinfo, TRUE);
353
354
0
    unsigned int width = cinfo->image_width * QIntC::to_uint(cinfo->input_components);
355
0
    size_t expected_size = QIntC::to_size(cinfo->image_height) *
356
0
        QIntC::to_size(cinfo->image_width) * QIntC::to_size(cinfo->input_components);
357
0
    if (m->buf.size() != expected_size) {
358
0
        throw std::runtime_error(
359
0
            "Pl_DCT: image buffer size = " + std::to_string(m->buf.size()) +
360
0
            "; expected size = " + std::to_string(expected_size));
361
0
    }
362
0
    JSAMPROW row_pointer[1];
363
0
    auto buffer = reinterpret_cast<unsigned char*>(m->buf.data());
364
0
    while (cinfo->next_scanline < cinfo->image_height) {
365
        // We already verified that the buffer is big enough.
366
0
        row_pointer[0] = &buffer[cinfo->next_scanline * width];
367
0
        (void)jpeg_write_scanlines(cinfo, row_pointer, 1);
368
0
    }
369
0
    jpeg_finish_compress(cinfo);
370
0
    next()->finish();
371
0
}
372
373
void
374
Pl_DCT::decompress(void* cinfo_p)
375
0
{
376
0
    auto* cinfo = reinterpret_cast<jpeg_decompress_struct*>(cinfo_p);
377
378
0
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || defined(__clang__))
379
0
# pragma GCC diagnostic push
380
0
# pragma GCC diagnostic ignored "-Wold-style-cast"
381
0
#endif
382
0
    jpeg_create_decompress(cinfo);
383
0
#if ((defined(__GNUC__) && ((__GNUC__ * 100) + __GNUC_MINOR__) >= 406) || defined(__clang__))
384
0
# pragma GCC diagnostic pop
385
0
#endif
386
387
0
    if (memory_limit > 0) {
388
0
        cinfo->mem->max_memory_to_use = memory_limit;
389
0
    }
390
391
0
    jpeg_buffer_src(cinfo, m->buf);
392
393
0
    (void)jpeg_read_header(cinfo, TRUE);
394
0
    jpeg_calc_output_dimensions(cinfo);
395
0
    unsigned int width = cinfo->output_width * QIntC::to_uint(cinfo->output_components);
396
0
    if (memory_limit > 0 &&
397
0
        width > (static_cast<unsigned long>(memory_limit) / (20U * cinfo->output_height))) {
398
        // Even if jpeglib does not run out of memory, qpdf will while buffering the data before
399
        // writing it. Furthermore, for very large images runtime can be significant before the
400
        // first warning is encountered causing a timeout in oss-fuzz.
401
0
        throw std::runtime_error("Pl_DCT::decompress: JPEG data large - may be too slow");
402
0
    }
403
0
    jpeg_progress_mgr progress_mgr;
404
0
    if (scan_limit > 0) {
405
0
        progress_mgr.progress_monitor = &progress_monitor;
406
0
        cinfo->progress = &progress_mgr;
407
0
    }
408
0
    JSAMPARRAY buffer =
409
0
        (*cinfo->mem->alloc_sarray)(reinterpret_cast<j_common_ptr>(cinfo), JPOOL_IMAGE, width, 1);
410
411
0
    (void)jpeg_start_decompress(cinfo);
412
0
    while (cinfo->output_scanline < cinfo->output_height) {
413
0
        (void)jpeg_read_scanlines(cinfo, buffer, 1);
414
0
        next()->write(buffer[0], width * sizeof(buffer[0][0]));
415
0
    }
416
0
    (void)jpeg_finish_decompress(cinfo);
417
0
    next()->finish();
418
0
}
419
420
std::unique_ptr<Pl_DCT::CompressConfig>
421
Pl_DCT::make_compress_config(std::function<void(jpeg_compress_struct*)> f)
422
0
{
423
0
    return std::make_unique<FunctionCallbackConfig>(f);
424
0
}