UNIXworkcode

1 /* 2 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. 3 * 4 * Copyright 2021 Mike Becker, Olaf Wintermann All rights reserved. 5 * 6 * Redistribution and use in source and binary forms, with or without 7 * modification, are permitted provided that the following conditions are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 20 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 21 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 22 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 23 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 24 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 25 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 26 * POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29 #include "util_allocator.h" 30 #include "cx/test.h" 31 32 #if !defined(__clang__) && __GNUC__ > 11 33 // this utility is explicitly designed to track UAF 34 #pragma GCC diagnostic ignored "-Wuse-after-free" 35 #endif 36 37 static void cx_testing_allocator_track(CxTestingAllocator *alloc, void *ptr) { 38 for (size_t i = 0; i < alloc->tracked_count; i++) { 39 if (alloc->tracked[i] == ptr) return; // is already tracked 40 } 41 42 if (alloc->tracked_count == alloc->tracked_capacity) { 43 size_t newcapa = alloc->tracked_capacity + 64; 44 void *newarr = realloc(alloc->tracked, newcapa * sizeof(void *)); 45 if (newarr == NULL) abort(); 46 alloc->tracked = newarr; 47 alloc->tracked_capacity = newcapa; 48 } 49 50 alloc->tracked[alloc->tracked_count] = ptr; 51 alloc->tracked_count++; 52 } 53 54 static bool cx_testing_allocator_untrack(CxTestingAllocator *alloc, void *ptr) { 55 for (size_t i = 0; i < alloc->tracked_count; i++) { 56 if (alloc->tracked[i] == ptr) { 57 size_t last = alloc->tracked_count - 1; 58 if (i < last) { 59 alloc->tracked[i] = alloc->tracked[last]; 60 } 61 alloc->tracked_count--; 62 return true; 63 } 64 } 65 return false; 66 } 67 68 static void *cx_malloc_testing(void *d, size_t n) { 69 CxTestingAllocator *data = d; 70 void *ptr = malloc(n); 71 data->alloc_total++; 72 if (ptr == NULL) { 73 data->alloc_failed++; 74 } else { 75 cx_testing_allocator_track(data, ptr); 76 } 77 return ptr; 78 } 79 80 static void *cx_realloc_testing(void *d, void *mem, size_t n) { 81 CxTestingAllocator *data = d; 82 void *ptr = realloc(mem, n); 83 if (mem != NULL && ptr == mem) { 84 return ptr; 85 } else { 86 data->alloc_total++; 87 if (ptr == NULL) { 88 data->alloc_failed++; 89 } else { 90 if (mem != NULL) { 91 data->free_total++; 92 if (!cx_testing_allocator_untrack(data, mem)) { 93 data->free_failed++; 94 } 95 } 96 cx_testing_allocator_track(data, ptr); 97 } 98 return ptr; 99 } 100 } 101 102 static void *cx_calloc_testing(void *d, size_t nelem, size_t n) { 103 CxTestingAllocator *data = d; 104 void *ptr = calloc(nelem, n); 105 data->alloc_total++; 106 if (ptr == NULL) { 107 data->alloc_failed++; 108 } else { 109 cx_testing_allocator_track(data, ptr); 110 } 111 return ptr; 112 } 113 114 static void cx_free_testing(void *d, void *mem) { 115 if (mem == NULL) return; 116 CxTestingAllocator *data = d; 117 data->free_total++; 118 if (cx_testing_allocator_untrack(data, mem)) { 119 free(mem); 120 } else { 121 data->free_failed++; 122 // do not even attempt to free mem, because it is likely to segfault 123 } 124 } 125 126 cx_allocator_class cx_testing_allocator_class = { 127 cx_malloc_testing, 128 cx_realloc_testing, 129 cx_calloc_testing, 130 cx_free_testing 131 }; 132 133 134 void cx_testing_allocator_init(CxTestingAllocator *alloc) { 135 alloc->base.cl = &cx_testing_allocator_class; 136 alloc->base.data = alloc; 137 alloc->alloc_failed = 0; 138 alloc->alloc_total = 0; 139 alloc->free_failed = 0; 140 alloc->free_total = 0; 141 size_t initial_capa = 16; 142 alloc->tracked_capacity = initial_capa; 143 alloc->tracked_count = 0; 144 alloc->tracked = calloc(initial_capa, sizeof(void *)); 145 } 146 147 void cx_testing_allocator_destroy(CxTestingAllocator *alloc) { 148 free(alloc->tracked); 149 } 150 151 bool cx_testing_allocator_used(const CxTestingAllocator *alloc) { 152 return alloc->alloc_total > 0; 153 } 154 155 bool cx_testing_allocator_verify(const CxTestingAllocator *alloc) { 156 return alloc->tracked_count == 0 && alloc->alloc_failed == 0 && alloc->free_failed == 0 157 && alloc->alloc_total == alloc->free_total; 158 } 159 160 // SELF-TEST 161 162 CX_TEST(test_util_allocator_expect_free) { 163 CxTestingAllocator talloc; 164 cx_testing_allocator_init(&talloc); 165 CxAllocator *alloc = &talloc.base; 166 CX_TEST_DO { 167 CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc), 168 "Fresh testing allocator fails to verify."); 169 CX_TEST_ASSERTM(!cx_testing_allocator_used(&talloc), 170 "Fresh testing allocator already used."); 171 void *ptr = cxMalloc(alloc, 16); 172 CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), 173 "Testing allocator verifies with unfreed memory."); 174 CX_TEST_ASSERT(cx_testing_allocator_used(&talloc)); 175 CX_TEST_ASSERT(ptr != NULL); 176 177 cxFree(alloc, ptr); 178 CX_TEST_ASSERTM(cx_testing_allocator_verify(&talloc), 179 "Testing allocator fails to verify after everything freed."); 180 } 181 cx_testing_allocator_destroy(&talloc); 182 } 183 184 CX_TEST(test_util_allocator_detect_double_free) { 185 CxTestingAllocator talloc; 186 cx_testing_allocator_init(&talloc); 187 CxAllocator *alloc = &talloc.base; 188 CX_TEST_DO { 189 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 190 void *ptr = cxMalloc(alloc, 16); 191 CX_TEST_ASSERT(ptr != NULL); 192 cxFree(alloc, ptr); 193 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 194 cxFree(alloc, ptr); 195 CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), 196 "Testing allocator does not detect double-free."); 197 } 198 cx_testing_allocator_destroy(&talloc); 199 } 200 201 CX_TEST(test_util_allocator_free_untracked) { 202 CxTestingAllocator talloc; 203 cx_testing_allocator_init(&talloc); 204 CxAllocator *alloc = &talloc.base; 205 void *ptr = malloc(16); 206 CX_TEST_DO { 207 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 208 cxFree(alloc, ptr); 209 CX_TEST_ASSERTM(!cx_testing_allocator_verify(&talloc), 210 "Testing allocator does not detect free of untracked memory."); 211 } 212 free(ptr); 213 cx_testing_allocator_destroy(&talloc); 214 } 215 216 CX_TEST(test_util_allocator_full_lifecycle_with_realloc) { 217 CxTestingAllocator talloc; 218 cx_testing_allocator_init(&talloc); 219 CxAllocator *alloc = &talloc.base; 220 CX_TEST_DO { 221 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 222 void *ptr = cxMalloc(alloc, 16); 223 CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); 224 CX_TEST_ASSERT(ptr != NULL); 225 CX_TEST_ASSERT(talloc.tracked_count == 1); 226 ptr = cxRealloc(alloc, ptr, 256); 227 CX_TEST_ASSERT(!cx_testing_allocator_verify(&talloc)); 228 CX_TEST_ASSERT(ptr != NULL); 229 CX_TEST_ASSERT(talloc.tracked_count == 1); 230 cxFree(alloc, ptr); 231 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 232 } 233 cx_testing_allocator_destroy(&talloc); 234 } 235 236 CX_TEST(test_util_allocator_calloc_initializes) { 237 CxTestingAllocator talloc; 238 cx_testing_allocator_init(&talloc); 239 CxAllocator *alloc = &talloc.base; 240 CX_TEST_DO { 241 const char zeros[16] = {0}; 242 void *ptr = cxCalloc(alloc, 16, 1); 243 CX_TEST_ASSERT(memcmp(ptr, zeros, 16) == 0); 244 cxFree(alloc, ptr); 245 CX_TEST_ASSERT(cx_testing_allocator_verify(&talloc)); 246 } 247 cx_testing_allocator_destroy(&talloc); 248 } 249 250 CxTestSuite *cx_test_suite_testing_allocator(void) { 251 CxTestSuite *suite = cx_test_suite_new("testing allocator self-test"); 252 253 cx_test_register(suite, test_util_allocator_expect_free); 254 cx_test_register(suite, test_util_allocator_detect_double_free); 255 cx_test_register(suite, test_util_allocator_free_untracked); 256 cx_test_register(suite, test_util_allocator_full_lifecycle_with_realloc); 257 cx_test_register(suite, test_util_allocator_calloc_initializes); 258 259 return suite; 260 } 261 262