harden realloc/free to detect simple overflows
[musl.git] / src / malloc / malloc.c
blob4044eb2af921ffee684e70c2cb849a06af72e454
1 #define _GNU_SOURCE
2 #include <stdlib.h>
3 #include <string.h>
4 #include <limits.h>
5 #include <stdint.h>
6 #include <errno.h>
7 #include <sys/mman.h>
8 #include "libc.h"
9 #include "atomic.h"
10 #include "pthread_impl.h"
12 #if defined(__GNUC__) && defined(__PIC__)
13 #define inline inline __attribute__((always_inline))
14 #endif
16 uintptr_t __brk(uintptr_t);
17 void *__mmap(void *, size_t, int, int, int, off_t);
18 int __munmap(void *, size_t);
19 void *__mremap(void *, size_t, size_t, int, ...);
20 int __madvise(void *, size_t, int);
22 struct chunk {
23 size_t psize, csize;
24 struct chunk *next, *prev;
27 struct bin {
28 int lock[2];
29 struct chunk *head;
30 struct chunk *tail;
33 static struct {
34 uintptr_t brk;
35 size_t *heap;
36 uint64_t binmap;
37 struct bin bins[64];
38 int brk_lock[2];
39 int free_lock[2];
40 } mal;
43 #define SIZE_ALIGN (4*sizeof(size_t))
44 #define SIZE_MASK (-SIZE_ALIGN)
45 #define OVERHEAD (2*sizeof(size_t))
46 #define MMAP_THRESHOLD (0x1c00*SIZE_ALIGN)
47 #define DONTCARE 16
48 #define RECLAIM 163840
50 #define CHUNK_SIZE(c) ((c)->csize & -2)
51 #define CHUNK_PSIZE(c) ((c)->psize & -2)
52 #define PREV_CHUNK(c) ((struct chunk *)((char *)(c) - CHUNK_PSIZE(c)))
53 #define NEXT_CHUNK(c) ((struct chunk *)((char *)(c) + CHUNK_SIZE(c)))
54 #define MEM_TO_CHUNK(p) (struct chunk *)((char *)(p) - OVERHEAD)
55 #define CHUNK_TO_MEM(c) (void *)((char *)(c) + OVERHEAD)
56 #define BIN_TO_CHUNK(i) (MEM_TO_CHUNK(&mal.bins[i].head))
58 #define C_INUSE ((size_t)1)
60 #define IS_MMAPPED(c) !((c)->csize & (C_INUSE))
63 /* Synchronization tools */
65 static inline void lock(volatile int *lk)
67 if (!libc.threads_minus_1) return;
68 while(a_swap(lk, 1)) __wait(lk, lk+1, 1, 1);
71 static inline void unlock(volatile int *lk)
73 if (!libc.threads_minus_1) return;
74 a_store(lk, 0);
75 if (lk[1]) __wake(lk, 1, 1);
78 static inline void lock_bin(int i)
80 if (libc.threads_minus_1)
81 lock(mal.bins[i].lock);
82 if (!mal.bins[i].head)
83 mal.bins[i].head = mal.bins[i].tail = BIN_TO_CHUNK(i);
86 static inline void unlock_bin(int i)
88 if (!libc.threads_minus_1) return;
89 unlock(mal.bins[i].lock);
92 static int first_set(uint64_t x)
94 #if 1
95 return a_ctz_64(x);
96 #else
97 static const char debruijn64[64] = {
98 0, 1, 2, 53, 3, 7, 54, 27, 4, 38, 41, 8, 34, 55, 48, 28,
99 62, 5, 39, 46, 44, 42, 22, 9, 24, 35, 59, 56, 49, 18, 29, 11,
100 63, 52, 6, 26, 37, 40, 33, 47, 61, 45, 43, 21, 23, 58, 17, 10,
101 51, 25, 36, 32, 60, 20, 57, 16, 50, 31, 19, 15, 30, 14, 13, 12
103 static const char debruijn32[32] = {
104 0, 1, 23, 2, 29, 24, 19, 3, 30, 27, 25, 11, 20, 8, 4, 13,
105 31, 22, 28, 18, 26, 10, 7, 12, 21, 17, 9, 6, 16, 5, 15, 14
107 if (sizeof(long) < 8) {
108 uint32_t y = x;
109 if (!y) {
110 y = x>>32;
111 return 32 + debruijn32[(y&-y)*0x076be629 >> 27];
113 return debruijn32[(y&-y)*0x076be629 >> 27];
115 return debruijn64[(x&-x)*0x022fdd63cc95386dull >> 58];
116 #endif
119 static int bin_index(size_t x)
121 x = x / SIZE_ALIGN - 1;
122 if (x <= 32) return x;
123 if (x > 0x1c00) return 63;
124 return ((union { float v; uint32_t r; }){(int)x}.r>>21) - 496;
127 static int bin_index_up(size_t x)
129 x = x / SIZE_ALIGN - 1;
130 if (x <= 32) return x;
131 return ((union { float v; uint32_t r; }){(int)x}.r+0x1fffff>>21) - 496;
134 #if 0
135 void __dump_heap(int x)
137 struct chunk *c;
138 int i;
139 for (c = (void *)mal.heap; CHUNK_SIZE(c); c = NEXT_CHUNK(c))
140 fprintf(stderr, "base %p size %zu (%d) flags %d/%d\n",
141 c, CHUNK_SIZE(c), bin_index(CHUNK_SIZE(c)),
142 c->csize & 15,
143 NEXT_CHUNK(c)->psize & 15);
144 for (i=0; i<64; i++) {
145 if (mal.bins[i].head != BIN_TO_CHUNK(i) && mal.bins[i].head) {
146 fprintf(stderr, "bin %d: %p\n", i, mal.bins[i].head);
147 if (!(mal.binmap & 1ULL<<i))
148 fprintf(stderr, "missing from binmap!\n");
149 } else if (mal.binmap & 1ULL<<i)
150 fprintf(stderr, "binmap wrongly contains %d!\n", i);
153 #endif
155 static struct chunk *expand_heap(size_t n)
157 struct chunk *w;
158 uintptr_t new;
160 lock(mal.brk_lock);
162 if (n > SIZE_MAX - mal.brk - 2*PAGE_SIZE) goto fail;
163 new = mal.brk + n + SIZE_ALIGN + PAGE_SIZE - 1 & -PAGE_SIZE;
164 n = new - mal.brk;
166 if (__brk(new) != new) goto fail;
168 w = MEM_TO_CHUNK(new);
169 w->psize = n | C_INUSE;
170 w->csize = 0 | C_INUSE;
172 w = MEM_TO_CHUNK(mal.brk);
173 w->csize = n | C_INUSE;
174 mal.brk = new;
176 unlock(mal.brk_lock);
178 return w;
179 fail:
180 unlock(mal.brk_lock);
181 return 0;
184 static int init_malloc(size_t n)
186 static int init, waiters;
187 int state;
188 struct chunk *c;
190 if (init == 2) return 0;
192 while ((state=a_swap(&init, 1)) == 1)
193 __wait(&init, &waiters, 1, 1);
194 if (state) {
195 a_store(&init, 2);
196 return 0;
199 mal.brk = __brk(0);
200 #ifdef SHARED
201 mal.brk = mal.brk + PAGE_SIZE-1 & -PAGE_SIZE;
202 #endif
203 mal.brk = mal.brk + 2*SIZE_ALIGN-1 & -SIZE_ALIGN;
205 c = expand_heap(n);
207 if (!c) {
208 a_store(&init, 0);
209 if (waiters) __wake(&init, 1, 1);
210 return -1;
213 mal.heap = (void *)c;
214 c->psize = 0 | C_INUSE;
215 free(CHUNK_TO_MEM(c));
217 a_store(&init, 2);
218 if (waiters) __wake(&init, -1, 1);
219 return 1;
222 static int adjust_size(size_t *n)
224 /* Result of pointer difference must fit in ptrdiff_t. */
225 if (*n-1 > PTRDIFF_MAX - SIZE_ALIGN - PAGE_SIZE) {
226 if (*n) {
227 errno = ENOMEM;
228 return -1;
229 } else {
230 *n = SIZE_ALIGN;
231 return 0;
234 *n = (*n + OVERHEAD + SIZE_ALIGN - 1) & SIZE_MASK;
235 return 0;
238 static void unbin(struct chunk *c, int i)
240 if (c->prev == c->next)
241 a_and_64(&mal.binmap, ~(1ULL<<i));
242 c->prev->next = c->next;
243 c->next->prev = c->prev;
244 c->csize |= C_INUSE;
245 NEXT_CHUNK(c)->psize |= C_INUSE;
248 static int alloc_fwd(struct chunk *c)
250 int i;
251 size_t k;
252 while (!((k=c->csize) & C_INUSE)) {
253 i = bin_index(k);
254 lock_bin(i);
255 if (c->csize == k) {
256 unbin(c, i);
257 unlock_bin(i);
258 return 1;
260 unlock_bin(i);
262 return 0;
265 static int alloc_rev(struct chunk *c)
267 int i;
268 size_t k;
269 while (!((k=c->psize) & C_INUSE)) {
270 i = bin_index(k);
271 lock_bin(i);
272 if (c->psize == k) {
273 unbin(PREV_CHUNK(c), i);
274 unlock_bin(i);
275 return 1;
277 unlock_bin(i);
279 return 0;
283 /* pretrim - trims a chunk _prior_ to removing it from its bin.
284 * Must be called with i as the ideal bin for size n, j the bin
285 * for the _free_ chunk self, and bin j locked. */
286 static int pretrim(struct chunk *self, size_t n, int i, int j)
288 size_t n1;
289 struct chunk *next, *split;
291 /* We cannot pretrim if it would require re-binning. */
292 if (j < 40) return 0;
293 if (j < i+3) {
294 if (j != 63) return 0;
295 n1 = CHUNK_SIZE(self);
296 if (n1-n <= MMAP_THRESHOLD) return 0;
297 } else {
298 n1 = CHUNK_SIZE(self);
300 if (bin_index(n1-n) != j) return 0;
302 next = NEXT_CHUNK(self);
303 split = (void *)((char *)self + n);
305 split->prev = self->prev;
306 split->next = self->next;
307 split->prev->next = split;
308 split->next->prev = split;
309 split->psize = n | C_INUSE;
310 split->csize = n1-n;
311 next->psize = n1-n;
312 self->csize = n | C_INUSE;
313 return 1;
316 static void trim(struct chunk *self, size_t n)
318 size_t n1 = CHUNK_SIZE(self);
319 struct chunk *next, *split;
321 if (n >= n1 - DONTCARE) return;
323 next = NEXT_CHUNK(self);
324 split = (void *)((char *)self + n);
326 split->psize = n | C_INUSE;
327 split->csize = n1-n | C_INUSE;
328 next->psize = n1-n | C_INUSE;
329 self->csize = n | C_INUSE;
331 free(CHUNK_TO_MEM(split));
334 void *malloc(size_t n)
336 struct chunk *c;
337 int i, j;
339 if (adjust_size(&n) < 0) return 0;
341 if (n > MMAP_THRESHOLD) {
342 size_t len = n + OVERHEAD + PAGE_SIZE - 1 & -PAGE_SIZE;
343 char *base = __mmap(0, len, PROT_READ|PROT_WRITE,
344 MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
345 if (base == (void *)-1) return 0;
346 c = (void *)(base + SIZE_ALIGN - OVERHEAD);
347 c->csize = len - (SIZE_ALIGN - OVERHEAD);
348 c->psize = SIZE_ALIGN - OVERHEAD;
349 return CHUNK_TO_MEM(c);
352 i = bin_index_up(n);
353 for (;;) {
354 uint64_t mask = mal.binmap & -(1ULL<<i);
355 if (!mask) {
356 if (init_malloc(n) > 0) continue;
357 c = expand_heap(n);
358 if (!c) return 0;
359 if (alloc_rev(c)) {
360 struct chunk *x = c;
361 c = PREV_CHUNK(c);
362 NEXT_CHUNK(x)->psize = c->csize =
363 x->csize + CHUNK_SIZE(c);
365 break;
367 j = first_set(mask);
368 lock_bin(j);
369 c = mal.bins[j].head;
370 if (c != BIN_TO_CHUNK(j) && j == bin_index(c->csize)) {
371 if (!pretrim(c, n, i, j)) unbin(c, j);
372 unlock_bin(j);
373 break;
375 unlock_bin(j);
378 /* Now patch up in case we over-allocated */
379 trim(c, n);
381 return CHUNK_TO_MEM(c);
384 void *realloc(void *p, size_t n)
386 struct chunk *self, *next;
387 size_t n0, n1;
388 void *new;
390 if (!p) return malloc(n);
392 if (adjust_size(&n) < 0) return 0;
394 self = MEM_TO_CHUNK(p);
395 n1 = n0 = CHUNK_SIZE(self);
397 if (IS_MMAPPED(self)) {
398 size_t extra = self->psize;
399 char *base = (char *)self - extra;
400 size_t oldlen = n0 + extra;
401 size_t newlen = n + extra;
402 /* Crash on realloc of freed chunk */
403 if (extra & 1) a_crash();
404 if (newlen < PAGE_SIZE && (new = malloc(n))) {
405 memcpy(new, p, n-OVERHEAD);
406 free(p);
407 return new;
409 newlen = (newlen + PAGE_SIZE-1) & -PAGE_SIZE;
410 if (oldlen == newlen) return p;
411 base = __mremap(base, oldlen, newlen, MREMAP_MAYMOVE);
412 if (base == (void *)-1)
413 return newlen < oldlen ? p : 0;
414 self = (void *)(base + extra);
415 self->csize = newlen - extra;
416 return CHUNK_TO_MEM(self);
419 next = NEXT_CHUNK(self);
421 /* Crash on corrupted footer (likely from buffer overflow) */
422 if (next->psize != self->csize) a_crash();
424 /* Merge adjacent chunks if we need more space. This is not
425 * a waste of time even if we fail to get enough space, because our
426 * subsequent call to free would otherwise have to do the merge. */
427 if (n > n1 && alloc_fwd(next)) {
428 n1 += CHUNK_SIZE(next);
429 next = NEXT_CHUNK(next);
431 /* FIXME: find what's wrong here and reenable it..? */
432 if (0 && n > n1 && alloc_rev(self)) {
433 self = PREV_CHUNK(self);
434 n1 += CHUNK_SIZE(self);
436 self->csize = n1 | C_INUSE;
437 next->psize = n1 | C_INUSE;
439 /* If we got enough space, split off the excess and return */
440 if (n <= n1) {
441 //memmove(CHUNK_TO_MEM(self), p, n0-OVERHEAD);
442 trim(self, n);
443 return CHUNK_TO_MEM(self);
446 /* As a last resort, allocate a new chunk and copy to it. */
447 new = malloc(n-OVERHEAD);
448 if (!new) return 0;
449 memcpy(new, p, n0-OVERHEAD);
450 free(CHUNK_TO_MEM(self));
451 return new;
454 void free(void *p)
456 struct chunk *self = MEM_TO_CHUNK(p);
457 struct chunk *next;
458 size_t final_size, new_size, size;
459 int reclaim=0;
460 int i;
462 if (!p) return;
464 if (IS_MMAPPED(self)) {
465 size_t extra = self->psize;
466 char *base = (char *)self - extra;
467 size_t len = CHUNK_SIZE(self) + extra;
468 /* Crash on double free */
469 if (extra & 1) a_crash();
470 __munmap(base, len);
471 return;
474 final_size = new_size = CHUNK_SIZE(self);
475 next = NEXT_CHUNK(self);
477 /* Crash on corrupted footer (likely from buffer overflow) */
478 if (next->psize != self->csize) a_crash();
480 for (;;) {
481 /* Replace middle of large chunks with fresh zero pages */
482 if (reclaim && (self->psize & next->csize & C_INUSE)) {
483 uintptr_t a = (uintptr_t)self + SIZE_ALIGN+PAGE_SIZE-1 & -PAGE_SIZE;
484 uintptr_t b = (uintptr_t)next - SIZE_ALIGN & -PAGE_SIZE;
485 #if 1
486 __madvise((void *)a, b-a, MADV_DONTNEED);
487 #else
488 __mmap((void *)a, b-a, PROT_READ|PROT_WRITE,
489 MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
490 #endif
493 if (self->psize & next->csize & C_INUSE) {
494 self->csize = final_size | C_INUSE;
495 next->psize = final_size | C_INUSE;
496 i = bin_index(final_size);
497 lock_bin(i);
498 lock(mal.free_lock);
499 if (self->psize & next->csize & C_INUSE)
500 break;
501 unlock(mal.free_lock);
502 unlock_bin(i);
505 if (alloc_rev(self)) {
506 self = PREV_CHUNK(self);
507 size = CHUNK_SIZE(self);
508 final_size += size;
509 if (new_size+size > RECLAIM && (new_size+size^size) > size)
510 reclaim = 1;
513 if (alloc_fwd(next)) {
514 size = CHUNK_SIZE(next);
515 final_size += size;
516 if (new_size+size > RECLAIM && (new_size+size^size) > size)
517 reclaim = 1;
518 next = NEXT_CHUNK(next);
522 self->csize = final_size;
523 next->psize = final_size;
524 unlock(mal.free_lock);
526 self->next = BIN_TO_CHUNK(i);
527 self->prev = mal.bins[i].tail;
528 self->next->prev = self;
529 self->prev->next = self;
531 if (!(mal.binmap & 1ULL<<i))
532 a_or_64(&mal.binmap, 1ULL<<i);
534 unlock_bin(i);