multi_heap_poisoning.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. // Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. // http://www.apache.org/licenses/LICENSE-2.0
  7. //
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. #include <stdint.h>
  14. #include <stdlib.h>
  15. #include <stdbool.h>
  16. #include <assert.h>
  17. #include <string.h>
  18. #include <stddef.h>
  19. #include <stdio.h>
  20. #include <sys/param.h>
  21. #include <multi_heap.h>
  22. #include "multi_heap_internal.h"
  23. /* Note: Keep platform-specific parts in this header, this source
  24. file should depend on libc only */
  25. #include "multi_heap_platform.h"
  26. /* Defines compile-time configuration macros */
  27. #include "multi_heap_config.h"
  28. #ifdef MULTI_HEAP_POISONING
  29. /* Alias MULTI_HEAP_POISONING_SLOW to SLOW for better readabilty */
  30. #ifdef SLOW
  31. #error "external header has defined SLOW"
  32. #endif
  33. #ifdef MULTI_HEAP_POISONING_SLOW
  34. #define SLOW 1
  35. #endif
  36. #define MALLOC_FILL_PATTERN 0xce
  37. #define FREE_FILL_PATTERN 0xfe
  38. #define HEAD_CANARY_PATTERN 0xABBA1234
  39. #define TAIL_CANARY_PATTERN 0xBAAD5678
  40. #define ALIGN_UP(num, align) (((num) + ((align) - 1)) & ~((align) - 1))
  41. typedef struct {
  42. uint32_t head_canary;
  43. MULTI_HEAP_BLOCK_OWNER
  44. size_t alloc_size;
  45. } poison_head_t;
  46. typedef struct {
  47. uint32_t tail_canary;
  48. } poison_tail_t;
  49. #define POISON_OVERHEAD (sizeof(poison_head_t) + sizeof(poison_tail_t))
  50. /* Given a "poisoned" region with pre-data header 'head', and actual data size 'alloc_size', fill in the head and tail
  51. region checks.
  52. Returns the pointer to the actual usable data buffer (ie after 'head')
  53. */
  54. static uint8_t *poison_allocated_region(poison_head_t *head, size_t alloc_size)
  55. {
  56. uint8_t *data = (uint8_t *)(&head[1]); /* start of data ie 'real' allocated buffer */
  57. poison_tail_t *tail = (poison_tail_t *)(data + alloc_size);
  58. head->alloc_size = alloc_size;
  59. head->head_canary = HEAD_CANARY_PATTERN;
  60. MULTI_HEAP_SET_BLOCK_OWNER(head);
  61. uint32_t tail_canary = TAIL_CANARY_PATTERN;
  62. if ((intptr_t)tail % sizeof(void *) == 0) {
  63. tail->tail_canary = tail_canary;
  64. } else {
  65. /* unaligned tail_canary */
  66. memcpy(&tail->tail_canary, &tail_canary, sizeof(uint32_t));
  67. }
  68. return data;
  69. }
  70. /* Given a pointer to some allocated data, check the head & tail poison structures (before & after it) that were
  71. previously injected by poison_allocated_region().
  72. Returns a pointer to the poison header structure, or NULL if the poison structures are corrupt.
  73. */
  74. static poison_head_t *verify_allocated_region(void *data, bool print_errors)
  75. {
  76. poison_head_t *head = (poison_head_t *)((intptr_t)data - sizeof(poison_head_t));
  77. poison_tail_t *tail = (poison_tail_t *)((intptr_t)data + head->alloc_size);
  78. /* check if the beginning of the data was overwritten */
  79. if (head->head_canary != HEAD_CANARY_PATTERN) {
  80. if (print_errors) {
  81. MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Bad head at %p. Expected 0x%08x got 0x%08x\n", &head->head_canary,
  82. HEAD_CANARY_PATTERN, head->head_canary);
  83. }
  84. return NULL;
  85. }
  86. /* check if the end of the data was overrun */
  87. uint32_t canary;
  88. if ((intptr_t)tail % sizeof(void *) == 0) {
  89. canary = tail->tail_canary;
  90. } else {
  91. /* tail is unaligned */
  92. memcpy(&canary, &tail->tail_canary, sizeof(canary));
  93. }
  94. if (canary != TAIL_CANARY_PATTERN) {
  95. if (print_errors) {
  96. MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Bad tail at %p. Expected 0x%08x got 0x%08x\n", &tail->tail_canary,
  97. TAIL_CANARY_PATTERN, canary);
  98. }
  99. return NULL;
  100. }
  101. return head;
  102. }
  103. #ifdef SLOW
  104. /* Go through a region that should have the specified fill byte 'pattern',
  105. verify it.
  106. if expect_free is true, expect FREE_FILL_PATTERN otherwise MALLOC_FILL_PATTERN.
  107. if swap_pattern is true, swap patterns in the buffer (ie replace MALLOC_FILL_PATTERN with FREE_FILL_PATTERN, and vice versa.)
  108. Returns true if verification checks out.
  109. */
  110. static bool verify_fill_pattern(void *data, size_t size, bool print_errors, bool expect_free, bool swap_pattern)
  111. {
  112. const uint32_t FREE_FILL_WORD = (FREE_FILL_PATTERN << 24) | (FREE_FILL_PATTERN << 16) | (FREE_FILL_PATTERN << 8) | FREE_FILL_PATTERN;
  113. const uint32_t MALLOC_FILL_WORD = (MALLOC_FILL_PATTERN << 24) | (MALLOC_FILL_PATTERN << 16) | (MALLOC_FILL_PATTERN << 8) | MALLOC_FILL_PATTERN;
  114. const uint32_t EXPECT_WORD = expect_free ? FREE_FILL_WORD : MALLOC_FILL_WORD;
  115. const uint32_t REPLACE_WORD = expect_free ? MALLOC_FILL_WORD : FREE_FILL_WORD;
  116. bool valid = true;
  117. /* Use 4-byte operations as much as possible */
  118. if ((intptr_t)data % 4 == 0) {
  119. uint32_t *p = data;
  120. while (size >= 4) {
  121. if (*p != EXPECT_WORD) {
  122. if (print_errors) {
  123. MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Invalid data at %p. Expected 0x%08x got 0x%08x\n", p, EXPECT_WORD, *p);
  124. }
  125. valid = false;
  126. #ifndef NDEBUG
  127. /* If an assertion is going to fail as soon as we're done verifying the pattern, leave the rest of the
  128. buffer contents as-is for better post-mortem analysis
  129. */
  130. swap_pattern = false;
  131. #endif
  132. }
  133. if (swap_pattern) {
  134. *p = REPLACE_WORD;
  135. }
  136. p++;
  137. size -= 4;
  138. }
  139. data = p;
  140. }
  141. uint8_t *p = data;
  142. for (size_t i = 0; i < size; i++) {
  143. if (p[i] != (uint8_t)EXPECT_WORD) {
  144. if (print_errors) {
  145. MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Invalid data at %p. Expected 0x%02x got 0x%02x\n", p, (uint8_t)EXPECT_WORD, *p);
  146. }
  147. valid = false;
  148. #ifndef NDEBUG
  149. swap_pattern = false; // same as above
  150. #endif
  151. }
  152. if (swap_pattern) {
  153. p[i] = (uint8_t)REPLACE_WORD;
  154. }
  155. }
  156. return valid;
  157. }
  158. #endif
  159. void *multi_heap_aligned_alloc(multi_heap_handle_t heap, size_t size, size_t alignment)
  160. {
  161. if (!size) {
  162. return NULL;
  163. }
  164. if (size > SIZE_MAX - POISON_OVERHEAD) {
  165. return NULL;
  166. }
  167. multi_heap_internal_lock(heap);
  168. poison_head_t *head = multi_heap_aligned_alloc_impl_offs(heap, size + POISON_OVERHEAD,
  169. alignment, sizeof(poison_head_t));
  170. uint8_t *data = NULL;
  171. if (head != NULL) {
  172. data = poison_allocated_region(head, size);
  173. #ifdef SLOW
  174. /* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */
  175. bool ret = verify_fill_pattern(data, size, true, true, true);
  176. assert( ret );
  177. #endif
  178. } else {
  179. multi_heap_internal_unlock(heap);
  180. return NULL;
  181. }
  182. multi_heap_internal_unlock(heap);
  183. return data;
  184. }
  185. void *multi_heap_malloc(multi_heap_handle_t heap, size_t size)
  186. {
  187. if (!size) {
  188. return NULL;
  189. }
  190. if(size > SIZE_MAX - POISON_OVERHEAD) {
  191. return NULL;
  192. }
  193. multi_heap_internal_lock(heap);
  194. poison_head_t *head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD);
  195. uint8_t *data = NULL;
  196. if (head != NULL) {
  197. data = poison_allocated_region(head, size);
  198. #ifdef SLOW
  199. /* check everything we got back is FREE_FILL_PATTERN & swap for MALLOC_FILL_PATTERN */
  200. bool ret = verify_fill_pattern(data, size, true, true, true);
  201. assert( ret );
  202. #endif
  203. }
  204. multi_heap_internal_unlock(heap);
  205. return data;
  206. }
  207. void multi_heap_free(multi_heap_handle_t heap, void *p)
  208. {
  209. if (p == NULL) {
  210. return;
  211. }
  212. multi_heap_internal_lock(heap);
  213. poison_head_t *head = verify_allocated_region(p, true);
  214. assert(head != NULL);
  215. #ifdef SLOW
  216. /* replace everything with FREE_FILL_PATTERN, including the poison head/tail */
  217. memset(head, FREE_FILL_PATTERN,
  218. head->alloc_size + POISON_OVERHEAD);
  219. #endif
  220. multi_heap_free_impl(heap, head);
  221. multi_heap_internal_unlock(heap);
  222. }
  223. void multi_heap_aligned_free(multi_heap_handle_t heap, void *p)
  224. {
  225. multi_heap_free(heap, p);
  226. }
  227. void *multi_heap_realloc(multi_heap_handle_t heap, void *p, size_t size)
  228. {
  229. poison_head_t *head = NULL;
  230. poison_head_t *new_head;
  231. void *result = NULL;
  232. if(size > SIZE_MAX - POISON_OVERHEAD) {
  233. return NULL;
  234. }
  235. if (p == NULL) {
  236. return multi_heap_malloc(heap, size);
  237. }
  238. if (size == 0) {
  239. multi_heap_free(heap, p);
  240. return NULL;
  241. }
  242. /* p != NULL, size != 0 */
  243. head = verify_allocated_region(p, true);
  244. assert(head != NULL);
  245. multi_heap_internal_lock(heap);
  246. #ifndef SLOW
  247. new_head = multi_heap_realloc_impl(heap, head, size + POISON_OVERHEAD);
  248. if (new_head != NULL) {
  249. /* For "fast" poisoning, we only overwrite the head/tail of the new block so it's safe
  250. to poison, so no problem doing this even if realloc resized in place.
  251. */
  252. result = poison_allocated_region(new_head, size);
  253. }
  254. #else // SLOW
  255. /* When slow poisoning is enabled, it becomes very fiddly to try and correctly fill memory when resizing in place
  256. (where the buffer may be moved (including to an overlapping address with the old buffer), grown, or shrunk in
  257. place.)
  258. For now we just malloc a new buffer, copy, and free. :|
  259. Note: If this ever changes, multi_heap defrag realloc test should be enabled.
  260. */
  261. size_t orig_alloc_size = head->alloc_size;
  262. new_head = multi_heap_malloc_impl(heap, size + POISON_OVERHEAD);
  263. if (new_head != NULL) {
  264. result = poison_allocated_region(new_head, size);
  265. memcpy(result, p, MIN(size, orig_alloc_size));
  266. multi_heap_free(heap, p);
  267. }
  268. #endif
  269. multi_heap_internal_unlock(heap);
  270. return result;
  271. }
  272. void *multi_heap_get_block_address(multi_heap_block_handle_t block)
  273. {
  274. char *head = multi_heap_get_block_address_impl(block);
  275. return head + sizeof(poison_head_t);
  276. }
  277. void *multi_heap_get_block_owner(multi_heap_block_handle_t block)
  278. {
  279. return MULTI_HEAP_GET_BLOCK_OWNER((poison_head_t*)multi_heap_get_block_address_impl(block));
  280. }
  281. multi_heap_handle_t multi_heap_register(void *start, size_t size)
  282. {
  283. #ifdef SLOW
  284. if (start != NULL) {
  285. memset(start, FREE_FILL_PATTERN, size);
  286. }
  287. #endif
  288. return multi_heap_register_impl(start, size);
  289. }
  290. static inline void subtract_poison_overhead(size_t *arg) {
  291. if (*arg > POISON_OVERHEAD) {
  292. *arg -= POISON_OVERHEAD;
  293. } else {
  294. *arg = 0;
  295. }
  296. }
  297. size_t multi_heap_get_allocated_size(multi_heap_handle_t heap, void *p)
  298. {
  299. poison_head_t *head = verify_allocated_region(p, true);
  300. assert(head != NULL);
  301. size_t result = multi_heap_get_allocated_size_impl(heap, head);
  302. return result;
  303. }
  304. void multi_heap_get_info(multi_heap_handle_t heap, multi_heap_info_t *info)
  305. {
  306. multi_heap_get_info_impl(heap, info);
  307. /* don't count the heap poison head & tail overhead in the allocated bytes size */
  308. info->total_allocated_bytes -= info->allocated_blocks * POISON_OVERHEAD;
  309. /* trim largest_free_block to account for poison overhead */
  310. subtract_poison_overhead(&info->largest_free_block);
  311. /* similarly, trim total_free_bytes so there's no suggestion that
  312. a block this big may be available. */
  313. subtract_poison_overhead(&info->total_free_bytes);
  314. subtract_poison_overhead(&info->minimum_free_bytes);
  315. }
  316. size_t multi_heap_free_size(multi_heap_handle_t heap)
  317. {
  318. size_t r = multi_heap_free_size_impl(heap);
  319. subtract_poison_overhead(&r);
  320. return r;
  321. }
  322. size_t multi_heap_minimum_free_size(multi_heap_handle_t heap)
  323. {
  324. size_t r = multi_heap_minimum_free_size_impl(heap);
  325. subtract_poison_overhead(&r);
  326. return r;
  327. }
  328. /* Internal hooks used by multi_heap to manage poisoning, while keeping some modularity */
  329. bool multi_heap_internal_check_block_poisoning(void *start, size_t size, bool is_free, bool print_errors)
  330. {
  331. if (is_free) {
  332. #ifdef SLOW
  333. return verify_fill_pattern(start, size, print_errors, true, false);
  334. #else
  335. return true; /* can only verify empty blocks in SLOW mode */
  336. #endif
  337. } else {
  338. void *data = (void *)((intptr_t)start + sizeof(poison_head_t));
  339. poison_head_t *head = verify_allocated_region(data, print_errors);
  340. if (head != NULL && head->alloc_size > size - POISON_OVERHEAD) {
  341. /* block can be bigger than alloc_size, for reasons of alignment & fragmentation,
  342. but block can never be smaller than head->alloc_size... */
  343. if (print_errors) {
  344. MULTI_HEAP_STDERR_PRINTF("CORRUPT HEAP: Size at %p expected <=0x%08x got 0x%08x\n", &head->alloc_size,
  345. size - POISON_OVERHEAD, head->alloc_size);
  346. }
  347. return false;
  348. }
  349. return head != NULL;
  350. }
  351. }
  352. void multi_heap_internal_poison_fill_region(void *start, size_t size, bool is_free)
  353. {
  354. memset(start, is_free ? FREE_FILL_PATTERN : MALLOC_FILL_PATTERN, size);
  355. }
  356. #else // !MULTI_HEAP_POISONING
  357. #ifdef MULTI_HEAP_POISONING_SLOW
  358. #error "MULTI_HEAP_POISONING_SLOW requires MULTI_HEAP_POISONING"
  359. #endif
  360. #endif // MULTI_HEAP_POISONING