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