xref: /openssl/fuzz/hashtable.c (revision 3c1713ae)
1 /*
2  * Copyright 2024 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 #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 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 #define OP_INSERT  0
56 #define OP_DELETE  1
57 #define OP_LOOKUP  2
58 #define OP_FLUSH   3
59 #define OP_FOREACH 4
60 #define OP_FILTER  5
61 #define OP_END     6
62 
63 #define OP_MASK 0x3f
64 #define INSERT_REPLACE_MASK 0x40
65 #define OPERATION(x) (((x) & OP_MASK) % OP_END)
66 #define IS_REPLACE(x) ((x) & INSERT_REPLACE_MASK)
67 
table_iterator(HT_VALUE * v,void * arg)68 static int table_iterator(HT_VALUE *v, void *arg)
69 {
70     uint16_t keyval = (*(uint16_t *)arg);
71     FUZZER_VALUE *f = ossl_ht_fz_FUZZER_VALUE_from_value(v);
72 
73     if (f != NULL && f == &prediction_table[keyval]) {
74         valfound = 1;
75         return 0;
76     }
77 
78     return 1;
79 }
80 
filter_iterator(HT_VALUE * v,void * arg)81 static int filter_iterator(HT_VALUE *v, void *arg)
82 {
83     uint16_t keyval = (*(uint16_t *)arg);
84     FUZZER_VALUE *f = ossl_ht_fz_FUZZER_VALUE_from_value(v);
85 
86     if (f != NULL && f == &prediction_table[keyval])
87         return 1;
88 
89     return 0;
90 }
91 
fuzz_free_cb(HT_VALUE * v)92 static void fuzz_free_cb(HT_VALUE *v)
93 {
94     FUZZER_VALUE *f = ossl_ht_fz_FUZZER_VALUE_from_value(v);
95 
96     if (f != NULL)
97         f->flags &= ~FZ_FLAG_ALLOCATED;
98 }
99 
FuzzerInitialize(int * argc,char *** argv)100 int FuzzerInitialize(int *argc, char ***argv)
101 {
102     HT_CONFIG fuzz_conf = {NULL, fuzz_free_cb, NULL, 0, 1};
103 
104     OPENSSL_init_crypto(OPENSSL_INIT_LOAD_CRYPTO_STRINGS, NULL);
105     ERR_clear_error();
106     prediction_table = OPENSSL_zalloc(sizeof(FUZZER_VALUE) * 65537);
107     if (prediction_table == NULL)
108         return -1;
109     fuzzer_table = ossl_ht_new(&fuzz_conf);
110     if (fuzzer_table == NULL) {
111         OPENSSL_free(prediction_table);
112         return -1;
113     }
114 
115     return 0;
116 }
117 
FuzzerTestOneInput(const uint8_t * buf,size_t len)118 int FuzzerTestOneInput(const uint8_t *buf, size_t len)
119 {
120     uint8_t op_flags;
121     uint16_t keyval;
122     int rc;
123     int rc_prediction = 1;
124     size_t i;
125     FUZZER_VALUE *valptr, *lval;
126     FUZZER_KEY key;
127     HT_VALUE *v = NULL;
128     HT_VALUE tv;
129     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     if (len < 11) {
137         skipped_values++;
138         return -1;
139     }
140 
141     /*
142      * parse out our operation flags and key
143      */
144     op_flags = buf[0];
145     memcpy(&keyval, &buf[1], sizeof(uint16_t));
146 
147     /*
148      * Initialize our key
149      */
150     HT_INIT_KEY(&key);
151 
152     /*
153      * Now do our operation
154      */
155     switch(OPERATION(op_flags)) {
156     case OP_INSERT:
157         valptr = &prediction_table[keyval];
158 
159         /* reset our key */
160         HT_KEY_RESET(&key);
161 
162         /* set the proper key value */
163         HT_SET_KEY_FIELD(&key, fuzzkey, keyval);
164 
165         /* lock the table */
166         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         if (valptr->flags & FZ_FLAG_ALLOCATED) {
176             if (!IS_REPLACE(op_flags))
177                 rc_prediction = 0;
178         }
179 
180         memcpy(&valptr->value, &buf[3], sizeof(uint64_t));
181         /*
182          * do the insert/replace
183          */
184         if (IS_REPLACE(op_flags))
185             rc = ossl_ht_fz_FUZZER_VALUE_insert(fuzzer_table, TO_HT_KEY(&key),
186                                                 valptr, &lval);
187         else
188             rc = ossl_ht_fz_FUZZER_VALUE_insert(fuzzer_table, TO_HT_KEY(&key),
189                                                 valptr, NULL);
190 
191         if (rc == -1)
192             /* failed to grow the hash table due to too many collisions */
193             break;
194 
195         /*
196          * mark the entry as being allocated
197          */
198         valptr->flags |= FZ_FLAG_ALLOCATED;
199 
200         /*
201          * unlock the table
202          */
203         ossl_ht_write_unlock(fuzzer_table);
204 
205         /*
206          * Now check to make sure we did the right thing
207          */
208         OPENSSL_assert(rc == rc_prediction);
209 
210         /*
211          * successful insertion if there wasn't a conflict
212          */
213         if (rc_prediction == 1)
214             IS_REPLACE(op_flags) ? replacements++ : inserts++;
215         break;
216 
217     case OP_DELETE:
218         valptr = &prediction_table[keyval];
219 
220         /* reset our key */
221         HT_KEY_RESET(&key);
222 
223         /* set the proper key value */
224         HT_SET_KEY_FIELD(&key, fuzzkey, keyval);
225 
226         /* lock the table */
227         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         if (!(valptr->flags & FZ_FLAG_ALLOCATED))
236             rc_prediction = 0;
237 
238         /*
239          * do the delete
240          */
241         rc = ossl_ht_delete(fuzzer_table, TO_HT_KEY(&key));
242 
243         /*
244          * unlock the table
245          */
246         ossl_ht_write_unlock(fuzzer_table);
247 
248         /*
249          * Now check to make sure we did the right thing
250          */
251         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         OPENSSL_assert(!(valptr->flags & FZ_FLAG_ALLOCATED));
259 
260         /*
261          * successful deletion if there wasn't a conflict
262          */
263         if (rc_prediction == 1)
264             deletes++;
265 
266         break;
267 
268     case OP_LOOKUP:
269         valptr = &prediction_table[keyval];
270         lval = NULL;
271 
272         /* reset our key */
273         HT_KEY_RESET(&key);
274 
275         /* set the proper key value */
276         HT_SET_KEY_FIELD(&key, fuzzkey, keyval);
277 
278         /* lock the table for reading */
279         ossl_ht_read_lock(fuzzer_table);
280 
281         /*
282          * If the value to find is not already allocated
283          * then we expect a miss in the lookup
284          * i.e. we predict a return code of NULL instead
285          * of a pointer
286          */
287         if (!(valptr->flags & FZ_FLAG_ALLOCATED))
288             valptr = NULL;
289 
290         /*
291          * do the lookup
292          */
293         lval = ossl_ht_fz_FUZZER_VALUE_get(fuzzer_table, TO_HT_KEY(&key), &v);
294 
295         /*
296          * unlock the table
297          */
298         ossl_ht_read_unlock(fuzzer_table);
299 
300         /*
301          * Now check to make sure we did the right thing
302          */
303         OPENSSL_assert(lval == valptr);
304 
305         /*
306          * if we expect a positive lookup, make sure that
307          * we can use the _type and to_value functions
308          */
309         if (valptr != NULL) {
310             OPENSSL_assert(ossl_ht_fz_FUZZER_VALUE_type(v) == 1);
311 
312             v = ossl_ht_fz_FUZZER_VALUE_to_value(lval, &tv);
313             OPENSSL_assert(v->value == lval);
314         }
315 
316         /*
317          * successful lookup if we didn't expect a miss
318          */
319         if (valptr != NULL)
320             lookups++;
321 
322         break;
323 
324     case OP_FLUSH:
325         /*
326          * only flush the table rarely
327          */
328         if ((flushes % 100000) != 1) {
329             skipped_values++;
330             flushes++;
331             return 0;
332         }
333 
334         /*
335          * lock the table
336          */
337         ossl_ht_write_lock(fuzzer_table);
338         ossl_ht_flush(fuzzer_table);
339         ossl_ht_write_unlock(fuzzer_table);
340 
341         /*
342          * now check to make sure everything is free
343          */
344        for (i = 0; i < USHRT_MAX; i++)
345             OPENSSL_assert((prediction_table[i].flags & FZ_FLAG_ALLOCATED) == 0);
346 
347         /* good flush */
348         flushes++;
349         break;
350 
351     case OP_FOREACH:
352         valfound = 0;
353         valptr = &prediction_table[keyval];
354 
355         rc_prediction = 0;
356         if (valptr->flags & FZ_FLAG_ALLOCATED)
357             rc_prediction = 1;
358 
359         ossl_ht_foreach_until(fuzzer_table, table_iterator, &keyval);
360 
361         OPENSSL_assert(valfound == rc_prediction);
362 
363         foreaches++;
364         break;
365 
366     case OP_FILTER:
367         valptr = &prediction_table[keyval];
368 
369         rc_prediction = 0;
370         if (valptr->flags & FZ_FLAG_ALLOCATED)
371             rc_prediction = 1;
372 
373         htvlist = ossl_ht_filter(fuzzer_table, 1, filter_iterator, &keyval);
374 
375         OPENSSL_assert(htvlist->list_len == (size_t)rc_prediction);
376 
377         ossl_ht_value_list_free(htvlist);
378         filters++;
379         break;
380 
381     default:
382         return -1;
383     }
384 
385     return 0;
386 }
387 
FuzzerCleanup(void)388 void FuzzerCleanup(void)
389 {
390     ossl_ht_free(fuzzer_table);
391     OPENSSL_free(prediction_table);
392     OPENSSL_cleanup();
393 }
394