Coverage Report

Created: 2026-07-23 06:28

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/openssl36/fuzz/hashtable.c
Line
Count
Source
1
/*
2
 * Copyright 2024-2025 The OpenSSL Project Authors. All Rights Reserved.
3
 *
4
 * Licensed under the Apache License 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 * https://www.openssl.org/source/license.html
8
 * or in the file LICENSE in the source distribution.
9
 */
10
11
/*
12
 * Test hashtable operation.
13
 */
14
#include <limits.h>
15
#include <openssl/err.h>
16
#include <openssl/bio.h>
17
#include <internal/common.h>
18
#include <internal/hashtable.h>
19
#include "fuzzer.h"
20
21
/*
22
 * Make the key space very small here to make lookups
23
 * easy to predict for the purposes of validation
24
 * A two byte key gives us 65536 possible entries
25
 * so we can allocate a flat table to compare to
26
 */
27
HT_START_KEY_DEFN(fuzzer_key)
28
HT_DEF_KEY_FIELD(fuzzkey, uint16_t)
29
HT_END_KEY_DEFN(FUZZER_KEY)
30
31
161
#define FZ_FLAG_ALLOCATED (1 << 0)
32
typedef struct fuzzer_value_st {
33
    uint64_t flags;
34
    uint64_t value;
35
} FUZZER_VALUE;
36
37
982
IMPLEMENT_HT_VALUE_TYPE_FNS(FUZZER_VALUE, fz, static)
hashtable.c:ossl_ht_fz_FUZZER_VALUE_from_value
Line
Count
Source
37
IMPLEMENT_HT_VALUE_TYPE_FNS(FUZZER_VALUE, fz, static)
hashtable.c:ossl_ht_fz_FUZZER_VALUE_insert
Line
Count
Source
37
IMPLEMENT_HT_VALUE_TYPE_FNS(FUZZER_VALUE, fz, static)
hashtable.c:ossl_ht_fz_FUZZER_VALUE_get
Line
Count
Source
37
IMPLEMENT_HT_VALUE_TYPE_FNS(FUZZER_VALUE, fz, static)
38
39
static size_t skipped_values = 0;
40
static size_t inserts = 0;
41
static size_t replacements = 0;
42
static size_t deletes = 0;
43
static size_t flushes = 0;
44
static size_t lookups = 0;
45
static size_t foreaches = 0;
46
static size_t filters = 0;
47
static int valfound;
48
49
static FUZZER_VALUE *prediction_table = NULL;
50
static HT *fuzzer_table = NULL;
51
52
/*
53
 * Operational values
54
 */
55
27
#define OP_INSERT 0
56
18
#define OP_DELETE 1
57
21
#define OP_LOOKUP 2
58
8
#define OP_FLUSH 3
59
22
#define OP_FOREACH 4
60
14
#define OP_FILTER 5
61
110
#define OP_END 6
62
63
110
#define OP_MASK 0x3f
64
57
#define INSERT_REPLACE_MASK 0x40
65
110
#define OPERATION(x) (((x) & OP_MASK) % OP_END)
66
57
#define IS_REPLACE(x) ((x) & INSERT_REPLACE_MASK)
67
68
static int table_iterator(HT_VALUE *v, void *arg)
69
488
{
70
488
    uint16_t keyval = (*(uint16_t *)arg);
71
488
    FUZZER_VALUE *f = ossl_ht_fz_FUZZER_VALUE_from_value(v);
72
73
488
    if (f != NULL && f == &prediction_table[keyval]) {
74
3
        valfound = 1;
75
3
        return 0;
76
3
    }
77
78
485
    return 1;
79
488
}
80
81
static int filter_iterator(HT_VALUE *v, void *arg)
82
295
{
83
295
    uint16_t keyval = (*(uint16_t *)arg);
84
295
    FUZZER_VALUE *f = ossl_ht_fz_FUZZER_VALUE_from_value(v);
85
86
295
    if (f != NULL && f == &prediction_table[keyval])
87
5
        return 1;
88
89
290
    return 0;
90
295
}
91
92
static void fuzz_free_cb(HT_VALUE *v)
93
32
{
94
32
    FUZZER_VALUE *f = ossl_ht_fz_FUZZER_VALUE_from_value(v);
95
96
32
    if (f != NULL)
97
32
        f->flags &= ~FZ_FLAG_ALLOCATED;
98
32
}
99
100
int FuzzerInitialize(int *argc, char ***argv)
101
8
{
102
8
    HT_CONFIG fuzz_conf = { NULL, fuzz_free_cb, NULL, 0, 1 };
103
104
8
    OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
105
8
    ERR_clear_error();
106
8
    prediction_table = OPENSSL_calloc(65537, sizeof(FUZZER_VALUE));
107
8
    if (prediction_table == NULL)
108
0
        return -1;
109
8
    fuzzer_table = ossl_ht_new(&fuzz_conf);
110
8
    if (fuzzer_table == NULL) {
111
0
        OPENSSL_free(prediction_table);
112
0
        return -1;
113
0
    }
114
115
8
    return 0;
116
8
}
117
118
int FuzzerTestOneInput(const uint8_t *buf, size_t len)
119
116
{
120
116
    uint8_t op_flags;
121
116
    uint16_t keyval;
122
116
    int rc;
123
116
    int rc_prediction = 1;
124
116
    size_t i;
125
116
    FUZZER_VALUE *valptr, *lval;
126
116
    FUZZER_KEY key;
127
116
    HT_VALUE *v = NULL;
128
116
    HT_VALUE tv;
129
116
    HT_VALUE_LIST *htvlist;
130
131
    /*
132
     * We need at least 11 bytes to be able to do anything here
133
     * 1 byte to detect the operation to perform, 2 bytes
134
     * for the lookup key, and 8 bytes of value
135
     */
136
116
    if (len < 11) {
137
6
        skipped_values++;
138
6
        return -1;
139
6
    }
140
141
    /*
142
     * parse out our operation flags and key
143
     */
144
110
    op_flags = buf[0];
145
110
    memcpy(&keyval, &buf[1], sizeof(uint16_t));
146
147
    /*
148
     * Initialize our key
149
     */
150
110
    HT_INIT_KEY(&key);
151
152
    /*
153
     * Now do our operation
154
     */
155
110
    switch (OPERATION(op_flags)) {
156
27
    case OP_INSERT:
157
27
        valptr = &prediction_table[keyval];
158
159
        /* reset our key */
160
27
        HT_KEY_RESET(&key);
161
162
        /* set the proper key value */
163
27
        HT_SET_KEY_FIELD(&key, fuzzkey, keyval);
164
165
        /* lock the table */
166
27
        ossl_ht_write_lock(fuzzer_table);
167
168
        /*
169
         * If the value to insert is already allocated
170
         * then we expect a conflict in the insert
171
         * i.e. we predict a return code of 0 instead
172
         * of 1. On replacement, we expect it to succeed
173
         * always
174
         */
175
27
        if (valptr->flags & FZ_FLAG_ALLOCATED) {
176
6
            if (!IS_REPLACE(op_flags))
177
3
                rc_prediction = 0;
178
6
        }
179
180
27
        memcpy(&valptr->value, &buf[3], sizeof(uint64_t));
181
        /*
182
         * do the insert/replace
183
         */
184
27
        if (IS_REPLACE(op_flags))
185
9
            rc = ossl_ht_fz_FUZZER_VALUE_insert(fuzzer_table, TO_HT_KEY(&key),
186
9
                valptr, &lval);
187
18
        else
188
18
            rc = ossl_ht_fz_FUZZER_VALUE_insert(fuzzer_table, TO_HT_KEY(&key),
189
18
                valptr, NULL);
190
191
27
        if (rc == -1)
192
            /* failed to grow the hash table due to too many collisions */
193
0
            break;
194
195
        /*
196
         * mark the entry as being allocated
197
         */
198
27
        valptr->flags |= FZ_FLAG_ALLOCATED;
199
200
        /*
201
         * unlock the table
202
         */
203
27
        ossl_ht_write_unlock(fuzzer_table);
204
205
        /*
206
         * Now check to make sure we did the right thing
207
         */
208
27
        OPENSSL_assert(rc == rc_prediction);
209
210
        /*
211
         * successful insertion if there wasn't a conflict
212
         */
213
27
        if (rc_prediction == 1)
214
24
            IS_REPLACE(op_flags) ? replacements++ : inserts++;
215
27
        break;
216
217
18
    case OP_DELETE:
218
18
        valptr = &prediction_table[keyval];
219
220
        /* reset our key */
221
18
        HT_KEY_RESET(&key);
222
223
        /* set the proper key value */
224
18
        HT_SET_KEY_FIELD(&key, fuzzkey, keyval);
225
226
        /* lock the table */
227
18
        ossl_ht_write_lock(fuzzer_table);
228
229
        /*
230
         * If the value to delete is not already allocated
231
         * then we expect a miss in the delete
232
         * i.e. we predict a return code of 0 instead
233
         * of 1
234
         */
235
18
        if (!(valptr->flags & FZ_FLAG_ALLOCATED))
236
14
            rc_prediction = 0;
237
238
        /*
239
         * do the delete
240
         */
241
18
        rc = ossl_ht_delete(fuzzer_table, TO_HT_KEY(&key));
242
243
        /*
244
         * unlock the table
245
         */
246
18
        ossl_ht_write_unlock(fuzzer_table);
247
248
        /*
249
         * Now check to make sure we did the right thing
250
         */
251
18
        OPENSSL_assert(rc == rc_prediction);
252
253
        /*
254
         * once the unlock is done, the table rcu will have synced
255
         * meaning the free function has run, so we can confirm now
256
         * that the valptr is no longer allocated
257
         */
258
18
        OPENSSL_assert(!(valptr->flags & FZ_FLAG_ALLOCATED));
259
260
        /*
261
         * successful deletion if there wasn't a conflict
262
         */
263
18
        if (rc_prediction == 1)
264
4
            deletes++;
265
266
18
        break;
267
268
21
    case OP_LOOKUP:
269
21
        valptr = &prediction_table[keyval];
270
21
        lval = NULL;
271
272
        /* reset our key */
273
21
        HT_KEY_RESET(&key);
274
275
        /* set the proper key value */
276
21
        HT_SET_KEY_FIELD(&key, fuzzkey, keyval);
277
278
        /* lock the table for reading */
279
21
        if (!ossl_ht_read_lock(fuzzer_table))
280
0
            return 0;
281
282
        /*
283
         * If the value to find is not already allocated
284
         * then we expect a miss in the lookup
285
         * i.e. we predict a return code of NULL instead
286
         * of a pointer
287
         */
288
21
        if (!(valptr->flags & FZ_FLAG_ALLOCATED))
289
17
            valptr = NULL;
290
291
        /*
292
         * do the lookup
293
         */
294
21
        lval = ossl_ht_fz_FUZZER_VALUE_get(fuzzer_table, TO_HT_KEY(&key), &v);
295
296
        /*
297
         * unlock the table
298
         */
299
21
        ossl_ht_read_unlock(fuzzer_table);
300
301
        /*
302
         * Now check to make sure we did the right thing
303
         */
304
21
        OPENSSL_assert(lval == valptr);
305
306
        /*
307
         * if we expect a positive lookup, make sure that
308
         * we can use the _type and to_value functions
309
         */
310
21
        if (valptr != NULL) {
311
4
            OPENSSL_assert(ossl_ht_fz_FUZZER_VALUE_type(v) == 1);
312
313
4
            v = ossl_ht_fz_FUZZER_VALUE_to_value(lval, &tv);
314
4
            OPENSSL_assert(v->value == lval);
315
4
        }
316
317
        /*
318
         * successful lookup if we didn't expect a miss
319
         */
320
21
        if (valptr != NULL)
321
4
            lookups++;
322
323
21
        break;
324
325
8
    case OP_FLUSH:
326
        /*
327
         * only flush the table rarely
328
         */
329
8
        if ((flushes % 100000) != 1) {
330
7
            skipped_values++;
331
7
            flushes++;
332
7
            return 0;
333
7
        }
334
335
        /*
336
         * lock the table
337
         */
338
1
        ossl_ht_write_lock(fuzzer_table);
339
1
        ossl_ht_flush(fuzzer_table);
340
1
        ossl_ht_write_unlock(fuzzer_table);
341
342
        /*
343
         * now check to make sure everything is free
344
         */
345
65.5k
        for (i = 0; i < USHRT_MAX; i++)
346
65.5k
            OPENSSL_assert((prediction_table[i].flags & FZ_FLAG_ALLOCATED) == 0);
347
348
        /* good flush */
349
1
        flushes++;
350
1
        break;
351
352
22
    case OP_FOREACH:
353
22
        valfound = 0;
354
22
        valptr = &prediction_table[keyval];
355
356
22
        rc_prediction = 0;
357
22
        if (valptr->flags & FZ_FLAG_ALLOCATED)
358
0
            rc_prediction = 1;
359
360
22
        ossl_ht_foreach_until(fuzzer_table, table_iterator, &keyval);
361
362
22
        OPENSSL_assert(valfound == rc_prediction);
363
364
22
        foreaches++;
365
22
        break;
366
367
14
    case OP_FILTER:
368
14
        valptr = &prediction_table[keyval];
369
370
14
        rc_prediction = 0;
371
14
        if (valptr->flags & FZ_FLAG_ALLOCATED)
372
0
            rc_prediction = 1;
373
374
14
        htvlist = ossl_ht_filter(fuzzer_table, 1, filter_iterator, &keyval);
375
376
14
        OPENSSL_assert(htvlist->list_len == (size_t)rc_prediction);
377
378
14
        ossl_ht_value_list_free(htvlist);
379
14
        filters++;
380
14
        break;
381
382
0
    default:
383
0
        return -1;
384
110
    }
385
386
103
    return 0;
387
110
}
388
389
void FuzzerCleanup(void)
390
0
{
391
0
    ossl_ht_free(fuzzer_table);
392
0
    OPENSSL_free(prediction_table);
393
0
    OPENSSL_cleanup();
394
0
}