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