Coverage Report

Created: 2025-10-08 19:34

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