Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement tracking of nested parameters #432

Merged
merged 45 commits into from
Dec 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9f18101
better file structure
Enkelmann Nov 6, 2023
e319f97
generate recursive abstract locations on load instructions
Enkelmann Nov 6, 2023
d6cf1a8
more FnSig refactoring
Enkelmann Nov 8, 2023
24b11ea
nested param generation for PointerInference
Enkelmann Nov 9, 2023
9bdf0db
refactor abstract ID definition into several files
Enkelmann Nov 13, 2023
5712454
draft of ID deduplication process on return instructions
Enkelmann Nov 13, 2023
6eb338c
implement object merging for return object unification
Enkelmann Nov 15, 2023
59c8a2c
id renaming part one
Enkelmann Nov 16, 2023
4619c2b
pointer renaming, eval abstract locations
Enkelmann Nov 20, 2023
e6c959c
finish id renaming from callee to caller
Enkelmann Nov 20, 2023
2ad20f0
adjust VsaResult trait
Enkelmann Nov 20, 2023
9c45fc6
fix bugs and linter warnings
Enkelmann Nov 20, 2023
33b1324
cargo clippy
Enkelmann Nov 20, 2023
d80c04d
fix sizes of loaded values
Enkelmann Nov 20, 2023
0761220
add unit tests for AbstractLocation, fix bug
Enkelmann Nov 21, 2023
688b843
FnSig state memory handling unit tests
Enkelmann Nov 21, 2023
3198a95
refactoring, more unit tests
Enkelmann Nov 22, 2023
9318c8b
finish call handling unit tests
Enkelmann Nov 23, 2023
9bea23e
more context unit tests
Enkelmann Nov 23, 2023
dcdb7ea
fix bug in param sanitation
Enkelmann Nov 23, 2023
a3d568a
filter out negative stack offsets as parameter
Enkelmann Nov 23, 2023
4d6c143
fix stack register value for calls on x86
Enkelmann Nov 24, 2023
4192d61
do not mark offset IDs as dereferenced, filter non-dereferenced neste…
Enkelmann Nov 24, 2023
928a08a
Only track globals that are properly accessed.
Enkelmann Nov 28, 2023
feac417
update doc comment for FnSig analysis
Enkelmann Nov 28, 2023
9158419
fix eval of global abstract locations
Enkelmann Nov 29, 2023
2365ba5
test state creation from fn sig
Enkelmann Nov 29, 2023
d2d5784
unit test for state minimizaton
Enkelmann Nov 29, 2023
149bd72
add more unit tests
Enkelmann Nov 30, 2023
19245b3
small test code refactoring
Enkelmann Dec 1, 2023
9c10be0
more unit tests
Enkelmann Dec 1, 2023
aaff9ae
fix pointer sizes in eval_abstract_memory_location
Enkelmann Dec 1, 2023
3da7e2e
add unit test for full object replacement process
Enkelmann Dec 1, 2023
ad6be21
fix more unit tests
Enkelmann Dec 1, 2023
b57b27a
Track parameter IDs in return values of calls
Enkelmann Dec 4, 2023
075e005
fix for unkown parents locations of nested globals
Enkelmann Dec 5, 2023
5df1484
filter out IDs whose parent location was filtered out, reduce nesting…
Enkelmann Dec 5, 2023
afc3ea5
fix missing IDs in create_full_callee_id_to_caller_data_map
Enkelmann Dec 7, 2023
72c7011
fix callee ID to caller data map again
Enkelmann Dec 8, 2023
54f9d5c
fix clippy warning
Enkelmann Dec 8, 2023
73e87dc
disable test case due to unrelated false positive
Enkelmann Dec 11, 2023
490a95f
Fix edge case for stack parameter that were not properly marked as re…
Enkelmann Dec 11, 2023
698f0bb
Denote limited interprocedurality for CWE-119 check in doc comment
Enkelmann Dec 12, 2023
374657b
add missing unit test
Enkelmann Dec 12, 2023
90a20cc
adjust formatting format for abstract memory locations
Enkelmann Dec 12, 2023
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
332 changes: 332 additions & 0 deletions src/cwe_checker_lib/src/abstract_domain/identifier/location.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,332 @@
use super::AbstractMemoryLocation;
use crate::intermediate_representation::*;
use crate::prelude::*;

/// An abstract location describes how to find the value of a variable in memory at a given time.
///
/// It is defined recursively, where the root is either a register or a (constant) global address.
/// This way only locations that the local state knows about are representable.
/// It is also impossible to accidentally describe circular references.
#[derive(Serialize, Deserialize, Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
pub enum AbstractLocation {
/// The location is given by a register.
Register(Variable),
/// The value itself is a constant address to global memory.
/// Note that the `size` is the size of the pointer and not the size
/// of the value residing at the specific address in global memory.
GlobalAddress {
/// The address in global memory.
address: u64,
/// The byte size of the address (not the pointed-to value!).
size: ByteSize,
},
/// The location is in memory.
/// One needs to follow the pointer in the given register
/// and then follow the abstract memory location inside the pointed to memory object
/// to find the actual memory location.
Pointer(Variable, AbstractMemoryLocation),
/// The location is in memory.
/// One needs to follow the pointer located at the given global address
/// and then follow the abstract memory location inside the pointed to memory object
/// to find the actual memory location.
GlobalPointer(u64, AbstractMemoryLocation),
}

impl std::fmt::Display for AbstractLocation {
fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Self::Register(var) => write!(formatter, "{}", var.name)?,
Self::GlobalAddress { address, size: _ } => write!(formatter, "0x{address:x}")?,
Self::Pointer(var, location) => write!(formatter, "{}{}", var.name, location)?,
Self::GlobalPointer(address, location) => write!(formatter, "0x{address:x}{location}")?,
};
write!(formatter, ":i{}", self.bytesize().as_bit_length())
}
}

impl AbstractLocation {
/// Create an abstract location from a variable corresponding to a register.
/// This function returns an error if the variable is not a physical register.
pub fn from_var(variable: &Variable) -> Result<AbstractLocation, Error> {
if variable.is_temp {
return Err(anyhow!(
"Cannot create abstract location from temporary variables."
));
}
Ok(AbstractLocation::Register(variable.clone()))
}

/// Create an abstract location on the stack.
/// The returned location describes the value of the given `size`
/// at the given `offset` relative to the memory location that the `stack_register` is pointing to.
pub fn from_stack_position(
stack_register: &Variable,
offset: i64,
size: ByteSize,
) -> AbstractLocation {
let stack_pos = AbstractMemoryLocation::Location { offset, size };
AbstractLocation::Pointer(stack_register.clone(), stack_pos)
}

/// Create an abstract location representing an address pointing to global memory.
pub fn from_global_address(address: &Bitvector) -> AbstractLocation {
let size = address.bytesize();
let address = address
.try_to_u64()
.expect("Global address larger than 64 bits encountered.");
AbstractLocation::GlobalAddress { address, size }
}

/// Add an offset to the abstract location.
pub fn with_offset_addendum(self, addendum: i64) -> AbstractLocation {
match self {
Self::Register(_) => panic!("Cannot add an offset to a register abstract location"),
Self::GlobalAddress { address, size } => Self::GlobalAddress {
address: address + (addendum as u64),
size,
},
Self::Pointer(var, mut location) => {
location.add_offset(addendum);
Self::Pointer(var, location)
}
Self::GlobalPointer(address, mut location) => {
location.add_offset(addendum);
Self::GlobalPointer(address, location)
}
}
}

/// Return the abstract location that one gets when dereferencing the pointer that `self` is pointing to.
///
/// Panics if `self` is not pointer-sized.
pub fn dereferenced(
self,
new_size: ByteSize,
generic_pointer_size: ByteSize,
) -> AbstractLocation {
match self {
Self::Register(var) => Self::Pointer(
var,
AbstractMemoryLocation::Location {
offset: 0,
size: new_size,
},
),
Self::GlobalAddress { address, size } => {
assert_eq!(
size, generic_pointer_size,
"Cannot dereference an abstract memory location that is not pointer-sized."
);
Self::GlobalPointer(
address,
AbstractMemoryLocation::Location {
offset: 0,
size: new_size,
},
)
}
Self::GlobalPointer(address, mut location) => {
location.dereference(new_size, generic_pointer_size);
Self::GlobalPointer(address, location)
}
Self::Pointer(var, mut location) => {
location.dereference(new_size, generic_pointer_size);
Self::Pointer(var.clone(), location)
}
}
}

/// Get the bytesize of the value represented by the abstract location.
pub fn bytesize(&self) -> ByteSize {
match self {
Self::Register(var) => var.size,
Self::GlobalAddress { size, .. } => *size,
Self::Pointer(_, mem_location) | Self::GlobalPointer(_, mem_location) => {
mem_location.bytesize()
}
}
}

/// Get the recursion depth of the abstract location,
/// i.e. how many times one has to dereference a pointer until reaching the actual location.
pub fn recursion_depth(&self) -> u64 {
match self {
Self::Register(_) => 0,
Self::GlobalAddress { .. } => 1,
Self::Pointer(_, mem_location) | Self::GlobalPointer(_, mem_location) => {
1 + mem_location.recursion_depth()
}
}
}

/// Extend the location string by adding further derefence operations to it according to the given extension.
pub fn extend(&mut self, extension: AbstractMemoryLocation, generic_pointer_size: ByteSize) {
match self {
Self::Pointer(_, location) | Self::GlobalPointer(_, location) => {
location.extend(extension, generic_pointer_size);
}
Self::GlobalAddress { address, size } => {
assert_eq!(*size, generic_pointer_size);
*self = Self::GlobalPointer(*address, extension);
}
Self::Register(var) => {
assert_eq!(var.size, generic_pointer_size);
*self = Self::Pointer(var.clone(), extension);
}
}
}

/// Get the abstract location representing the pointer pointing to the memory object
/// that contains the location represented by `self`
/// together with the offset that one has to add to the pointer to get the location of self.
///
/// Returns an error if the abstract location contains no dereference operation
/// (e.g. if `self` represents a register value).
pub fn get_parent_location(
&self,
generic_pointer_size: ByteSize,
) -> Result<(AbstractLocation, i64), Error> {
match self {
AbstractLocation::GlobalAddress { .. } | AbstractLocation::Register(_) => {
Err(anyhow!("Root location without a parent."))
}
AbstractLocation::GlobalPointer(address, location) => {
match location.get_parent_location(generic_pointer_size) {
Ok((inner_parent_location, innermost_offset)) => Ok((
Self::GlobalPointer(*address, inner_parent_location),
innermost_offset,
)),
Err(innermost_offset) => Ok((
Self::GlobalAddress {
address: *address,
size: generic_pointer_size,
},
innermost_offset,
)),
}
}
AbstractLocation::Pointer(var, location) => {
match location.get_parent_location(generic_pointer_size) {
Ok((inner_parent_location, innermost_offset)) => Ok((
Self::Pointer(var.clone(), inner_parent_location),
innermost_offset,
)),
Err(innermost_offset) => Ok((Self::Register(var.clone()), innermost_offset)),
}
}
}
}

/// Get a list of all (recursive) parent locations.
/// The list is sorted by recursion depth, starting with the root location.
pub fn get_all_parent_locations(
&self,
generic_pointer_size: ByteSize,
) -> Vec<AbstractLocation> {
match self {
AbstractLocation::GlobalAddress { .. } | AbstractLocation::Register(_) => Vec::new(),
AbstractLocation::GlobalPointer(_, _) | AbstractLocation::Pointer(_, _) => {
let (parent, _) = self.get_parent_location(generic_pointer_size).unwrap();
let mut all_parents = parent.get_all_parent_locations(generic_pointer_size);
all_parents.push(parent);
all_parents
}
}
}
}

#[cfg(test)]
pub mod tests {
use super::*;
use crate::variable;

impl AbstractLocation {
/// Mock an abstract location with a variable as root.
pub fn mock(
root_var: &str,
offsets: &[i64],
size: impl Into<ByteSize>,
) -> AbstractLocation {
let var = variable!(root_var);
match offsets {
[] => {
assert_eq!(var.size, size.into());
AbstractLocation::Register(var)
}
_ => AbstractLocation::Pointer(var, AbstractMemoryLocation::mock(offsets, size)),
}
}
/// Mock an abstract location with a global address as root.
pub fn mock_global(
root_address: u64,
offsets: &[i64],
size: impl Into<ByteSize>,
) -> AbstractLocation {
match offsets {
[] => AbstractLocation::GlobalAddress {
address: root_address,
size: size.into(),
},
_ => AbstractLocation::GlobalPointer(
root_address,
AbstractMemoryLocation::mock(offsets, size),
),
}
}
}

#[test]
fn test_from_variants() {
let loc = AbstractLocation::from_var(&variable!("RAX:8")).unwrap();
assert_eq!(&format!("{loc}"), "RAX:i64");
let loc = AbstractLocation::from_global_address(&Bitvector::from_u64(32));
assert_eq!(
loc,
AbstractLocation::GlobalAddress {
address: 32,
size: ByteSize::new(8)
}
);
let loc = AbstractLocation::from_stack_position(&variable!("RSP:8"), 16, ByteSize::new(8));
assert_eq!(loc, AbstractLocation::mock("RSP:8", &[16], 8));
}

#[test]
fn test_with_offset_addendum() {
let loc = AbstractLocation::mock("RAX:8", &[1, 2, 3], 4).with_offset_addendum(12);
assert_eq!(loc, AbstractLocation::mock("RAX:8", &[1, 2, 15], 4));
}

#[test]
fn test_dereferenced() {
let loc = AbstractLocation::mock("RAX:8", &[], 8)
.dereferenced(ByteSize::new(4), ByteSize::new(8));
assert_eq!(loc, AbstractLocation::mock("RAX:8", &[0], 4));
}

#[test]
fn test_recursion_depth() {
let loc = AbstractLocation::mock("RAX:8", &[1, 2, 3], 4);
assert_eq!(loc.recursion_depth(), 3);
}

#[test]
fn test_extend() {
let mut loc = AbstractLocation::mock("RAX:8", &[1, 2, 3], 4);
let extension = AbstractMemoryLocation::mock(&[4, 5, 6], 1);
loc.extend(extension, ByteSize::new(4));
assert_eq!(loc, AbstractLocation::mock("RAX:8", &[1, 2, 3, 4, 5, 6], 1));
}

#[test]
fn test_get_parent_location() {
let loc = AbstractLocation::mock("RAX:8", &[1], 4);
let (parent, last_offset) = loc.get_parent_location(ByteSize::new(8)).unwrap();
assert_eq!(parent, AbstractLocation::mock("RAX:8", &[], 8));
assert_eq!(last_offset, 1);
let loc = AbstractLocation::mock("RAX:8", &[1, 2, 3], 4);
let (parent, last_offset) = loc.get_parent_location(ByteSize::new(8)).unwrap();
assert_eq!(parent, AbstractLocation::mock("RAX:8", &[1, 2], 8));
assert_eq!(last_offset, 3);
}
}
Loading
Loading