1 // Assume that this file is called only when shadow memory is used
2 #ifndef WASM_CHECK_SHADOW_MEMORY
3 #define WASM_CHECK_SHADOW_MEMORY
4 #endif
5
6 #include "wasm-rt.h"
7
8 #include <assert.h>
9 #include <inttypes.h>
10 #include <stdint.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14
15 #include <map>
16 #include <functional>
17
18 typedef enum class ALLOC_STATE : uint8_t {
19 UNINIT = 0, ALLOCED, INITIALIZED,
20 } ALLOC_STATE;
21
22 static const char * alloc_state_strings[] = {
23 "UNINIT", "ALLOCED", "INITIALIZED",
24 };
25
26 typedef enum class USED_STATE : uint8_t {
27 UNUSED = 0, FREED
28 } USED_STATE;
29
30 static const char * used_state_strings[] = {
31 "UNUSED", "FREED"
32 };
33
34 typedef enum class OWN_STATE : uint8_t {
35 UNKNOWN = 0, PROGRAM, MALLOC
36 } OWN_STATE;
37
38 static const char * own_state_strings[] = {
39 "UNKNOWN", "PROGRAM", "MALLOC"
40 };
41
42 typedef struct {
43 ALLOC_STATE alloc_state;
44 USED_STATE used_state;
45 OWN_STATE own_state;
46 } cell_data_t;
47
pack(cell_data_t data)48 static wasm2c_shadow_memory_cell_t pack(cell_data_t data) {
49 uint8_t alloc_bits = ((uint8_t)data.alloc_state) & 0b11;
50 uint8_t used_bits = (((uint8_t)data.used_state) & 0b11) << 2;
51 uint8_t own_bits = (((uint8_t)data.own_state) & 0b11) << 4;
52 uint8_t ret = alloc_bits | used_bits | own_bits;
53 return ret;
54 }
55
unpack(wasm2c_shadow_memory_cell_t byte)56 static cell_data_t unpack(wasm2c_shadow_memory_cell_t byte) {
57 uint8_t alloc_bits = byte & 0b11;
58 uint8_t used_bits = (byte & 0b1100) >> 2;
59 uint8_t own_bits = (byte & 0b110000) >> 4;
60 cell_data_t ret { (ALLOC_STATE) alloc_bits, (USED_STATE) used_bits, (OWN_STATE) own_bits};
61 return ret;
62 }
63
wasm2c_shadow_memory_create(wasm_rt_memory_t * mem)64 void wasm2c_shadow_memory_create(wasm_rt_memory_t* mem) {
65 uint64_t new_size = ((uint64_t) mem->size) * sizeof(wasm2c_shadow_memory_cell_t);
66 mem->shadow_memory.data = (wasm2c_shadow_memory_cell_t*) calloc(new_size, 1);
67 assert(mem->shadow_memory.data != 0);
68 mem->shadow_memory.data_size = new_size;
69 mem->shadow_memory.allocation_sizes_map = new std::map<uint32_t, uint32_t>;
70 mem->shadow_memory.heap_base = 0;
71 }
72
wasm2c_shadow_memory_expand(wasm_rt_memory_t * mem)73 void wasm2c_shadow_memory_expand(wasm_rt_memory_t* mem) {
74 uint64_t new_size = ((uint64_t) mem->size) * sizeof(wasm2c_shadow_memory_cell_t);
75 uint8_t* new_data = (uint8_t*) realloc(mem->shadow_memory.data, new_size);
76 assert(mem->shadow_memory.data != 0);
77 uint64_t old_size = mem->shadow_memory.data_size;
78 memset(new_data + old_size, 0, new_size - old_size);
79 mem->shadow_memory.data = new_data;
80 mem->shadow_memory.data_size = new_size;
81 }
82
83 #define allocation_sizes_map(mem) ((std::map<uint32_t, uint32_t>*) mem->shadow_memory.allocation_sizes_map)
84
wasm2c_shadow_memory_destroy(wasm_rt_memory_t * mem)85 void wasm2c_shadow_memory_destroy(wasm_rt_memory_t* mem) {
86 free(mem->shadow_memory.data);
87 mem->shadow_memory.data = 0;
88 mem->shadow_memory.data_size = 0;
89 delete allocation_sizes_map(mem);
90 mem->shadow_memory.allocation_sizes_map = 0;
91 mem->shadow_memory.heap_base = 0;
92 }
93
94 typedef void (iterate_t)(uint32_t index, cell_data_t* data);
95
memory_state_iterate(wasm_rt_memory_t * mem,uint32_t ptr,uint32_t ptr_size,std::function<iterate_t> callback)96 static inline void memory_state_iterate(wasm_rt_memory_t* mem, uint32_t ptr, uint32_t ptr_size, std::function<iterate_t> callback) {
97 uint64_t max = ((uint64_t) ptr) + ptr_size;
98 assert(max <= UINT32_MAX);
99
100 assert(max <= mem->shadow_memory.data_size);
101
102 for (uint32_t i = ptr; i < ptr + ptr_size; i++) {
103 wasm2c_shadow_memory_cell_t curr_state = mem->shadow_memory.data[i];
104 cell_data_t unpacked = unpack(curr_state);
105 callback(i, &unpacked);
106 wasm2c_shadow_memory_cell_t new_state = pack(unpacked);
107 mem->shadow_memory.data[i] = new_state;
108 }
109 }
110
wasm2c_shadow_memory_reserve(wasm_rt_memory_t * mem,uint32_t ptr,uint32_t ptr_size)111 void wasm2c_shadow_memory_reserve(wasm_rt_memory_t* mem, uint32_t ptr, uint32_t ptr_size) {
112 memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){
113 if (data->alloc_state == ALLOC_STATE::UNINIT) {
114 data->alloc_state = ALLOC_STATE::ALLOCED;
115 }
116 });
117 }
118
report_error(wasm_rt_memory_t * mem,const char * func_name,const char * error_message,uint32_t index,cell_data_t * data)119 static void report_error(wasm_rt_memory_t* mem, const char* func_name, const char* error_message, uint32_t index, cell_data_t* data) {
120 const char* alloc_state_string = "<>";
121 const char* used_state_string = "<>";
122 const char* own_state_string = "<>";
123
124 if (data != 0) {
125 alloc_state_string = alloc_state_strings[(int) data->alloc_state];
126 used_state_string = used_state_strings[(int) data->used_state];
127 own_state_string = own_state_strings[(int) data->own_state];
128 }
129 const uint64_t used_mem = wasm2c_shadow_memory_print_total_allocations(mem);
130 printf("WASM Shadow memory ASAN failed! %s (Func: %s, Index: %" PRIu32 ") (Cell state: %s, %s, %s) (Allocated mem: %" PRIu64 ")!\n",
131 error_message, func_name, index,
132 alloc_state_string, used_state_string, own_state_string,
133 used_mem
134 );
135 fflush(stdout);
136 #ifndef WASM_CHECK_SHADOW_MEMORY_NO_ABORT_ON_FAIL
137 wasm_rt_trap(WASM_RT_TRAP_SHADOW_MEM);
138 #endif
139 }
140
wasm2c_shadow_memory_dlmalloc(wasm_rt_memory_t * mem,uint32_t ptr,uint32_t ptr_size)141 void wasm2c_shadow_memory_dlmalloc(wasm_rt_memory_t* mem, uint32_t ptr, uint32_t ptr_size) {
142 if (ptr == 0) {
143 // malloc failed
144 return;
145 }
146
147 const char* func_name = "<MALLOC>";
148 if (ptr < mem->shadow_memory.heap_base) {
149 report_error(mem, func_name, "malloc returning a pointer outside the heap", ptr, 0);
150 }
151
152 memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){
153 if (data->alloc_state != ALLOC_STATE::UNINIT) {
154 report_error(mem, func_name, "Malloc returned a pointer in already occupied memory!", index, data);
155 } else {
156 data->alloc_state = ALLOC_STATE::ALLOCED;
157 }
158 });
159
160 auto alloc_map = allocation_sizes_map(mem);
161 (*alloc_map)[ptr] = ptr_size;
162 }
163
wasm2c_shadow_memory_dlfree(wasm_rt_memory_t * mem,uint32_t ptr)164 void wasm2c_shadow_memory_dlfree(wasm_rt_memory_t* mem, uint32_t ptr) {
165 if (ptr == 0) {
166 // free noop
167 return;
168 }
169
170 const char* func_name = "<FREE>";
171
172 auto alloc_map = allocation_sizes_map(mem);
173 auto it = alloc_map->find(ptr);
174 if (it == alloc_map->end()) {
175 report_error(mem, func_name, "Freeing a pointer that was not allocated!", ptr, 0);
176 } else {
177 uint32_t ptr_size = it->second;
178 alloc_map->erase(it);
179
180 memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){
181 if (data->alloc_state == ALLOC_STATE::UNINIT) {
182 report_error(mem, func_name, "Freeing uninitialized memory", index, data);
183 } else {
184 data->alloc_state = ALLOC_STATE::UNINIT;
185 }
186
187 // This memory is now "previously used" memory
188 data->used_state = USED_STATE::FREED;
189 });
190 }
191 }
192
wasm2c_shadow_memory_mark_globals_heap_boundary(wasm_rt_memory_t * mem,uint32_t ptr)193 void wasm2c_shadow_memory_mark_globals_heap_boundary(wasm_rt_memory_t* mem, uint32_t ptr) {
194 mem->shadow_memory.heap_base = ptr;
195 wasm2c_shadow_memory_reserve(mem, 0, ptr);
196 }
197
check_heap_base_straddle(wasm_rt_memory_t * mem,const char * func_name,uint32_t ptr,uint32_t ptr_size)198 static void check_heap_base_straddle(wasm_rt_memory_t* mem, const char* func_name, uint32_t ptr, uint32_t ptr_size) {
199 uint32_t heap_base = mem->shadow_memory.heap_base;
200 uint64_t max = ((uint64_t) ptr) + ptr_size;
201 assert(max <= UINT32_MAX);
202
203 if (ptr < heap_base) {
204 if (ptr + ptr_size > heap_base) {
205 report_error(mem, func_name, "Memory operation straddling heap base!", ptr, 0 /* cell_data */);
206 }
207 }
208 }
209
is_malloc_family_function(const char * func_name,bool include_calloc,bool include_realloc)210 static bool is_malloc_family_function(const char* func_name, bool include_calloc, bool include_realloc) {
211 if(strcmp(func_name, "w2c_dlmalloc") == 0 || strcmp(func_name, "w2c_dlfree") == 0 || strcmp(func_name, "w2c_sbrk") == 0) {
212 return true;
213 }
214 if (include_calloc) {
215 if(strcmp(func_name, "w2c_calloc") == 0) {
216 return true;
217 }
218 }
219 if (include_realloc) {
220 if(strcmp(func_name, "w2c_realloc") == 0) {
221 return true;
222 }
223 }
224 return false;
225 }
226
227 // Functions that intentionally read beyond the end of an allocation for performance
228 // The limit is upto 7 bytes past the end of the allocation
is_overread_function(const char * func_name)229 static bool is_overread_function(const char* func_name) {
230 return strcmp(func_name, "w2c_strlen") == 0;
231 }
232
wasm2c_shadow_memory_load(wasm_rt_memory_t * mem,const char * func_name,uint32_t ptr,uint32_t ptr_size)233 WASM2C_FUNC_EXPORT void wasm2c_shadow_memory_load(wasm_rt_memory_t* mem, const char* func_name, uint32_t ptr, uint32_t ptr_size) {
234 check_heap_base_straddle(mem, func_name, ptr, ptr_size);
235
236 bool malloc_read_family = is_malloc_family_function(func_name, true, true);
237 bool malloc_core_family = is_malloc_family_function(func_name, false, false);
238 bool malloc_any_family = malloc_read_family;
239 bool overread_func_family = is_overread_function(func_name);
240
241 memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){
242 // Is this function exempt from checking
243 bool exempt = false;
244
245 // Malloc family of functions store chunk related information in the heap
246 // These look like unitialized operations
247 if (index >= mem->shadow_memory.heap_base && malloc_read_family) {
248 exempt = true;
249 }
250
251 bool is_first_iteration = index == ptr;
252 // Functions that intentionally read beyond the end of an allocation for performance
253 // The limit is upto 7 bytes past the end of the allocation
254 if (overread_func_family && !is_first_iteration) {
255 exempt = true;
256 }
257
258 if (!exempt) {
259 #ifdef WASM_CHECK_SHADOW_MEMORY_UNINIT_READ
260 if (data->alloc_state != ALLOC_STATE::INITIALIZED) {
261 report_error(mem, func_name, "Reading uninitialized or unallocated memory", index, data);
262 }
263 #else
264 if (data->alloc_state == ALLOC_STATE::UNINIT) {
265 report_error(mem, func_name, "Reading uninitialized memory", index, data);
266 }
267 #endif
268 }
269
270 // Accessing C globals --- infer if this is malloc or program data
271 if (index < mem->shadow_memory.heap_base) {
272 if (malloc_core_family) {
273 // Accessing C globals from core malloc functions
274 if(data->own_state == OWN_STATE::PROGRAM) {
275 report_error(mem, func_name, "Core malloc functions reading non malloc globals", index, data);
276 }
277 data->own_state = OWN_STATE::MALLOC;
278 } else if (malloc_any_family) {
279 // Accessing C globals from malloc wrapper functions
280 // hard to infer as these access both regular and chunk memory, so leave things unchanged
281 } else {
282 if(data->own_state == OWN_STATE::MALLOC) {
283 report_error(mem, func_name, "Program reading malloc globals", index, data);
284 }
285 data->own_state = OWN_STATE::PROGRAM;
286 }
287 }
288 });
289 }
290
wasm2c_shadow_memory_store(wasm_rt_memory_t * mem,const char * func_name,uint32_t ptr,uint32_t ptr_size)291 WASM2C_FUNC_EXPORT void wasm2c_shadow_memory_store(wasm_rt_memory_t* mem, const char* func_name, uint32_t ptr, uint32_t ptr_size) {
292 check_heap_base_straddle(mem, func_name, ptr, ptr_size);
293
294 bool malloc_write_family = is_malloc_family_function(func_name, false /* calloc shouldn't modify metadata */, true);
295 bool malloc_core_family = is_malloc_family_function(func_name, false, false);
296 bool malloc_any_family = is_malloc_family_function(func_name, true, true);
297
298 memory_state_iterate(mem, ptr, ptr_size, [&](uint32_t index, cell_data_t* data){
299 // Is this function exempt from checking
300 bool exempt = false;
301
302 // Malloc family of functions store chunk related information in the heap
303 // These look like unitialized operations
304 if (index >= mem->shadow_memory.heap_base && malloc_write_family) {
305 exempt = true;
306 }
307
308 if (!exempt) {
309 if (data->alloc_state == ALLOC_STATE::UNINIT) {
310 report_error(mem, func_name, "Writing uninitialized memory", index, data);
311 } else if (data->alloc_state == ALLOC_STATE::ALLOCED) {
312 data->alloc_state = ALLOC_STATE::INITIALIZED;
313 }
314 }
315
316 // Accessing C globals --- infer if this is malloc or program data
317 if (index < mem->shadow_memory.heap_base) {
318 if (malloc_core_family) {
319 // Accessing C globals from core malloc functions
320 if(data->own_state == OWN_STATE::PROGRAM) {
321 report_error(mem, func_name, "Core malloc functions writing non malloc globals", index, data);
322 }
323 data->own_state = OWN_STATE::MALLOC;
324 } else if (malloc_any_family) {
325 // Accessing C globals from malloc wrapper functions
326 // hard to infer as these access both regular and chunk memory, so leave things unchanged
327 } else {
328 if(data->own_state == OWN_STATE::MALLOC) {
329 report_error(mem, func_name, "Program writing malloc globals", index, data);
330 }
331 data->own_state = OWN_STATE::PROGRAM;
332 }
333 }
334 });
335 }
336
wasm2c_shadow_memory_print_allocations(wasm_rt_memory_t * mem)337 WASM2C_FUNC_EXPORT void wasm2c_shadow_memory_print_allocations(wasm_rt_memory_t* mem) {
338 auto alloc_map = allocation_sizes_map(mem);
339 puts("{ ");
340 int counter = 0;
341 for (auto i = alloc_map->begin(); i != alloc_map->end(); ++i)
342 {
343 printf("%" PRIu32 ": %" PRIu32 ", ", i->first, i->second);
344 counter++;
345 if (counter >= 40) {
346 counter = 0;
347 puts("\n");
348 }
349 }
350 puts(" }");
351 }
352
wasm2c_shadow_memory_print_total_allocations(wasm_rt_memory_t * mem)353 WASM2C_FUNC_EXPORT uint64_t wasm2c_shadow_memory_print_total_allocations(wasm_rt_memory_t* mem) {
354 auto alloc_map = allocation_sizes_map(mem);
355 uint64_t used_memory = mem->shadow_memory.heap_base;
356 for (auto i = alloc_map->begin(); i != alloc_map->end(); ++i)
357 {
358 used_memory += i->second;
359 }
360 return used_memory;
361 }
362