1 /* The wrapper functions in this file work like regular malloc() and free(),
2  * but store check values before and after the allocation. This helps to catch
3  * any buffer overrun errors in the test cases.
4  */
5 
6 #include "malloc_wrappers.h"
7 #include <stdint.h>
8 #include <stdio.h>
9 #include <assert.h>
10 #include <string.h>
11 
12 #define GUARD_SIZE (sizeof(size_t)*3)
13 #define PREFIX_SIZE (sizeof(size_t)*2)
14 #define CHECK1 ((size_t)0xDEADBEEF)
15 #define CHECK2 ((size_t)0x600DCAFE)
16 
17 #ifndef MAX_ALLOC_BYTES
18 #define MAX_ALLOC_BYTES 16*1024*1024
19 #endif
20 
21 #ifndef DEBUG_MALLOC
22 #define DEBUG_MALLOC 0
23 #endif
24 
25 static size_t g_alloc_count = 0;
26 static size_t g_alloc_bytes = 0;
27 static size_t g_max_alloc_bytes = MAX_ALLOC_BYTES;
28 
29 #ifdef LLVMFUZZER
30 /* LLVM libsanitizer has a realloc() implementation that always copies
31  * the whole memory block, even if there would be space to expand it in
32  * place. This gets pretty slow when fuzzing, so this wrapper limits the
33  * realloc() calls by rounding allocation size upwards. Real world
34  * realloc() implementations are hopefully smarter. */
round_blocksize(size_t size)35 static size_t round_blocksize(size_t size)
36 {
37     if (size < 256)
38     {
39         return size;
40     }
41     else
42     {
43         return (size + 1023) / 1024 * 1024;
44     }
45 }
46 #else
round_blocksize(size_t size)47 static size_t round_blocksize(size_t size)
48 {
49     return size;
50 }
51 #endif
52 
53 /* Allocate memory and place check values before and after. */
malloc_with_check(size_t size)54 void* malloc_with_check(size_t size)
55 {
56     char *buf = NULL;
57 
58     if (size <= g_max_alloc_bytes - g_alloc_bytes)
59     {
60         buf = malloc(round_blocksize(size + GUARD_SIZE));
61     }
62 
63     if (buf)
64     {
65         ((size_t*)buf)[0] = size;
66         ((size_t*)buf)[1] = CHECK1;
67         ((size_t*)(buf + size))[2] = CHECK2;
68         g_alloc_count++;
69         g_alloc_bytes += size;
70         if (DEBUG_MALLOC) fprintf(stderr, "Alloc 0x%04x/%u\n", (unsigned)(uintptr_t)(buf + PREFIX_SIZE), (unsigned)size);
71         return buf + PREFIX_SIZE;
72     }
73     else
74     {
75         if (DEBUG_MALLOC) fprintf(stderr, "malloc(%u) failed\n", (unsigned)size);
76         return NULL;
77     }
78 }
79 
80 /* Free memory allocated with malloc_with_check() and do the checks. */
free_with_check(void * mem)81 void free_with_check(void *mem)
82 {
83     if (mem)
84     {
85         char *buf = (char*)mem - PREFIX_SIZE;
86         size_t size = ((size_t*)buf)[0];
87         if (DEBUG_MALLOC) fprintf(stderr, "Release 0x%04x/%u\n", (unsigned)(uintptr_t)mem, (unsigned)size);
88         assert(((size_t*)buf)[1] == CHECK1);
89         assert(((size_t*)(buf + size))[2] == CHECK2);
90         assert(g_alloc_count > 0);
91         assert(g_alloc_bytes >= size);
92         ((size_t*)buf)[1] = 0;
93         ((size_t*)(buf + size))[2] = 0;
94         g_alloc_count--;
95         g_alloc_bytes -= size;
96         free(buf);
97     }
98 }
99 
100 /* Reallocate block and check / write guard values */
realloc_with_check(void * ptr,size_t size)101 void* realloc_with_check(void *ptr, size_t size)
102 {
103     if (!ptr && size)
104     {
105         /* Allocate new block and write guard values */
106         return malloc_with_check(size);
107     }
108     else if (ptr && size)
109     {
110         /* Change block size */
111         char *buf = (char*)ptr - PREFIX_SIZE;
112         size_t oldsize = ((size_t*)buf)[0];
113         assert(((size_t*)buf)[1] == CHECK1);
114         assert(((size_t*)(buf + oldsize))[2] == CHECK2);
115         assert(g_alloc_count > 0);
116         assert(g_alloc_bytes >= oldsize);
117 
118         if (size <= g_max_alloc_bytes - (g_alloc_bytes - oldsize))
119         {
120             size_t new_rounded = round_blocksize(size + GUARD_SIZE);
121             size_t old_rounded = round_blocksize(oldsize + GUARD_SIZE);
122 
123             if (new_rounded != old_rounded)
124             {
125                 buf = realloc(buf, new_rounded);
126             }
127         }
128         else
129         {
130             buf = NULL;
131         }
132 
133         if (!buf)
134         {
135             if (DEBUG_MALLOC) fprintf(stderr, "Realloc 0x%04x/%u to %u failed\n", (unsigned)(uintptr_t)ptr, (unsigned)oldsize, (unsigned)size);
136             return NULL;
137         }
138 
139         ((size_t*)buf)[0] = size;
140         ((size_t*)buf)[1] = CHECK1;
141         ((size_t*)(buf + size))[2] = CHECK2;
142         g_alloc_bytes -= oldsize;
143         g_alloc_bytes += size;
144 
145         if (DEBUG_MALLOC) fprintf(stderr, "Realloc 0x%04x/%u to 0x%04x/%u\n", (unsigned)(uintptr_t)ptr, (unsigned)oldsize, (unsigned)(uintptr_t)(buf + PREFIX_SIZE), (unsigned)size);
146         return buf + PREFIX_SIZE;
147     }
148     else if (ptr && !size)
149     {
150         /* Deallocate */
151         free_with_check(ptr);
152         return NULL;
153     }
154     else
155     {
156         /* No action */
157         return NULL;
158     }
159 }
160 
161 /* Return total number of allocations not yet released */
get_alloc_count()162 size_t get_alloc_count()
163 {
164     return g_alloc_count;
165 }
166 
167 /* Return allocated size for a pointer returned from malloc(). */
get_allocation_size(const void * mem)168 size_t get_allocation_size(const void *mem)
169 {
170     char *buf = (char*)mem - PREFIX_SIZE;
171     return ((size_t*)buf)[0];
172 }
173 
174 /* Get total number of allocated bytes */
get_alloc_bytes()175 size_t get_alloc_bytes()
176 {
177     return g_alloc_bytes;
178 }
179 
180 /* Set limit for allocation size */
set_max_alloc_bytes(size_t max_bytes)181 void set_max_alloc_bytes(size_t max_bytes)
182 {
183     g_max_alloc_bytes = max_bytes;
184 }
185 
get_max_alloc_bytes()186 size_t get_max_alloc_bytes()
187 {
188     return g_max_alloc_bytes;
189 }
190