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