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