Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,7 @@ examples/openssl_reach_out.json
reach_wrap_input.json
reach_wrap_output.json

resolve_log_*.out

# compiled library folder
comp_libs/
11 changes: 7 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,17 @@ all: build

build: build-resolve-facts build-llvm-plugin build-libresolve build-reach build-klee

lib-folder:
mkdir -p comp_libs

build-resolve-facts: resolve-facts
+$(MAKE) -C resolve-facts

build-llvm-plugin: llvm-plugin build-libresolve build-resolve-facts
+$(MAKE) -C llvm-plugin
build-llvm-plugin: lib-folder llvm-plugin build-libresolve build-resolve-facts
+$(MAKE) -C llvm-plugin && cp llvm-plugin/build/lib*.so comp_libs/

build-libresolve: libresolve
cd libresolve && RUSTFLAGS="-D warnings" cargo build --release
build-libresolve: lib-folder libresolve
cd libresolve && RUSTFLAGS="-D warnings" cargo build --release && cp target/release/libresolve.so ../comp_libs

test: test-llvm-plugin test-libresolve

Expand Down
4 changes: 4 additions & 0 deletions libresolve/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
// LGPL-3; See LICENSE.txt in the repo root for details.

#![feature(btree_cursors)]
#![feature(macro_metavar_expr_concat)]
#![feature(test)]
extern crate test;


mod remediate;
mod shadowobjs;
Expand Down
186 changes: 130 additions & 56 deletions libresolve/src/remediate.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,13 @@
// 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 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 crate::shadowobjs::{
ALIVE_OBJ_LIST, AllocType, FREED_OBJ_LIST, STACK_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
Expand All @@ -24,28 +16,61 @@ use log::{error, info, trace, warn};
#[unsafe(no_mangle)]
pub extern "C" fn resolve_stack_obj(ptr: *mut c_void, size: usize) -> () {
let base = ptr as Vaddr;

STACK_OBJ_LIST.with_borrow_mut(|l| {
l.add_shadow_object(AllocType::Stack, base, size);
});

/*
{
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, limit: *mut c_void) {
pub extern "C" fn resolve_invalidate_stack(base: *mut c_void) {
let base = base as Vaddr;
let limit = limit as Vaddr;

{
let mut obj_list = ALIVE_OBJ_LIST.lock();
// TODO: Add these to a free list?
obj_list.invalidate_region(base, limit);
}
STACK_OBJ_LIST.with_borrow_mut(|l| {
l.invalidate_at(base);
});

info!("[STACK] Free address 0x{base:x}");
}
*/

macro_rules! invalidate_stacks {
($name:ident; $($ns:literal),+) => {

#[unsafe(no_mangle)]
pub extern "C" fn $name($(${concat(base_, $ns)}: *mut c_void, )+) {

STACK_OBJ_LIST.with_borrow_mut(|l| {
$(
l.invalidate_at(${concat(base_, $ns)} as Vaddr);
info!("[STACK] Free address 0x{:x}", ${concat(base_, $ns)} as Vaddr);
)+
});

info!("[STACK] Free range 0x{base:x}..=0x{limit:x}");
}

}
}

// the x64 ABI allows up to 6 arguments to be passed via register,
// so provide that many functions as we cannot define a variadic
invalidate_stacks!(resolve_invalidate_stack; 1);
invalidate_stacks!(resolve_invalidate_stack_2; 1, 2);
invalidate_stacks!(resolve_invalidate_stack_3; 1, 2, 3);
invalidate_stacks!(resolve_invalidate_stack_4; 1, 2, 3, 4);
invalidate_stacks!(resolve_invalidate_stack_5; 1, 2, 3, 4, 5);
invalidate_stacks!(resolve_invalidate_stack_6; 1, 2, 3, 4, 5, 6);

/**
* @brief - Allocator logging interface for malloc
* @input - size of the allocation in bytes
Expand Down Expand Up @@ -78,15 +103,46 @@ pub extern "C" fn resolve_malloc(size: usize) -> *mut c_void {
* @input
* - ptr: unique pointer root
* - derived: pointer derived from unique root ptr
* - max_access: the largest size of a load/store to the derived address.
* @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 {
pub extern "C" fn resolve_gep(
ptr: *mut c_void,
derived: *mut c_void,
max_access: usize,
) -> *mut c_void {
let base = ptr as Vaddr;
let derived = derived as Vaddr;

let contains_or_err = |obj: &ShadowObject| {
// If shadow object exists then check if the access is within bounds
if obj.contains(ShadowObject::limit(derived, max_access)) {
trace!(
"[GEP] ptr {max_access}x{derived:x} valid for base 0x{base:x}, obj: {}@0x{:x}",
obj.size(),
obj.base
);
return derived as *mut c_void;
}

error!(
"[GEP] ptr {max_access}x{derived:x} not valid for base 0x{base:x}, obj: {}@0x{:x}",
obj.size(),
obj.base
);

// Pointer known-invalid, return NULL to indicate failure
0 as *mut c_void
};

// Check the local stack list first since it is cheaper.
if let Some(sobj) = STACK_OBJ_LIST.with_borrow(|l| l.search_intersection(base).cloned()) {
return contains_or_err(&sobj);
};

let sobj_table = ALIVE_OBJ_LIST.lock();

// Look up the shadow object corresponding to this access.
Expand All @@ -103,27 +159,9 @@ pub extern "C" fn resolve_gep(ptr: *mut c_void, derived: *mut c_void) -> *mut c_
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
contains_or_err(sobj)
}


/**
* @brief - Allocator logging interface for free
* @input - ptr to the allocation
Expand Down Expand Up @@ -184,10 +222,10 @@ pub extern "C" fn resolve_free(ptr: *mut c_void) -> () {
#[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
// 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) };
Expand All @@ -196,7 +234,6 @@ pub extern "C" fn resolve_realloc(ptr: *mut c_void, size: usize) -> *mut c_void
return realloc_ptr;
}


{
let mut obj_list = ALIVE_OBJ_LIST.lock();
// Remove shadow object for original pointer
Expand Down Expand Up @@ -319,27 +356,34 @@ pub extern "C" fn resolve_strndup(ptr: *mut c_char, size: usize) -> *mut c_char
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) {
let contains_or_err = |obj: &ShadowObject| {
// If shadow object exists then check if the access is within bounds
if sobj.contains(ShadowObject::limit(base, size)) {
if obj.contains(ShadowObject::limit(base, size)) {
// Access in Bounds
trace!(
"[BOUNDS] Access allowed {size}@0x{base:x} for allocation {}@0x{:x}",
sobj.size(),
sobj.base
obj.size(),
obj.base
);
return true;
} else {
error!(
"[BOUNDS] OOB access at {size}@0x{base:x} too big for allocation {}@0x{:x}",
sobj.size(),
sobj.base
obj.size(),
obj.base
);
return false;
}
};

if let Some(sobj) = STACK_OBJ_LIST.with_borrow(|l| l.search_intersection(base).cloned()) {
return contains_or_err(&sobj);
}

// Look up the shadow object corresponding to this access
let sobj_table = ALIVE_OBJ_LIST.lock();
if let Some(sobj) = sobj_table.search_intersection(base) {
return contains_or_err(sobj);
}

// Check if this is an invalid pointer for one of the known shadow objects
Expand All @@ -352,6 +396,8 @@ pub extern "C" fn resolve_check_bounds(base_ptr: *mut c_void, size: usize) -> bo
return false;
}

warn!("[BOUNDS] unknown pointer 0x:{base:x}");

// 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
Expand All @@ -361,13 +407,21 @@ pub extern "C" fn resolve_check_bounds(base_ptr: *mut c_void, size: usize) -> bo
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)
let find_in = |table: &crate::shadowobjs::ShadowObjectTable| {
table.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));
let alloc_type = STACK_OBJ_LIST
.with_borrow(|l| find_in(l))
.or_else(|| {
let l = FREED_OBJ_LIST.lock();
find_in(&l)
})
.or_else(|| {
let l = ALIVE_OBJ_LIST.lock();
find_in(&l)
});

alloc_type.unwrap_or(AllocType::Unknown)
}
Expand Down Expand Up @@ -398,6 +452,7 @@ pub extern "C" fn resolve_report_sanitizer_triggered() -> () {
mod tests {
use super::*;
use crate::{resolve_init, shadowobjs::AllocType};
use test::Bencher;

#[test]
fn test_malloc_free() {
Expand Down Expand Up @@ -481,4 +536,23 @@ mod tests {
resolve_free(p);
}
}

#[bench]
fn bench_resolve_stack(b: &mut Bencher) {
resolve_init();

let addrs: Vec<_> = (0x7FFF_0000_0000_0000..0x7FFF_0000_0001_0000)
.map(|a: usize| a as *mut c_void)
.collect();

b.iter(|| {
addrs.iter().for_each(|a| resolve_stack_obj(*a, 1));

addrs.iter().for_each(|&a| {
let _ = resolve_gep(a, a, 1);
});

addrs.iter().for_each(|a| resolve_invalidate_stack(*a));
});
}
}
5 changes: 5 additions & 0 deletions libresolve/src/shadowobjs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// LGPL-3; See LICENSE.txt in the repo root for details.

use crate::MutexWrap;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::ops::RangeInclusive;
use std::ops::Bound::Included;
Expand Down Expand Up @@ -94,6 +95,7 @@ impl ShadowObjectTable {
}

/// Removes any allocation with a base address within the supplied region
#[allow(dead_code)]
pub fn invalidate_region(&mut self, base: Vaddr, limit: Vaddr) {
self.table
.extract_if(base..=limit, |_, _| true)
Expand Down Expand Up @@ -130,6 +132,9 @@ impl ShadowObjectTable {
}

// static object lists to store all objects
thread_local! {
pub static STACK_OBJ_LIST: RefCell<ShadowObjectTable> = RefCell::new(ShadowObjectTable::new());
}
pub static ALIVE_OBJ_LIST: MutexWrap<ShadowObjectTable> = MutexWrap::new(ShadowObjectTable::new());
pub static FREED_OBJ_LIST: MutexWrap<ShadowObjectTable> = MutexWrap::new(ShadowObjectTable::new());

Expand Down
7 changes: 4 additions & 3 deletions llvm-plugin/Makefile
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
all: build test

.PHONY: clangformat test
.PHONY: all build clangformat test

all: build test

build: src
mkdir -p build
cd build && cmake ../src && make -j4
cmake -S ./src -B ./build && cd build && make -j4

clangformat: src
cd build && make clangformat
Expand Down
2 changes: 1 addition & 1 deletion llvm-plugin/src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ if(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE RelWithDebInfo)
endif()

# set(CMAKE_CXX_STANDARD 17 STRING "")
set(CMAKE_CXX_STANDARD 20 STRING "")

# LLVM is normally built without RTTI. Be consistent with that.
if(NOT LLVM_ENABLE_RTTI)
Expand Down
Loading
Loading