Coverage Report

Created: 2026-10-17 07:17

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libtiff/contrib/oss-fuzz/tiff_packbits_fuzzer.cc
Line
Count
Source
1
/*
2
 * Copyright (c) 2026 Anthony Adame
3
 *
4
 * Permission to use, copy, modify, distribute, and sell this software and
5
 * its documentation for any purpose is hereby granted without fee, provided
6
 * that (i) the above copyright notice and this permission notice appear in
7
 * all copies of the software and related documentation, and (ii) the name of
8
 * the author may not be used in any advertising or publicity relating to the
9
 * software without the specific, prior written permission of the author.
10
 *
11
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
12
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
13
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
14
 *
15
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
16
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
17
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
18
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
19
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
20
 * OF THIS SOFTWARE.
21
 */
22
23
/*
24
 * Targeted fuzzer for the PackBits decoder (tif_packbits.c).
25
 *
26
 * Feeding raw bytes to a whole-file TIFF fuzzer rarely reaches a specific
27
 * codec: almost every input dies in header/IFD parsing first. This harness
28
 * instead synthesises a minimal, always-valid single-strip TIFF container in
29
 * memory and uses the fuzz input as the *compressed strip payload*, so every
30
 * iteration reaches PackBitsDecode() with an attacker-controlled bitstream.
31
 *
32
 * A few leading bytes select the image geometry, which varies the size of the
33
 * output buffer the decoder must not overrun (the `occ` cursor) independently
34
 * of the payload it is decoding -- that pairing is what exercises the bounds
35
 * logic. Dimensions are clamped so the decoded strip stays small and the
36
 * fuzzer reports real memory errors rather than out-of-memory.
37
 */
38
39
#include <cstddef>
40
#include <cstdint>
41
#include <cstring>
42
43
#include <vector>
44
45
#include <fuzzer/FuzzedDataProvider.h>
46
47
#include "tiffio.h"
48
49
/* Keep the decoded strip small: OOM is not the bug class under test. */
50
static const uint32_t kMaxWidth = 1024;
51
static const uint32_t kMaxHeight = 64;
52
53
namespace
54
{
55
56
/* ---- in-memory TIFF client ------------------------------------------- */
57
58
struct MemFile
59
{
60
    const uint8_t *data;
61
    toff_t size;
62
    toff_t pos;
63
};
64
65
tmsize_t memRead(thandle_t handle, void *buf, tmsize_t want)
66
1.49k
{
67
1.49k
    MemFile *m = static_cast<MemFile *>(handle);
68
1.49k
    if (want < 0 || m->pos >= m->size)
69
0
        return 0;
70
1.49k
    toff_t avail = m->size - m->pos;
71
1.49k
    if (static_cast<toff_t>(want) > avail)
72
0
        want = static_cast<tmsize_t>(avail);
73
1.49k
    memcpy(buf, m->data + m->pos, static_cast<size_t>(want));
74
1.49k
    m->pos += static_cast<toff_t>(want);
75
1.49k
    return want;
76
1.49k
}
77
78
0
tmsize_t memWrite(thandle_t, void *, tmsize_t) { return 0; }
79
80
toff_t memSeek(thandle_t handle, toff_t off, int whence)
81
596
{
82
596
    MemFile *m = static_cast<MemFile *>(handle);
83
596
    toff_t base;
84
596
    switch (whence)
85
596
    {
86
596
        case SEEK_SET:
87
596
            base = 0;
88
596
            break;
89
0
        case SEEK_CUR:
90
0
            base = m->pos;
91
0
            break;
92
0
        case SEEK_END:
93
0
            base = m->size;
94
0
            break;
95
0
        default:
96
0
            return static_cast<toff_t>(-1);
97
596
    }
98
    /* Refuse wrap-around rather than silently aliasing a valid offset. */
99
596
    if (off > static_cast<toff_t>(-1) - base)
100
0
        return static_cast<toff_t>(-1);
101
596
    m->pos = base + off;
102
596
    return m->pos;
103
596
}
104
105
298
int memClose(thandle_t) { return 0; }
106
107
toff_t memSize(thandle_t handle)
108
0
{
109
0
    return static_cast<MemFile *>(handle)->size;
110
0
}
111
112
/* memMap/memUnmap are passed as null: libtiff substitutes no-op stubs. */
113
114
/* ---- quiet libtiff diagnostics --------------------------------------- */
115
116
174
void silentError(const char *, const char *, va_list) {}
117
252
void silentWarning(const char *, const char *, va_list) {}
118
119
/* ---- minimal little-endian TIFF writer -------------------------------- */
120
121
void put16(std::vector<uint8_t> &v, uint16_t x)
122
5.96k
{
123
5.96k
    v.push_back(static_cast<uint8_t>(x & 0xff));
124
5.96k
    v.push_back(static_cast<uint8_t>((x >> 8) & 0xff));
125
5.96k
}
126
127
void put32(std::vector<uint8_t> &v, uint32_t x)
128
5.96k
{
129
5.96k
    v.push_back(static_cast<uint8_t>(x & 0xff));
130
5.96k
    v.push_back(static_cast<uint8_t>((x >> 8) & 0xff));
131
5.96k
    v.push_back(static_cast<uint8_t>((x >> 16) & 0xff));
132
5.96k
    v.push_back(static_cast<uint8_t>((x >> 24) & 0xff));
133
5.96k
}
134
135
void putEntry(std::vector<uint8_t> &v, uint16_t tag, uint16_t type,
136
              uint32_t count, uint32_t value)
137
2.68k
{
138
2.68k
    put16(v, tag);
139
2.68k
    put16(v, type);
140
2.68k
    put32(v, count);
141
    /* Single SHORT/LONG values fit inline; little-endian puts a SHORT in the
142
     * first two bytes of the field, so writing the full word is correct. */
143
2.68k
    put32(v, value);
144
2.68k
}
145
146
1.19k
#define TIFF_TYPE_SHORT 3
147
1.49k
#define TIFF_TYPE_LONG 4
148
149
/* 9 directory entries, one strip, 8-bit grayscale, PackBits-compressed. */
150
894
#define ENTRY_COUNT 9
151
894
#define IFD_OFFSET 8
152
596
#define IFD_BYTES (2 + (ENTRY_COUNT * 12) + 4)
153
596
#define STRIP_OFFSET (IFD_OFFSET + IFD_BYTES)
154
155
std::vector<uint8_t> buildPackBitsTiff(uint32_t width, uint32_t height,
156
                                       const std::vector<uint8_t> &payload)
157
298
{
158
298
    std::vector<uint8_t> t;
159
298
    t.reserve(STRIP_OFFSET + payload.size());
160
161
    /* Header: little-endian, classic TIFF, IFD immediately after. */
162
298
    t.push_back('I');
163
298
    t.push_back('I');
164
298
    put16(t, 42);
165
298
    put32(t, IFD_OFFSET);
166
167
298
    put16(t, ENTRY_COUNT); /* entries must be in ascending tag order */
168
298
    putEntry(t, 256, TIFF_TYPE_LONG, 1, width);  /* ImageWidth      */
169
298
    putEntry(t, 257, TIFF_TYPE_LONG, 1, height); /* ImageLength     */
170
298
    putEntry(t, 258, TIFF_TYPE_SHORT, 1, 8);     /* BitsPerSample   */
171
298
    putEntry(t, 259, TIFF_TYPE_SHORT, 1, COMPRESSION_PACKBITS);
172
298
    putEntry(t, 262, TIFF_TYPE_SHORT, 1, PHOTOMETRIC_MINISBLACK);
173
298
    putEntry(t, 273, TIFF_TYPE_LONG, 1, STRIP_OFFSET); /* StripOffsets    */
174
298
    putEntry(t, 277, TIFF_TYPE_SHORT, 1, 1);           /* SamplesPerPixel */
175
298
    putEntry(t, 278, TIFF_TYPE_LONG, 1, height);       /* RowsPerStrip    */
176
298
    putEntry(t, 279, TIFF_TYPE_LONG, 1,
177
298
             static_cast<uint32_t>(payload.size())); /* StripByteCounts */
178
298
    put32(t, 0);                                     /* no next IFD     */
179
180
298
    t.insert(t.end(), payload.begin(), payload.end());
181
298
    return t;
182
298
}
183
184
} /* namespace */
185
186
extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
187
301
{
188
301
    FuzzedDataProvider fdp(data, size);
189
190
301
    const uint32_t width = fdp.ConsumeIntegralInRange<uint32_t>(1, kMaxWidth);
191
301
    const uint32_t height = fdp.ConsumeIntegralInRange<uint32_t>(1, kMaxHeight);
192
193
301
    const std::vector<uint8_t> payload = fdp.ConsumeRemainingBytes<uint8_t>();
194
301
    if (payload.empty())
195
3
        return 0;
196
197
298
    const std::vector<uint8_t> tiff = buildPackBitsTiff(width, height, payload);
198
199
298
    TIFFSetErrorHandler(silentError);
200
298
    TIFFSetWarningHandler(silentWarning);
201
202
298
    MemFile mem;
203
298
    mem.data = tiff.data();
204
298
    mem.size = static_cast<toff_t>(tiff.size());
205
298
    mem.pos = 0;
206
207
298
    TIFF *tif = TIFFClientOpen("packbits_fuzz", "r",
208
298
                               static_cast<thandle_t>(&mem), memRead, memWrite,
209
298
                               memSeek, memClose, memSize, nullptr, nullptr);
210
298
    if (!tif)
211
0
        return 0;
212
213
298
    const tmsize_t stripSize = TIFFStripSize(tif);
214
298
    if (stripSize > 0)
215
298
    {
216
298
        void *buf = _TIFFmalloc(stripSize);
217
298
        if (buf)
218
298
        {
219
            /* The decode itself: PackBitsDecode() expands `payload` into a
220
             * buffer of exactly stripSize bytes and must not step outside it.
221
             */
222
298
            TIFFReadEncodedStrip(tif, 0, buf, stripSize);
223
298
            _TIFFfree(buf);
224
298
        }
225
298
    }
226
227
298
    TIFFClose(tif);
228
298
    return 0;
229
298
}