Coverage Report

Created: 2024-01-26 01:52

/work/testing/fuzzing/fuzz_support.h
Line
Count
Source (jump to first uncovered line)
1
/* SPDX-License-Identifier: GPL-3.0-or-later
2
 * Copyright © 2021-2022 The TokTok team.
3
 */
4
5
#ifndef C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H
6
#define C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H
7
8
#include <cstdint>
9
#include <cstdio>
10
#include <cstdlib>
11
#include <cstring>
12
#include <deque>
13
#include <memory>
14
#include <unordered_map>
15
#include <utility>
16
#include <vector>
17
18
#include "../../toxcore/tox.h"
19
20
struct Fuzz_Data {
21
    static constexpr bool DEBUG = false;
22
    static constexpr std::size_t TRACE_TRAP = -1; // 579;
23
24
private:
25
    const uint8_t *data_;
26
    const uint8_t *base_;
27
    std::size_t size_;
28
29
public:
30
    Fuzz_Data(const uint8_t *input_data, std::size_t input_size)
31
        : data_(input_data)
32
        , base_(input_data)
33
        , size_(input_size)
34
2.99k
    {
35
2.99k
    }
36
37
    Fuzz_Data &operator=(const Fuzz_Data &rhs) = delete;
38
    Fuzz_Data(const Fuzz_Data &rhs) = delete;
39
40
    struct Consumer {
41
        const char *func;
42
        Fuzz_Data &fd;
43
44
        operator bool()
45
46.0k
        {
46
            // Special case because memcpy causes UB for bool (which can't be
47
            // anything other than 0 or 1).
48
46.0k
            const bool val = fd.data_[0];
49
46.0k
            if (DEBUG) {
50
0
                std::printf("consume@%zu(%s): bool %s\n", fd.pos(), func, val ? "true" : "false");
51
0
            }
52
46.0k
            ++fd.data_;
53
46.0k
            --fd.size_;
54
46.0k
            return val;
55
46.0k
        }
56
57
        template <typename T>
58
        operator T()
59
38.7k
        {
60
38.7k
            const uint8_t *bytes = fd.consume(func, sizeof(T));
61
38.7k
            T val;
62
38.7k
            std::memcpy(&val, bytes, sizeof(T));
63
38.7k
            return val;
64
38.7k
        }
_ZN9Fuzz_Data8ConsumercvT_IhEEv
Line
Count
Source
59
33.9k
        {
60
33.9k
            const uint8_t *bytes = fd.consume(func, sizeof(T));
61
33.9k
            T val;
62
33.9k
            std::memcpy(&val, bytes, sizeof(T));
63
33.9k
            return val;
64
33.9k
        }
_ZN9Fuzz_Data8ConsumercvT_ItEEv
Line
Count
Source
59
4.82k
        {
60
4.82k
            const uint8_t *bytes = fd.consume(func, sizeof(T));
61
4.82k
            T val;
62
4.82k
            std::memcpy(&val, bytes, sizeof(T));
63
4.82k
            return val;
64
4.82k
        }
65
    };
66
67
84.8k
    Consumer consume1(const char *func) { return Consumer{func, *this}; }
68
228k
    std::size_t size() const { return size_; }
69
0
    std::size_t pos() const { return data_ - base_; }
70
160
    const uint8_t *data() const { return data_; }
71
94.9k
    bool empty() const { return size_ == 0; }
72
73
    const uint8_t *consume(const char *func, std::size_t count)
74
109k
    {
75
109k
        const uint8_t *val = data_;
76
109k
        if (DEBUG) {
77
0
            if (pos() == TRACE_TRAP) {
78
0
                __asm__("int $3");
79
0
            }
80
0
            if (count == 1) {
81
0
                std::printf("consume@%zu(%s): %d (0x%02x)\n", pos(), func, val[0], val[0]);
82
0
            } else if (count != 0) {
83
0
                std::printf("consume@%zu(%s): %02x..%02x[%zu]\n", pos(), func, val[0],
84
0
                    val[count - 1], count);
85
0
            }
86
0
        }
87
109k
        data_ += count;
88
109k
        size_ -= count;
89
109k
        return val;
90
109k
    }
91
};
92
93
/** @brief Consumes 1 byte of the fuzzer input or returns if no data available.
94
 *
95
 * This advances the fuzzer input data by 1 byte and consumes that byte in the
96
 * declaration.
97
 *
98
 * @example
99
 * @code
100
 * CONSUME1_OR_RETURN(const uint8_t, one_byte, input);
101
 * @endcode
102
 */
103
#define CONSUME1_OR_RETURN(TYPE, NAME, INPUT) \
104
38.8k
    if (INPUT.size() < sizeof(TYPE)) {        \
105
40
        return;                               \
106
40
    }                                         \
107
38.8k
    TYPE NAME = INPUT.consume1(__func__)
108
109
/** @brief Consumes 1 byte of the fuzzer input or returns a value if no data
110
 * available.
111
 *
112
 * This advances the fuzzer input data by 1 byte and consumes that byte in the
113
 * declaration.
114
 *
115
 * @example
116
 * @code
117
 * CONSUME1_OR_RETURN_VAL(const uint8_t one_byte, input, nullptr);
118
 * @endcode
119
 */
120
#define CONSUME1_OR_RETURN_VAL(TYPE, NAME, INPUT, VAL) \
121
48.8k
    if (INPUT.size() < sizeof(TYPE)) {                 \
122
2.81k
        return VAL;                                    \
123
2.81k
    }                                                  \
124
48.8k
    TYPE NAME = INPUT.consume1(__func__)
125
126
/** @brief Consumes SIZE bytes of the fuzzer input or returns if not enough data available.
127
 *
128
 * This advances the fuzzer input data by SIZE byte and consumes those bytes in
129
 * the declaration. If less than SIZE bytes are available in the fuzzer input,
130
 * this macro returns from the enclosing function.
131
 *
132
 * @example
133
 * @code
134
 * CONSUME_OR_RETURN(const uint8_t *ten_bytes, input, 10);
135
 * @endcode
136
 */
137
#define CONSUME_OR_RETURN(DECL, INPUT, SIZE) \
138
9.96k
    if (INPUT.size() < SIZE) {               \
139
98
        return;                              \
140
98
    }                                        \
141
9.96k
    DECL = INPUT.consume(__func__, SIZE)
142
143
#define CONSUME_OR_RETURN_VAL(DECL, INPUT, SIZE, VAL) \
144
119
    if (INPUT.size() < SIZE) {                        \
145
13
        return VAL;                                   \
146
13
    }                                                 \
147
119
    DECL = INPUT.consume(__func__, SIZE)
148
149
#define CONSUME_OR_ABORT(DECL, INPUT, SIZE) \
150
61.0k
    if (INPUT.size() < SIZE) {              \
151
0
        abort();                            \
152
0
    }                                       \
153
61.0k
    DECL = INPUT.consume(__func__, SIZE)
154
155
using Fuzz_Target = void (*)(Fuzz_Data &input);
156
157
template <Fuzz_Target... Args>
158
struct Fuzz_Target_Selector;
159
160
template <Fuzz_Target Arg, Fuzz_Target... Args>
161
struct Fuzz_Target_Selector<Arg, Args...> {
162
    static void select(uint8_t selector, Fuzz_Data &input)
163
914
    {
164
914
        if (selector == sizeof...(Args)) {
165
418
            return Arg(input);
166
418
        }
167
496
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
914
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_117TestHandleRequestER9Fuzz_DataEEXadL_ZNS0_15TestUnpackNodesES2_EEEE6selectEhS2_
Line
Count
Source
163
41
    {
164
41
        if (selector == sizeof...(Args)) {
165
6
            return Arg(input);
166
6
        }
167
35
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
41
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_115TestUnpackNodesER9Fuzz_DataEEEE6selectEhS2_
Line
Count
Source
163
35
    {
164
35
        if (selector == sizeof...(Args)) {
165
34
            return Arg(input);
166
34
        }
167
1
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
35
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_122TestSendForwardRequestER9Fuzz_DataEEXadL_ZNS0_16TestForwardReplyES2_EEEE6selectEhS2_
Line
Count
Source
163
40
    {
164
40
        if (selector == sizeof...(Args)) {
165
16
            return Arg(input);
166
16
        }
167
24
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
40
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_116TestForwardReplyER9Fuzz_DataEEEE6selectEhS2_
Line
Count
Source
163
24
    {
164
24
        if (selector == sizeof...(Args)) {
165
23
            return Arg(input);
166
23
        }
167
1
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
24
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_123TestUnpackAnnouncesListER9Fuzz_DataEEXadL_ZNS0_24TestUnpackPublicAnnounceES2_EEXadL_ZNS0_9TestDoGcaES2_EEEE6selectEhS2_
Line
Count
Source
163
312
    {
164
312
        if (selector == sizeof...(Args)) {
165
103
            return Arg(input);
166
103
        }
167
209
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
312
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_124TestUnpackPublicAnnounceER9Fuzz_DataEEXadL_ZNS0_9TestDoGcaES2_EEEE6selectEhS2_
Line
Count
Source
163
209
    {
164
209
        if (selector == sizeof...(Args)) {
165
6
            return Arg(input);
166
6
        }
167
203
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
209
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_19TestDoGcaER9Fuzz_DataEEEE6selectEhS2_
Line
Count
Source
163
203
    {
164
203
        if (selector == sizeof...(Args)) {
165
202
            return Arg(input);
166
202
        }
167
1
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
203
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_117TestModListUnpackER9Fuzz_DataEEXadL_ZNS0_23TestSanctionsListUnpackES2_EEXadL_ZNS0_23TestSanctionCredsUnpackES2_EEEE6selectEhS2_
Line
Count
Source
163
29
    {
164
29
        if (selector == sizeof...(Args)) {
165
11
            return Arg(input);
166
11
        }
167
18
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
29
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_123TestSanctionsListUnpackER9Fuzz_DataEEXadL_ZNS0_23TestSanctionCredsUnpackES2_EEEE6selectEhS2_
Line
Count
Source
163
18
    {
164
18
        if (selector == sizeof...(Args)) {
165
15
            return Arg(input);
166
15
        }
167
3
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
18
    }
unity_0_cxx.cxx:_ZN20Fuzz_Target_SelectorIJXadL_ZN12_GLOBAL__N_123TestSanctionCredsUnpackER9Fuzz_DataEEEE6selectEhS2_
Line
Count
Source
163
3
    {
164
3
        if (selector == sizeof...(Args)) {
165
2
            return Arg(input);
166
2
        }
167
1
        return Fuzz_Target_Selector<Args...>::select(selector, input);
168
3
    }
169
};
170
171
template <>
172
struct Fuzz_Target_Selector<> {
173
    static void select(uint8_t selector, Fuzz_Data &input)
174
4
    {
175
        // The selector selected no function, so we do nothing and rely on the
176
        // fuzzer to come up with a better selector.
177
4
    }
178
};
179
180
template <Fuzz_Target... Args>
181
void fuzz_select_target(const uint8_t *data, std::size_t size)
182
422
{
183
422
    Fuzz_Data input{data, size};
184
185
422
    CONSUME1_OR_RETURN(const uint8_t, selector, input);
186
422
    return Fuzz_Target_Selector<Args...>::select(selector, input);
187
422
}
unity_0_cxx.cxx:_Z18fuzz_select_targetIJXadL_ZN12_GLOBAL__N_117TestHandleRequestER9Fuzz_DataEEXadL_ZNS0_15TestUnpackNodesES2_EEEEvPKhm
Line
Count
Source
182
41
{
183
41
    Fuzz_Data input{data, size};
184
185
41
    CONSUME1_OR_RETURN(const uint8_t, selector, input);
186
41
    return Fuzz_Target_Selector<Args...>::select(selector, input);
187
41
}
unity_0_cxx.cxx:_Z18fuzz_select_targetIJXadL_ZN12_GLOBAL__N_122TestSendForwardRequestER9Fuzz_DataEEXadL_ZNS0_16TestForwardReplyES2_EEEEvPKhm
Line
Count
Source
182
40
{
183
40
    Fuzz_Data input{data, size};
184
185
40
    CONSUME1_OR_RETURN(const uint8_t, selector, input);
186
40
    return Fuzz_Target_Selector<Args...>::select(selector, input);
187
40
}
unity_0_cxx.cxx:_Z18fuzz_select_targetIJXadL_ZN12_GLOBAL__N_123TestUnpackAnnouncesListER9Fuzz_DataEEXadL_ZNS0_24TestUnpackPublicAnnounceES2_EEXadL_ZNS0_9TestDoGcaES2_EEEEvPKhm
Line
Count
Source
182
312
{
183
312
    Fuzz_Data input{data, size};
184
185
312
    CONSUME1_OR_RETURN(const uint8_t, selector, input);
186
312
    return Fuzz_Target_Selector<Args...>::select(selector, input);
187
312
}
unity_0_cxx.cxx:_Z18fuzz_select_targetIJXadL_ZN12_GLOBAL__N_117TestModListUnpackER9Fuzz_DataEEXadL_ZNS0_23TestSanctionsListUnpackES2_EEXadL_ZNS0_23TestSanctionCredsUnpackES2_EEEEvPKhm
Line
Count
Source
182
29
{
183
29
    Fuzz_Data input{data, size};
184
185
29
    CONSUME1_OR_RETURN(const uint8_t, selector, input);
186
29
    return Fuzz_Target_Selector<Args...>::select(selector, input);
187
29
}
188
189
struct Memory;
190
struct Network;
191
struct Random;
192
193
struct System {
194
    /** @brief Deterministic system clock for this instance.
195
     *
196
     * Different instances can evolve independently. The time is initialised
197
     * with a large number, because otherwise many zero-initialised "empty"
198
     * friends inside toxcore will be "not timed out" for a long time, messing
199
     * up some logic. Tox moderately depends on the clock being fairly high up
200
     * (not close to 0).
201
     *
202
     * We make it a nice large round number so we can recognise it when debugging.
203
     */
204
    uint64_t clock = 1000000000;
205
206
    std::unique_ptr<Tox_System> sys;
207
    std::unique_ptr<Memory> mem;
208
    std::unique_ptr<Network> ns;
209
    std::unique_ptr<Random> rng;
210
211
    System(std::unique_ptr<Tox_System> sys, std::unique_ptr<Memory> mem,
212
        std::unique_ptr<Network> ns, std::unique_ptr<Random> rng);
213
    System(System &&);
214
215
    // Not inline because sizeof of the above 2 structs is not known everywhere.
216
    ~System();
217
218
    /**
219
     * During bootstrap, move the time forward a decent amount, because friend
220
     * finding and bootstrapping takes significant (around 10 seconds) wall
221
     * clock time that should be advanced more quickly in the test.
222
     */
223
    static constexpr uint8_t BOOTSTRAP_ITERATION_INTERVAL = 200;
224
    /**
225
     * Less than BOOTSTRAP_ITERATION_INTERVAL because otherwise we'll spam
226
     * onion announce packets.
227
     */
228
    static constexpr uint8_t MESSAGE_ITERATION_INTERVAL = 20;
229
    /**
230
     * Move the clock forward at least 20ms so at least some amount of
231
     * time passes on each iteration.
232
     */
233
    static constexpr uint8_t MIN_ITERATION_INTERVAL = 20;
234
};
235
236
/**
237
 * A Tox_System implementation that consumes fuzzer input to produce network
238
 * inputs and random numbers. Once it runs out of fuzzer input, network receive
239
 * functions return no more data and the random numbers are always zero.
240
 */
241
struct Fuzz_System : System {
242
    Fuzz_Data &data;
243
244
    explicit Fuzz_System(Fuzz_Data &input);
245
};
246
247
/**
248
 * A Tox_System implementation that consumes no fuzzer input but still has a
249
 * working and deterministic RNG. Network receive functions always fail, send
250
 * always succeeds.
251
 */
252
struct Null_System : System {
253
    uint64_t seed = 4;  // chosen by fair dice roll. guaranteed to be random.
254
255
    Null_System();
256
};
257
258
/**
259
 * A Tox_System implementation that records all I/O but does not actually
260
 * perform any real I/O. Everything inside this system is hermetic in-process
261
 * and fully deterministic.
262
 *
263
 * Note: take care not to initialise two systems with the same seed, since
264
 * that's the only thing distinguishing the system's behaviour. Two toxes
265
 * initialised with the same seed will be identical (same keys, etc.).
266
 */
267
struct Record_System : System {
268
    static constexpr bool DEBUG = Fuzz_Data::DEBUG;
269
270
    /** @brief State shared between all tox instances. */
271
    struct Global {
272
        /** @brief Bound UDP ports and their system instance.
273
         *
274
         * This implements an in-process network where instances can send
275
         * packets to other instances by inserting them into the receiver's
276
         * recvq using the receive function.
277
         *
278
         * We need to keep track of ports associated with recv queues because
279
         * toxcore sends packets to itself sometimes when doing onion routing
280
         * with only 2 nodes in the network.
281
         */
282
        std::unordered_map<uint16_t, Record_System *> bound;
283
    };
284
285
    Global &global_;
286
    uint64_t seed_;  //!< Current PRNG state.
287
    const char *name_;  //!< Tox system name ("tox1"/"tox2") for logging.
288
289
    std::deque<std::pair<uint16_t, std::vector<uint8_t>>> recvq;
290
    uint16_t port = 0;  //!< Sending port for this system instance.
291
292
    Record_System(Global &global, uint64_t seed, const char *name);
293
    Record_System(const Record_System &) = delete;
294
    Record_System operator=(const Record_System &) = delete;
295
296
    /** @brief Deposit a network packet in this instance's recvq.
297
     */
298
    void receive(uint16_t send_port, const uint8_t *buf, size_t len);
299
300
    void push(bool byte)
301
0
    {
302
0
        if (DEBUG) {
303
0
            if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
304
0
                __asm__("int $3");
305
0
            }
306
0
            std::printf("%s: produce@%zu(bool %s)\n", name_, recording_.size(), byte ? "true" : "false");
307
0
        }
308
0
        recording_.push_back(byte);
309
0
    }
310
311
    void push(uint8_t byte)
312
0
    {
313
0
        if (DEBUG) {
314
0
            if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
315
0
                __asm__("int $3");
316
0
            }
317
0
            std::printf("%s: produce@%zu(%u (0x%02x))\n", name_, recording_.size(), byte, byte);
318
0
        }
319
0
        recording_.push_back(byte);
320
0
    }
321
322
    void push(const uint8_t *bytes, std::size_t size)
323
0
    {
324
0
        if (DEBUG) {
325
0
            if (recording_.size() == Fuzz_Data::TRACE_TRAP) {
326
0
                __asm__("int $3");
327
0
            }
328
0
            std::printf("%s: produce@%zu(%02x..%02x[%zu])\n", name_, recording_.size(), bytes[0],
329
0
                bytes[size - 1], size);
330
0
        }
331
0
        recording_.insert(recording_.end(), bytes, bytes + size);
332
0
    }
333
334
    template <std::size_t N>
335
    void push(const char (&bytes)[N])
336
0
    {
337
0
        push(reinterpret_cast<const uint8_t *>(bytes), N - 1);
338
0
    }
339
340
0
    const std::vector<uint8_t> &recording() const { return recording_; }
341
0
    std::vector<uint8_t> take_recording() const { return std::move(recording_); }
342
343
private:
344
    std::vector<uint8_t> recording_;
345
};
346
347
/** @brief Enable debug logging.
348
 *
349
 * This should not be enabled in fuzzer code while fuzzing, as console I/O slows
350
 * everything down drastically. It's useful while developing the fuzzer and the
351
 * protodump program.
352
 */
353
extern const bool DEBUG;
354
355
inline constexpr char tox_log_level_name(Tox_Log_Level level)
356
0
{
357
0
    switch (level) {
358
0
    case TOX_LOG_LEVEL_TRACE:
359
0
        return 'T';
360
0
    case TOX_LOG_LEVEL_DEBUG:
361
0
        return 'D';
362
0
    case TOX_LOG_LEVEL_INFO:
363
0
        return 'I';
364
0
    case TOX_LOG_LEVEL_WARNING:
365
0
        return 'W';
366
0
    case TOX_LOG_LEVEL_ERROR:
367
0
        return 'E';
368
0
    }
369
0
370
0
    return '?';
371
0
}
372
373
#endif  // C_TOXCORE_TESTING_FUZZING_FUZZ_SUPPORT_H