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