-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathremediate.rs
More file actions
482 lines (403 loc) · 14.1 KB
/
remediate.rs
File metadata and controls
482 lines (403 loc) · 14.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
// Copyright (c) 2025 Riverside Research.
// LGPL-3; See LICENSE.txt in the repo root for details.
use libc::{
c_char, c_void, calloc, free, malloc, realloc, strdup, strlen, strndup, strnlen,
};
use crate::shadowobjs::{ALIVE_OBJ_LIST, AllocType, FREED_OBJ_LIST, ShadowObject, Vaddr};
use log::{error, info, trace, warn};
/**
* NOTE
* Libresolve supports adding shadow objects for stack objects
* but we do not currently support removing stack objects from
* ALIVE_OBJ_LIST once the stack objects are freed at the end
* of a function scope
**/
/**
* @brief - Allocator interface for stack objects
* @input - size of the pointer allocation in bytes
* @return - none
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_stack_obj(ptr: *mut c_void, size: usize) -> () {
let base = ptr as Vaddr;
{
let mut obj_list = ALIVE_OBJ_LIST.lock();
obj_list.add_shadow_object(AllocType::Stack, base, size);
}
info!("[STACK] Object allocated with size: {size}, address: 0x{base:x}");
}
#[unsafe(no_mangle)]
pub extern "C" fn resolve_invalidate_stack(base: *mut c_void) {
let base = base as Vaddr;
{
let mut obj_list = ALIVE_OBJ_LIST.lock();
obj_list.invalidate_at(base);
}
info!("[STACK] Free addr 0x{base:x}");
}
/**
* @brief - Allocator logging interface for malloc
* @input - size of the allocation in bytes
* @return - ptr to the allocation
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_malloc(size: usize) -> *mut c_void {
let ptr = unsafe { malloc(size + 1) };
if ptr.is_null() {
return ptr;
}
{
let mut obj_list = ALIVE_OBJ_LIST.lock();
obj_list.add_shadow_object(AllocType::Heap, ptr as Vaddr, size);
}
info!(
"[HEAP] Object allocated with size: {size}, address: 0x{:x}",
ptr as Vaddr
);
// Return the pointer
ptr
}
/**
* @brief - Function call to replace llvm 'gep' instruction
* @input
* - ptr: unique pointer root
* - derived: pointer derived from unique root ptr
* @return valid pointer within bounds of allocation or
* pointer 1-past limit of allocation
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_gep(ptr: *mut c_void, derived: *mut c_void) -> *mut c_void {
let base = ptr as Vaddr;
let derived = derived as Vaddr;
let sobj_table = ALIVE_OBJ_LIST.lock();
// Look up the shadow object corresponding to this access.
let Some(sobj) = sobj_table.search_intersection(base) else {
warn!("[GEP] Cannot find ptr 0x{base:x} in shadow table");
// NOTE: Not doing this right now
// In theory it could catch bugs where integers are forced to pointers...
// But there are too many allocation we don't know about, like those in libc
// or the argv pointer.
// return 0 as *mut c_void;
// Assume unknown pointers are safe
return derived as *mut c_void;
};
// If shadow object exists then check if the access is within bounds
if sobj.contains(derived as Vaddr) {
info!(
"[GEP] ptr 0x{derived:x} valid for base 0x{base:x}, obj: {}@0x{:x}",
sobj.size(),
sobj.base
);
return derived as *mut c_void;
}
error!(
"[GEP] ptr 0x{derived:x} not valid for base 0x{base:x}, obj: {}@0x{:x}",
sobj.size(),
sobj.base
);
// Return 1-past limit of allocation @ ptr
sobj.past_limit() as *mut c_void
}
/**
* @brief - Allocator logging interface for free
* @input - ptr to the allocation
* @return - none
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_free(ptr: *mut c_void) -> () {
// Insert a function to find the object and return the pointer size
// Do I need to handle if the sobj cannot be found?
info!(
"[FREE] Allocated object freed at address: 0x{:x}",
ptr as Vaddr
);
let ptr_size = {
let mut obj_list = ALIVE_OBJ_LIST.lock();
// Lookup shadow object
let sobj_opt = obj_list.search_intersection(ptr as Vaddr);
let size = sobj_opt.map(|o| o.size());
// remove shadow obj from live list
obj_list.invalidate_at(ptr as Vaddr);
size
};
// Check if the shadow object exists
match ptr_size {
Some(size) => {
info!(
"[FREE] Found shadow object for allocated object, 0x{:x}, size = {size}",
ptr as Vaddr,
);
}
None => {
warn!(
"[FREE] No shadow object found for allocated object: 0x{:x}",
ptr as Vaddr
);
}
}
{
// Insert shadow object into freed object list
let mut freed_guard = FREED_OBJ_LIST.lock();
freed_guard.add_shadow_object(AllocType::Unallocated, ptr as Vaddr, ptr_size.unwrap_or(0));
}
let _ = unsafe { free(ptr) };
}
/**
* @brief - Allocator logging interface for realloc
* @input
* - ptr: ptr to the original allocation
* - size: size of the allocation in bytes
* @return - none
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_realloc(ptr: *mut c_void, size: usize) -> *mut c_void {
// Edge cases
// 1. returned memory may not be allocated
// 2. pointer passed to realloc may be NULL
// 3. size fits within original allocation (returns the original ptr)
// Consideration: Pointer passed in may be invalidated so we need a mechanism
// to remove the shadow object for the orignal allocation
let realloc_ptr = unsafe { realloc(ptr, size + 1) };
if realloc_ptr.is_null() {
return realloc_ptr;
}
{
let mut obj_list = ALIVE_OBJ_LIST.lock();
// Remove shadow object for original pointer
obj_list.invalidate_at(ptr as Vaddr); // if ptr == NULL this does not do anything
obj_list.add_shadow_object(AllocType::Heap, realloc_ptr as Vaddr, size);
}
info!(
"[HEAP] Allocated object reallocated mem from src: {ptr:?}, size: {size}, dst ptr: 0x{:x}",
realloc_ptr as Vaddr
);
realloc_ptr
}
/**
* @brief - Allocator logging interface for calloc
* @input
* - n_items: number of items in the allocation
* - size: size of the allocation in bytes
* @return - none
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_calloc(n_items: usize, item_size: usize) -> *mut c_void {
let ptr = unsafe { calloc(n_items, item_size) };
let size = n_items * item_size;
if ptr.is_null() {
return ptr;
}
{
let mut obj_list = ALIVE_OBJ_LIST.lock();
obj_list.add_shadow_object(AllocType::Heap, ptr as Vaddr, size);
}
info!(
"[HEAP] Logging allocation with {n_items} items, size (bytes): {size}, dst ptr: 0x{:x}",
ptr as Vaddr
);
ptr
}
/**
* @brief - Allocator logging interface for strdup
* @input
* - ptr: ptr to the original allocation
* @return - pointer to the copied string
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_strdup(ptr: *mut c_char) -> *mut c_char {
let string_ptr = unsafe { strdup(ptr) };
if string_ptr.is_null() {
return string_ptr;
}
// +1 to include null termination byte. We should allow program to read this value.
// Otherwise how would the program find the end of the string?
// Although writing it to something else is probably a bad idea, this too should be allowed.
let sizeofstr = unsafe { strlen(ptr) + 1 };
{
let mut obj_list = ALIVE_OBJ_LIST.lock();
obj_list.add_shadow_object(AllocType::Heap, string_ptr as Vaddr, sizeofstr);
}
info!(
"[HEAP] Logging 'strdup' function call with dst ptr: 0x{:x}",
string_ptr as Vaddr
);
string_ptr
}
/**
* @brief - Allocator logging interface for strdup
* @input
* - ptr: ptr to the original allocation
* - size: number of bytes to copied
* @return - pointer to the copied string
* NOTE: Read this link to understand the nature of strdup & strndup
* https://pubs.opengroup.org/onlinepubs/9699919799/functions/strdup.html
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_strndup(ptr: *mut c_char, size: usize) -> *mut c_char {
let string_ptr = unsafe { strndup(ptr, size) };
if string_ptr.is_null() {
return string_ptr;
}
// +1 to include null termination byte. We should allow program to read this value.
// We don't actually know how much memory the libc will allocate, but
// strnlen(ptr, size) + 1 is a safe lower bound.
// strlen(string_ptr) + 1 would also be valid I think.
let sizeofstr = unsafe { strnlen(ptr, size) + 1 };
{
let mut obj_list = ALIVE_OBJ_LIST.lock();
obj_list.add_shadow_object(AllocType::Heap, string_ptr as Vaddr, sizeofstr);
}
info!(
"[HEAP] Logging 'strndup' function call with size (bytes): {size}, dst ptr: {:?}",
string_ptr as Vaddr
);
string_ptr
}
/**
* @brief - Returns true if pointer access is within bounds of a known allocation
* @input
* - base_ptr: address to be dereferenced
* - size: size of dereference in bytes
* @return true if base_ptr..size is totally within a valid shadow object
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_check_bounds(base_ptr: *mut c_void, size: usize) -> bool {
let base = base_ptr as Vaddr;
let sobj_table = ALIVE_OBJ_LIST.lock();
// Look up the shadow object corresponding to this access
if let Some(sobj) = sobj_table.search_intersection(base) {
// If shadow object exists then check if the access is within bounds
if sobj.contains(ShadowObject::limit(base, size)) {
// Access in Bounds
trace!(
"[BOUNDS] Access allowed {size}@0x{base:x} for allocation {}@0x{:x}",
sobj.size(),
sobj.base
);
return true;
} else {
error!(
"[BOUNDS] OOB access at {size}@0x{base:x} too big for allocation {}@0x{:x}",
sobj.size(),
sobj.base
);
return false;
}
}
// Check if this is an invalid pointer for one of the known shadow objects
if let Some(sobj) = sobj_table.search_invalid(base) {
error!(
"[BOUNDS] OOB access for {}@0x{:x}, invalid address computation",
sobj.size(),
sobj.base
);
return false;
}
// Not a tracked pointer, assume good to avoid trapping on otherwise valid pointers
// TODO: add a strict mode to reject here / add extra tracking.
true
}
#[unsafe(no_mangle)]
pub extern "C" fn resolve_obj_type(base_ptr: *mut c_void) -> AllocType {
let base = base_ptr as Vaddr;
let find_in = |table: &crate::MutexWrap<crate::shadowobjs::ShadowObjectTable>| {
let t = table.lock();
t.search_intersection(base).map(|o| o.alloc_type)
};
// Why does this search freed before alive?
let alloc_type = find_in(&FREED_OBJ_LIST).or_else(|| find_in(&ALIVE_OBJ_LIST));
alloc_type.unwrap_or(AllocType::Unknown)
}
/**
* @brief - Logs when program enters a sanitization basic block
* @input
* - ptr: Pointer that is being sanitized
* @return
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_report_sanitize_mem_inst_triggered(ptr: *mut c_void) {
info!(
"[SANITIZE] Applying sanitizer to address 0x{:x}",
ptr as Vaddr
);
}
/**
* @brief - Logs when program enters sanitization basic block for arithmetic operations
*/
#[unsafe(no_mangle)]
pub extern "C" fn resolve_report_sanitizer_triggered() -> () {
info!("[SANITIZE] Applying arithmetic sanitization in basic block");
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{resolve_init, shadowobjs::AllocType};
#[test]
fn test_malloc_free() {
resolve_init();
// Allocation should successfully return a memory block
let ptr = resolve_malloc(0x10);
assert!(!ptr.is_null());
// We should track the obj correctly
{
let table = ALIVE_OBJ_LIST.lock();
let obj = table.search_intersection(ptr as Vaddr);
assert!(obj.is_some());
let obj = obj.unwrap();
assert!(obj.size() == 0x10);
assert!(obj.base == ptr as Vaddr);
assert!(obj.alloc_type == AllocType::Heap);
}
resolve_free(ptr);
// After freeing a block we should track that it has been freed
{
let table = FREED_OBJ_LIST.lock();
let obj = table.search_intersection(ptr as Vaddr);
assert!(obj.is_some());
}
// And it should no longer be in the alive obj list.
{
let table = ALIVE_OBJ_LIST.lock();
let obj = table.search_intersection(ptr as Vaddr);
assert!(obj.is_none());
}
}
#[test]
fn test_resolve_check_bounds() {
resolve_init();
// Spray the heap a little
let ptrs: Vec<_> = (0x01..0x100).map(|i| resolve_malloc(i)).collect();
//let mut rng = rand::rng();
for (i, p) in ptrs.into_iter().enumerate() {
let i = i + 1;
assert!(!p.is_null());
// Check all valid bounds
for offset in 0..i {
// the size/offset must be greater than 0
assert!(
resolve_check_bounds(p, offset + 1),
"{:x}, {:x}",
p as usize,
offset + 1
);
for j in offset..i {
assert!(resolve_check_bounds(
unsafe { p.offset(offset as isize) },
i - j
));
}
}
// out of bounds accesses
// before first
// Current code allows this because we allow pointers we haven't tracked
// assert!(!resolve_check_bounds(unsafe { p.offset(-1) }, 1));
// after last
assert!(!resolve_check_bounds(unsafe { p.offset(i as isize) }, 1));
assert!(!resolve_check_bounds(p, i + 1));
// In theory all pointer arithmetic instructions translate into GetElementPtr
// instructions in llvm-ir which will verify that the base/derived pointers are valid
// and within the correct allocations.
resolve_free(p);
}
}
}