Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
C
cwe_checker
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
fact-depend
cwe_checker
Commits
f6ced95c
Unverified
Commit
f6ced95c
authored
a year ago
by
Enkelmann
Committed by
GitHub
a year ago
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Implement tracking of nested parameters (#432)
parent
270b4d4e
Hide whitespace changes
Inline
Side-by-side
Showing
40 changed files
with
3529 additions
and
895 deletions
+3529
-895
location.rs
...we_checker_lib/src/abstract_domain/identifier/location.rs
+332
-0
mem_location.rs
...hecker_lib/src/abstract_domain/identifier/mem_location.rs
+206
-0
mod.rs
src/cwe_checker_lib/src/abstract_domain/identifier/mod.rs
+23
-134
mod.rs
...hecker_lib/src/analysis/function_signature/context/mod.rs
+72
-43
tests.rs
...cker_lib/src/analysis/function_signature/context/tests.rs
+140
-10
global_var_propagation.rs
...src/analysis/function_signature/global_var_propagation.rs
+121
-26
mod.rs
src/cwe_checker_lib/src/analysis/function_signature/mod.rs
+162
-157
mod.rs
...rc/analysis/function_signature/state/call_handling/mod.rs
+170
-76
tests.rs
.../analysis/function_signature/state/call_handling/tests.rs
+214
-0
memory_handling.rs
.../src/analysis/function_signature/state/memory_handling.rs
+328
-0
mod.rs
..._checker_lib/src/analysis/function_signature/state/mod.rs
+99
-133
tests.rs
...hecker_lib/src/analysis/function_signature/state/tests.rs
+0
-67
tests.rs
src/cwe_checker_lib/src/analysis/function_signature/tests.rs
+43
-34
id_manipulation.rs
...src/analysis/pointer_inference/context/id_manipulation.rs
+77
-22
mod.rs
...checker_lib/src/analysis/pointer_inference/context/mod.rs
+46
-10
tests.rs
...ecker_lib/src/analysis/pointer_inference/context/tests.rs
+28
-13
trait_impls.rs
...lib/src/analysis/pointer_inference/context/trait_impls.rs
+26
-19
mod.rs
src/cwe_checker_lib/src/analysis/pointer_inference/mod.rs
+3
-1
mod.rs
..._checker_lib/src/analysis/pointer_inference/object/mod.rs
+12
-0
list_manipulation.rs
...alysis/pointer_inference/object_list/list_manipulation.rs
+5
-1
mod.rs
...ker_lib/src/analysis/pointer_inference/object_list/mod.rs
+20
-0
access_handling.rs
...b/src/analysis/pointer_inference/state/access_handling.rs
+88
-2
id_manipulation.rs
...b/src/analysis/pointer_inference/state/id_manipulation.rs
+387
-0
mod.rs
...e_checker_lib/src/analysis/pointer_inference/state/mod.rs
+207
-23
access_handling.rs
...analysis/pointer_inference/state/tests/access_handling.rs
+152
-0
id_manipulation.rs
...analysis/pointer_inference/state/tests/id_manipulation.rs
+312
-0
mod.rs
...ker_lib/src/analysis/pointer_inference/state/tests/mod.rs
+197
-112
vsa_result_impl.rs
...ker_lib/src/analysis/pointer_inference/vsa_result_impl.rs
+12
-1
mod.rs
...hecker_lib/src/analysis/string_abstraction/context/mod.rs
+1
-1
mod.rs
src/cwe_checker_lib/src/analysis/vsa_results/mod.rs
+8
-0
bounds_computation.rs
...er_lib/src/checkers/cwe_119/context/bounds_computation.rs
+4
-2
mod.rs
src/cwe_checker_lib/src/checkers/cwe_119/context/mod.rs
+2
-0
param_replacement.rs
...ker_lib/src/checkers/cwe_119/context/param_replacement.rs
+3
-2
mod.rs
src/cwe_checker_lib/src/checkers/cwe_119/mod.rs
+19
-2
state.rs
src/cwe_checker_lib/src/checkers/cwe_119/state.rs
+1
-1
context.rs
src/cwe_checker_lib/src/checkers/cwe_416/context.rs
+1
-1
mod.rs
src/cwe_checker_lib/src/checkers/cwe_416/mod.rs
+1
-1
term.rs
src/cwe_checker_lib/src/intermediate_representation/term.rs
+5
-0
mod.rs
src/cwe_checker_lib/src/pipeline/mod.rs
+1
-1
lib.rs
test/src/lib.rs
+1
-0
No files found.
src/cwe_checker_lib/src/abstract_domain/identifier/location.rs
0 → 100644
View file @
f6ced95c
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
);
}
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/abstract_domain/identifier/mem_location.rs
0 → 100644
View file @
f6ced95c
use
crate
::
prelude
::
*
;
/// An abstract memory location is either an offset from the given location, where the actual value can be found,
/// or an offset to a pointer to another memory location,
/// where the value can be found by (recursively) following the embedded `target` memory location.
///
/// The offset and size variables are given in bytes.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone,
PartialOrd,
Ord)]
pub
enum
AbstractMemoryLocation
{
/// A location inside the current memory object.
Location
{
/// The offset with respect to the zero offset of the memory object where the value can be found.
offset
:
i64
,
/// The size in bytes of the value that the memory location points to.
size
:
ByteSize
,
},
/// A pointer which needs to be followed to get to the actual memory location
Pointer
{
/// The offset inside the current memory object where the pointer can be found.
offset
:
i64
,
/// The memory location inside the target of the pointer that this memory location points to.
target
:
Box
<
AbstractMemoryLocation
>
,
},
}
impl
AbstractMemoryLocation
{
/// Get the abstract memory 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.
///
/// If `self` is a location (and not a pointer), return the offset in the location instead.
pub
fn
get_parent_location
(
&
self
,
generic_pointer_size
:
ByteSize
,
)
->
Result
<
(
AbstractMemoryLocation
,
i64
),
i64
>
{
match
self
{
Self
::
Location
{
offset
,
..
}
=>
Err
(
*
offset
),
Self
::
Pointer
{
offset
,
target
}
=>
{
match
target
.get_parent_location
(
generic_pointer_size
)
{
Ok
((
inner_parent
,
innermost_offset
))
=>
Ok
((
Self
::
Pointer
{
offset
:
*
offset
,
target
:
Box
::
new
(
inner_parent
),
},
innermost_offset
,
)),
Err
(
inner_offset
)
=>
Ok
((
Self
::
Location
{
offset
:
*
offset
,
size
:
generic_pointer_size
,
},
inner_offset
,
)),
}
}
}
}
/// Add an offset to a memory location.
pub
fn
add_offset
(
&
mut
self
,
addendum
:
i64
)
{
match
self
{
Self
::
Location
{
offset
,
..
}
=>
*
offset
+=
addendum
,
Self
::
Pointer
{
target
,
..
}
=>
target
.add_offset
(
addendum
),
}
}
/// Add an offset to the root location of the memory location.
pub
fn
add_offset_at_root
(
&
mut
self
,
addendum
:
i64
)
{
match
self
{
Self
::
Location
{
offset
,
..
}
|
Self
::
Pointer
{
offset
,
..
}
=>
*
offset
+=
addendum
,
}
}
/// Dereference the pointer that `self` is pointing to.
///
/// Panics if the old value of `self` is not pointer-sized.
pub
fn
dereference
(
&
mut
self
,
new_size
:
ByteSize
,
generic_pointer_size
:
ByteSize
)
{
match
self
{
Self
::
Pointer
{
target
,
..
}
=>
target
.dereference
(
new_size
,
generic_pointer_size
),
Self
::
Location
{
offset
,
size
}
=>
{
assert_eq!
(
*
size
,
generic_pointer_size
,
"Cannot dereference an abstract memory location that is not pointer-sized."
);
*
self
=
Self
::
Pointer
{
offset
:
*
offset
,
target
:
Box
::
new
(
Self
::
Location
{
offset
:
0
,
size
:
new_size
,
}),
}
}
};
}
/// 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
::
Location
{
offset
,
size
}
=>
{
assert_eq!
(
*
size
,
generic_pointer_size
);
*
self
=
Self
::
Pointer
{
offset
:
*
offset
,
target
:
Box
::
new
(
extension
),
};
}
Self
::
Pointer
{
target
,
..
}
=>
target
.extend
(
extension
,
generic_pointer_size
),
}
}
/// Get the bytesize of the value represented by the abstract memory location.
pub
fn
bytesize
(
&
self
)
->
ByteSize
{
match
self
{
Self
::
Location
{
size
,
..
}
=>
*
size
,
Self
::
Pointer
{
target
,
..
}
=>
target
.bytesize
(),
}
}
/// Get the recursion depth of the abstract memory 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
::
Location
{
..
}
=>
0
,
Self
::
Pointer
{
target
,
..
}
=>
1
+
target
.recursion_depth
(),
}
}
}
impl
std
::
fmt
::
Display
for
AbstractMemoryLocation
{
fn
fmt
(
&
self
,
formatter
:
&
mut
std
::
fmt
::
Formatter
)
->
std
::
fmt
::
Result
{
match
self
{
Self
::
Location
{
offset
,
..
}
=>
write!
(
formatter
,
"[0x{offset:x}]"
),
Self
::
Pointer
{
offset
,
target
}
=>
write!
(
formatter
,
"[0x{offset:x}]{target}"
),
}
}
}
#[cfg(test)]
pub
mod
tests
{
use
super
::
*
;
impl
AbstractMemoryLocation
{
/// Mock a memory location with a given sequence of offsets.
/// The first element in the sequence is the root offset.
pub
fn
mock
(
offsets
:
&
[
i64
],
size
:
impl
Into
<
ByteSize
>
)
->
AbstractMemoryLocation
{
match
offsets
{
[]
=>
panic!
(),
[
offset
]
=>
AbstractMemoryLocation
::
Location
{
offset
:
*
offset
,
size
:
size
.into
(),
},
[
offset
,
tail
@
..
]
=>
AbstractMemoryLocation
::
Pointer
{
offset
:
*
offset
,
target
:
Box
::
new
(
AbstractMemoryLocation
::
mock
(
tail
,
size
)),
},
}
}
}
#[test]
fn
test_mock
()
{
let
loc
=
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
3
],
4
);
assert_eq!
(
&
format!
(
"{loc}"
),
"[0x1][0x2][0x3]"
);
}
#[test]
fn
test_get_parent_location
()
{
let
loc
=
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
3
],
4
);
let
(
parent_loc
,
last_offset
)
=
loc
.get_parent_location
(
ByteSize
::
new
(
8
))
.unwrap
();
assert_eq!
(
parent_loc
,
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
],
8
));
assert_eq!
(
last_offset
,
3
);
let
loc
=
AbstractMemoryLocation
::
mock
(
&
[
1
],
4
);
assert
!
(
loc
.get_parent_location
(
ByteSize
::
new
(
8
))
.is_err
());
}
#[test]
fn
test_offset_addendums
()
{
let
mut
loc
=
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
3
],
4
);
loc
.add_offset
(
6
);
assert_eq!
(
&
loc
,
&
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
9
],
4
));
loc
.add_offset_at_root
(
-
5
);
assert_eq!
(
&
loc
,
&
AbstractMemoryLocation
::
mock
(
&
[
-
4
,
2
,
9
],
4
));
}
#[test]
fn
test_dereference
()
{
let
mut
loc
=
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
3
],
4
);
loc
.dereference
(
ByteSize
::
new
(
8
),
ByteSize
::
new
(
4
));
assert_eq!
(
loc
,
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
3
,
0
],
8
))
}
#[test]
fn
test_extend
()
{
let
mut
loc
=
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
3
],
4
);
let
extension
=
AbstractMemoryLocation
::
mock
(
&
[
4
,
5
,
6
],
1
);
loc
.extend
(
extension
,
ByteSize
::
new
(
4
));
assert_eq!
(
loc
,
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
3
,
4
,
5
,
6
],
1
));
}
#[test]
fn
test_recursion_depth
()
{
let
loc
=
AbstractMemoryLocation
::
mock
(
&
[
1
,
2
,
3
],
4
);
assert_eq!
(
loc
.recursion_depth
(),
2
);
let
loc
=
AbstractMemoryLocation
::
mock
(
&
[
1
],
4
);
assert_eq!
(
loc
.recursion_depth
(),
0
);
}
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/abstract_domain/identifier.rs
→
src/cwe_checker_lib/src/abstract_domain/identifier
/mod
.rs
View file @
f6ced95c
...
...
@@ -3,12 +3,17 @@ use crate::prelude::*;
use
derive_more
::
Deref
;
use
std
::
sync
::
Arc
;
mod
location
;
pub
use
location
::
AbstractLocation
;
mod
mem_location
;
pub
use
mem_location
::
AbstractMemoryLocation
;
/// An abstract identifier is used to identify an object or a value in an abstract state.
///
/// Since many program states can be represented by the same abstract state in data-flow analysis,
/// one sometimes needs a way to uniquely identify a variable or a memory object in all of the represented program states.
/// Abstract identifiers achieve this by identifying a *time*, i.e. a specific abstract state,
/// and a *location*, i.e. a recipe for
abstrac
ting a concrete value from any concrete state that is represented by the abstract state.
/// and a *location*, i.e. a recipe for
compu
ting a concrete value from any concrete state that is represented by the abstract state.
/// The value in question then serves as the identifier.
/// For example, a pointer may uniquely determine the memory object it is pointing to.
/// Or a value may represent the value of a variable at a certain time,
...
...
@@ -20,15 +25,15 @@ use std::sync::Arc;
/// E.g. it may represent the union of all values at the specific *location* for each time the program point is visited during an execution trace
/// or it may only represent the value at the last time the program point was visited.
///
/// Alternatively one can also add path hints to an identifier to further distinguish points in time in an execution trace.
/// Alternatively
,
one can also add path hints to an identifier to further distinguish points in time in an execution trace.
/// Path hints are given as a possibly empty array of time identifiers.
/// To prevent infinitely long path hints, each time identifier is only allowed to appear at most once in the array.
/// The specific meaning of the path hints depends upon the use case.
///
/// An abstract identifier is given by a time identifier, a location identifier and a path hints array (containing time identifiers).
///
/// For the location identifier see
`AbstractLocation`
.
/// The time identifier is given by a
`Tid`
.
/// For the location identifier see
[`AbstractLocation`]
.
/// The time identifier is given by a
[`Tid`]
.
/// If it is the `Tid` of a basic block, then it describes the point in time *before* execution of the first instruction in the block.
/// If it is the `Tid` of a `Def` or `Jmp`, then it describes the point in time *after* the execution of the `Def` or `Jmp`.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone,
PartialOrd,
Ord,
Deref)]
...
...
@@ -161,136 +166,6 @@ impl std::fmt::Display for AbstractIdentifier {
}
}
/// 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 always a register.
/// 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}"
)
}
}
}
}
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
}
}
/// 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
()
}
}
}
}
/// An abstract memory location is either an offset from the given location, where the actual value can be found,
/// or an offset to a pointer to another memory location,
/// where the value can be found by (recursively) following the embedded `target` memory location.
///
/// The offset and size variables are given in bytes.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone,
PartialOrd,
Ord)]
pub
enum
AbstractMemoryLocation
{
/// A location inside the current memory object.
Location
{
/// The offset with respect to the zero offset of the memory object where the value can be found.
offset
:
i64
,
/// The size in bytes of the value that the memory location points to.
size
:
ByteSize
,
},
/// A pointer which needs to be followed to get to the actual memory location
Pointer
{
/// The offset inside the current memory object where the pointer can be found.
offset
:
i64
,
/// The memory location inside the target of the pointer that this memory location points to.
target
:
Box
<
AbstractMemoryLocation
>
,
},
}
impl
AbstractMemoryLocation
{
/// Get the bytesize of the value represented by the abstract memory location.
pub
fn
bytesize
(
&
self
)
->
ByteSize
{
match
self
{
Self
::
Location
{
size
,
..
}
=>
*
size
,
Self
::
Pointer
{
target
,
..
}
=>
target
.bytesize
(),
}
}
}
impl
std
::
fmt
::
Display
for
AbstractMemoryLocation
{
fn
fmt
(
&
self
,
formatter
:
&
mut
std
::
fmt
::
Formatter
)
->
std
::
fmt
::
Result
{
match
self
{
Self
::
Location
{
offset
,
..
}
=>
write!
(
formatter
,
"({offset})"
),
Self
::
Pointer
{
offset
,
target
}
=>
write!
(
formatter
,
"({offset})->{target}"
),
}
}
}
#[cfg(test)]
pub
mod
tests
{
use
super
::
*
;
...
...
@@ -313,6 +188,20 @@ pub mod tests {
.unwrap
(),
)
}
/// Mock an abstract identifier with the given TID name
/// and with a nested abstract location starting at the register given by `var`.
pub
fn
mock_nested
(
tid
:
impl
ToString
,
var
:
&
str
,
offsets
:
&
[
i64
],
size
:
impl
Into
<
ByteSize
>
,
)
->
Self
{
AbstractIdentifier
::
new
(
Tid
::
new
(
tid
.to_string
()),
AbstractLocation
::
mock
(
var
,
offsets
,
size
),
)
}
}
#[test]
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/context.rs
→
src/cwe_checker_lib/src/analysis/function_signature/context
/mod
.rs
View file @
f6ced95c
use
super
::
*
;
use
crate
::
abstract_domain
::{
AbstractDomain
,
AbstractIdentifier
,
AbstractLocation
,
BitvectorDomain
,
DataDomain
,
SizedDomain
,
TryToBitvec
,
AbstractDomain
,
AbstractIdentifier
,
AbstractLocation
,
BitvectorDomain
,
DataDomain
,
RegisterDomain
as
_
,
SizedDomain
,
TryToBitvec
,
};
use
crate
::
utils
::
arguments
;
use
crate
::{
...
...
@@ -8,8 +9,6 @@ use crate::{
intermediate_representation
::
Project
,
};
use
super
::
*
;
/// The context struct for the fixpoint algorithm.
pub
struct
Context
<
'a
>
{
graph
:
&
'a
Graph
<
'a
>
,
...
...
@@ -35,11 +34,9 @@ impl<'a> Context<'a> {
/// Compute the return values of a call and return them (without adding them to the caller state).
///
/// The `callee_state` is the state of the callee at the return site.
/// The return values are expressed in the abstract IDs that are known to the caller.
/// If a return value may contain `Top` values,
/// i.e. values for which the origin is not known or not expressible in the abstract IDs known to the caller,
/// then a call- and register-specific abstract ID is added to the corresponding return value.
/// This ID is not added to the tracked IDs of the caller state.
/// Return values corresponding to callee parameters are expressed in the abstract IDs that are known to the caller.
/// Additionally, each return value also contains one abstract ID specific to the call instruction and return register.
/// This ID is used to track abstract location access patterns to the return value of the call in the caller.
fn
compute_return_values_of_call
<
'cconv
>
(
&
self
,
caller_state
:
&
mut
State
,
...
...
@@ -74,8 +71,9 @@ impl<'a> Context<'a> {
/// Compute the return value for the given register.
///
/// The return value contains the IDs of all possible input IDs of the call that it may reference.
/// If the value may also contain a value not originating from the caller
/// then replace it with a call- and register-specific abstract ID.
/// Additionally, it also contains a call- and register-specific abstract ID,
/// which can be used to track the access patterns of the return value
/// independently of whether the return value only references caller values or not.
fn
compute_return_register_value_of_call
(
&
self
,
caller_state
:
&
mut
State
,
...
...
@@ -86,20 +84,18 @@ impl<'a> Context<'a> {
let
callee_value
=
callee_state
.get_register
(
return_register
);
let
mut
return_value
:
DataDomain
<
BitvectorDomain
>
=
DataDomain
::
new_empty
(
return_register
.size
);
// For absolute or Top-values originating in the callee the Top-flag of the return value is set.
if
callee_value
.contains_top
()
||
callee_value
.get_absolute_value
()
.is_some
()
{
return_value
.set_contains_top_flag
();
}
// For every relative value in the callee we check whether it is relative a parameter to the callee.
// If yes, we can compute it relative to the value of the parameter at the callsite and add the result to the return value.
// Else we just set the Top-flag of the return value to indicate some value originating in the callee.
for
(
callee_id
,
callee_offset
)
in
callee_value
.get_relative_values
()
{
if
callee_id
.get_tid
()
==
callee_state
.get_current_function_tid
()
&&
matches!
(
callee_id
.get_location
(),
AbstractLocation
::
GlobalAddress
{
..
}
)
{
for
(
callee_id
,
callee_offset
)
in
callee_value
.get_relative_values
()
.iter
()
.filter
(|(
callee_id
,
_
)|
callee_id
.get_tid
()
==
callee_state
.get_current_function_tid
())
{
if
matches!
(
callee_id
.get_location
(),
AbstractLocation
::
GlobalAddress
{
..
}
|
AbstractLocation
::
GlobalPointer
(
_
,
_
)
)
{
// Globals get the same ID as if the global pointer originated in the caller.
let
caller_global_id
=
AbstractIdentifier
::
new
(
caller_state
.get_current_function_tid
()
.clone
(),
...
...
@@ -109,13 +105,13 @@ impl<'a> Context<'a> {
let
caller_global
=
DataDomain
::
from_target
(
caller_global_id
,
callee_offset
.clone
());
return_value
=
return_value
.merge
(
&
caller_global
);
}
else
if
let
Some
(
param_arg
)
=
callee_state
.get_arg_corresponding_to_id
(
callee_id
)
{
let
param_value
=
caller_state
.eval_parameter_arg
(
&
param_arg
);
}
else
{
let
param_value
=
caller_state
.eval_param_location
(
callee_id
.get_location
(),
&
self
.project.runtime_memory_image
,
);
let
param_value
=
caller_state
.substitute_global_mem_address
(
param_value
,
&
self
.project.runtime_memory_image
);
if
param_value
.contains_top
()
||
param_value
.get_absolute_value
()
.is_some
()
{
return_value
.set_contains_top_flag
()
}
for
(
param_id
,
param_offset
)
in
param_value
.get_relative_values
()
{
let
value
=
DataDomain
::
from_target
(
param_id
.clone
(),
...
...
@@ -123,19 +119,14 @@ impl<'a> Context<'a> {
);
return_value
=
return_value
.merge
(
&
value
);
}
}
else
{
return_value
.set_contains_top_flag
();
}
}
// If the Top-flag of the return value was set we replace it with an ID representing the return register
// to indicate where the unknown value originated from.
if
return_value
.contains_top
()
{
let
id
=
AbstractIdentifier
::
from_var
(
call
.tid
.clone
(),
return_register
);
let
value
=
DataDomain
::
from_target
(
id
,
Bitvector
::
zero
(
return_register
.size
.into
())
.into
());
return_value
=
return_value
.merge
(
&
value
);
return_value
.unset_contains_top_flag
();
}
// Also add an ID representing the return register (regardless of what was added before).
// This ID is used to track abstract location access patterns in relation to the return value.
let
id
=
AbstractIdentifier
::
from_var
(
call
.tid
.clone
(),
return_register
);
let
value
=
DataDomain
::
from_target
(
id
,
Bitvector
::
zero
(
return_register
.size
.into
())
.into
());
return_value
=
return_value
.merge
(
&
value
);
return_value
}
...
...
@@ -314,6 +305,34 @@ impl<'a> Context<'a> {
}
None
}
/// Adjust the stack register after a call to a function.
///
/// On x86, this removes the return address from the stack
/// (other architectures pass the return address in a register, not on the stack).
/// On other architectures the stack register retains the value it had before the call.
/// Note that in some calling conventions the callee also clears function parameters from the stack.
/// We do not detect and handle these cases yet.
fn
adjust_stack_register_on_return_from_call
(
&
self
,
state_before_call
:
&
State
,
new_state
:
&
mut
State
,
)
{
let
stack_register
=
&
self
.project.stack_pointer_register
;
let
stack_pointer
=
state_before_call
.get_register
(
stack_register
);
match
self
.project.cpu_architecture
.as_str
()
{
"x86"
|
"x86_32"
|
"x86_64"
=>
{
let
offset
=
Bitvector
::
from_u64
(
stack_register
.size
.into
())
.into_truncate
(
apint
::
BitWidth
::
from
(
stack_register
.size
))
.unwrap
();
new_state
.set_register
(
stack_register
,
stack_pointer
.bin_op
(
BinOpType
::
IntAdd
,
&
offset
.into
()),
);
}
_
=>
new_state
.set_register
(
stack_register
,
stack_pointer
),
}
}
}
impl
<
'a
>
forward_interprocedural_fixpoint
::
Context
<
'a
>
for
Context
<
'a
>
{
...
...
@@ -339,7 +358,8 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
new_state
.set_register
(
var
,
value
);
}
Def
::
Load
{
var
,
address
}
=>
{
new_state
.set_deref_flag_for_input_ids_of_expression
(
address
);
new_state
.set_deref_flag_for_pointer_inputs_of_expression
(
address
);
new_state
.set_read_flag_for_input_ids_of_expression
(
address
);
let
address
=
new_state
.substitute_global_mem_address
(
state
.eval
(
address
),
&
self
.project.runtime_memory_image
,
...
...
@@ -352,10 +372,13 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
);
let
value
=
new_state
.substitute_global_mem_address
(
value
,
&
self
.project.runtime_memory_image
);
new_state
.track_contained_ids
(
&
value
);
new_state
.set_read_flag_for_contained_ids
(
&
value
);
new_state
.set_register
(
var
,
value
);
}
Def
::
Store
{
address
,
value
}
=>
{
new_state
.set_mutable_deref_flag_for_input_ids_of_expression
(
address
);
new_state
.set_mutable_deref_flag_for_pointer_inputs_of_expression
(
address
);
new_state
.set_read_flag_for_input_ids_of_expression
(
address
);
let
address
=
new_state
.substitute_global_mem_address
(
state
.eval
(
address
),
&
self
.project.runtime_memory_image
,
...
...
@@ -420,6 +443,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
cconv
,
&
self
.project.runtime_memory_image
,
);
self
.adjust_stack_register_on_return_from_call
(
state
,
&
mut
new_state
);
return
Some
(
new_state
);
}
}
...
...
@@ -427,6 +451,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
if
let
Some
(
extern_symbol
)
=
self
.project.program.term.extern_symbols
.get
(
target
)
{
self
.handle_extern_symbol_call
(
&
mut
new_state
,
extern_symbol
,
&
call
.tid
);
if
!
extern_symbol
.no_return
{
self
.adjust_stack_register_on_return_from_call
(
state
,
&
mut
new_state
);
return
Some
(
new_state
);
}
}
else
if
let
Some
(
cconv
)
=
self
.project
.get_standard_calling_convention
()
{
...
...
@@ -435,6 +460,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
cconv
,
&
self
.project.runtime_memory_image
,
);
self
.adjust_stack_register_on_return_from_call
(
state
,
&
mut
new_state
);
return
Some
(
new_state
);
}
}
...
...
@@ -462,9 +488,9 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
Some
(
cconv
)
=>
cconv
,
None
=>
return
None
,
};
let
old_state
=
state_before_call
.unwrap
();
let
state_before_call
=
state_before_call
.unwrap
();
let
callee_state
=
state
.unwrap
();
let
mut
new_state
=
old_state
.clone
();
let
mut
new_state
=
state_before_call
.clone
();
// Merge parameter access patterns with the access patterns from the callee.
let
parameters
=
callee_state
.get_params_of_current_function
();
new_state
.merge_parameter_access
(
&
parameters
,
&
self
.project.runtime_memory_image
);
...
...
@@ -480,8 +506,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
new_state
.clear_non_callee_saved_register
(
&
calling_convention
.callee_saved_register
);
// Now we can insert the return values into the state
for
(
var
,
value
)
in
return_value_list
{
// The return values may contain new IDs that have to be tracked.
new_state
.track_contained_ids
(
&
value
);
new_state
.set_register
(
var
,
value
);
}
self
.adjust_stack_register_on_return_from_call
(
state_before_call
,
&
mut
new_state
);
Some
(
new_state
)
}
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/context/tests.rs
View file @
f6ced95c
use
super
::
*
;
use
crate
::{
bitvec
,
variable
};
use
crate
::{
analysis
::
forward_interprocedural_fixpoint
::
Context
as
_
,
bitvec
,
def
,
variable
};
#[test]
fn
test_compute_return_values_of_call
()
{
...
...
@@ -25,21 +25,27 @@ fn test_compute_return_values_of_call() {
&
call
,
);
let
expected_val
=
DataDomain
::
from_target
(
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"call_tid"
),
&
variable!
(
"RAX:8"
)
),
AbstractIdentifier
::
mock
(
"call_tid"
,
"RAX"
,
8
),
bitvec!
(
"0x0:8"
)
.into
(),
);
assert_eq!
(
return_values
.iter
()
.len
(),
3
);
assert_eq!
(
return_values
[
0
],
(
&
variable!
(
"RAX:8"
),
expected_val
));
// Test returning a known value.
let
param_ref
=
DataDomain
::
from_target
(
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"callee"
),
&
variable!
(
"RDI:8"
)
),
AbstractIdentifier
::
mock
(
"callee"
,
"RDI"
,
8
),
bitvec!
(
"0x0:8"
)
.into
(),
);
callee_state
.set_register
(
&
variable!
(
"RAX:8"
),
param_ref
);
let
expected_val
=
DataDomain
::
from_target
(
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"caller"
),
&
variable!
(
"RDI:8"
)),
bitvec!
(
"0x0:8"
)
.into
(),
);
let
expected_val
=
DataDomain
::
mock_from_target_map
(
BTreeMap
::
from
([
(
AbstractIdentifier
::
mock
(
"caller"
,
"RDI"
,
8
),
bitvec!
(
"0x0:8"
)
.into
(),
),
(
AbstractIdentifier
::
mock
(
"call_tid"
,
"RAX"
,
8
),
bitvec!
(
"0x0:8"
)
.into
(),
),
]));
let
return_values
=
context
.compute_return_values_of_call
(
&
mut
caller_state
,
&
callee_state
,
...
...
@@ -69,7 +75,7 @@ fn test_call_stub_handling() {
assert_eq!
(
state
.get_params_of_current_function
(),
vec!
[(
Arg
::
from_var
(
variable!
(
"r0:4"
),
None
),
&
AbstractLocation
::
from_var
(
&
variable!
(
"r0:4"
))
.unwrap
(
),
AccessPattern
::
new
()
.with_read_flag
()
)]
);
...
...
@@ -97,14 +103,14 @@ fn test_call_stub_handling() {
assert_eq!
(
params
[
0
],
(
Arg
::
from_var
(
variable!
(
"r0:4"
),
None
),
&
AbstractLocation
::
from_var
(
&
variable!
(
"r0:4"
))
.unwrap
(
),
AccessPattern
::
new_unknown_access
()
)
);
assert_eq!
(
params
[
1
],
(
Arg
::
from_var
(
variable!
(
"r2:4"
),
None
),
&
AbstractLocation
::
from_var
(
&
variable!
(
"r2:4"
))
.unwrap
(
),
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
()
...
...
@@ -114,6 +120,51 @@ fn test_call_stub_handling() {
}
#[test]
fn
test_stack_register_adjustment_after_call
()
{
let
project
=
Project
::
mock_x64
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
);
let
context
=
Context
::
new
(
&
project
,
&
graph
);
let
mut
state_before_call
=
State
::
mock_x64
(
"mock_fn"
);
let
stack_id
=
AbstractIdentifier
::
mock
(
"mock_fn"
,
"RSP"
,
8
);
state_before_call
.set_register
(
&
variable!
(
"RSP:8"
),
DataDomain
::
from_target
(
stack_id
.clone
(),
bitvec!
(
"0x-20:8"
)
.into
()),
);
let
call_term
=
Term
{
tid
:
Tid
::
new
(
"call_tid"
),
term
:
Jmp
::
CallInd
{
target
:
Expression
::
Var
(
variable!
(
"R15:8"
)),
return_
:
Some
(
Tid
::
new
(
"return_"
)),
},
};
// Test adjustment on extern calls
let
state_after_call
=
context
.update_call_stub
(
&
state_before_call
,
&
call_term
)
.unwrap
();
let
adjusted_sp
=
state_after_call
.get_register
(
&
variable!
(
"RSP:8"
));
assert_eq!
(
adjusted_sp
,
DataDomain
::
from_target
(
stack_id
.clone
(),
bitvec!
(
"0x-18:8"
)
.into
())
);
// Test adjustment on intern calls
let
state_before_return
=
State
::
mock_x64
(
"callee"
);
let
state_after_call
=
context
.update_return
(
Some
(
&
state_before_return
),
Some
(
&
state_before_call
),
&
call_term
,
&
call_term
,
&
None
,
)
.unwrap
();
let
adjusted_sp
=
state_after_call
.get_register
(
&
variable!
(
"RSP:8"
));
assert_eq!
(
adjusted_sp
,
DataDomain
::
from_target
(
stack_id
.clone
(),
bitvec!
(
"0x-18:8"
)
.into
())
);
}
#[test]
fn
test_get_global_mem_address
()
{
let
project
=
Project
::
mock_arm32
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
);
...
...
@@ -135,3 +186,82 @@ fn test_get_global_mem_address() {
let
result
=
context
.get_global_mem_address
(
&
value
);
assert
!
(
result
.is_none
());
}
#[test]
fn
test_generation_of_nested_ids_and_access_patterns_on_load_and_store
()
{
let
project
=
Project
::
mock_arm32
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
);
let
context
=
Context
::
new
(
&
project
,
&
graph
);
let
state
=
State
::
mock_arm32
();
// Load from a tracked pointer value
let
def
=
def!
[
"load_instr: r0:4 := Load from r1:4 + 0x10:4"
];
let
new_state
=
context
.update_def
(
&
state
,
&
def
)
.unwrap
();
let
loaded_value
=
new_state
.get_register
(
&
variable!
(
"r0:4"
));
assert_eq!
(
loaded_value
,
DataDomain
::
from_target
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
mock
(
"r1:4"
,
&
[
16
],
4
)
),
bitvec!
(
"0x0:4"
)
.into
()
)
);
let
params
=
new_state
.get_params_of_current_function
();
assert_eq!
(
params
.len
(),
1
);
assert
!
(
params
.contains
(
&
(
&
AbstractLocation
::
mock
(
"r1:4"
,
&
[],
4
),
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
()
)));
// Load from an untracked register value
let
def
=
def!
[
"load_instr: r0:4 := Load from r8:4 + 0x10:4"
];
let
new_state
=
context
.update_def
(
&
state
,
&
def
)
.unwrap
();
let
loaded_value
=
new_state
.get_register
(
&
variable!
(
"r0:4"
));
assert
!
(
loaded_value
.is_top
());
assert_eq!
(
new_state
.get_params_of_current_function
(),
[]);
// Store a tracked pointer value
let
def
=
def!
[
"store_instr: Store at r0:4 := r1:4 + 0x10:4"
];
let
new_state
=
context
.update_def
(
&
state
,
&
def
)
.unwrap
();
let
params
=
new_state
.get_params_of_current_function
();
assert_eq!
(
params
.len
(),
2
);
assert
!
(
params
.contains
(
&
(
&
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
),
AccessPattern
::
new
()
.with_read_flag
()
.with_mutably_dereferenced_flag
()
)));
assert
!
(
params
.contains
(
&
(
&
AbstractLocation
::
mock
(
"r1:4"
,
&
[],
4
),
AccessPattern
::
new
()
.with_read_flag
()
)));
// Store to an untracked register value
let
def
=
def!
[
"store_instr: Store at r8:4 := r1:4 + 0x10:4"
];
let
new_state
=
context
.update_def
(
&
state
,
&
def
)
.unwrap
();
let
params
=
new_state
.get_params_of_current_function
();
assert_eq!
(
params
.len
(),
1
);
assert
!
(
params
.contains
(
&
(
&
AbstractLocation
::
mock
(
"r1:4"
,
&
[],
4
),
AccessPattern
::
new
()
.with_read_flag
()
)));
}
#[test]
fn
test_stack_param_loaded_but_not_accessed
()
{
// Regression test for the case that a stack parameter is loaded into a register but then not directly accessed.
// In such a case the stack parameter must still be proactively marked as read,
// because its later usage might simply be missed by the analysis
let
project
=
Project
::
mock_arm32
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
);
let
context
=
Context
::
new
(
&
project
,
&
graph
);
let
state
=
State
::
mock_arm32
();
let
def
=
def!
[
"r0:4 := Load from sp:4"
];
let
new_state
=
context
.update_def
(
&
state
,
&
def
)
.unwrap
();
let
fn_sig
=
new_state
.get_params_of_current_function
();
assert
!
(
fn_sig
.contains
(
&
(
&
AbstractLocation
::
mock
(
"sp:4"
,
&
[
0
],
4
),
AccessPattern
::
new
()
.with_read_flag
()
)));
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/global_var_propagation.rs
View file @
f6ced95c
...
...
@@ -4,6 +4,7 @@
use
super
::
AccessPattern
;
use
super
::
FunctionSignature
;
use
crate
::
abstract_domain
::
AbstractDomain
;
use
crate
::
abstract_domain
::
AbstractLocation
;
use
crate
::
abstract_domain
::
DomainMap
;
use
crate
::
abstract_domain
::
UnionMergeStrategy
;
use
crate
::
analysis
::
callgraph
::
get_program_callgraph
;
...
...
@@ -12,6 +13,7 @@ use crate::analysis::fixpoint::{Computation, Context};
use
crate
::
intermediate_representation
::
*
;
use
crate
::
utils
::
log
::
LogMessage
;
use
std
::
collections
::
BTreeMap
;
use
std
::
collections
::
BTreeSet
;
use
std
::
collections
::
HashSet
;
/// The context object for propagating known global variables top-down in the call graph.
...
...
@@ -31,7 +33,7 @@ impl<'a> Context for KnownGlobalsContext<'a> {
type
EdgeLabel
=
&
'a
Term
<
Jmp
>
;
type
NodeLabel
=
Tid
;
/// The values at nodes are the sets of known addresses of global variables for that function.
type
NodeValue
=
HashSet
<
u64
>
;
type
NodeValue
=
BTreeSet
<
AbstractLocation
>
;
/// Get the call graph corresponding to the context object.
fn
get_graph
(
&
self
)
->
&
CallGraph
<
'a
>
{
...
...
@@ -39,10 +41,14 @@ impl<'a> Context for KnownGlobalsContext<'a> {
}
/// The merge function returns the union of the two input sets of global addresses.
fn
merge
(
&
self
,
set1
:
&
HashSet
<
u64
>
,
set2
:
&
HashSet
<
u64
>
)
->
HashSet
<
u64
>
{
fn
merge
(
&
self
,
set1
:
&
BTreeSet
<
AbstractLocation
>
,
set2
:
&
BTreeSet
<
AbstractLocation
>
,
)
->
BTreeSet
<
AbstractLocation
>
{
let
mut
result
=
set1
.clone
();
for
address
in
set2
{
result
.insert
(
*
address
);
result
.insert
(
address
.clone
()
);
}
result
}
...
...
@@ -50,9 +56,9 @@ impl<'a> Context for KnownGlobalsContext<'a> {
/// We always propagate all known addresses of global variables along the edges of the call graph.
fn
update_edge
(
&
self
,
globals
:
&
HashSet
<
u64
>
,
globals
:
&
BTreeSet
<
AbstractLocation
>
,
_edge
:
petgraph
::
stable_graph
::
EdgeIndex
,
)
->
Option
<
HashSet
<
u64
>>
{
)
->
Option
<
BTreeSet
<
AbstractLocation
>>
{
Some
(
globals
.clone
())
}
}
...
...
@@ -66,7 +72,7 @@ impl<'a> Context for KnownGlobalsContext<'a> {
fn
propagate_known_globals_top_down
(
project
:
&
Project
,
fn_sigs
:
&
BTreeMap
<
Tid
,
FunctionSignature
>
,
)
->
BTreeMap
<
Tid
,
HashSet
<
u64
>>
{
)
->
BTreeMap
<
Tid
,
BTreeSet
<
AbstractLocation
>>
{
let
graph
=
get_program_callgraph
(
&
project
.program
);
let
context
=
KnownGlobalsContext
::
new
(
&
graph
);
let
mut
computation
=
Computation
::
new
(
context
,
None
);
...
...
@@ -96,12 +102,15 @@ struct GlobalsPropagationContext<'a> {
/// The reversed (!) call graph of the program.
graph
:
&
'a
CallGraph
<
'a
>
,
/// A map from TIDs of functions to the set of known addresses of global variables for that function.
known_globals
:
&
'a
BTreeMap
<
Tid
,
HashSet
<
u64
>>
,
known_globals
:
&
'a
BTreeMap
<
Tid
,
BTreeSet
<
AbstractLocation
>>
,
}
impl
<
'a
>
GlobalsPropagationContext
<
'a
>
{
/// Create a new [`GlobalsPropagationContext`] object.
fn
new
(
graph
:
&
'a
CallGraph
<
'a
>
,
known_globals
:
&
'a
BTreeMap
<
Tid
,
HashSet
<
u64
>>
)
->
Self
{
fn
new
(
graph
:
&
'a
CallGraph
<
'a
>
,
known_globals
:
&
'a
BTreeMap
<
Tid
,
BTreeSet
<
AbstractLocation
>>
,
)
->
Self
{
GlobalsPropagationContext
{
graph
,
known_globals
,
...
...
@@ -113,9 +122,9 @@ impl<'a> Context for GlobalsPropagationContext<'a> {
type
EdgeLabel
=
&
'a
Term
<
Jmp
>
;
type
NodeLabel
=
Tid
;
/// The node values for the fixpoint comutation
/// are maps from
addresses of
global variables known to the function represented by the node
/// are maps from
locations of (possibly nested)
global variables known to the function represented by the node
/// to the corresponding access pattern of the global variable.
type
NodeValue
=
DomainMap
<
u64
,
AccessPattern
,
UnionMergeStrategy
>
;
type
NodeValue
=
DomainMap
<
AbstractLocation
,
AccessPattern
,
UnionMergeStrategy
>
;
/// Get the (reversed!) call graph corresponding to the program
fn
get_graph
(
&
self
)
->
&
CallGraph
<
'a
>
{
...
...
@@ -144,7 +153,7 @@ impl<'a> Context for GlobalsPropagationContext<'a> {
.iter
()
.filter_map
(|(
address
,
access_pattern
)|
{
if
caller_known_globals
.contains
(
address
)
{
Some
((
*
address
,
*
access_pattern
))
Some
((
address
.clone
()
,
*
access_pattern
))
}
else
{
None
}
...
...
@@ -161,7 +170,7 @@ impl<'a> Context for GlobalsPropagationContext<'a> {
/// that are known to the caller anyway (i.e. some function upwards in the call graph accesses the global variable).
fn
propagate_globals_bottom_up
(
project
:
&
Project
,
known_globals
:
&
BTreeMap
<
Tid
,
HashSet
<
u64
>>
,
known_globals
:
&
BTreeMap
<
Tid
,
BTreeSet
<
AbstractLocation
>>
,
fn_sigs
:
&
mut
BTreeMap
<
Tid
,
FunctionSignature
>
,
logs
:
&
mut
Vec
<
LogMessage
>
,
)
{
...
...
@@ -178,7 +187,7 @@ fn propagate_globals_bottom_up(
let
globals
=
fn_sig
.global_parameters
.iter
()
.map
(|(
address
,
access_pattern
)|
(
*
address
,
*
access_pattern
))
.map
(|(
address
,
access_pattern
)|
(
address
.clone
()
,
*
access_pattern
))
.collect
();
computation
.set_node_value
(
node
,
globals
);
}
...
...
@@ -198,7 +207,7 @@ fn propagate_globals_bottom_up(
let
fn_globals
=
&
mut
fn_sigs
.get_mut
(
fn_tid
)
.unwrap
()
.global_parameters
;
for
(
address
,
propagated_access_pattern
)
in
propagated_globals
.iter
()
{
fn_globals
.entry
(
*
address
)
.entry
(
address
.clone
()
)
.and_modify
(|
access_pattern
|
{
*
access_pattern
=
access_pattern
.merge
(
propagated_access_pattern
);
})
...
...
@@ -207,6 +216,48 @@ fn propagate_globals_bottom_up(
}
}
/// For all nested global parameters add the corresponding parent locations to the function signatures.
///
/// This ensures that subsequent analyses can safely assume
/// that for each nested parameter the parent location is also a parameter.
fn
add_parents_of_known_nested_globals
(
fn_sigs
:
&
mut
BTreeMap
<
Tid
,
FunctionSignature
>
,
generic_pointer_size
:
ByteSize
,
)
{
for
fn_sig
in
fn_sigs
.values_mut
()
{
let
mut
parents_to_add
=
HashSet
::
new
();
for
global
in
fn_sig
.global_parameters
.keys
()
{
parents_to_add
.extend
(
get_parents_of_global
(
global
,
generic_pointer_size
)
.into_iter
());
}
for
parent
in
parents_to_add
{
fn_sig
.global_parameters
.entry
(
parent
)
.and_modify
(|
pattern
|
pattern
.set_dereference_flag
())
.or_insert
(
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
(),
);
}
}
}
/// get all parent locations for the given potentially nested global location.
fn
get_parents_of_global
(
location
:
&
AbstractLocation
,
generic_pointer_size
:
ByteSize
,
)
->
Vec
<
AbstractLocation
>
{
if
let
AbstractLocation
::
GlobalPointer
(
_
,
_
)
=
location
{
let
(
parent
,
_offset
)
=
location
.get_parent_location
(
generic_pointer_size
)
.unwrap
();
let
mut
parents
=
get_parents_of_global
(
&
parent
,
generic_pointer_size
);
parents
.push
(
parent
);
parents
}
else
{
Vec
::
new
()
}
}
/// Propagate the access patterns of global variables along the edges of the call graph of the given project.
///
/// The propagation works as follows:
...
...
@@ -230,14 +281,22 @@ pub fn propagate_globals(
)
{
let
known_globals
=
propagate_known_globals_top_down
(
project
,
fn_sigs
);
propagate_globals_bottom_up
(
project
,
&
known_globals
,
fn_sigs
,
logs
);
// Also add parent locations of propagated globals to the function signatures
add_parents_of_known_nested_globals
(
fn_sigs
,
project
.get_pointer_bytesize
());
}
#[cfg(test)]
pub
mod
tests
{
use
std
::
collections
::
HashMap
;
use
super
::
*
;
/// Mock the abstract location of a global parameter.
fn
mock_global
(
address
:
u64
)
->
AbstractLocation
{
AbstractLocation
::
GlobalAddress
{
address
:
address
,
size
:
ByteSize
::
new
(
4
),
}
}
#[test]
fn
test_globals_propagation
()
{
let
mut
project
=
Project
::
mock_arm32
();
...
...
@@ -265,15 +324,16 @@ pub mod tests {
let
mut
sig_main
=
FunctionSignature
::
new
();
sig_main
.global_parameters
.insert
(
1000
,
AccessPattern
::
new
()
.with_read_flag
());
.insert
(
mock_global
(
1000
)
,
AccessPattern
::
new
()
.with_read_flag
());
let
mut
sig_callee1
=
FunctionSignature
::
new
();
sig_callee1
.global_parameters
.insert
(
2000
,
AccessPattern
::
new
()
.with_dereference_flag
());
sig_callee1
.global_parameters
.insert
(
mock_global
(
2000
),
AccessPattern
::
new
()
.with_dereference_flag
(),
);
let
mut
sig_callee2
=
FunctionSignature
::
new
();
sig_callee2
.global_parameters
.insert
(
1000
,
AccessPattern
::
new_unknown_access
());
.insert
(
mock_global
(
1000
)
,
AccessPattern
::
new_unknown_access
());
let
mut
fn_sigs
=
BTreeMap
::
from
([
(
Tid
::
new
(
"main"
),
sig_main
),
(
Tid
::
new
(
"callee1"
),
sig_callee1
),
...
...
@@ -285,18 +345,53 @@ pub mod tests {
// Check propagation results
assert_eq!
(
&
fn_sigs
[
&
Tid
::
new
(
"main"
)]
.global_parameters
,
&
HashMap
::
from
([(
1000
,
AccessPattern
::
new_unknown_access
())])
&
BTreeMap
::
from
([(
mock_global
(
1000
)
,
AccessPattern
::
new_unknown_access
())])
);
assert_eq!
(
&
fn_sigs
[
&
Tid
::
new
(
"callee1"
)]
.global_parameters
,
&
HashMap
::
from
([
(
1000
,
AccessPattern
::
new_unknown_access
()),
(
2000
,
AccessPattern
::
new
()
.with_dereference_flag
())
&
BTreeMap
::
from
([
(
mock_global
(
1000
),
AccessPattern
::
new_unknown_access
()),
(
mock_global
(
2000
),
AccessPattern
::
new
()
.with_dereference_flag
()
)
])
);
assert_eq!
(
&
fn_sigs
[
&
Tid
::
new
(
"callee2"
)]
.global_parameters
,
&
HashMap
::
from
([(
1000
,
AccessPattern
::
new_unknown_access
())])
&
BTreeMap
::
from
([(
mock_global
(
1000
),
AccessPattern
::
new_unknown_access
())])
);
}
#[test]
fn
test_add_parent_locations
()
{
// The case of a known nested global parameter without knowing the parent locations happens
// when a callee returns a nested global in a return register.
let
location
=
AbstractLocation
::
mock_global
(
0x2000
,
&
[
8
,
16
],
8
);
let
globals
=
BTreeMap
::
from
([(
location
,
AccessPattern
::
new_unknown_access
())]);
let
fn_sig
=
FunctionSignature
{
parameters
:
BTreeMap
::
new
(),
global_parameters
:
globals
,
};
let
mut
fn_sigs
=
BTreeMap
::
from
([(
Tid
::
new
(
"func"
),
fn_sig
)]);
add_parents_of_known_nested_globals
(
&
mut
fn_sigs
,
ByteSize
::
new
(
8
));
let
fn_sig
=
&
fn_sigs
[
&
Tid
::
new
(
"func"
)];
let
deref_pattern
=
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
();
assert_eq!
(
fn_sig
.global_parameters
,
BTreeMap
::
from
([
(
AbstractLocation
::
mock_global
(
0x2000
,
&
[
8
,
16
],
8
),
AccessPattern
::
new_unknown_access
()
),
(
AbstractLocation
::
mock_global
(
0x2000
,
&
[
8
],
8
),
deref_pattern
),
(
AbstractLocation
::
mock_global
(
0x2000
,
&
[],
8
),
deref_pattern
),
])
);
}
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/mod.rs
View file @
f6ced95c
...
...
@@ -6,6 +6,11 @@
//! (is the value read, dereferenced for read access or dereferenced for write access).
//! Accesses to constant addresses that may correspond to global variables are also tracked.
//!
//! For values that are not directly tracked,
//! the algorithm tracks the abstract location that describes how the pointer to that value was computed.
//! This enables tracking of nested parameter objects
//! without actually tracking the memory objects where these objects are located.
//!
//! Known limitations of the analysis:
//! * The analysis is an overapproximation in the sense that it may generate more input parameters
//! than actually exist in some cases.
...
...
@@ -17,16 +22,19 @@
//! * Parameters that are used as input values for variadic functions may be missed.
//! Some variadic functions are stubbed, i.e. parameter recognition should work for these.
//! But not all variadic functions are stubbed.
//! * If only a part (e.g. a single byte) of a stack parameter is accessed instead of the whole parameter
//! then a duplicate stack parameter may be generated.
//! A proper sanitation for this case is not yet implemented,
//! although error messages are generated if such a case is detected.
//! * For floating point parameter registers the base register is detected as a parameter,
//! although only a smaller sub-register is the actual parameter in many cases.
//! Also, if a function uses sub-registers of floating point registers as local variables,
//! the registers may be incorrectly flagged as input parameters.
//! * Tracking of nested parameters via their abstract locations is an unsound, heuristic approach,
//! as the analysis does not keep track of when such nested pointers might get overwritten.
//! Nevertheless, it should result in an overapproximation of parameters and their access patterns in most cases.
//! * The nesting depth for tracked nested parameters is limited
//! to avoid generating infinitely many parameters for recursive types like linked lists.
use
crate
::
abstract_domain
::
AbstractDomain
;
use
crate
::
abstract_domain
::
AbstractLocation
;
use
crate
::
abstract_domain
::
AbstractMemoryLocation
;
use
crate
::
analysis
::
fixpoint
::
Computation
;
use
crate
::
analysis
::
forward_interprocedural_fixpoint
::
create_computation
;
use
crate
::
analysis
::
forward_interprocedural_fixpoint
::
GeneralizedContext
;
...
...
@@ -36,12 +44,10 @@ use crate::intermediate_representation::*;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
log
::
LogMessage
;
use
std
::
collections
::
BTreeMap
;
use
std
::
collections
::
HashMap
;
mod
context
;
use
context
::
*
;
mod
state
;
use
itertools
::
Itertools
;
use
state
::
State
;
mod
access_pattern
;
pub
use
access_pattern
::
AccessPattern
;
...
...
@@ -49,6 +55,11 @@ mod global_var_propagation;
use
global_var_propagation
::
propagate_globals
;
pub
mod
stubs
;
/// The recursion depth limit for abstract locations to be tracked by the function signature analysis,
/// i.e. how many dereference operations an abstract location is allowed to contain
/// before the analysis stops tracking the location.
const
POINTER_RECURSION_DEPTH_LIMIT
:
u64
=
2
;
/// Generate the computation object for the fixpoint computation
/// and set the node values for all function entry nodes.
fn
generate_fixpoint_computation
<
'a
>
(
...
...
@@ -147,7 +158,7 @@ pub fn compute_function_signatures<'a>(
// Sanitize the parameters
let
mut
logs
=
Vec
::
new
();
for
(
fn_tid
,
fn_sig
)
in
fn_sig_map
.iter_mut
()
{
let
(
info_log
,
debug_log
)
=
fn_sig
.sanitize
(
project
);
let
info_log
=
fn_sig
.sanitize
(
project
);
for
log
in
info_log
{
logs
.push
(
LogMessage
::
new_info
(
log
)
...
...
@@ -155,13 +166,6 @@ pub fn compute_function_signatures<'a>(
.source
(
"Function Signature Analysis"
),
)
}
for
log
in
debug_log
{
logs
.push
(
LogMessage
::
new_debug
(
log
)
.location
(
fn_tid
.clone
())
.source
(
"Function Signature Analysis"
),
)
}
}
// Propagate globals in bottom-up direction in the call graph
propagate_globals
(
project
,
&
mut
fn_sig_map
,
&
mut
logs
);
...
...
@@ -174,30 +178,42 @@ pub fn compute_function_signatures<'a>(
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Clone)]
pub
struct
FunctionSignature
{
/// The parameters of the function together with their access patterns.
pub
parameters
:
HashMap
<
Arg
,
AccessPattern
>
,
pub
parameters
:
BTreeMap
<
AbstractLocation
,
AccessPattern
>
,
/// Values in writeable global memory accessed by the function.
/// Does not contain indirectly accessed values, e.g. values accessed by callees of this function.
pub
global_parameters
:
HashMap
<
u64
,
AccessPattern
>
,
pub
global_parameters
:
BTreeMap
<
AbstractLocation
,
AccessPattern
>
,
}
impl
FunctionSignature
{
/// Generate an empty function signature.
pub
fn
new
()
->
Self
{
Self
{
parameters
:
Hash
Map
::
new
(),
global_parameters
:
Hash
Map
::
new
(),
parameters
:
BTree
Map
::
new
(),
global_parameters
:
BTree
Map
::
new
(),
}
}
/// The returned number is the maximum of stack offset plus parameter size
/// taken over all stack parameters in the function signature.
pub
fn
get_stack_params_total_size
(
&
self
)
->
i64
{
pub
fn
get_stack_params_total_size
(
&
self
,
stack_register
:
&
Variable
)
->
i64
{
let
mut
stack_params_total_size
:
i64
=
0
;
for
param
in
self
.parameters
.keys
()
{
if
let
Ok
(
param_offset
)
=
param
.eval_stack_offset
()
{
let
param_upper_bound
=
param_offset
.try_to_i64
()
.unwrap
()
+
(
u64
::
from
(
param
.bytesize
())
as
i64
);
stack_params_total_size
=
std
::
cmp
::
max
(
stack_params_total_size
,
param_upper_bound
);
if
let
AbstractLocation
::
Pointer
(
var
,
mem_location
)
=
param
{
if
var
==
stack_register
{
match
mem_location
{
AbstractMemoryLocation
::
Location
{
offset
,
size
}
=>
{
stack_params_total_size
=
std
::
cmp
::
max
(
stack_params_total_size
,
offset
+
(
u64
::
from
(
*
size
)
as
i64
),
);
}
AbstractMemoryLocation
::
Pointer
{
offset
,
target
:
_
}
=>
{
stack_params_total_size
=
std
::
cmp
::
max
(
stack_params_total_size
,
offset
+
(
u64
::
from
(
stack_register
.size
)
as
i64
),
);
}
}
}
}
}
stack_params_total_size
...
...
@@ -206,21 +222,21 @@ impl FunctionSignature {
/// Merge the parameter list and the global parameter list of `self` with the given lists.
fn
merge_parameter_lists
(
&
mut
self
,
params
:
&
[(
Arg
,
AccessPattern
)],
global_params
:
&
[(
u64
,
AccessPattern
)],
params
:
&
[(
&
AbstractLocation
,
AccessPattern
)],
global_params
:
&
[(
&
AbstractLocation
,
AccessPattern
)],
)
{
for
(
arg
,
sig_new
)
in
params
{
if
let
Some
(
sig_self
)
=
self
.parameters
.get_mut
(
arg
)
{
*
sig_self
=
sig_self
.merge
(
sig_new
);
}
else
{
self
.parameters
.insert
(
arg
.clone
(),
*
sig_new
);
self
.parameters
.insert
(
(
*
arg
)
.clone
(),
*
sig_new
);
}
}
for
(
address
,
sig_new
)
in
global_params
{
if
let
Some
(
sig_self
)
=
self
.global_parameters
.get_mut
(
address
)
{
*
sig_self
=
sig_self
.merge
(
sig_new
);
}
else
{
self
.global_parameters
.insert
(
*
address
,
*
sig_new
);
self
.global_parameters
.insert
(
(
*
address
)
.clone
()
,
*
sig_new
);
}
}
}
...
...
@@ -239,172 +255,161 @@ impl FunctionSignature {
/// This may indicate an error in the analysis
/// as no proper sanitation pass is implemented for such cases yet.
/// * Merge intersecting stack parameters
fn
sanitize
(
&
mut
self
,
project
:
&
Project
)
->
(
Vec
<
String
>
,
Vec
<
String
>
)
{
fn
sanitize
(
&
mut
self
,
project
:
&
Project
)
->
Vec
<
String
>
{
match
project
.cpu_architecture
.as_str
()
{
"x86"
|
"x86_32"
|
"x86_64"
=>
{
let
return_addr_expr
=
Expression
::
Var
(
project
.stack_pointer_register
.clone
());
let
return_addr_arg
=
Arg
::
Stack
{
address
:
return_addr_expr
,
size
:
project
.stack_pointer_register.size
,
data_type
:
None
,
};
self
.parameters
.remove
(
&
return_addr_arg
);
let
return_addr_location
=
AbstractLocation
::
from_stack_position
(
&
project
.stack_pointer_register
,
0
,
project
.get_pointer_bytesize
(),
);
self
.parameters
.remove
(
&
return_addr_location
);
}
_
=>
(),
}
let
debug_messages
=
self
.merge_intersecting_stack_parameters
();
let
info_messages
=
self
.check_for_unaligned_stack_params
(
&
project
.stack_pointer_register
);
(
info_messages
,
debug_messages
)
// FIXME: We check for intersecting stack parameter register, but not for intersecting nested parameters.
// We should add a check for these to generate log messages (but probably without trying to merge such parameters)
self
.merge_intersecting_stack_parameters
(
&
project
.stack_pointer_register
);
self
.check_for_unaligned_stack_params
(
&
project
.stack_pointer_register
)
}
/// Return a log message for every unaligned stack parameter
/// or a stack parameter of different size than the generic pointer size is found.
fn
check_for_unaligned_stack_params
(
&
self
,
stack_register
:
&
Variable
)
->
Vec
<
String
>
{
let
mut
log_messages
:
Vec
<
String
>
=
vec!
[];
for
arg
in
self
.parameters
.keys
()
{
if
let
Arg
::
Stack
{
size
,
..
}
=
arg
{
if
*
size
!=
stack_register
.size
{
for
param
in
self
.parameters
.keys
()
{
if
let
Some
(
offset
)
=
get_offset_if_simple_stack_param
(
param
,
stack_register
)
{
if
param
.bytesize
()
!=
stack_register
.size
{
log_messages
.push
(
"Unexpected stack parameter size"
.into
());
}
if
let
Ok
(
offset
)
=
arg
.eval_stack_offset
()
{
if
offset
.try_to_u64
()
.unwrap_or
(
0
)
%
u64
::
from
(
stack_register
.size
)
!=
0
{
log_messages
.push
(
"Unexpected stack parameter alignment"
.into
());
}
if
offset
%
u64
::
from
(
stack_register
.size
)
as
i64
!=
0
{
log_messages
.push
(
"Unexpected stack parameter alignment"
.into
());
}
}
}
log_messages
}
/// Merges two intersecting stack parameters by joining them into one stack parameter.
/// Merges intersecting stack parameters by joining them into one stack parameter.
///
/// Two [Arg](crate::intermediate_representation::Arg) are merged if *all* of the following applies:
/// * parameters return `Ok` on `Arg::eval_stack_offset()`
/// * parameters intersect
fn
merge_intersecting_stack_parameters
(
&
mut
self
)
->
Vec
<
String
>
{
let
mut
stack_parms
=
self
/// Only non-nested stack parameters are joined by this function.
fn
merge_intersecting_stack_parameters
(
&
mut
self
,
stack_register
:
&
Variable
)
{
let
stack_params
:
BTreeMap
<
(
i64
,
ByteSize
),
(
AbstractLocation
,
AccessPattern
)
>
=
self
.parameters
.clone
()
.into_iter
()
.filter
(|
x
|
x
.
0
.eval_stack_offset
()
.is_ok
())
.sorted_by
(|
a
,
b
|
{
match
a
.
0
.eval_stack_offset
()
.unwrap
()
.checked_sgt
(
&
b
.
0
.eval_stack_offset
()
.unwrap
())
.unwrap
()
{
true
=>
std
::
cmp
::
Ordering
::
Greater
,
false
=>
std
::
cmp
::
Ordering
::
Less
,
}
.iter
()
.filter_map
(|(
location
,
access_pattern
)|
{
get_offset_if_simple_stack_param
(
location
,
stack_register
)
.map
(|
offset
|
{
(
(
offset
,
location
.bytesize
()),
(
location
.clone
(),
*
access_pattern
),
)
})
})
.collect_vec
();
let
mut
logs
=
vec!
[];
if
!
stack_parms
.is_empty
()
{
let
mut
i
=
0
;
while
i
<
stack_parms
.len
()
-
1
{
if
let
Ok
((
merged_arg
,
log
))
=
get_bounds_intersecting_stack_arg
(
&
stack_parms
[
i
]
.
0
,
&
stack_parms
[
i
+
1
]
.
0
)
{
self
.parameters
.remove
(
&
stack_parms
[
i
]
.
0
);
self
.parameters
.remove
(
&
stack_parms
[
i
+
1
]
.
0
);
self
.parameters
.insert
(
merged_arg
.clone
(),
stack_parms
[
i
]
.
1
.merge
(
&
stack_parms
[
i
+
1
]
.
1
),
);
.collect
();
stack_parms
.insert
(
i
,
(
merged_arg
,
stack_parms
[
i
]
.
1
.merge
(
&
stack_parms
[
i
+
1
]
.
1
)),
let
mut
current_param
:
Option
<
(
i64
,
i64
,
AccessPattern
)
>
=
None
;
for
((
offset
,
_
),
(
param
,
access_pattern
))
in
stack_params
.into_iter
()
{
self
.parameters
.remove
(
&
param
);
if
let
Some
((
cur_offset
,
cur_size
,
cur_access_pattern
))
=
current_param
{
if
offset
<
cur_offset
+
cur_size
{
let
merged_size
=
std
::
cmp
::
max
(
cur_size
,
offset
-
cur_offset
+
u64
::
from
(
param
.bytesize
())
as
i64
,
);
stack_parms
.remove
(
i
+
1
);
stack_parms
.remove
(
i
+
1
);
logs
.extend
(
log
);
let
merged_access_pattern
=
cur_access_pattern
.merge
(
&
access_pattern
);
current_param
=
Some
((
cur_offset
,
merged_size
,
merged_access_pattern
));
}
else
{
i
+=
1
;
self
.parameters
.insert
(
generate_simple_stack_param
(
cur_offset
,
ByteSize
::
new
(
cur_size
as
u64
),
stack_register
,
),
cur_access_pattern
,
);
current_param
=
Some
((
offset
,
u64
::
from
(
param
.bytesize
())
as
i64
,
access_pattern
));
}
}
else
{
current_param
=
Some
((
offset
,
u64
::
from
(
param
.bytesize
())
as
i64
,
access_pattern
));
}
}
logs
if
let
Some
((
cur_offset
,
cur_size
,
cur_access_pattern
))
=
current_param
{
self
.parameters
.insert
(
generate_simple_stack_param
(
cur_offset
,
ByteSize
::
new
(
cur_size
as
u64
),
stack_register
,
),
cur_access_pattern
,
);
}
}
}
/// Merges two stack parameters and returns the merged [Arg](crate::intermediate_representation::Arg).
/// Also returns a message, if one argument is not a subset of the other one.
///
/// Assumes the provided `Arg` are ordered by equal or increasing stack offset.
///
/// Returns `Err` if `first_arg` or `second_arg`:
/// * are not `Arg::Stack` types
/// * return `Err` on `Arg::eval_stack_offset()`
/// * do not intersect
fn
get_bounds_intersecting_stack_arg
(
first_arg
:
&
Arg
,
second_arg
:
&
Arg
,
)
->
Result
<
(
Arg
,
Vec
<
String
>
),
Error
>
{
if
let
(
Arg
::
Stack
{
data_type
:
_
,
size
:
first_size
,
address
:
first_address
,
},
Arg
::
Stack
{
data_type
:
_
,
size
:
second_size
,
..
},
)
=
(
first_arg
,
second_arg
)
{
let
first_arg_offset
=
first_arg
.eval_stack_offset
()
?
.try_to_u64
()
?
;
let
first_arg_size
=
u64
::
from
(
*
first_size
);
let
second_arg_offset
=
second_arg
.eval_stack_offset
()
?
.try_to_u64
()
?
;
let
second_arg_size
=
u64
::
from
(
*
second_size
);
let
mut
logs
=
vec!
[];
let
first_arg_upper_bound
=
first_arg_offset
+
first_arg_size
;
// Check if they intersect
if
first_arg_upper_bound
>
second_arg_offset
{
let
second_arg_upper_bound
=
second_arg_offset
+
second_arg_size
;
impl
Default
for
FunctionSignature
{
fn
default
()
->
Self
{
Self
::
new
()
}
}
// Check if subset
if
second_arg_upper_bound
<=
first_arg_upper_bound
&&
second_arg_offset
>=
first_arg_offset
{
// second arg is a subset, we just keep first_arg
return
Ok
((
first_arg
.clone
(),
logs
));
}
if
first_arg_upper_bound
<=
second_arg_upper_bound
&&
first_arg_offset
>=
second_arg_offset
{
// first arg is a subset, we just keep second_arg
return
Ok
((
second_arg
.clone
(),
logs
));
}
logs
.push
(
"Merged a stack parameter, that intersect another but is no subset"
.to_string
(),
impl
FunctionSignature
{
/// Generate a compact JSON-representation of the function signature for pretty printing.
#[allow(dead_code)]
pub
fn
to_json_compact
(
&
self
)
->
serde_json
::
Value
{
let
mut
json_map
=
serde_json
::
Map
::
new
();
let
mut
param_map
=
serde_json
::
Map
::
new
();
for
(
param
,
pattern
)
in
self
.parameters
.iter
()
{
param_map
.insert
(
format!
(
"{param}"
),
serde_json
::
Value
::
String
(
format!
(
"{pattern}"
)),
);
}
json_map
.insert
(
"Parameters"
.to_string
(),
serde_json
::
Value
::
Object
(
param_map
),
);
let
mut
global_param_map
=
serde_json
::
Map
::
new
();
for
(
param
,
pattern
)
in
self
.global_parameters
.iter
()
{
global_param_map
.insert
(
format!
(
"{param}"
),
serde_json
::
Value
::
String
(
format!
(
"{pattern}"
)),
);
let
merged_arg
=
Arg
::
Stack
{
address
:
first_address
.clone
(),
size
:
(
second_arg_upper_bound
-
first_arg_offset
)
.into
(),
data_type
:
None
,
};
return
Ok
((
merged_arg
,
logs
));
}
else
{
return
Err
(
anyhow!
(
"Args do not intersect"
));
}
json_map
.insert
(
"Globals"
.to_string
(),
serde_json
::
Value
::
Object
(
global_param_map
),
);
serde_json
::
Value
::
Object
(
json_map
)
}
Err
(
anyhow!
(
"Args are no stack arguments"
))
}
impl
Default
for
FunctionSignature
{
fn
default
()
->
Self
{
Self
::
new
()
/// If the abstract location is a location on the stack
/// then return its offset relative to the zero position on the stack.
fn
get_offset_if_simple_stack_param
(
param
:
&
AbstractLocation
,
stack_register
:
&
Variable
,
)
->
Option
<
i64
>
{
if
let
AbstractLocation
::
Pointer
(
var
,
mem_location
)
=
param
{
if
var
==
stack_register
{
if
let
AbstractMemoryLocation
::
Location
{
offset
,
..
}
=
mem_location
{
return
Some
(
*
offset
);
}
}
}
None
}
/// Generate an abstract location of a (non-nested) stack parameter.
fn
generate_simple_stack_param
(
offset
:
i64
,
size
:
ByteSize
,
stack_register
:
&
Variable
,
)
->
AbstractLocation
{
AbstractLocation
::
Pointer
(
stack_register
.clone
(),
AbstractMemoryLocation
::
Location
{
offset
,
size
},
)
}
#[cfg(test)]
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/state/call_handling.rs
→
src/cwe_checker_lib/src/analysis/function_signature/state/call_handling
/mod
.rs
View file @
f6ced95c
...
...
@@ -91,22 +91,21 @@ impl State {
/// Fill every return register that might be a pointer with a value that may point to any pointer-sized input ID
/// or to an output ID specific to the call and output register.
/// Non-pointer-sized output registers are only filled with an ID specific to the call and output register.
fn
generate_return_values_for_call
(
&
mut
self
,
input_ids
:
&
BTreeSet
<
AbstractIdentifier
>
,
return_args
:
&
[
Arg
],
call_tid
:
&
Tid
,
)
{
// Fill every output register with a value that may point to any pointer-sized input ID
// or to an output ID specific to the call and output register.
let
generic_pointer_size
=
self
.stack_id
.unwrap_register
()
.size
;
let
generic_pointer_size
=
self
.stack_id
.bytesize
();
let
generic_output_relative_values
:
BTreeMap
<
AbstractIdentifier
,
BitvectorDomain
>
=
input_ids
.iter
()
.filter
(|
id
|
id
.bytesize
()
==
generic_pointer_size
)
.map
(|
id
|
(
id
.clone
(),
BitvectorDomain
::
new_top
(
generic_pointer_size
)))
.collect
();
let
mut
generic_output
=
DataDomain
::
new_
top
(
generic_pointer_size
);
let
mut
generic_output
=
DataDomain
::
new_
empty
(
generic_pointer_size
);
generic_output
.set_relative_values
(
generic_output_relative_values
);
for
output_arg
in
return_args
{
...
...
@@ -115,13 +114,15 @@ impl State {
data_type
:
_
,
}
=
output_arg
{
let
specific_id
=
AbstractIdentifier
::
from_var
(
call_tid
.clone
(),
var
);
self
.add_id_to_tracked_ids
(
&
specific_id
);
let
specific_target
=
DataDomain
::
from_target
(
specific_id
,
Bitvector
::
zero
(
var
.size
.into
())
.into
());
if
var
.size
==
generic_pointer_size
{
let
specific_target
=
DataDomain
::
from_target
(
AbstractIdentifier
::
from_var
(
call_tid
.clone
(),
var
),
Bitvector
::
zero
(
var
.size
.into
())
.into
(),
);
let
output
=
generic_output
.merge
(
&
specific_target
);
self
.set_register
(
var
,
output
);
}
else
{
self
.set_register
(
var
,
specific_target
);
}
}
}
...
...
@@ -131,21 +132,21 @@ impl State {
///
/// A register (or stack position with positive offset) is considered a parameter
/// if any access to its value at function start is recorded in the corresponding object signature.
pub
fn
get_params_of_current_function
(
&
self
)
->
Vec
<
(
Arg
,
AccessPattern
)
>
{
/// A nested location is considered a parameter if it was dereferenced during the function execution.
pub
fn
get_params_of_current_function
(
&
self
)
->
Vec
<
(
&
AbstractLocation
,
AccessPattern
)
>
{
let
mut
params
=
Vec
::
new
();
for
(
id
,
access_pattern
)
in
self
.tracked_ids
.iter
()
{
if
id
.get_tid
()
==
self
.get_current_function_tid
()
{
if
let
Ok
(
param_arg
)
=
generate_param_arg_from_abstract_id
(
id
)
{
if
access_pattern
.is_accessed
()
{
params
.push
((
param_arg
,
*
access_pattern
));
}
else
if
matches!
(
id
.get_location
(),
&
AbstractLocation
::
Pointer
{
..
})
{
// This is a stack parameter.
// If it was only loaded into a register but otherwise not used, then the read-flag needs to be set.
let
mut
access_pattern
=
*
access_pattern
;
access_pattern
.set_read_flag
();
params
.push
((
param_arg
,
access_pattern
));
}
if
self
.is_register_based_param_id
(
id
)
{
if
(
id
.get_location
()
.recursion_depth
()
>
0
&&
access_pattern
.is_dereferenced
())
||
(
id
.get_location
()
.recursion_depth
()
==
0
&&
access_pattern
.is_accessed
())
{
params
.push
((
id
.get_location
(),
*
access_pattern
));
}
}
else
if
self
.is_stack_based_param_id
(
id
)
&&
((
id
.get_location
()
.recursion_depth
()
>
1
&&
access_pattern
.is_dereferenced
())
||
(
id
.get_location
()
.recursion_depth
()
==
1
&&
access_pattern
.is_accessed
()))
{
params
.push
((
id
.get_location
(),
*
access_pattern
));
}
}
params
...
...
@@ -153,16 +154,26 @@ impl State {
/// Return a list of all potential global memory addresses
/// for which any type of access has been tracked by the current state.
pub
fn
get_global_mem_params_of_current_function
(
&
self
)
->
Vec
<
(
u64
,
AccessPattern
)
>
{
pub
fn
get_global_mem_params_of_current_function
(
&
self
,
)
->
Vec
<
(
&
AbstractLocation
,
AccessPattern
)
>
{
let
mut
global_params
=
Vec
::
new
();
for
(
id
,
access_pattern
)
in
self
.tracked_ids
.iter
()
{
if
id
.get_tid
()
==
self
.get_current_function_tid
()
{
match
id
.get_location
()
{
AbstractLocation
::
GlobalPointer
(
address
,
_
)
|
AbstractLocation
::
GlobalAddress
{
address
,
..
}
=>
{
global_params
.push
((
*
address
,
*
access_pattern
));
let
location
=
id
.get_location
();
match
location
{
AbstractLocation
::
GlobalAddress
{
..
}
=>
{
if
access_pattern
.is_accessed
()
{
global_params
.push
((
location
,
*
access_pattern
));
}
}
AbstractLocation
::
GlobalPointer
(
_
,
_
)
=>
{
// Nested parameters are only explicitly tracked if they are dereferenced.
if
access_pattern
.is_dereferenced
()
{
global_params
.push
((
location
,
*
access_pattern
));
}
}
AbstractLocation
::
Pointer
(
_
,
_
)
|
AbstractLocation
::
Register
(
_
)
=>
(),
_
=>
(),
}
}
}
...
...
@@ -177,52 +188,155 @@ impl State {
/// Note that this may create new stack parameter objects for self.
pub
fn
merge_parameter_access
(
&
mut
self
,
params
:
&
[(
Arg
,
AccessPattern
)],
params
:
&
[(
&
AbstractLocation
,
AccessPattern
)],
global_memory
:
&
RuntimeMemoryImage
,
)
{
for
(
parameter
,
call_access_pattern
)
in
params
{
let
param_value
=
self
.eval_param
eter_arg
(
parameter
);
let
param_value
=
self
.eval_param
_location
(
parameter
,
global_memory
);
let
param_value
=
self
.substitute_global_mem_address
(
param_value
,
global_memory
);
for
(
id
,
offset
)
in
param_value
.get_relative_values
()
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
*
object
=
object
.merge
(
call_access_pattern
);
}
if
*
id
==
self
.stack_id
&&
call_access_pattern
.is_dereferenced
()
{
if
let
Ok
(
offset
)
=
offset
.try_to_bitvec
()
{
// We also have to dereference the stack pointer and set the access flags of the pointed-to value
let
value
=
self
.load_unsized_value_from_stack
(
offset
.clone
());
for
id
in
value
.referenced_ids
()
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
// Since we do not know whether the value itself was also dereferenced in the callee,
// we have to assume some unknown access to the value.
object
.set_unknown_access_flags
(
);
}
else
if
*
id
==
self
.stack_id
{
// Add stack IDs only if they correspond to stack parameters, i.e. the offset is positive.
if
let
Ok
(
concrete_
offset
)
=
offset
.try_to_bitvec
()
{
if
!
concrete_offset
.sign_bit
()
.to_bool
()
{
if
let
Some
(
stack_param
)
=
self
.generate_stack_param_id_if_nonexistent
(
concrete_offset
,
id
.bytesize
(),
)
{
let
object
=
self
.tracked_ids
.get_mut
(
&
stack_param
)
.unwrap
();
*
object
=
object
.merge
(
call_access_pattern
);
}
}
}
if
call_access_pattern
.is_mutably_dereferenced
()
{
// The stack value may have been overwritten by the call
if
let
Ok
(
offset
)
=
offset
.try_to_offset
()
{
self
.stack
.mark_interval_values_as_top
(
offset
,
offset
,
ByteSize
::
new
(
1
),
);
}
}
else
{
self
.tracked_ids
.insert
(
id
.clone
(),
*
call_access_pattern
);
}
if
*
id
==
self
.stack_id
&&
call_access_pattern
.is_mutably_dereferenced
()
{
// The stack value may have been overwritten by the call
if
let
Ok
(
offset
)
=
offset
.try_to_offset
()
{
self
.stack
.mark_interval_values_as_top
(
offset
,
offset
,
ByteSize
::
new
(
1
));
}
}
}
}
}
/// If the given abstract ID represents a possible parameter of the current function
/// then return an argument object corresponding to the parameter.
pub
fn
get_arg_corresponding_to_id
(
&
self
,
id
:
&
AbstractIdentifier
)
->
Option
<
Arg
>
{
if
id
.get_tid
()
==
self
.stack_id
.get_tid
()
{
generate_param_arg_from_abstract_id
(
id
)
.ok
()
}
else
{
None
/// Evaluate the value of a parameter location from a call on the current state.
///
/// This function panics for global parameters.
pub
fn
eval_param_location
(
&
mut
self
,
param_location
:
&
AbstractLocation
,
global_memory
:
&
RuntimeMemoryImage
,
)
->
DataDomain
<
BitvectorDomain
>
{
match
param_location
{
AbstractLocation
::
GlobalAddress
{
..
}
|
AbstractLocation
::
GlobalPointer
(
_
,
_
)
=>
{
panic!
(
"Globals are not valid parameter locations."
)
}
AbstractLocation
::
Register
(
var
)
=>
{
let
value
=
self
.get_register
(
var
);
self
.substitute_global_mem_address
(
value
,
global_memory
)
}
AbstractLocation
::
Pointer
(
var
,
mem_location
)
=>
{
if
var
==
self
.stack_id
.unwrap_register
()
{
self
.eval_stack_pointer_param_location
(
mem_location
,
global_memory
)
}
else
{
let
value
=
self
.get_register
(
var
);
let
value
=
self
.substitute_global_mem_address
(
value
,
global_memory
);
self
.eval_mem_location_relative_value
(
value
,
mem_location
)
}
}
}
}
/// Evaluate the value of a parameter location relative to the stack pointer position in the current state.
fn
eval_stack_pointer_param_location
(
&
mut
self
,
mem_location
:
&
AbstractMemoryLocation
,
global_memory
:
&
RuntimeMemoryImage
,
)
->
DataDomain
<
BitvectorDomain
>
{
let
stack_register
=
self
.stack_id
.unwrap_register
();
match
mem_location
{
AbstractMemoryLocation
::
Location
{
offset
,
size
}
=>
{
if
let
Some
(
stack_offset
)
=
self
.get_offset_if_exact_stack_pointer
(
&
self
.get_register
(
stack_register
))
{
let
stack_offset
=
stack_offset
+
&
Bitvector
::
from_i64
(
*
offset
)
.into_sign_resize
(
self
.stack_id
.bytesize
());
self
.load_value_from_stack
(
stack_offset
,
*
size
)
}
else
{
DataDomain
::
new_top
(
*
size
)
}
}
AbstractMemoryLocation
::
Pointer
{
offset
,
target
:
inner_mem_location
,
}
=>
{
if
let
Some
(
stack_offset
)
=
self
.get_offset_if_exact_stack_pointer
(
&
self
.get_register
(
stack_register
))
{
let
stack_offset
=
stack_offset
+
&
Bitvector
::
from_i64
(
*
offset
)
.into_sign_resize
(
self
.stack_id
.bytesize
());
let
value
=
self
.load_value_from_stack
(
stack_offset
,
self
.stack_id
.bytesize
());
let
value
=
self
.substitute_global_mem_address
(
value
,
global_memory
);
self
.eval_mem_location_relative_value
(
value
,
inner_mem_location
)
}
else
{
DataDomain
::
new_top
(
inner_mem_location
.bytesize
())
}
}
}
}
/// Return `true` if the given ID is a parameter ID,
/// but not a global parameter.
/// This function does not check access patterns for the ID.
fn
is_register_based_param_id
(
&
self
,
id
:
&
AbstractIdentifier
)
->
bool
{
if
id
.get_tid
()
!=
self
.get_current_function_tid
()
||
id
==
&
self
.stack_id
{
return
false
;
}
// Filter out global IDs
if
matches!
(
id
.get_location
(),
AbstractLocation
::
GlobalAddress
{
..
}
|
AbstractLocation
::
GlobalPointer
(
_
,
_
)
)
{
return
false
;
}
// Filter out stack based IDs
if
let
AbstractLocation
::
Pointer
(
var
,
_
)
=
id
.get_location
()
{
if
var
==
self
.stack_id
.unwrap_register
()
{
return
false
;
}
}
true
}
/// Return `true` if the given ID is a stack parameter ID or a nested stack parameter ID.
/// This function does not check access patterns for the ID.
fn
is_stack_based_param_id
(
&
self
,
id
:
&
AbstractIdentifier
)
->
bool
{
if
id
.get_tid
()
!=
self
.get_current_function_tid
()
||
id
==
&
self
.stack_id
{
return
false
;
}
if
let
AbstractLocation
::
Pointer
(
register
,
mem_location
)
=
id
.get_location
()
{
if
register
==
self
.stack_id
.unwrap_register
()
{
// ID is stack based, we have to filter out negative stack offsets.
match
mem_location
{
AbstractMemoryLocation
::
Location
{
offset
,
..
}
|
AbstractMemoryLocation
::
Pointer
{
offset
,
..
}
=>
{
if
*
offset
<
0
{
return
false
;
}
}
}
return
true
;
}
}
false
}
}
/// Generate register arguments from a list of registers.
...
...
@@ -233,25 +347,5 @@ fn generate_args_from_registers(registers: &[Variable]) -> Vec<Arg> {
.collect
()
}
/// Generate an argument representing the location in the given abstract ID.
/// If the location is a pointer, it is assumed that the pointer points to the stack.
/// Returns an error if the location contains a second level of indirection
/// or if the location is associated to global memory.
fn
generate_param_arg_from_abstract_id
(
id
:
&
AbstractIdentifier
)
->
Result
<
Arg
,
Error
>
{
match
id
.get_location
()
{
AbstractLocation
::
Register
(
var
)
=>
Ok
(
Arg
::
from_var
(
var
.clone
(),
None
)),
AbstractLocation
::
Pointer
(
var
,
mem_location
)
=>
match
mem_location
{
AbstractMemoryLocation
::
Location
{
offset
,
size
}
=>
Ok
(
Arg
::
Stack
{
address
:
Expression
::
Var
(
var
.clone
())
.plus_const
(
*
offset
),
size
:
*
size
,
data_type
:
None
,
}),
AbstractMemoryLocation
::
Pointer
{
..
}
=>
{
Err
(
anyhow!
(
"Memory location is not a stack offset."
))
}
},
AbstractLocation
::
GlobalAddress
{
..
}
|
AbstractLocation
::
GlobalPointer
(
_
,
_
)
=>
{
Err
(
anyhow!
(
"Global values are not parameters."
))
}
}
}
#[cfg(test)]
pub
mod
tests
;
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/state/call_handling/tests.rs
0 → 100644
View file @
f6ced95c
use
super
::
*
;
use
crate
::{
bitvec
,
variable
};
#[test]
fn
test_generate_return_values_for_call
()
{
let
mut
state
=
State
::
mock_arm32
();
let
input_ids
=
BTreeSet
::
from
([
AbstractIdentifier
::
mock
(
"mock_fn"
,
"r0"
,
4
),
AbstractIdentifier
::
mock
(
"mock_fn"
,
"big_register"
,
16
),
]);
let
return_args
=
[
Arg
::
mock_register
(
"r1"
,
4
)];
let
call_tid
=
Tid
::
new
(
"call"
);
state
.generate_return_values_for_call
(
&
input_ids
,
&
return_args
,
&
call_tid
);
assert
!
(
state
.tracked_ids
.get
(
&
AbstractIdentifier
::
mock
(
"call"
,
"r1"
,
4
))
.is_some
());
let
expected_return_value
=
DataDomain
::
mock_from_target_map
(
BTreeMap
::
from
([
(
AbstractIdentifier
::
mock
(
"mock_fn"
,
"r0"
,
4
),
BitvectorDomain
::
new_top
(
ByteSize
::
new
(
4
)),
),
(
AbstractIdentifier
::
mock
(
"call"
,
"r1"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
]));
assert_eq!
(
state
.register
[
&
variable!
(
"r1:4"
)],
expected_return_value
);
}
#[test]
fn
test_get_params_of_current_function
()
{
let
mut
state
=
State
::
mock_arm32
();
let
param_one
=
AbstractIdentifier
::
mock
(
"mock_fn"
,
"param_one"
,
4
);
let
param_two
=
AbstractIdentifier
::
mock
(
"mock_fn"
,
"param_two"
,
4
);
let
not_param
=
AbstractIdentifier
::
mock
(
"call_tid"
,
"r0"
,
4
);
let
non_param_stack_offset
=
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
mock
(
"sp:4"
,
&
[
-
8
],
4
),
);
let
global_param
=
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
GlobalAddress
{
address
:
0x1000
,
size
:
ByteSize
::
new
(
4
),
},
);
state
.tracked_ids
.insert
(
param_one
.clone
(),
AccessPattern
::
new
()
.with_read_flag
());
state
.tracked_ids
.insert
(
param_two
.clone
(),
AccessPattern
::
new
()
.with_dereference_flag
(),
);
state
.tracked_ids
.insert
(
not_param
,
AccessPattern
::
new_unknown_access
());
state
.tracked_ids
.insert
(
non_param_stack_offset
,
AccessPattern
::
new_unknown_access
());
state
.tracked_ids
.insert
(
global_param
.clone
(),
AccessPattern
::
new_unknown_access
());
let
params
=
state
.get_params_of_current_function
();
let
global_params
=
state
.get_global_mem_params_of_current_function
();
assert_eq!
(
params
,
Vec
::
from
([
(
param_one
.get_location
(),
AccessPattern
::
new
()
.with_read_flag
()
),
(
param_two
.get_location
(),
AccessPattern
::
new
()
.with_dereference_flag
()
)
])
);
assert_eq!
(
global_params
,
Vec
::
from
([(
global_param
.get_location
(),
AccessPattern
::
new_unknown_access
()
)])
);
}
#[test]
fn
test_merge_parameter_access
()
{
let
mut
state
=
State
::
mock_arm32
();
let
num_original_tracked_ids
=
state
.tracked_ids
.len
();
let
global_memory
=
RuntimeMemoryImage
::
mock
();
state
.register
.insert
(
variable!
(
"sp:4"
),
DataDomain
::
from_target
(
state
.stack_id
.clone
(),
bitvec!
(
"0x-20:4"
)
.into
()),
);
state
.register
.insert
(
variable!
(
"r1:4"
),
DataDomain
::
from_target
(
AbstractIdentifier
::
mock
(
"mock_fn"
,
"r0"
,
4
),
bitvec!
(
"0x2:4"
)
.into
(),
),
);
let
param_loc
=
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
);
let
stack_param_loc
=
AbstractLocation
::
mock
(
"sp:4"
,
&
[
0
],
4
);
let
high_stack_param_loc
=
AbstractLocation
::
mock
(
"sp:4"
,
&
[
32
],
4
);
let
nested_param_loc
=
AbstractLocation
::
mock
(
"r1:4"
,
&
[
6
],
4
);
let
params
=
[
(
&
param_loc
,
AccessPattern
::
new_unknown_access
()),
(
&
stack_param_loc
,
AccessPattern
::
new_unknown_access
()),
(
&
high_stack_param_loc
,
AccessPattern
::
new_unknown_access
()),
(
&
nested_param_loc
,
AccessPattern
::
new_unknown_access
()),
];
state
.merge_parameter_access
(
&
params
,
&
global_memory
);
// Merge normal param access
assert_eq!
(
state
.tracked_ids
.get
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
param_loc
.clone
()
))
.unwrap
(),
&
AccessPattern
::
new_unknown_access
()
);
// Do not merge/track access to local stack variable
assert
!
(
state
.tracked_ids
.get
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
mock
(
"sp:4"
,
&
[
-
32
],
4
)
))
.is_none
());
// Generate new stack param if necessary
assert_eq!
(
state
.tracked_ids
.get
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
mock
(
"sp:4"
,
&
[
0
],
4
)
))
.unwrap
(),
&
AccessPattern
::
new_unknown_access
()
);
// Track new nested parameter (in the right register)
assert_eq!
(
state
.tracked_ids
.get
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[
8
],
4
)
))
.unwrap
(),
&
AccessPattern
::
new_unknown_access
()
);
assert_eq!
(
state
.tracked_ids
.len
(),
num_original_tracked_ids
+
2
);
}
#[test]
fn
test_eval_param_location
()
{
let
mut
state
=
State
::
mock_arm32
();
let
global_memory
=
RuntimeMemoryImage
::
mock
();
// Param is a register
state
.register
.insert
(
variable!
(
"r0:4"
),
bitvec!
(
"0x123:4"
)
.into
());
let
value
=
state
.eval_param_location
(
&
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
),
&
global_memory
);
assert_eq!
(
value
,
bitvec!
(
"0x123:4"
)
.into
());
// Param is a nested register (and values in nested objects are not tracked)
state
.register
.insert
(
variable!
(
"r0:4"
),
DataDomain
::
from_target
(
AbstractIdentifier
::
mock
(
"mock_fn"
,
"r3"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
);
let
value
=
state
.eval_param_location
(
&
AbstractLocation
::
mock
(
"r0:4"
,
&
[
8
],
4
),
&
global_memory
);
assert_eq!
(
value
,
DataDomain
::
from_target
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
mock
(
"r3:4"
,
&
[
8
],
4
)),
bitvec!
(
"0x0:4"
)
.into
()
)
);
// Read the value at a stack offset
state
.stack
.insert_at_byte_index
(
bitvec!
(
"0x42:4"
)
.into
(),
-
8
);
let
value
=
state
.eval_param_location
(
&
AbstractLocation
::
mock
(
"sp:4"
,
&
[
-
8
],
4
),
&
global_memory
);
assert_eq!
(
value
,
bitvec!
(
"0x42:4"
)
.into
());
// Read a nested pointer from the stack. The read has to remove one level of indirection if the stack value can be read.
state
.stack
.insert_at_byte_index
(
DataDomain
::
from_target
(
AbstractIdentifier
::
mock
(
"mock_fn"
,
"r0"
,
4
),
bitvec!
(
"0x5:4"
)
.into
(),
),
-
8
,
);
let
value
=
state
.eval_param_location
(
&
AbstractLocation
::
mock
(
"sp:4"
,
&
[
-
8
,
2
,
6
],
4
),
&
global_memory
,
);
assert_eq!
(
value
,
DataDomain
::
from_target
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[
7
,
6
],
4
)
),
bitvec!
(
"0x0:4"
)
.into
()
)
);
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/state/memory_handling.rs
0 → 100644
View file @
f6ced95c
use
super
::
State
;
use
super
::
POINTER_RECURSION_DEPTH_LIMIT
;
use
crate
::
abstract_domain
::
*
;
use
crate
::
intermediate_representation
::
*
;
impl
State
{
/// Load the value at the given address.
///
/// Only values on the stack and in registers are tracked directly.
/// For all other values abstract location strings are generated
/// that track how the pointer to the value is computed.
///
/// This function does not set any access flags for input IDs in the address value.
pub
fn
load_value
(
&
mut
self
,
address
:
DataDomain
<
BitvectorDomain
>
,
size
:
ByteSize
,
global_memory
:
Option
<&
RuntimeMemoryImage
>
,
)
->
DataDomain
<
BitvectorDomain
>
{
let
mut
loaded_value
=
DataDomain
::
new_empty
(
size
);
for
(
id
,
offset
)
in
address
.get_relative_values
()
{
loaded_value
=
loaded_value
.merge
(
&
self
.load_value_via_id_and_offset
(
id
,
offset
,
size
));
}
if
let
Some
(
global_address
)
=
address
.get_absolute_value
()
{
loaded_value
=
loaded_value
.merge
(
&
self
.load_global_address
(
global_address
,
size
,
global_memory
));
}
if
address
.contains_top
()
{
loaded_value
.set_contains_top_flag
();
}
loaded_value
}
/// Load the value whose position is given by derefencing the given ID and then adding an offset.
///
/// If the ID is the stack then this function actually loads the value at the given stack position.
/// Otherwise it only generates the abstract location of the value and returns it as a relative value.
fn
load_value_via_id_and_offset
(
&
mut
self
,
id
:
&
AbstractIdentifier
,
offset
:
&
BitvectorDomain
,
size
:
ByteSize
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
*
id
==
self
.stack_id
{
// Try to load a value from the stack (which may generate a new stack parameter)
match
offset
.try_to_bitvec
()
{
Ok
(
stack_offset
)
=>
self
.load_value_from_stack
(
stack_offset
,
size
),
Err
(
_
)
=>
DataDomain
::
new_top
(
size
),
}
}
else
if
let
(
true
,
Ok
(
constant_offset
))
=
(
id
.get_location
()
.recursion_depth
()
<
POINTER_RECURSION_DEPTH_LIMIT
,
offset
.try_to_offset
(),
)
{
// Extend the abstract location string
let
new_id
=
AbstractIdentifier
::
new
(
id
.get_tid
()
.clone
(),
id
.get_location
()
.clone
()
.dereferenced
(
size
,
self
.stack_id
.bytesize
())
.with_offset_addendum
(
constant_offset
),
);
DataDomain
::
from_target
(
new_id
,
Bitvector
::
zero
(
size
.into
())
.into
())
}
else
{
// The abstract location string cannot be extended
DataDomain
::
new_top
(
size
)
}
}
/// Load a value from the global address space.
/// If the address is located in writeable global memory then generate a new abstract ID for the value
/// and return a value relative to the new ID.
fn
load_global_address
(
&
mut
self
,
global_address
:
&
BitvectorDomain
,
size
:
ByteSize
,
global_memory
:
Option
<&
RuntimeMemoryImage
>
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
let
(
Ok
(
offset
),
Some
(
global_mem
))
=
(
global_address
.try_to_bitvec
(),
global_memory
)
{
match
global_mem
.read
(
&
offset
,
size
)
{
Ok
(
Some
(
value
))
=>
value
.into
(),
Ok
(
None
)
=>
{
let
address
=
global_address
.try_to_offset
()
.unwrap
()
as
u64
;
let
global_mem_location
=
AbstractLocation
::
GlobalAddress
{
address
,
size
};
let
global_mem_id
=
AbstractIdentifier
::
new
(
self
.get_current_function_tid
()
.clone
(),
global_mem_location
,
);
DataDomain
::
from_target
(
global_mem_id
,
Bitvector
::
zero
(
size
.into
())
.into
())
}
Err
(
_
)
=>
DataDomain
::
new_top
(
size
),
}
}
else
{
DataDomain
::
new_top
(
size
)
}
}
/// Load the value at the given stack offset.
/// If the offset is non-negative a corresponding stack parameter is generated if necessary.
pub
fn
load_value_from_stack
(
&
mut
self
,
stack_offset
:
Bitvector
,
size
:
ByteSize
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
!
stack_offset
.sign_bit
()
.to_bool
()
{
// Stack offset is nonnegative, i.e. this is a stack parameter access.
self
.get_stack_param
(
stack_offset
,
size
)
}
else
{
self
.stack
.get
(
stack_offset
,
size
)
}
}
/// Load a value of unknown bytesize at the given stack offset.
/// If the offset is non-negative, a corresponding stack parameter is generated if necessary.
///
/// One must be careful to not rely on the correctness of the bytesize of the returned value!
/// If the size of the value cannot be guessed from the contents of the stack,
/// then a size of 1 byte is assumed, which will be wrong in general!
pub
fn
load_unsized_value_from_stack
(
&
mut
self
,
offset
:
Bitvector
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
!
offset
.sign_bit
()
.to_bool
()
{
// This is a pointer to a stack parameter of the current function
self
.stack
.get_unsized
(
offset
.clone
())
.unwrap_or_else
(||
self
.get_stack_param
(
offset
,
ByteSize
::
new
(
1
)))
}
else
{
self
.stack
.get_unsized
(
offset
)
.unwrap_or_else
(||
DataDomain
::
new_top
(
ByteSize
::
new
(
1
)))
}
}
/// If `address` is a stack offset, then write `value` onto the stack.
///
/// If address points to a stack parameter, whose ID does not yet exists,
/// then the ID is generated and added to the tracked IDs.
///
/// This function does not set any access flags for input IDs of the given address or value.
pub
fn
write_value
(
&
mut
self
,
address
:
DataDomain
<
BitvectorDomain
>
,
value
:
DataDomain
<
BitvectorDomain
>
,
)
{
if
let
Some
(
stack_offset
)
=
self
.get_offset_if_exact_stack_pointer
(
&
address
)
{
if
!
stack_offset
.sign_bit
()
.to_bool
()
{
// We generate a new stack parameter object, but do not set any access flags,
// since the stack parameter is not accessed but overwritten.
let
_
=
self
.generate_stack_param_id_if_nonexistent
(
stack_offset
.clone
(),
value
.bytesize
());
}
self
.stack
.add
(
value
,
stack_offset
);
}
else
if
let
Some
(
stack_offset_domain
)
=
address
.get_relative_values
()
.get
(
&
self
.stack_id
)
{
if
let
Ok
(
stack_offset
)
=
stack_offset_domain
.try_to_bitvec
()
{
if
!
stack_offset
.sign_bit
()
.to_bool
()
{
// We generate a new stack parameter object, but do not set any access flags,
// since the stack parameter is not accessed but overwritten.
let
_
=
self
.generate_stack_param_id_if_nonexistent
(
stack_offset
.clone
(),
value
.bytesize
(),
);
}
let
previous_value
=
self
.stack
.get
(
stack_offset
.clone
(),
value
.bytesize
());
self
.stack
.add
(
previous_value
.merge
(
&
value
),
stack_offset
);
}
else
{
self
.stack
.mark_all_values_as_top
();
}
}
}
/// Get the value located at a positive stack offset.
/// This function panics if the address is a negative offset.
///
/// If no corresponding stack parameter ID exists for the value,
/// generate it and then return it as an unmodified stack parameter.
/// Otherwise just read the value at the given stack address.
fn
get_stack_param
(
&
mut
self
,
address
:
Bitvector
,
size
:
ByteSize
,
)
->
DataDomain
<
BitvectorDomain
>
{
assert
!
(
!
address
.sign_bit
()
.to_bool
());
if
let
Some
(
param_id
)
=
self
.generate_stack_param_id_if_nonexistent
(
address
.clone
(),
size
)
{
let
stack_param
=
DataDomain
::
from_target
(
param_id
,
Bitvector
::
zero
(
size
.into
())
.into
());
self
.stack
.add
(
stack_param
.clone
(),
address
);
stack_param
}
else
{
self
.stack
.get
(
address
,
size
)
}
}
/// If the address is an exactly known pointer to the stack with a constant offset, then return the offset.
pub
fn
get_offset_if_exact_stack_pointer
(
&
self
,
address
:
&
DataDomain
<
BitvectorDomain
>
,
)
->
Option
<
Bitvector
>
{
if
let
Some
((
target
,
offset
))
=
address
.get_if_unique_target
()
{
if
*
target
==
self
.stack_id
{
return
offset
.try_to_bitvec
()
.ok
();
}
}
None
}
}
#[cfg(test)]
pub
mod
tests
{
use
super
::
*
;
use
crate
::{
bitvec
,
variable
};
/// Mock an abstract ID representing the stack.
fn
mock_stack_id
()
->
AbstractIdentifier
{
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"mock_fn"
),
&
variable!
(
"sp:4"
))
}
/// Mock an abstract ID of a stack parameter
fn
mock_stack_param_id
(
offset
:
i64
,
size
:
u64
)
->
AbstractIdentifier
{
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
from_stack_position
(
mock_stack_id
()
.unwrap_register
(),
offset
,
ByteSize
::
new
(
size
),
),
)
}
#[test]
fn
test_get_offset_if_exact_stack_pointer
()
{
let
state
=
State
::
mock_arm32
();
let
stack_pointer
=
DataDomain
::
from_target
(
mock_stack_id
(),
Bitvector
::
from_i32
(
-
10
)
.into
());
assert_eq!
(
state
.get_offset_if_exact_stack_pointer
(
&
stack_pointer
),
Some
(
Bitvector
::
from_i32
(
-
10
))
);
}
#[test]
fn
test_get_stack_param
()
{
// Reading a previously non-existing stack parameter
let
mut
state
=
State
::
mock_arm32
();
let
stack_param
=
state
.get_stack_param
(
bitvec!
(
"0xc:4"
),
ByteSize
::
new
(
8
));
let
expected_stack_id
=
AbstractIdentifier
::
mock_nested
(
"mock_fn"
,
"sp:4"
,
&
[
12
],
8
);
let
expected_value
=
DataDomain
::
from_target
(
expected_stack_id
.clone
(),
bitvec!
(
"0x0:8"
)
.into
());
assert_eq!
(
&
stack_param
,
&
expected_value
);
assert
!
(
state
.tracked_ids
.contains_key
(
&
expected_stack_id
));
// Reading the stack parameter again. The position should still contain the stack parameter.
let
stack_param
=
state
.get_stack_param
(
bitvec!
(
"0xc:4"
),
ByteSize
::
new
(
8
));
assert_eq!
(
&
stack_param
,
&
expected_value
);
// Reading the stack parameter after it has been overwritten with a value.
state
.stack
.insert_at_byte_index
(
bitvec!
(
"0x2a:8"
)
.into
(),
12
);
let
value
=
state
.get_stack_param
(
bitvec!
(
"0xc:4"
),
ByteSize
::
new
(
8
));
assert_eq!
(
value
,
bitvec!
(
"0x2a:8"
)
.into
());
}
#[test]
fn
test_store_and_load_from_stack
()
{
let
mut
state
=
State
::
mock_arm32
();
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
bitvec!
(
"-4:4"
)
.into
());
let
value
:
DataDomain
<
BitvectorDomain
>
=
bitvec!
(
"0x0:4"
)
.into
();
// write and load a value to the current stack frame
state
.write_value
(
address
.clone
(),
value
.clone
());
assert_eq!
(
state
.stack
.iter
()
.len
(),
1
);
assert_eq!
(
state
.stack
.get
(
bitvec!
(
"-4:4"
),
ByteSize
::
new
(
4
)),
value
.clone
()
);
assert_eq!
(
state
.load_value
(
address
,
ByteSize
::
new
(
4
),
None
),
value
);
// Load a parameter register and check that the parameter gets generated
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
bitvec!
(
"0x4:4"
)
.into
());
let
stack_param_id
=
mock_stack_param_id
(
4
,
4
);
let
stack_param
=
DataDomain
::
from_target
(
stack_param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
());
assert_eq!
(
state
.tracked_ids
.iter
()
.len
(),
6
);
assert_eq!
(
state
.load_value
(
address
.clone
(),
ByteSize
::
new
(
4
),
None
),
stack_param
);
assert_eq!
(
state
.tracked_ids
.iter
()
.len
(),
7
);
assert_eq!
(
state
.tracked_ids
.get
(
&
stack_param_id
)
.unwrap
()
.is_accessed
(),
false
);
// The load method does not set access flags.
}
#[test]
fn
test_load_unsized_from_stack
()
{
let
mut
state
=
State
::
mock_arm32
();
// Load an existing stack param (generated by a sized load at the same address)
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
bitvec!
(
"0x0:4"
)
.into
());
let
stack_param_id
=
mock_stack_param_id
(
0
,
4
);
let
stack_param
=
DataDomain
::
from_target
(
stack_param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
());
state
.load_value
(
address
,
ByteSize
::
new
(
4
),
None
);
let
unsized_load
=
state
.load_unsized_value_from_stack
(
bitvec!
(
"0x0:4"
)
.into
());
assert_eq!
(
unsized_load
,
stack_param
);
assert
!
(
state
.tracked_ids
.get
(
&
stack_param_id
)
.is_some
());
// Load a non-existing stack param
let
stack_param_id
=
mock_stack_param_id
(
4
,
1
);
let
stack_param
=
DataDomain
::
from_target
(
stack_param_id
.clone
(),
bitvec!
(
"0x0:1"
)
.into
());
let
unsized_load
=
state
.load_unsized_value_from_stack
(
bitvec!
(
"0x4:4"
));
assert_eq!
(
unsized_load
,
stack_param
);
assert
!
(
state
.tracked_ids
.get
(
&
stack_param_id
)
.is_some
());
// Unsized load from the current stack frame
let
unsized_load
=
state
.load_unsized_value_from_stack
(
bitvec!
(
"-4:4"
));
assert_eq!
(
unsized_load
,
DataDomain
::
new_top
(
ByteSize
::
new
(
1
)));
}
#[test]
fn
test_load_nested_pointers
()
{
let
mut
state
=
State
::
mock_arm32
();
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
parent_id
=
AbstractIdentifier
::
mock_nested
(
"mock_fn"
,
"r0:4"
,
&
[
4
],
4
);
let
pointer
=
DataDomain
::
from_target
(
parent_id
.clone
(),
bitvec!
(
"0x8:4"
)
.into
());
let
loaded_value
=
state
.load_value
(
pointer
,
ByteSize
::
new
(
4
),
Some
(
&
global_memory
));
let
expected_id
=
AbstractIdentifier
::
mock_nested
(
"mock_fn"
,
"r0:4"
,
&
[
4
,
8
],
4
);
let
expected_value
=
DataDomain
::
from_target
(
expected_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
());
assert_eq!
(
loaded_value
,
expected_value
);
}
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/state.rs
→
src/cwe_checker_lib/src/analysis/function_signature/state
/mod
.rs
View file @
f6ced95c
use
std
::
collections
::
BTreeMap
;
use
std
::
collections
::
BTreeSet
;
use
super
::
AccessPattern
;
use
super
::
POINTER_RECURSION_DEPTH_LIMIT
;
use
crate
::
abstract_domain
::
*
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
prelude
::
*
;
use
s
uper
::
AccessPattern
;
use
std
::
collections
::
BTreeMap
;
use
s
td
::
collections
::
BTreeSet
;
/// Methods of [`State`] related to handling call instructions.
mod
call_handling
;
/// Methods of [`State`] related to handling load and store instructions.
mod
memory_handling
;
/// The state tracks knowledge about known register values,
/// known values on the stack, and access patterns of tracked variables.
...
...
@@ -113,90 +114,6 @@ impl State {
self
.stack_id
.get_tid
()
}
/// Load the value at the given address.
///
/// Only constant addresses on the stack are tracked.
/// Thus this function will always return a `Top` domain for any address
/// that may not be a stack address with constant offset.
///
/// This function does not set any access flags for input IDs in the address value.
pub
fn
load_value
(
&
mut
self
,
address
:
DataDomain
<
BitvectorDomain
>
,
size
:
ByteSize
,
global_memory
:
Option
<&
RuntimeMemoryImage
>
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
let
Some
(
stack_offset
)
=
self
.get_offset_if_exact_stack_pointer
(
&
address
)
{
self
.load_value_from_stack
(
stack_offset
,
size
)
}
else
if
let
(
Ok
(
global_address
),
Some
(
global_mem
))
=
(
address
.try_to_bitvec
(),
global_memory
)
{
if
let
Ok
(
Some
(
value
))
=
global_mem
.read
(
&
global_address
,
size
)
{
value
.into
()
}
else
{
DataDomain
::
new_top
(
size
)
}
}
else
{
DataDomain
::
new_top
(
size
)
}
}
/// Load the value at the given stack offset.
/// If the offset is non-negative a corresponding stack parameter is generated if necessary.
fn
load_value_from_stack
(
&
mut
self
,
stack_offset
:
Bitvector
,
size
:
ByteSize
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
!
stack_offset
.sign_bit
()
.to_bool
()
{
// Stack offset is nonnegative, i.e. this is a stack parameter access.
self
.get_stack_param
(
stack_offset
,
size
)
}
else
{
self
.stack
.get
(
stack_offset
,
size
)
}
}
/// Load a value of unknown bytesize at the given stack offset.
/// If the offset is non-negative, a corresponding stack parameter is generated if necessary.
///
/// One must be careful to not rely on the correctness of the bytesize of the returned value!
/// If the size of the value cannot be guessed from the contents of the stack,
/// then a size of 1 byte is assumed, which will be wrong in general!
fn
load_unsized_value_from_stack
(
&
mut
self
,
offset
:
Bitvector
)
->
DataDomain
<
BitvectorDomain
>
{
if
!
offset
.sign_bit
()
.to_bool
()
{
// This is a pointer to a stack parameter of the current function
self
.stack
.get_unsized
(
offset
.clone
())
.unwrap_or_else
(||
self
.get_stack_param
(
offset
,
ByteSize
::
new
(
1
)))
}
else
{
self
.stack
.get_unsized
(
offset
)
.unwrap_or_else
(||
DataDomain
::
new_top
(
ByteSize
::
new
(
1
)))
}
}
/// If `address` is a stack offset, then write `value` onto the stack.
///
/// If address points to a stack parameter, whose ID does not yet exists,
/// then the ID is generated and added to the tracked IDs.
///
/// This function does not set any access flags for input IDs of the given address or value.
pub
fn
write_value
(
&
mut
self
,
address
:
DataDomain
<
BitvectorDomain
>
,
value
:
DataDomain
<
BitvectorDomain
>
,
)
{
if
let
Some
(
stack_offset
)
=
self
.get_offset_if_exact_stack_pointer
(
&
address
)
{
// We generate a new stack parameter object, but do not set any access flags,
// since the stack parameter is not accessed but overwritten.
if
!
stack_offset
.sign_bit
()
.to_bool
()
{
let
_
=
self
.generate_stack_param_id_if_nonexistent
(
stack_offset
.clone
(),
value
.bytesize
());
}
self
.stack
.add
(
value
,
stack_offset
);
}
}
/// If the stack parameter ID corresponding to the given stack offset does not exist
/// then generate it, add it to the list of tracked IDs, and return it.
fn
generate_stack_param_id_if_nonexistent
(
...
...
@@ -228,40 +145,6 @@ impl State {
}
}
/// Get the value located at a positive stack offset.
///
/// If no corresponding stack parameter ID exists for the value,
/// generate it and then return it as an unmodified stack parameter.
/// Otherwise just read the value at the given stack address.
fn
get_stack_param
(
&
mut
self
,
address
:
Bitvector
,
size
:
ByteSize
,
)
->
DataDomain
<
BitvectorDomain
>
{
assert
!
(
!
address
.sign_bit
()
.to_bool
());
if
let
Some
(
param_id
)
=
self
.generate_stack_param_id_if_nonexistent
(
address
.clone
(),
size
)
{
let
stack_param
=
DataDomain
::
from_target
(
param_id
,
Bitvector
::
zero
(
size
.into
())
.into
());
self
.stack
.add
(
stack_param
.clone
(),
address
);
stack_param
}
else
{
self
.stack
.get
(
address
,
size
)
}
}
/// If the address is an exactly known pointer to the stack with a constant offset, then return the offset.
pub
fn
get_offset_if_exact_stack_pointer
(
&
self
,
address
:
&
DataDomain
<
BitvectorDomain
>
,
)
->
Option
<
Bitvector
>
{
if
let
Some
((
target
,
offset
))
=
address
.get_if_unique_target
()
{
if
*
target
==
self
.stack_id
{
return
offset
.try_to_bitvec
()
.ok
();
}
}
None
}
/// Merges the access pattern of the given abstract identifer in `self` with the provided access pattern.
///
/// Does not add the identifier to the list of tracked identifiers if it is not already tracked in `self`.
...
...
@@ -308,13 +191,56 @@ impl State {
size
,
data_type
:
_
,
}
=>
{
self
.set_deref_flag_for_input_ids_of_expression
(
address
);
self
.set_deref_flag_for_pointer_inputs_of_expression
(
address
);
self
.set_read_flag_for_input_ids_of_expression
(
address
);
let
address
=
self
.eval
(
address
);
self
.load_value
(
address
,
*
size
,
None
)
}
}
}
/// Evaluate the value at the given memory location
/// where `value` represents the root pointer relative to which the memory location needs to be computed.
fn
eval_mem_location_relative_value
(
&
mut
self
,
value
:
DataDomain
<
BitvectorDomain
>
,
mem_location
:
&
AbstractMemoryLocation
,
)
->
DataDomain
<
BitvectorDomain
>
{
let
target_size
=
mem_location
.bytesize
();
let
mut
eval_result
=
DataDomain
::
new_empty
(
target_size
);
for
(
id
,
offset
)
in
value
.get_relative_values
()
{
let
mut
location
=
id
.get_location
()
.clone
();
let
mut
mem_location
=
mem_location
.clone
();
match
offset
.try_to_offset
()
{
Ok
(
concrete_offset
)
=>
mem_location
.add_offset_at_root
(
concrete_offset
),
Err
(
_
)
=>
{
eval_result
.set_contains_top_flag
();
continue
;
}
};
location
.extend
(
mem_location
,
self
.stack_id
.bytesize
());
if
location
.recursion_depth
()
<=
POINTER_RECURSION_DEPTH_LIMIT
{
eval_result
=
eval_result
.merge
(
&
DataDomain
::
from_target
(
AbstractIdentifier
::
new
(
id
.get_tid
()
.clone
(),
location
),
Bitvector
::
zero
(
target_size
.into
())
.into
(),
));
}
else
{
eval_result
.set_contains_top_flag
();
}
}
if
value
.contains_top
()
||
value
.get_absolute_value
()
.is_some
()
{
eval_result
.set_contains_top_flag
();
}
eval_result
}
/// Add all relative IDs in `data` to the list of tracked IDs.
pub
fn
track_contained_ids
(
&
mut
self
,
data
:
&
DataDomain
<
BitvectorDomain
>
)
{
for
id
in
data
.referenced_ids
()
{
self
.add_id_to_tracked_ids
(
id
);
}
}
/// If the given expression is not an [`Expression::Var`] set the read flags
/// for all IDs that may be referenced when computing the value of the expression.
///
...
...
@@ -341,22 +267,34 @@ impl State {
}
}
/// Set the read and dereferenced flag for every tracked ID
/// that may be referenced when computing the value of the expression.
pub
fn
set_deref_flag_for_
input_id
s_of_expression
(
&
mut
self
,
expression
:
&
Expression
)
{
for
register
in
expression
.input_vars
(
)
{
/// Set the read and dereferenced flag for every tracked
pointer
ID
/// that may be referenced when computing the value of the
given address
expression.
pub
fn
set_deref_flag_for_
pointer_input
s_of_expression
(
&
mut
self
,
expression
:
&
Expression
)
{
for
register
in
get_pointer_inputs_vars_of_address_expression
(
expression
)
{
self
.set_deref_flag_for_contained_ids
(
&
self
.get_register
(
register
));
}
}
/// Set the read and mutably dereferenced flag for every tracked ID
/// that may be referenced when computing the value of the expression.
pub
fn
set_mutable_deref_flag_for_input_ids_of_expression
(
&
mut
self
,
expression
:
&
Expression
)
{
for
register
in
expression
.input_vars
()
{
/// Set the read and mutably dereferenced flag for every tracked pointer ID
/// that may be referenced when computing the value of the given address expression.
pub
fn
set_mutable_deref_flag_for_pointer_inputs_of_expression
(
&
mut
self
,
expression
:
&
Expression
,
)
{
for
register
in
get_pointer_inputs_vars_of_address_expression
(
expression
)
{
self
.set_deref_mut_flag_for_contained_ids
(
&
self
.get_register
(
register
));
}
}
/// Set the read flag for every tracked ID contained in the given value.
pub
fn
set_read_flag_for_contained_ids
(
&
mut
self
,
value
:
&
DataDomain
<
BitvectorDomain
>
)
{
for
id
in
value
.referenced_ids
()
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
object
.set_read_flag
();
}
}
}
/// Set the read and dereferenced flag for every tracked ID contained in the given value.
pub
fn
set_deref_flag_for_contained_ids
(
&
mut
self
,
value
:
&
DataDomain
<
BitvectorDomain
>
)
{
for
id
in
value
.referenced_ids
()
{
...
...
@@ -413,6 +351,34 @@ impl State {
}
}
/// Get a list of possible pointer input variables for the given address expression.
///
/// Only addition, subtraction and bitwise AND, OR, XOR can have pointers as inputs.
/// All other subexpressions are assumed to only compute offsets.
fn
get_pointer_inputs_vars_of_address_expression
(
expr
:
&
Expression
)
->
Vec
<&
Variable
>
{
let
mut
input_vars
=
Vec
::
new
();
match
expr
{
Expression
::
BinOp
{
op
,
lhs
,
rhs
}
=>
{
match
op
{
BinOpType
::
IntAdd
|
BinOpType
::
IntAnd
|
BinOpType
::
IntXOr
|
BinOpType
::
IntOr
=>
{
// There could be a pointer on either of the sides
input_vars
.extend
(
get_pointer_inputs_vars_of_address_expression
(
lhs
));
input_vars
.extend
(
get_pointer_inputs_vars_of_address_expression
(
rhs
));
}
BinOpType
::
IntSub
=>
{
// Only the left side could be a pointer
input_vars
.extend
(
get_pointer_inputs_vars_of_address_expression
(
lhs
));
}
_
=>
(),
}
}
Expression
::
Var
(
var
)
=>
input_vars
.push
(
var
),
_
=>
(),
}
input_vars
}
impl
AbstractDomain
for
State
{
/// Merge two states
fn
merge
(
&
self
,
other
:
&
Self
)
->
Self
{
...
...
@@ -472,4 +438,4 @@ impl State {
}
#[cfg(test)]
mod
tests
;
pub
mod
tests
;
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/state/tests.rs
View file @
f6ced95c
...
...
@@ -26,18 +26,6 @@ fn mock_stack_id() -> AbstractIdentifier {
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"mock_fn"
),
&
variable!
(
"sp:4"
))
}
/// Mock an abstract ID of a stack parameter
fn
mock_stack_param_id
(
offset
:
i64
,
size
:
u64
)
->
AbstractIdentifier
{
AbstractIdentifier
::
new
(
Tid
::
new
(
"mock_fn"
),
AbstractLocation
::
from_stack_position
(
mock_stack_id
()
.unwrap_register
(),
offset
,
ByteSize
::
new
(
size
),
),
)
}
#[test]
fn
test_new
()
{
let
state
=
State
::
mock_arm32
();
...
...
@@ -65,61 +53,6 @@ fn test_new() {
}
#[test]
fn
test_store_and_load_from_stack
()
{
let
mut
state
=
State
::
mock_arm32
();
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
bitvec!
(
"-4:4"
)
.into
());
let
value
:
DataDomain
<
BitvectorDomain
>
=
bitvec!
(
"0x0:4"
)
.into
();
// write and load a value to the current stack frame
state
.write_value
(
address
.clone
(),
value
.clone
());
assert_eq!
(
state
.stack
.iter
()
.len
(),
1
);
assert_eq!
(
state
.stack
.get
(
bitvec!
(
"-4:4"
),
ByteSize
::
new
(
4
)),
value
.clone
()
);
assert_eq!
(
state
.load_value
(
address
,
ByteSize
::
new
(
4
),
None
),
value
);
// Load a parameter register and check that the parameter gets generated
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
bitvec!
(
"0x4:4"
)
.into
());
let
stack_param_id
=
mock_stack_param_id
(
4
,
4
);
let
stack_param
=
DataDomain
::
from_target
(
stack_param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
());
assert_eq!
(
state
.tracked_ids
.iter
()
.len
(),
6
);
assert_eq!
(
state
.load_value
(
address
.clone
(),
ByteSize
::
new
(
4
),
None
),
stack_param
);
assert_eq!
(
state
.tracked_ids
.iter
()
.len
(),
7
);
assert_eq!
(
state
.tracked_ids
.get
(
&
stack_param_id
)
.unwrap
()
.is_accessed
(),
false
);
// The load method does not set access flags.
}
#[test]
fn
test_load_unsized_from_stack
()
{
let
mut
state
=
State
::
mock_arm32
();
// Load an existing stack param (generated by a sized load at the same address)
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
bitvec!
(
"0x0:4"
)
.into
());
let
stack_param_id
=
mock_stack_param_id
(
0
,
4
);
let
stack_param
=
DataDomain
::
from_target
(
stack_param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
());
state
.load_value
(
address
,
ByteSize
::
new
(
4
),
None
);
let
unsized_load
=
state
.load_unsized_value_from_stack
(
bitvec!
(
"0x0:4"
)
.into
());
assert_eq!
(
unsized_load
,
stack_param
);
assert
!
(
state
.tracked_ids
.get
(
&
stack_param_id
)
.is_some
());
// Load a non-existing stack param
let
stack_param_id
=
mock_stack_param_id
(
4
,
1
);
let
stack_param
=
DataDomain
::
from_target
(
stack_param_id
.clone
(),
bitvec!
(
"0x0:1"
)
.into
());
let
unsized_load
=
state
.load_unsized_value_from_stack
(
bitvec!
(
"0x4:4"
));
assert_eq!
(
unsized_load
,
stack_param
);
assert
!
(
state
.tracked_ids
.get
(
&
stack_param_id
)
.is_some
());
// Unsized load from the current stack frame
let
unsized_load
=
state
.load_unsized_value_from_stack
(
bitvec!
(
"-4:4"
));
assert_eq!
(
unsized_load
,
DataDomain
::
new_top
(
ByteSize
::
new
(
1
)));
}
#[test]
fn
test_eval
()
{
let
mut
state
=
State
::
mock_arm32
();
// Test the eval method
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/tests.rs
View file @
f6ced95c
use
super
::
*
;
use
crate
::{
expr
,
variable
};
use
crate
::
variable
;
/// Mock the abstract location of a global parameter.
fn
mock_global_x64
(
address
:
u64
)
->
AbstractLocation
{
AbstractLocation
::
GlobalAddress
{
address
:
address
,
size
:
ByteSize
::
new
(
8
),
}
}
impl
FunctionSignature
{
/// Create a mock x64 function signature with 2 parameters, one of which is accessed mutably,
/// one mutably accessed global variable at address 0x2000
...
...
@@ -7,40 +16,45 @@ impl FunctionSignature {
pub
fn
mock_x64
()
->
FunctionSignature
{
let
mut
write_access_pattern
=
AccessPattern
::
new
();
write_access_pattern
.set_unknown_access_flags
();
let
parameters
=
Hash
Map
::
from_iter
([
let
parameters
=
BTree
Map
::
from_iter
([
(
A
rg
::
from_var
(
variable!
(
"RDI:8"
),
None
),
A
bstractLocation
::
from_var
(
&
variable!
(
"RDI:8"
))
.unwrap
(
),
AccessPattern
::
new
(),
),
(
A
rg
::
from_var
(
variable!
(
"RSI:8"
),
None
),
A
bstractLocation
::
from_var
(
&
variable!
(
"RSI:8"
))
.unwrap
(
),
write_access_pattern
,
),
]);
FunctionSignature
{
parameters
,
global_parameters
:
HashMap
::
from
([
(
0x2000
,
AccessPattern
::
new_unknown_access
()),
(
0x3000
,
AccessPattern
::
new
()
.with_dereference_flag
()),
global_parameters
:
BTreeMap
::
from
([
(
mock_global_x64
(
0x2000
),
AccessPattern
::
new_unknown_access
()),
(
mock_global_x64
(
0x3000
),
AccessPattern
::
new
()
.with_dereference_flag
(),
),
]),
}
}
}
fn
mock_stack_arg
(
address
:
Expression
,
size
:
u64
)
->
Arg
{
Arg
::
Stack
{
address
,
size
:
size
.into
(),
data_type
:
None
,
}
fn
mock_stack_arg
(
offset
:
i64
,
size
:
u64
)
->
AbstractLocation
{
AbstractLocation
::
Pointer
(
variable!
(
"RSP:8"
),
AbstractMemoryLocation
::
Location
{
offset
:
offset
,
size
:
ByteSize
::
new
(
size
),
},
)
}
#[test]
fn
test_two_parameter_overlapping_merging
()
{
let
proj
=
Project
::
mock_x64
();
let
mut
func_sig
=
FunctionSignature
::
mock_x64
();
let
stack_parm_1
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1000:8"
)
,
8
);
let
stack_parm_2
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1004:8"
)
,
8
);
let
stack_parm_1
=
mock_stack_arg
(
0x1000
,
8
);
let
stack_parm_2
=
mock_stack_arg
(
0x1004
,
8
);
func_sig
.parameters
...
...
@@ -51,13 +65,10 @@ fn test_two_parameter_overlapping_merging() {
assert_eq!
(
func_sig
.sanitize
(
&
proj
),
(
vec!
[
"Unexpected stack parameter size"
.to_string
()],
vec!
[
"Merged a stack parameter, that intersect another but is no subset"
.to_string
()]
)
vec!
[
"Unexpected stack parameter size"
.to_string
()],
);
let
mut
expected_function_sig
=
FunctionSignature
::
mock_x64
();
let
expected_stack_arg
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1000:8"
)
,
12
);
let
expected_stack_arg
=
mock_stack_arg
(
0x1000
,
12
);
expected_function_sig
.parameters
...
...
@@ -69,10 +80,10 @@ fn test_two_parameter_overlapping_merging() {
fn
test_merging_multiple_parameters
()
{
let
proj
=
Project
::
mock_x64
();
let
mut
func_sig
=
FunctionSignature
::
mock_x64
();
let
stack_parm_1
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1000:8"
)
,
8
);
let
stack_parm_2
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1000:8"
)
,
1
);
let
stack_parm_3
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1007:8"
)
,
1
);
let
stack_parm_4
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1008:8"
)
,
8
);
let
stack_parm_1
=
mock_stack_arg
(
0x8
,
8
);
let
stack_parm_2
=
mock_stack_arg
(
0x8
,
1
);
let
stack_parm_3
=
mock_stack_arg
(
0xf
,
1
);
let
stack_parm_4
=
mock_stack_arg
(
0x10
,
8
);
func_sig
.parameters
.extend
([
(
stack_parm_1
.clone
(),
AccessPattern
::
new
()),
...
...
@@ -80,7 +91,8 @@ fn test_merging_multiple_parameters() {
(
stack_parm_3
,
AccessPattern
::
new
()),
(
stack_parm_4
.clone
(),
AccessPattern
::
new
()),
]);
assert_eq!
((
vec!
[],
vec!
[]),
func_sig
.sanitize
(
&
proj
));
let
logs
=
func_sig
.sanitize
(
&
proj
);
assert_eq!
(
logs
,
Vec
::
<
String
>
::
new
());
let
mut
expected_function_sig
=
FunctionSignature
::
mock_x64
();
expected_function_sig
.parameters
.extend
([
...
...
@@ -93,8 +105,8 @@ fn test_merging_multiple_parameters() {
fn
test_log_messages
()
{
let
proj
=
Project
::
mock_x64
();
let
mut
func_sig
=
FunctionSignature
::
mock_x64
();
let
stack_parm_1
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1001:8"
)
,
8
);
let
stack_parm_2
=
mock_stack_arg
(
expr!
(
"RSP:8 + 0x1007:8"
)
,
4
);
let
stack_parm_1
=
mock_stack_arg
(
0x1001
,
8
);
let
stack_parm_2
=
mock_stack_arg
(
0x1007
,
4
);
func_sig
.parameters
.extend
([
(
stack_parm_1
.clone
(),
AccessPattern
::
new
()),
...
...
@@ -103,13 +115,10 @@ fn test_log_messages() {
let
logs
=
func_sig
.sanitize
(
&
proj
);
assert_eq!
(
(
vec!
[
"Unexpected stack parameter size"
.to_string
(),
"Unexpected stack parameter alignment"
.to_string
()
],
vec!
[
"Merged a stack parameter, that intersect another but is no subset"
.to_string
()]
),
vec!
[
"Unexpected stack parameter size"
.to_string
(),
"Unexpected stack parameter alignment"
.to_string
()
],
logs
);
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/context/id_manipulation.rs
View file @
f6ced95c
...
...
@@ -6,50 +6,52 @@ impl<'a> Context<'a> {
/// to the value that represents it in the caller.
///
/// For parameter IDs this is the value of the parameter on function call.
/// For IDs of objects created in the callee it is the ID
together with a path hint given by the call TID
.
/// For IDs of objects created in the callee it is the ID
itself
.
/// For other IDs (including the callee stack frame ID) it is a `Top` value,
/// i.e. the value of the ID should be unknown to the caller.
///
/// Note that this function assumes that callee-originating IDs have already been renamed
/// to the name they should represent in the caller beforehand.
pub
fn
create_callee_id_to_caller_data_map
(
&
self
,
state_before_call
:
&
State
,
state_before_return
:
&
State
,
call_tid
:
&
Tid
,
)
->
BTreeMap
<
AbstractIdentifier
,
Data
>
{
let
stack_register
=
&
self
.project.stack_pointer_register
;
let
mut
id_map
=
BTreeMap
::
new
();
let
callee_tid
=
state_before_return
.get_fn_tid
();
let
callee_fn_sig
=
self
.fn_signatures
.get
(
callee_tid
)
.unwrap
();
for
param
in
callee_fn_sig
.parameters
.keys
()
{
let
param_id
=
AbstractIdentifier
::
from_arg
(
callee_tid
,
param
);
if
let
Ok
(
param_value
)
=
state_before_call
.eval_parameter_arg
(
param
,
&
self
.project.runtime_memory_image
)
{
if
let
Some
(
callee_fn_sig
)
=
self
.fn_signatures
.get
(
callee_tid
)
{
for
param
in
callee_fn_sig
.parameters
.keys
()
{
let
param_id
=
AbstractIdentifier
::
new
(
callee_tid
.clone
(),
param
.clone
());
let
param_value
=
state_before_call
.eval_abstract_location
(
param
,
&
self
.project.runtime_memory_image
);
id_map
.insert
(
param_id
,
param_value
);
}
else
{
id_map
.insert
(
param_id
,
Data
::
new_top
(
param
.bytesize
()));
}
for
global_param
in
callee_fn_sig
.global_parameters
.keys
()
{
let
global_param_id
=
AbstractIdentifier
::
new
(
callee_tid
.clone
(),
global_param
.clone
());
let
global_param_value
=
state_before_call
.eval_abstract_location
(
global_param
,
&
self
.project.runtime_memory_image
);
id_map
.insert
(
global_param_id
,
global_param_value
);
}
}
for
object_id
in
state_before_return
.memory
.get_all_object_ids
()
{
if
object_id
.get_tid
()
!=
callee_tid
||
!
object_id
.get_path_hints
()
.is_empty
()
{
// Object is neither a parameter object nor the stack frame of the callee.
if
let
Ok
(
new_object_id
)
=
object_id
.with_path_hint
(
call_tid
.clone
())
{
id_map
.insert
(
id_map
.insert
(
object_id
.clone
(),
Data
::
from_target
(
object_id
,
Data
::
from_target
(
new_object_id
,
Bitvector
::
zero
(
stack_register
.size
.into
())
.into
(),
),
);
}
else
{
id_map
.insert
(
object_id
,
Data
::
new_top
(
stack_register
.size
));
}
Bitvector
::
zero
(
stack_register
.size
.into
())
.into
(),
),
);
}
}
id_map
.insert
(
state_before_return
.stack_id
.clone
(),
Data
::
new_top
(
stack_register
.size
),
);
// Also insert the global memory ID
s
to the map.
// Also insert the global memory ID to the map.
id_map
.insert
(
state_before_return
.get_global_mem_id
(),
Data
::
from_target
(
...
...
@@ -61,6 +63,55 @@ impl<'a> Context<'a> {
id_map
}
/// Create a map that maps callee IDs to the value assigned to it in the caller after a return instruction.
///
/// This is *not* the map used in the internal `update_return` handling.
/// Instead, the created map combines several ID renaming steps used internally into one renaming map.
/// The map is intended for use in other analyses depending on the PointerInference,
/// but not in the PointerInference itself.
pub
fn
create_full_callee_id_to_caller_data_map
(
&
self
,
state_before_call
:
&
State
,
state_before_return
:
&
State
,
call_tid
:
&
Tid
,
)
->
BTreeMap
<
AbstractIdentifier
,
Data
>
{
let
cconv
=
&
self
.project.program.term.subs
[
state_before_return
.get_fn_tid
()]
.term
.calling_convention
;
let
cconv
=
match
self
.project
.get_specific_calling_convention
(
cconv
)
{
Some
(
cconv
)
=>
cconv
,
None
=>
{
return
BTreeMap
::
new
();
}
};
let
callee_fn_sig
=
self
.fn_signatures
.get
(
state_before_return
.get_fn_tid
())
.unwrap
();
let
mut
minimized_return_state
=
state_before_return
.clone
();
minimized_return_state
.minimize_before_return_instruction
(
callee_fn_sig
,
cconv
);
let
mut
location_to_data_map
=
minimized_return_state
.map_abstract_locations_to_pointer_data
(
call_tid
);
minimized_return_state
.filter_location_to_pointer_data_map
(
&
mut
location_to_data_map
);
let
mut
replacement_map
=
minimized_return_state
.get_id_to_unified_ids_replacement_map
(
&
location_to_data_map
);
minimized_return_state
.merge_mem_objects_with_unique_abstract_location
(
call_tid
);
let
unified_to_caller_replacement_map
=
self
.create_callee_id_to_caller_data_map
(
state_before_call
,
&
minimized_return_state
);
// In the ID-to-unified-ID map replace parameter IDs with their corresponding values in the caller.
for
value
in
replacement_map
.values_mut
()
{
value
.replace_all_ids
(
&
unified_to_caller_replacement_map
);
}
// Add all parameter IDs to the map
let
callee_tid
=
state_before_return
.get_fn_tid
();
for
(
id
,
value
)
in
unified_to_caller_replacement_map
{
if
id
.get_tid
()
==
callee_tid
&&
id
.get_path_hints
()
.is_empty
()
{
replacement_map
.insert
(
id
,
value
);
}
}
replacement_map
}
/// Create a map from the parameter IDs (of the function that the given state corresponds to)
/// to the corresponding access patterns.
pub
fn
create_id_to_access_pattern_map
(
...
...
@@ -71,7 +122,11 @@ impl<'a> Context<'a> {
let
fn_tid
=
state
.get_fn_tid
();
let
callee_fn_sig
=
self
.fn_signatures
.get
(
fn_tid
)
.unwrap
();
for
(
param
,
access_pattern
)
in
&
callee_fn_sig
.parameters
{
let
param_id
=
AbstractIdentifier
::
from_arg
(
fn_tid
,
param
);
let
param_id
=
AbstractIdentifier
::
new
(
fn_tid
.clone
(),
param
.clone
());
id_to_access_pattern_map
.insert
(
param_id
.clone
(),
access_pattern
);
}
for
(
param
,
access_pattern
)
in
&
callee_fn_sig
.global_parameters
{
let
param_id
=
AbstractIdentifier
::
new
(
fn_tid
.clone
(),
param
.clone
());
id_to_access_pattern_map
.insert
(
param_id
.clone
(),
access_pattern
);
}
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/context/mod.rs
View file @
f6ced95c
...
...
@@ -97,6 +97,18 @@ impl<'a> Context<'a> {
}
}
/// If `result` is an `Err`, log the error message as an error message through the `log_collector` channel.
pub
fn
log_error
(
&
self
,
result
:
Result
<
(),
Error
>
,
location
:
Option
<&
Tid
>
)
{
if
let
Err
(
err
)
=
result
{
let
mut
log_message
=
LogMessage
::
new_error
(
format!
(
"{err}"
))
.source
(
"Pointer Inference"
);
if
let
Some
(
loc
)
=
location
{
log_message
=
log_message
.location
(
loc
.clone
());
};
let
_
=
self
.log_collector
.send
(
LogThreadMsg
::
Log
(
log_message
));
}
}
/// Detect and log if the stack pointer is not as expected when returning from a function.
fn
detect_stack_pointer_information_loss_on_return
(
&
self
,
...
...
@@ -299,7 +311,7 @@ impl<'a> Context<'a> {
/// Merge global memory data from the callee global memory object to the caller global memory object
/// if the corresponding global variable is marked as mutable in both the caller and callee.
fn
merge_global_mem_from_callee
(
fn
merge_
non_nested_
global_mem_from_callee
(
&
self
,
caller_state
:
&
mut
State
,
callee_global_mem
:
&
AbstractObject
,
...
...
@@ -360,21 +372,45 @@ fn compute_call_return_global_var_access_intervals(
caller_fn_sig
:
&
FunctionSignature
,
callee_fn_sig
:
&
FunctionSignature
,
)
->
BTreeMap
<
u64
,
AccessPattern
>
{
let
caller_mut_indices
:
BTreeSet
<
u64
>
=
caller_fn_sig
.global_parameters
.iter
()
.filter_map
(|(
location
,
access_pattern
)|
{
if
let
AbstractLocation
::
GlobalAddress
{
address
,
..
}
=
location
{
if
access_pattern
.is_mutably_dereferenced
()
{
return
Some
(
*
address
);
}
}
None
})
.collect
();
let
callee_mut_indices
:
BTreeSet
<
u64
>
=
callee_fn_sig
.global_parameters
.iter
()
.filter_map
(|(
location
,
access_pattern
)|
{
if
let
AbstractLocation
::
GlobalAddress
{
address
,
..
}
=
location
{
if
access_pattern
.is_mutably_dereferenced
()
{
return
Some
(
*
address
);
}
}
None
})
.collect
();
let
mut
intervals
:
BTreeMap
<
u64
,
AccessPattern
>
=
caller_fn_sig
.global_parameters
.keys
()
.chain
(
callee_fn_sig
.global_parameters
.keys
())
.map
(|
index
|
(
*
index
,
AccessPattern
::
new
()))
.filter_map
(|
location
|
{
if
let
AbstractLocation
::
GlobalAddress
{
address
,
..
}
=
location
{
Some
((
*
address
,
AccessPattern
::
new
()))
}
else
{
None
}
})
.collect
();
for
(
index
,
access_pattern
)
in
intervals
.iter_mut
()
{
if
let
(
Some
(
caller_pattern
),
Some
(
callee_pattern
))
=
(
caller_fn_sig
.global_parameters
.get
(
index
),
callee_fn_sig
.global_parameters
.get
(
index
),
)
{
if
caller_pattern
.is_mutably_dereferenced
()
&&
callee_pattern
.is_mutably_dereferenced
()
{
access_pattern
.set_mutably_dereferenced_flag
();
}
if
caller_mut_indices
.contains
(
index
)
&&
callee_mut_indices
.contains
(
index
)
{
access_pattern
.set_mutably_dereferenced_flag
();
}
}
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/context/tests.rs
View file @
f6ced95c
...
...
@@ -173,6 +173,12 @@ fn update_return() {
&
variable!
(
"RDX:8"
),
Data
::
from_target
(
new_id
(
"callee"
,
"RDI"
),
bv
(
0
)),
);
state_before_return
.memory
.get_object_mut
(
&
callee_created_heap_id
)
.unwrap
()
.set_value
(
bitvec!
(
"0x42:8"
)
.into
(),
&
bitvec!
(
"0x6:8"
)
.into
())
.unwrap
();
let
state_before_call
=
State
::
new
(
&
variable!
(
"RSP:8"
),
Tid
::
new
(
"caller"
),
BTreeSet
::
new
());
let
mut
state_before_call
=
context
...
...
@@ -210,10 +216,8 @@ fn update_return() {
assert_eq!
(
state
.get_register
(
&
variable!
(
"RAX:8"
)),
Data
::
from_target
(
callee_created_heap_id
.with_path_hint
(
Tid
::
new
(
"call_callee"
))
.unwrap
(),
bv
(
16
)
.into
()
AbstractIdentifier
::
mock
(
"call_callee"
,
"RAX"
,
8
),
bv
(
0
)
.into
()
)
);
assert_eq!
(
...
...
@@ -234,15 +238,12 @@ fn update_return() {
.get_all_object_ids
()
.get
(
&
param_obj_id
)
.is_some
());
assert
!
(
state
let
value
=
state
.memory
.get_all_object_ids
()
.get
(
&
callee_created_heap_id
.with_path_hint
(
Tid
::
new
(
"call_callee"
))
.unwrap
()
)
.is_some
());
.get_object
(
&
AbstractIdentifier
::
mock
(
"call_callee"
,
"RAX"
,
8
))
.unwrap
()
.get_value
(
bitvec!
(
"0x-a:8"
),
ByteSize
::
new
(
8
));
assert_eq!
(
value
,
bitvec!
(
"0x42:8"
)
.into
());
}
#[test]
...
...
@@ -297,6 +298,20 @@ fn get_unsound_caller_ids() {
new_id
(
"callee"
,
"RSI"
),
Data
::
from_target
(
new_id
(
"caller"
,
"RAX"
),
bv
(
2
)
.into
()),
);
callee_id_to_caller_data_map
.insert
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"callee"
),
AbstractLocation
::
mock_global
(
0x2000
,
&
[],
8
),
),
bv
(
42
)
.into
(),
);
callee_id_to_caller_data_map
.insert
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"callee"
),
AbstractLocation
::
mock_global
(
0x3000
,
&
[],
8
),
),
bv
(
42
)
.into
(),
);
let
callee_tid
=
Tid
::
new
(
"callee"
);
let
callee_state
=
State
::
from_fn_sig
(
...
...
@@ -395,7 +410,7 @@ fn test_merge_global_mem_from_callee() {
Data
::
from_target
(
caller_state
.get_global_mem_id
(),
bitvec!
(
"0:8"
)
.into
()),
)]);
context
.merge_global_mem_from_callee
(
context
.merge_
non_nested_
global_mem_from_callee
(
&
mut
caller_state
,
callee_global_mem
,
&
replacement_map
,
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/context/trait_impls.rs
View file @
f6ced95c
...
...
@@ -124,6 +124,20 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
return
None
;
}
};
let
callee_fn_sig
=
match
self
.fn_signatures
.get
(
state_before_return
.get_fn_tid
())
{
Some
(
fn_sig
)
=>
fn_sig
,
None
=>
{
let
location
=
state_before_return
.get_fn_tid
();
self
.log_error
(
Err
(
anyhow!
(
"Internal function {} has no function signature."
,
location
)),
Some
(
location
),
);
return
None
;
}
};
// Detect possible information loss on the stack pointer and report it.
if
let
Err
(
err
)
=
self
.detect_stack_pointer_information_loss_on_return
(
state_before_return
)
...
...
@@ -133,19 +147,19 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
// or a call to a non-returning extern function that was not marked as non-returning.
return
None
;
}
// Minimize the callee state and replace callee-originating object IDs whenever possible.
let
mut
state_before_return
=
state_before_return
.clone
();
state_before_return
.minimize_before_return_instruction
(
callee_fn_sig
,
cconv
);
state_before_return
.merge_mem_objects_with_unique_abstract_location
(
&
call_term
.tid
);
// Create a mapping of IDs from the callee to IDs that should be used in the caller.
let
id_map
=
self
.create_callee_id_to_caller_data_map
(
state_before_call
,
state_before_return
,
&
call_term
.tid
,
);
let
id_map
=
self
.create_callee_id_to_caller_data_map
(
state_before_call
,
&
state_before_return
);
let
callee_id_to_access_pattern_map
=
self
.create_id_to_access_pattern_map
(
state_before_return
);
self
.create_id_to_access_pattern_map
(
&
state_before_return
);
// Identify caller IDs for which the callee analysis may be unsound for this callsite.
let
unsound_caller_ids
=
self
.get_unsound_caller_ids
(
&
id_map
,
&
callee_id_to_access_pattern_map
);
//
TODO
: Unsound caller IDs occur too often to log the cases right now.
//
FIXME
: Unsound caller IDs occur too often to log the cases right now.
// We have to investigate the reasons for it (maybe too many parameters on the caller stack?)
// and find better heuristics to prevent them poisoning the analysis soundness.
...
...
@@ -167,11 +181,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
continue
;
}
if
*
callee_object_id
==
state_before_return
.get_global_mem_id
()
{
let
callee_fn_sig
=
self
.fn_signatures
.get
(
state_before_return
.get_fn_tid
())
.unwrap
();
self
.merge_global_mem_from_callee
(
self
.merge_non_nested_global_mem_from_callee
(
&
mut
state_after_return
,
callee_object
,
&
id_map
,
...
...
@@ -196,11 +206,9 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
.is_none
()
{
// Add a callee object that does not correspond to a parameter to the caller or the stack of the callee.
if
let
Ok
(
new_object_id
)
=
callee_object_id
.with_path_hint
(
call_term
.tid
.clone
())
{
state_after_return
.memory
.insert
(
new_object_id
,
callee_object
);
}
state_after_return
.memory
.insert
(
callee_object_id
.clone
(),
callee_object
);
}
else
{
// The callee object is a parameter object.
self
.log_debug
(
...
...
@@ -217,7 +225,6 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
state_after_return
.memory
.assume_arbitrary_writes_to_object
(
id
,
&
BTreeSet
::
new
());
// TODO: We should specify more possible reference targets.
}
// Cleanup
state_after_return
.remove_unreferenced_objects
();
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/mod.rs
View file @
f6ced95c
...
...
@@ -49,6 +49,8 @@ pub use state::State;
/// The version number of the analysis.
const
VERSION
:
&
str
=
"0.2"
;
/// The recursion limit for nested pointers.
const
POINTER_RECURSION_DEPTH_LIMIT
:
u64
=
2
;
/// The name and version number of the "Memory" CWE check.
pub
static
CWE_MODULE
:
crate
::
CweModule
=
crate
::
CweModule
{
...
...
@@ -315,7 +317,7 @@ impl<'a> PointerInference<'a> {
})
=>
(
state_before_call
,
state_before_return
),
_
=>
continue
,
};
let
id_to_data_map
=
context
.create_callee_id_to_caller_data_map
(
let
id_to_data_map
=
context
.create_
full_
callee_id_to_caller_data_map
(
state_before_call
,
state_before_return
,
call_tid
,
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/object/mod.rs
View file @
f6ced95c
...
...
@@ -87,11 +87,23 @@ impl AbstractObject {
inner
.is_unique
=
false
;
}
/// Mark the abstract object as unique, i.e. it represents exactly one memory object.
pub
fn
mark_as_unique
(
&
mut
self
)
{
let
inner
=
Arc
::
make_mut
(
&
mut
self
.inner
);
inner
.is_unique
=
true
;
}
/// Get the type of the memory object.
pub
fn
get_object_type
(
&
self
)
->
Option
<
ObjectType
>
{
self
.inner.type_
}
/// Set the type of the memory object.
pub
fn
set_object_type
(
&
mut
self
,
object_type
:
Option
<
ObjectType
>
)
{
let
inner
=
Arc
::
make_mut
(
&
mut
self
.inner
);
inner
.type_
=
object_type
;
}
/// Overwrite the values in `self` with those in `other`
/// under the assumption that the zero offset in `other` corresponds to the offset `offset_other` in `self`.
///
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/object_list/list_manipulation.rs
View file @
f6ced95c
...
...
@@ -5,7 +5,6 @@ use super::*;
impl
AbstractObjectList
{
/// Get a reference to the object corresponding to the given ID.
#[cfg(test)]
pub
fn
get_object
(
&
self
,
id
:
&
AbstractIdentifier
)
->
Option
<&
AbstractObject
>
{
self
.objects
.get
(
id
)
}
...
...
@@ -64,6 +63,11 @@ impl AbstractObjectList {
self
.objects
.iter
()
}
/// Get an iterator of mutable references over the abstract objects in `self`.
pub
fn
iter_objects_mut
(
&
mut
self
)
->
impl
Iterator
<
Item
=
&
mut
AbstractObject
>
{
self
.objects
.values_mut
()
}
/// Get the number of objects that are currently tracked.
#[cfg(test)]
pub
fn
get_num_objects
(
&
self
)
->
usize
{
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/object_list/mod.rs
View file @
f6ced95c
...
...
@@ -137,6 +137,26 @@ impl AbstractObjectList {
None
=>
Err
(
anyhow!
(
"Object ID not contained in object list."
)),
}
}
/// Only retain those memory objects for which the provided predicate returns `true`.
/// All memory objects for which the predicate returns `False` are removed from `self`.
pub
fn
retain
<
F
>
(
&
mut
self
,
f
:
F
)
where
F
:
FnMut
(
&
AbstractIdentifier
,
&
mut
AbstractObject
)
->
bool
,
{
self
.objects
.retain
(
f
)
}
/// Remove an object from the object list.
/// Returns the removed object if its ID was indeed contained in the object list.
pub
fn
remove
(
&
mut
self
,
id
:
&
AbstractIdentifier
)
->
Option
<
AbstractObject
>
{
self
.objects
.remove
(
id
)
}
/// Return `true` if the object list contains a memory object indexed by the given ID.
pub
fn
contains
(
&
self
,
id
:
&
AbstractIdentifier
)
->
bool
{
self
.objects
.contains_key
(
id
)
}
}
impl
AbstractDomain
for
AbstractObjectList
{
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/state/access_handling.rs
View file @
f6ced95c
...
...
@@ -78,7 +78,7 @@ impl State {
self
.write_to_address
(
address
,
&
self
.eval
(
value
),
global_memory
)
}
/// Evaluate the given
load instruction and return the data read
on success.
/// Evaluate the given
address expression and return the data read from that address
on success.
pub
fn
load_value
(
&
self
,
address
:
&
Expression
,
...
...
@@ -86,6 +86,17 @@ impl State {
global_memory
:
&
RuntimeMemoryImage
,
)
->
Result
<
Data
,
Error
>
{
let
address
=
self
.eval
(
address
);
self
.load_value_from_address
(
&
address
,
size
,
global_memory
)
}
/// Load the value at the given address from the state and return the data read on success.
/// If the address contains more than one possible pointer target the results are merged for all possible pointer targets.
pub
fn
load_value_from_address
(
&
self
,
address
:
&
Data
,
size
:
ByteSize
,
global_memory
:
&
RuntimeMemoryImage
,
)
->
Result
<
Data
,
Error
>
{
let
mut
result
=
if
let
Some
(
global_address
)
=
address
.get_absolute_value
()
{
if
let
Ok
(
address_bitvector
)
=
global_address
.try_to_bitvec
()
{
match
global_memory
.read
(
&
address_bitvector
,
size
)
{
...
...
@@ -109,7 +120,7 @@ impl State {
}
else
{
Data
::
new_empty
(
size
)
};
result
=
result
.merge
(
&
self
.memory
.get_value
(
&
address
,
size
));
result
=
result
.merge
(
&
self
.memory
.get_value
(
address
,
size
));
if
let
Ok
(
offset
)
=
result
.try_to_offset
()
{
if
result
.bytesize
()
==
self
.stack_id
.bytesize
()
...
...
@@ -217,6 +228,81 @@ impl State {
}
}
/// Evaluate the value of the given abstract location on the current state.
/// If the actual value cannot be determined (e.g. if an intermediate pointer returns `Top`)
/// then a `Top` value is returned.
pub
fn
eval_abstract_location
(
&
self
,
location
:
&
AbstractLocation
,
global_memory
:
&
RuntimeMemoryImage
,
)
->
Data
{
match
location
{
AbstractLocation
::
GlobalAddress
{
address
,
size
}
=>
{
assert_eq!
(
*
size
,
self
.stack_id
.bytesize
());
Data
::
from_target
(
self
.get_global_mem_id
()
.clone
(),
Bitvector
::
from_u64
(
*
address
)
.into_resize_unsigned
(
self
.stack_id
.bytesize
())
.into
(),
)
}
AbstractLocation
::
GlobalPointer
(
address
,
nested_location
)
=>
{
let
pointer
=
Data
::
from_target
(
self
.get_global_mem_id
()
.clone
(),
Bitvector
::
from_u64
(
*
address
)
.into_resize_unsigned
(
self
.stack_id
.bytesize
())
.into
(),
);
self
.eval_abstract_memory_location
(
nested_location
,
pointer
,
global_memory
)
}
AbstractLocation
::
Register
(
var
)
=>
self
.get_register
(
var
),
AbstractLocation
::
Pointer
(
var
,
nested_location
)
=>
{
let
pointer
=
self
.get_register
(
var
);
self
.eval_abstract_memory_location
(
nested_location
,
pointer
,
global_memory
)
}
}
}
/// Evaluate the value of the given abstract memory location on the current state
/// with the given `root_pointer` as the start point of the location description.
fn
eval_abstract_memory_location
(
&
self
,
location
:
&
AbstractMemoryLocation
,
root_pointer
:
Data
,
global_memory
:
&
RuntimeMemoryImage
,
)
->
Data
{
match
location
{
AbstractMemoryLocation
::
Location
{
offset
,
size
}
=>
{
let
pointer
=
root_pointer
.add_offset
(
&
Bitvector
::
from_i64
(
*
offset
)
.into_resize_unsigned
(
self
.stack_id
.bytesize
())
.into
(),
);
self
.load_value_from_address
(
&
pointer
,
*
size
,
global_memory
)
.unwrap_or_else
(|
_
|
Data
::
new_top
(
*
size
))
}
AbstractMemoryLocation
::
Pointer
{
offset
,
target
}
=>
{
let
pointer
=
root_pointer
.add_offset
(
&
Bitvector
::
from_i64
(
*
offset
)
.into_resize_unsigned
(
self
.stack_id
.bytesize
())
.into
(),
);
match
self
.load_value_from_address
(
&
pointer
,
self
.stack_id
.bytesize
(),
global_memory
,
)
{
Ok
(
nested_root_pointer
)
=>
self
.eval_abstract_memory_location
(
target
,
nested_root_pointer
,
global_memory
,
),
Err
(
_
)
=>
Data
::
new_top
(
location
.bytesize
()),
}
}
}
}
/// Check whether the given `def` could result in a memory access through a NULL pointer.
///
/// If no NULL pointer dereference is detected then `Ok(false)` is returned.
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/state/id_manipulation.rs
View file @
f6ced95c
...
...
@@ -2,6 +2,7 @@
use
super
::
*
;
use
crate
::
analysis
::
pointer_inference
::
object
::
AbstractObject
;
use
crate
::
analysis
::
pointer_inference
::
POINTER_RECURSION_DEPTH_LIMIT
;
impl
State
{
/// Search (recursively) through all memory objects referenced by the given IDs
...
...
@@ -89,4 +90,390 @@ impl State {
}
Ok
(())
}
/// Create an ID renaming map that maps IDs in `self` to the values representing them
/// after unifying and renaming non-parameter objects in `self` in preparation of returning to a caller.
pub
fn
get_id_to_unified_ids_replacement_map
(
&
self
,
location_to_data_map
:
&
BTreeMap
<
AbstractIdentifier
,
Data
>
,
)
->
BTreeMap
<
AbstractIdentifier
,
Data
>
{
let
mut
id_replacement_map
=
BTreeMap
::
new
();
for
(
unified_id
,
value
)
in
location_to_data_map
.iter
()
{
for
(
old_id
,
offset
)
in
value
.get_relative_values
()
{
if
old_id
.get_tid
()
!=
self
.get_fn_tid
()
||
!
old_id
.get_path_hints
()
.is_empty
()
{
let
mut
pointer_to_unified_id
=
Data
::
from_target
(
unified_id
.clone
(),
offset
.un_op
(
UnOpType
::
Int2Comp
));
pointer_to_unified_id
.set_contains_top_flag
();
id_replacement_map
.insert
(
old_id
.clone
(),
pointer_to_unified_id
);
}
}
}
for
value
in
self
.register
.values
()
{
for
id
in
value
.get_relative_values
()
.keys
()
{
if
id
.get_tid
()
==
self
.get_fn_tid
()
&&
id
.get_path_hints
()
.is_empty
()
{
// This is a parameter ID
id_replacement_map
.insert
(
id
.clone
(),
Data
::
from_target
(
id
.clone
(),
Bitvector
::
zero
(
id
.bytesize
()
.into
())
.into
()),
);
}
}
}
for
object_id
in
self
.memory
.get_all_object_ids
()
{
for
id
in
self
.memory
.get_referenced_ids_overapproximation
(
&
object_id
)
{
if
id
.get_tid
()
==
self
.get_fn_tid
()
&&
id
.get_path_hints
()
.is_empty
()
{
// This is a parameter ID
id_replacement_map
.insert
(
id
.clone
(),
Data
::
from_target
(
id
.clone
(),
Bitvector
::
zero
(
id
.bytesize
()
.into
())
.into
()),
);
}
}
}
id_replacement_map
}
/// Replace all IDs pointing to non-parameter objects.
/// - IDs contained in the values of the location to data map are replaced by the corresponding key (with adjusted offset).
/// But the Top flag is also set, because the pointers may point to other objects.
/// - All other non-parameter IDs are replaced with Top.
pub
fn
replace_ids_to_non_parameter_objects
(
&
mut
self
,
location_to_data_map
:
&
BTreeMap
<
AbstractIdentifier
,
Data
>
,
)
{
let
id_replacement_map
=
self
.get_id_to_unified_ids_replacement_map
(
location_to_data_map
);
// Now use the replacement map to replace IDs
for
value
in
self
.register
.values_mut
()
{
value
.replace_all_ids
(
&
id_replacement_map
);
}
for
object
in
self
.memory
.iter_objects_mut
()
{
object
.replace_ids
(
&
id_replacement_map
);
}
// Clean up registers left as Top after the replacement
self
.register
.retain
(|
_var
,
value
|
!
value
.is_top
());
}
/// Explicitly insert pointers to unified objects at the locations specified by their abstract location.
///
/// Note that these are the only locations where we (by definition) know
/// that the pointer is unique, i.e. we do not have to set a Top flag.
/// However, we still have to add targets to parameter objects, absolute values or the `Top` flag
/// to the pointer if the original pointer value contained them,
/// because these targets were not merged to the unified object.
pub
fn
insert_pointers_to_unified_objects
(
&
mut
self
,
location_to_data_map
:
&
BTreeMap
<
AbstractIdentifier
,
Data
>
,
call_tid
:
&
Tid
,
)
{
for
(
unified_id
,
old_value
)
in
location_to_data_map
.iter
()
{
// Compute the pointer (which may also contain pointers to parameter objects and absolute values).
let
mut
pointer_to_unified_object
=
Data
::
from_target
(
unified_id
.clone
(),
Bitvector
::
zero
(
unified_id
.bytesize
()
.into
())
.into
(),
);
for
(
old_id
,
old_offset
)
in
old_value
.get_relative_values
()
{
if
old_id
.get_tid
()
==
self
.get_fn_tid
()
&&
old_id
.get_path_hints
()
.is_empty
()
{
pointer_to_unified_object
=
pointer_to_unified_object
.merge
(
&
Data
::
from_target
(
old_id
.clone
(),
old_offset
.clone
()));
}
}
pointer_to_unified_object
.set_absolute_value
(
old_value
.get_absolute_value
()
.cloned
());
if
old_value
.contains_top
()
{
pointer_to_unified_object
.set_contains_top_flag
()
}
// Insert the pointer at the corresponding abstract location
match
unified_id
.get_location
()
{
AbstractLocation
::
Register
(
var
)
=>
{
self
.set_register
(
var
,
pointer_to_unified_object
)
}
unified_location
=>
{
let
(
parent_location
,
offset_in_parent_object
)
=
unified_location
.get_parent_location
(
self
.stack_id
.bytesize
())
.unwrap
();
let
parent_tid
=
if
unified_id
.get_tid
()
==
call_tid
{
call_tid
.clone
()
}
else
{
// We know that the parent is a parameter object, since we cannot track nested pointers in parameter objects.
self
.stack_id
.get_tid
()
.clone
()
};
let
parent_object
=
self
.memory
.get_object_mut
(
&
AbstractIdentifier
::
new
(
parent_tid
,
parent_location
))
.unwrap
();
parent_object
.set_value
(
pointer_to_unified_object
,
&
Bitvector
::
from_i64
(
offset_in_parent_object
)
.into_resize_signed
(
self
.stack_id
.bytesize
())
.into
(),
)
.unwrap
();
}
}
}
}
/// Merge the target objects that are non-parameter objects for the given location to data mapping.
/// Return the results as a location to memory object map.
///
/// This function is a step in the process of unifying callee-originating memory objects on a return instruction.
/// The memory objects are also marked as unique, because they will represent a unique object in the caller.
pub
fn
generate_target_objects_for_new_locations
(
&
self
,
location_to_data_map
:
&
BTreeMap
<
AbstractIdentifier
,
Data
>
,
)
->
BTreeMap
<
AbstractIdentifier
,
AbstractObject
>
{
let
mut
location_to_object_map
:
BTreeMap
<
AbstractIdentifier
,
AbstractObject
>
=
BTreeMap
::
new
();
for
(
location_id
,
value
)
in
location_to_data_map
{
let
mut
new_object
:
Option
<
AbstractObject
>
=
None
;
'target_loop
:
for
(
target_id
,
target_offset
)
in
value
.get_relative_values
()
{
if
(
target_id
.get_tid
()
==
self
.get_fn_tid
()
&&
target_id
.get_path_hints
()
.is_empty
())
||
!
self
.memory
.contains
(
target_id
)
{
continue
'target_loop
;
}
let
target_offset
=
match
target_offset
.try_to_offset
()
{
Ok
(
offset
)
=>
offset
,
Err
(
_
)
=>
{
match
&
mut
new_object
{
Some
(
object
)
=>
object
.assume_arbitrary_writes
(
&
BTreeSet
::
new
()),
None
=>
{
new_object
=
Some
(
AbstractObject
::
new
(
None
,
self
.stack_id
.bytesize
()))
}
}
continue
'target_loop
;
}
};
let
target_object
=
self
.memory
.get_object
(
target_id
)
.unwrap
();
let
mut
target_object
=
target_object
.clone
();
target_object
.add_offset_to_all_indices
(
&
Bitvector
::
from_i64
(
-
target_offset
)
.into
());
match
&
mut
new_object
{
None
=>
new_object
=
Some
(
target_object
),
Some
(
object
)
=>
*
object
=
object
.merge
(
&
target_object
),
}
}
let
mut
new_object
=
new_object
.unwrap_or_else
(||
AbstractObject
::
new
(
None
,
self
.stack_id
.bytesize
()));
new_object
.mark_as_unique
();
new_object
.set_object_type
(
None
);
location_to_object_map
.insert
(
location_id
.clone
(),
new_object
);
}
location_to_object_map
}
/// Filter out those locations from the location to pointer data map
/// whose non-parameter object targets intersect with any of the other locations.
///
/// Note that this does not filter out locations whose targets contain the `Top` flag,
/// despite the fact that these locations theoretically may point to the same non-parameter object.
/// I.e. we trade soundness in the general case for exactness in the common case here.
pub
fn
filter_location_to_pointer_data_map
(
&
self
,
location_to_data_map
:
&
mut
BTreeMap
<
AbstractIdentifier
,
Data
>
,
)
{
let
mut
visited_targets
=
HashSet
::
new
();
let
mut
non_unique_targets
=
HashSet
::
new
();
for
value
in
location_to_data_map
.values
()
{
for
id
in
value
.get_relative_values
()
.keys
()
{
if
id
.get_tid
()
!=
self
.get_fn_tid
()
&&
self
.memory
.contains
(
id
)
{
if
visited_targets
.contains
(
id
)
{
non_unique_targets
.insert
(
id
.clone
());
}
else
{
visited_targets
.insert
(
id
.clone
());
}
}
}
}
let
mut
filtered_out_ids
=
HashSet
::
new
();
location_to_data_map
.retain
(|
location_id
,
value
|
{
for
id
in
value
.get_relative_values
()
.keys
()
{
if
non_unique_targets
.contains
(
id
)
{
filtered_out_ids
.insert
(
location_id
.clone
());
return
false
;
}
}
true
});
// Also filter out those locations whose parent locations were filtered out.
location_to_data_map
.retain
(|
location
,
_
|
{
if
location
.get_tid
()
.has_id_suffix
(
"_param"
)
{
return
true
;
}
for
parent
in
location
.get_location
()
.get_all_parent_locations
(
self
.stack_id
.bytesize
())
{
let
parent_id
=
AbstractIdentifier
::
new
(
location
.get_tid
()
.clone
(),
parent
);
if
filtered_out_ids
.contains
(
&
parent_id
)
{
return
false
;
}
}
true
});
}
/// Add abstract locations based on register values to the location to pointer data map.
/// The TID for the corresponding abstract IDs is the given `call_tid`.
///
/// This function assumes that `self` has already been minimized
/// and thus all non-parameter register values have been removed from the state.
fn
add_register_based_root_locations_to_location_to_pointer_data_map
(
&
self
,
call_tid
:
&
Tid
,
location_to_data_map
:
&
mut
BTreeMap
<
AbstractIdentifier
,
Data
>
,
)
{
for
(
var
,
value
)
in
self
.register
.iter
()
{
if
!
var
.is_temp
&&
self
.contains_non_param_pointer
(
value
)
{
let
location
=
AbstractLocation
::
from_var
(
var
)
.unwrap
();
let
id
=
AbstractIdentifier
::
new
(
call_tid
.clone
(),
location
);
location_to_data_map
.insert
(
id
.clone
(),
value
.clone
());
}
}
}
/// Add abstract locations based on parameter objects to the location to pointer data map.
/// The TID for the corresponding abstract IDs is the given `call_tid` with a `_param` suffix.
///
/// The TID suffix is necessary to prevent naming collisions with locations based on return registers.
///
/// This function assumes that the stack memory object of `self` has already been deleted by a call to
/// [`State::minimize_before_return_instruction`](crate::analysis::pointer_inference::State::minimize_before_return_instruction).
fn
add_param_based_root_locations_to_location_to_pointer_data_map
(
&
self
,
call_tid
:
&
Tid
,
location_to_data_map
:
&
mut
BTreeMap
<
AbstractIdentifier
,
Data
>
,
)
{
for
(
object_id
,
object
)
in
self
.memory
.iter
()
{
if
object_id
.get_tid
()
==
self
.get_fn_tid
()
&&
object_id
.get_path_hints
()
.is_empty
()
&&
object_id
.get_location
()
.recursion_depth
()
<
POINTER_RECURSION_DEPTH_LIMIT
{
for
(
index
,
value
)
in
object
.get_mem_region
()
.iter
()
{
if
self
.contains_non_param_pointer
(
value
)
{
let
location
=
object_id
.get_location
()
.clone
()
.dereferenced
(
value
.bytesize
(),
self
.stack_id
.bytesize
())
.with_offset_addendum
(
*
index
);
location_to_data_map
.insert
(
AbstractIdentifier
::
new
(
call_tid
.clone
()
.with_id_suffix
(
"_param"
),
location
,
),
value
.clone
(),
);
}
}
}
}
}
/// Derive nested locations from the given list of locations to derive
/// and add them to the location to pointer data map.
fn
add_derived_locations_to_location_to_pointer_data_map
(
&
self
,
location_to_data_map
:
&
mut
BTreeMap
<
AbstractIdentifier
,
Data
>
,
mut
locations_to_derive
:
BTreeMap
<
AbstractIdentifier
,
Data
>
,
)
{
while
let
Some
((
location_id
,
location_data
))
=
locations_to_derive
.pop_first
()
{
if
location_id
.get_location
()
.recursion_depth
()
>=
POINTER_RECURSION_DEPTH_LIMIT
{
continue
;
}
'data_target_loop
:
for
(
object_id
,
object_offset
)
in
location_data
.get_relative_values
()
{
if
object_id
.get_tid
()
==
self
.get_fn_tid
()
&&
object_id
.get_path_hints
()
.is_empty
()
{
// Ignore parameter objects
continue
'data_target_loop
;
}
let
object_offset
=
match
object_offset
.try_to_offset
()
{
Ok
(
offset
)
=>
offset
,
Err
(
_
)
=>
continue
'data_target_loop
,
};
let
mem_object
=
match
self
.memory
.get_object
(
object_id
)
{
Some
(
object
)
=>
object
,
None
=>
continue
'data_target_loop
,
};
for
(
elem_offset
,
elem_data
)
in
mem_object
.get_mem_region
()
.iter
()
{
if
self
.contains_non_param_pointer
(
elem_data
)
{
// We want to create a new abstract location for this element.
// But the same abstract location may already exist, so we may have to merge values instead.
let
new_location_offset
=
*
elem_offset
-
object_offset
;
// TODO: Check correctness of this offset!
let
new_location
=
location_id
.get_location
()
.clone
()
.dereferenced
(
elem_data
.bytesize
(),
self
.stack_id
.bytesize
())
.with_offset_addendum
(
new_location_offset
);
let
new_location_id
=
AbstractIdentifier
::
new
(
location_id
.get_tid
()
.clone
(),
new_location
);
let
new_location_data
=
elem_data
.clone
();
location_to_data_map
.entry
(
new_location_id
.clone
())
.and_modify
(|
loc_data
|
*
loc_data
=
loc_data
.merge
(
&
new_location_data
))
.or_insert
(
new_location_data
.clone
());
locations_to_derive
.entry
(
new_location_id
.clone
())
.and_modify
(|
loc_data
|
*
loc_data
=
loc_data
.merge
(
&
new_location_data
))
.or_insert
(
new_location_data
);
}
}
}
}
}
/// Generate a map from abstract locations pointing to non-parameter memory objects
/// to the data represented by the abstract location in the current state.
///
/// The abstract locations get different TIDs depending on the root of the location:
/// - If the root is a return register, then the TID is given by the provided `call_tid`.
/// - If the root is a parameter memory object, then the TID is given by appending the suffix `_param` to the `call_tid`.
/// Since parameter and return register can overlap, the abstract IDs would overlap
/// if one would use the same TID in both cases.
///
/// For return register based location this function also generates nested abstract locations.
///
/// This function assumes that
/// [`State::minimize_before_return_instruction`](crate::analysis::pointer_inference::State::minimize_before_return_instruction)
/// has been called on `self` beforehand.
pub
fn
map_abstract_locations_to_pointer_data
(
&
self
,
call_tid
:
&
Tid
,
)
->
BTreeMap
<
AbstractIdentifier
,
Data
>
{
let
mut
location_to_data_map
=
BTreeMap
::
new
();
self
.add_register_based_root_locations_to_location_to_pointer_data_map
(
call_tid
,
&
mut
location_to_data_map
,
);
let
locations_to_derive
=
location_to_data_map
.clone
();
self
.add_param_based_root_locations_to_location_to_pointer_data_map
(
call_tid
,
&
mut
location_to_data_map
,
);
// Add derived locations based on return register locations.
// FIXME: We cannot add derived locations based on parameter objects,
// because the location and ID of their parent objects would be ambiguous
// between parameter objects and other derived locations.
self
.add_derived_locations_to_location_to_pointer_data_map
(
&
mut
location_to_data_map
,
locations_to_derive
,
);
location_to_data_map
}
/// Returns `true` if the value contains at least one reference to a non-parameter
/// (and non-stack) memory object tracked by the current state.
fn
contains_non_param_pointer
(
&
self
,
value
:
&
Data
)
->
bool
{
for
id
in
value
.referenced_ids
()
{
if
(
id
.get_tid
()
!=
self
.get_fn_tid
()
||
!
id
.get_path_hints
()
.is_empty
())
&&
self
.memory
.contains
(
id
)
{
return
true
;
}
}
false
}
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/state/mod.rs
View file @
f6ced95c
use
super
::
object
::
AbstractObject
;
use
super
::
object_list
::
AbstractObjectList
;
use
super
::
Data
;
use
crate
::
abstract_domain
::
*
;
use
crate
::
analysis
::
function_signature
::
AccessPattern
;
use
crate
::
analysis
::
function_signature
::
FunctionSignature
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
prelude
::
*
;
use
std
::
collections
::
HashSet
;
use
std
::
collections
::{
BTreeMap
,
BTreeSet
};
use
std
::
sync
::
Arc
;
...
...
@@ -67,38 +70,120 @@ impl State {
stack_register
:
&
Variable
,
function_tid
:
Tid
,
)
->
State
{
let
global_addresses
=
fn_sig
.global_parameters
.keys
()
.cloned
()
.collect
();
let
global_addresses
=
fn_sig
.global_parameters
.keys
()
.map
(|
location
|
match
location
{
AbstractLocation
::
GlobalAddress
{
address
,
..
}
|
AbstractLocation
::
GlobalPointer
(
address
,
_
)
=>
*
address
,
_
=>
panic!
(
"Unexpected non-global parameter"
),
})
.collect
();
let
mock_global_memory
=
RuntimeMemoryImage
::
empty
(
true
);
let
mut
state
=
State
::
new
(
stack_register
,
function_tid
.clone
(),
global_addresses
);
// Set parameter values and create parameter memory objects.
for
(
arg
,
access_pattern
)
in
&
fn_sig
.parameters
{
let
param_id
=
AbstractIdentifier
::
from_arg
(
&
function_tid
,
arg
);
match
arg
{
Arg
::
Register
{
expr
:
Expression
::
Var
(
var
),
..
}
=>
state
.set_register
(
var
,
Data
::
from_target
(
param_id
.clone
(),
Bitvector
::
zero
(
var
.size
.into
())
.into
()),
),
Arg
::
Register
{
..
}
=>
continue
,
// Parameters in floating point registers are currently ignored.
Arg
::
Stack
{
address
,
size
,
..
}
=>
{
let
param_data
=
Data
::
from_target
(
param_id
.clone
(),
Bitvector
::
zero
((
*
size
)
.into
())
.into
());
state
.write_to_address
(
address
,
&
param_data
,
&
mock_global_memory
)
.unwrap
();
}
for
params
in
sort_params_by_recursion_depth
(
&
fn_sig
.parameters
)
.values
()
{
for
(
param_location
,
access_pattern
)
in
params
{
state
.add_param
(
param_location
,
access_pattern
,
&
mock_global_memory
);
}
if
access_pattern
.is_dereferenced
()
{
state
.memory
.add_abstract_object
(
param_id
,
stack_register
.size
,
None
);
}
for
(
recursion_depth
,
params
)
in
sort_params_by_recursion_depth
(
&
fn_sig
.global_parameters
)
{
if
recursion_depth
>
0
{
for
(
param_location
,
access_pattern
)
in
params
{
state
.add_param
(
param_location
,
access_pattern
,
&
mock_global_memory
);
}
}
}
state
}
/// Add the given parameter to the function start state represented by `self`:
/// For the given parameter location, add a parameter object if it was dereferenced (according to the access pattern)
/// and write the pointer to the parameter object to the corresponding existing memory object of `self`.
///
/// This function assumes that the parent memory object of `param` already exists if `param` is a nested parameter.
fn
add_param
(
&
mut
self
,
param
:
&
AbstractLocation
,
access_pattern
:
&
AccessPattern
,
global_memory
:
&
RuntimeMemoryImage
,
)
{
let
param_id
=
AbstractIdentifier
::
new
(
self
.stack_id
.get_tid
()
.clone
(),
param
.clone
());
if
!
matches!
(
param
,
AbstractLocation
::
GlobalAddress
{
..
})
&&
access_pattern
.is_dereferenced
()
{
self
.memory
.add_abstract_object
(
param_id
.clone
(),
self
.stack_id
.bytesize
(),
None
);
}
match
param
{
AbstractLocation
::
Register
(
var
)
=>
{
self
.set_register
(
var
,
Data
::
from_target
(
param_id
,
Bitvector
::
zero
(
param
.bytesize
()
.into
())
.into
()),
);
}
AbstractLocation
::
Pointer
(
_
,
_
)
=>
{
let
(
parent_location
,
offset
)
=
param
.get_parent_location
(
self
.stack_id
.bytesize
())
.unwrap
();
let
parent_id
=
AbstractIdentifier
::
new
(
self
.stack_id
.get_tid
()
.clone
(),
parent_location
);
self
.store_value
(
&
Data
::
from_target
(
parent_id
,
Bitvector
::
from_i64
(
offset
)
.into_resize_signed
(
self
.stack_id
.bytesize
())
.into
(),
),
&
Data
::
from_target
(
param_id
.clone
(),
Bitvector
::
zero
(
param_id
.bytesize
()
.into
())
.into
(),
),
global_memory
,
)
.unwrap
();
}
AbstractLocation
::
GlobalAddress
{
..
}
=>
(),
AbstractLocation
::
GlobalPointer
(
_
,
_
)
=>
{
let
(
parent_location
,
offset
)
=
param
.get_parent_location
(
self
.stack_id
.bytesize
())
.unwrap
();
if
let
AbstractLocation
::
GlobalAddress
{
address
,
size
:
_
}
=
parent_location
{
let
parent_id
=
self
.get_global_mem_id
();
self
.store_value
(
&
Data
::
from_target
(
parent_id
,
Bitvector
::
from_u64
(
address
+
offset
as
u64
)
.into_resize_signed
(
self
.stack_id
.bytesize
())
.into
(),
),
&
Data
::
from_target
(
param_id
.clone
(),
Bitvector
::
zero
(
param_id
.bytesize
()
.into
())
.into
(),
),
global_memory
,
)
.unwrap
();
}
else
{
let
parent_id
=
AbstractIdentifier
::
new
(
self
.stack_id
.get_tid
()
.clone
(),
parent_location
);
self
.store_value
(
&
Data
::
from_target
(
parent_id
,
Bitvector
::
from_i64
(
offset
)
.into_resize_signed
(
self
.stack_id
.bytesize
())
.into
(),
),
&
Data
::
from_target
(
param_id
.clone
(),
Bitvector
::
zero
(
param_id
.bytesize
()
.into
())
.into
(),
),
global_memory
,
)
.unwrap
();
}
}
}
}
/// Set the MIPS link register `t9` to the address of the callee TID.
///
/// According to the System V ABI for MIPS the caller has to save the callee address in register `t9`
...
...
@@ -124,6 +209,89 @@ impl State {
Ok
(())
}
/// Remove all objects and registers from the state whose contents will not be used after returning to a caller.
///
/// All remaining memory objects after the minimization are reachable in the caller
/// either via a parameter object that may have been mutated in the call
/// or via a return register.
pub
fn
minimize_before_return_instruction
(
&
mut
self
,
fn_sig
:
&
FunctionSignature
,
cconv
:
&
CallingConvention
,
)
{
self
.clear_non_return_register
(
cconv
);
self
.remove_immutable_parameter_objects
(
fn_sig
);
self
.memory
.remove
(
&
self
.stack_id
);
self
.remove_unreferenced_objects
();
}
/// Remove all parameter objects (including global parameter objects) that are not marked as mutably accessed.
/// Used to minimize state before a return instruction.
fn
remove_immutable_parameter_objects
(
&
mut
self
,
fn_sig
:
&
FunctionSignature
)
{
let
current_fn_tid
=
self
.get_fn_tid
()
.clone
();
self
.memory
.retain
(|
object_id
,
_object
|
{
if
*
object_id
.get_tid
()
==
current_fn_tid
&&
object_id
.get_path_hints
()
.is_empty
()
{
if
let
Some
(
access_pattern
)
=
fn_sig
.parameters
.get
(
object_id
.get_location
())
{
if
!
access_pattern
.is_mutably_dereferenced
()
{
return
false
;
}
}
if
let
Some
(
access_pattern
)
=
fn_sig
.global_parameters
.get
(
object_id
.get_location
())
{
if
!
access_pattern
.is_mutably_dereferenced
()
{
return
false
;
}
}
}
true
});
}
/// Clear all non-return registers from the state, including all virtual registers.
/// This function is used to minimize the state before a return instruction.
fn
clear_non_return_register
(
&
mut
self
,
cconv
:
&
CallingConvention
)
{
let
return_register
:
HashSet
<
Variable
>
=
cconv
.get_all_return_register
()
.into_iter
()
.cloned
()
.collect
();
self
.register
.retain
(|
var
,
_value
|
return_register
.contains
(
var
));
}
/// Try to determine unique pointer locations for non-parameter memory objects.
/// When successful, merge all referenced non-parameter objects for that location
/// and replace the pointer with a pointer to the merged object.
///
/// The merged objects get new abstract IDs generated from the call TID and their abstract location in the state.
///
/// This function leaves pointers to parameter objects as is,
/// while pointers to non-parameter objects, that were not merged (e.g. due to pointers being not unique) are replaced with `Top`.
pub
fn
merge_mem_objects_with_unique_abstract_location
(
&
mut
self
,
call_tid
:
&
Tid
)
{
let
mut
location_to_data_map
=
self
.map_abstract_locations_to_pointer_data
(
call_tid
);
self
.filter_location_to_pointer_data_map
(
&
mut
location_to_data_map
);
let
location_to_object_map
=
self
.generate_target_objects_for_new_locations
(
&
location_to_data_map
);
self
.replace_unified_mem_objects
(
location_to_object_map
);
self
.replace_ids_to_non_parameter_objects
(
&
location_to_data_map
);
self
.insert_pointers_to_unified_objects
(
&
location_to_data_map
,
call_tid
);
}
/// Remove all memory objects corresponding to non-parameter IDs.
/// Afterwards, add the memory objects in the location to object map to the state.
fn
replace_unified_mem_objects
(
&
mut
self
,
location_to_object_map
:
BTreeMap
<
AbstractIdentifier
,
AbstractObject
>
,
)
{
let
current_fn_tid
=
self
.get_fn_tid
()
.clone
();
self
.memory
.retain
(|
object_id
,
_
|
{
*
object_id
.get_tid
()
==
current_fn_tid
&&
object_id
.get_path_hints
()
.is_empty
()
});
for
(
id
,
object
)
in
location_to_object_map
{
self
.memory
.insert
(
id
,
object
);
}
}
/// Clear all non-callee-saved registers from the state.
/// This automatically also removes all virtual registers.
/// The parameter is a list of callee-saved register names.
...
...
@@ -260,5 +428,21 @@ impl State {
}
}
/// Sort parameters by recursion depth.
/// Helper function when one has to iterate over parameters in order of their recursion depth.
fn
sort_params_by_recursion_depth
(
params
:
&
BTreeMap
<
AbstractLocation
,
AccessPattern
>
,
)
->
BTreeMap
<
u64
,
BTreeMap
<&
AbstractLocation
,
&
AccessPattern
>>
{
let
mut
sorted_params
=
BTreeMap
::
new
();
for
(
param
,
access_pattern
)
in
params
{
let
recursion_depth
=
param
.recursion_depth
();
let
bucket
=
sorted_params
.entry
(
recursion_depth
)
.or_insert
(
BTreeMap
::
new
());
bucket
.insert
(
param
,
access_pattern
);
}
sorted_params
}
#[cfg(test)]
mod
tests
;
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/state/tests/access_handling.rs
0 → 100644
View file @
f6ced95c
use
super
::
*
;
#[test]
fn
handle_store
()
{
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
mut
state
=
State
::
new
(
&
variable!
(
"RSP:8"
),
Tid
::
new
(
"time0"
),
BTreeSet
::
new
());
let
stack_id
=
new_id
(
"time0"
,
"RSP"
);
assert_eq!
(
state
.eval
(
&
expr!
(
"RSP:8"
)),
Data
::
from_target
(
stack_id
.clone
(),
bv
(
0
))
);
state
.handle_register_assign
(
&
variable!
(
"RSP:8"
),
&
expr!
(
"RSP:8 - 32:8"
));
assert_eq!
(
state
.eval
(
&
expr!
(
"RSP:8"
)),
Data
::
from_target
(
stack_id
.clone
(),
bv
(
-
32
))
);
state
.handle_register_assign
(
&
variable!
(
"RSP:8"
),
&
expr!
(
"RSP:8 + -8:8"
));
assert_eq!
(
state
.eval
(
&
expr!
(
"RSP:8"
)),
Data
::
from_target
(
stack_id
.clone
(),
bv
(
-
40
))
);
state
.handle_store
(
&
expr!
(
"RSP:8 + 8:8"
),
&
expr!
(
"1:8"
),
&
global_memory
)
.unwrap
();
state
.handle_store
(
&
expr!
(
"RSP:8 - 8:8"
),
&
expr!
(
"2:8"
),
&
global_memory
)
.unwrap
();
state
.handle_store
(
&
expr!
(
"RSP:8 + -16:8"
),
&
expr!
(
"3:8"
),
&
global_memory
)
.unwrap
();
state
.handle_register_assign
(
&
variable!
(
"RSP:8"
),
&
expr!
(
"RSP:8 - 4:8"
));
assert_eq!
(
state
.load_value
(
&
expr!
(
"RSP:8 + 12:8"
),
ByteSize
::
new
(
8
),
&
global_memory
)
.unwrap
(),
bv
(
1
)
.into
()
);
assert_eq!
(
state
.load_value
(
&
expr!
(
"RSP:8 - 4:8"
),
ByteSize
::
new
(
8
),
&
global_memory
)
.unwrap
(),
bv
(
2
)
.into
()
);
assert_eq!
(
state
.load_value
(
&
expr!
(
"RSP:8 + -12:8"
),
ByteSize
::
new
(
8
),
&
global_memory
)
.unwrap
(),
bv
(
3
)
.into
()
);
}
#[test]
fn
global_mem_access
()
{
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
mut
state
=
State
::
new
(
&
variable!
(
"RSP:8"
),
Tid
::
new
(
"func_tid"
),
BTreeSet
::
from
([
0x2000
]),
);
// global read-only address
let
address_expr
=
expr!
(
"0x1000:8"
);
assert_eq!
(
state
.load_value
(
&
address_expr
,
ByteSize
::
new
(
4
),
&
global_memory
)
.unwrap
(),
bitvec!
(
"0xb3b2b1b0:4"
)
.into
()
// note that we read in little-endian byte order
);
assert
!
(
state
.write_to_address
(
&
address_expr
,
&
DataDomain
::
new_top
(
ByteSize
::
new
(
4
)),
&
global_memory
)
.is_err
());
// global writeable address
let
address_expr
=
expr!
(
"0x2000:8"
);
assert_eq!
(
state
.load_value
(
&
address_expr
,
ByteSize
::
new
(
4
),
&
global_memory
)
.unwrap
(),
DataDomain
::
new_top
(
ByteSize
::
new
(
4
))
);
assert
!
(
state
.write_to_address
(
&
address_expr
,
&
bitvec!
(
"21:4"
)
.into
(),
&
global_memory
)
.is_ok
());
assert_eq!
(
state
.load_value
(
&
address_expr
,
ByteSize
::
new
(
4
),
&
global_memory
)
.unwrap
(),
bitvec!
(
"21:4"
)
.into
()
);
// invalid global address
let
address_expr
=
expr!
(
"0x3456:8"
);
assert
!
(
state
.load_value
(
&
address_expr
,
ByteSize
::
new
(
4
),
&
global_memory
)
.is_err
());
assert
!
(
state
.write_to_address
(
&
address_expr
,
&
DataDomain
::
new_top
(
ByteSize
::
new
(
4
)),
&
global_memory
)
.is_err
());
}
#[test]
fn
test_eval_abstract_location
()
{
let
mut
state
=
State
::
new
(
&
variable!
(
"RSP:8"
),
Tid
::
new
(
"fn_tid"
),
BTreeSet
::
new
());
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
object_id
=
AbstractIdentifier
::
mock
(
"fn_tid"
,
"RSI"
,
8
);
state
.memory
.add_abstract_object
(
object_id
.clone
(),
ByteSize
::
new
(
8
),
None
);
state
.memory
.get_object_mut
(
&
state
.stack_id
)
.unwrap
()
.set_value
(
Data
::
from_target
(
object_id
.clone
(),
bitvec!
(
"0x0:8"
)
.into
()),
&
bitvec!
(
"0x-20:8"
)
.into
(),
)
.unwrap
();
state
.memory
.get_object_mut
(
&
object_id
)
.unwrap
()
.set_value
(
bitvec!
(
"0x42:8"
)
.into
(),
&
bitvec!
(
"0x10:8"
)
.into
())
.unwrap
();
let
location
=
AbstractLocation
::
mock
(
"RSP:8"
,
&
[
-
32
],
8
);
let
value
=
state
.eval_abstract_location
(
&
location
,
&
global_memory
);
assert_eq!
(
value
,
Data
::
from_target
(
object_id
.clone
(),
bitvec!
(
"0x0:8"
)
.into
())
);
let
location
=
AbstractLocation
::
mock
(
"RSP:8"
,
&
[
-
32
,
16
],
8
);
let
value
=
state
.eval_abstract_location
(
&
location
,
&
global_memory
);
assert_eq!
(
value
,
bitvec!
(
"0x42:8"
)
.into
());
// Also test evaluation of a global address
state
.memory
.get_object_mut
(
&
state
.get_global_mem_id
()
.clone
())
.unwrap
()
.set_value
(
bitvec!
(
"0x43:8"
)
.into
(),
&
bitvec!
(
"0x2000:8"
)
.into
())
.unwrap
();
let
location
=
AbstractLocation
::
mock_global
(
0x2000
,
&
[
0
],
8
);
let
value
=
state
.eval_abstract_location
(
&
location
,
&
global_memory
);
assert_eq!
(
value
,
bitvec!
(
"0x43:8"
)
.into
());
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/state/tests/id_manipulation.rs
0 → 100644
View file @
f6ced95c
use
super
::
*
;
/// Mock an ARM32 function start state with a function signature that has one mutably dereferenced parameter in r0
/// and mutably dereferenced global parameter at address 0x2000.
/// The function Tid of the state is named `callee`.
fn
mock_arm32_fn_start_state
()
->
(
State
,
FunctionSignature
)
{
let
full_access
=
AccessPattern
::
new_unknown_access
();
let
fn_sig
=
FunctionSignature
{
parameters
:
BTreeMap
::
from
([(
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
),
full_access
)]),
global_parameters
:
BTreeMap
::
from
([(
AbstractLocation
::
mock_global
(
0x2000
,
&
[],
4
),
full_access
,
)]),
};
let
state
=
State
::
from_fn_sig
(
&
fn_sig
,
&
variable!
(
"sp:4"
),
Tid
::
new
(
"callee"
));
(
state
,
fn_sig
)
}
#[test]
fn
test_map_abstract_locations_to_pointer_data
()
{
let
call_tid
=
Tid
::
new
(
"call"
);
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
(
mut
state
,
_
)
=
mock_arm32_fn_start_state
();
let
param_id
=
AbstractIdentifier
::
new
(
Tid
::
new
(
"callee"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
));
let
param_pointer
=
Data
::
from_target
(
param_id
.clone
(),
bitvec!
(
"0x2:4"
)
.into
());
let
global_param_pointer
=
Data
::
from_target
(
state
.get_global_mem_id
()
.clone
(),
bitvec!
(
"0x2000:4"
)
.into
(),
);
let
callee_orig_id
=
AbstractIdentifier
::
new
(
Tid
::
new
(
"inside_callee"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
),
);
let
callee_orig_pointer
=
Data
::
from_target
(
callee_orig_id
.clone
(),
bitvec!
(
"0x3:4"
)
.into
());
let
nested_callee_orig_id
=
AbstractIdentifier
::
new
(
Tid
::
new
(
"inside_callee"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[
0x10
],
4
),
);
let
nested_callee_orig_pointer
=
Data
::
from_target
(
nested_callee_orig_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
());
state
.memory
.add_abstract_object
(
callee_orig_id
.clone
(),
ByteSize
::
new
(
4
),
None
);
state
.memory
.add_abstract_object
(
nested_callee_orig_id
.clone
(),
ByteSize
::
new
(
4
),
None
);
state
.store_value
(
&
param_pointer
,
&
nested_callee_orig_pointer
,
&
global_memory
)
.unwrap
();
state
.store_value
(
&
global_param_pointer
,
&
nested_callee_orig_pointer
,
&
global_memory
,
)
.unwrap
();
state
.set_register
(
&
variable!
(
"r0:4"
),
callee_orig_pointer
.clone
());
state
.store_value
(
&
callee_orig_pointer
,
&
nested_callee_orig_pointer
,
&
global_memory
,
)
.unwrap
();
let
location_to_data_map
=
state
.map_abstract_locations_to_pointer_data
(
&
call_tid
);
let
expected_map
=
BTreeMap
::
from
([
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"call_param"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[
2
],
4
),
),
nested_callee_orig_pointer
.clone
(),
),
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"call_param"
),
AbstractLocation
::
mock_global
(
0x0
,
&
[
0x2000
],
4
),
),
nested_callee_orig_pointer
.clone
(),
),
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"call"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
)),
callee_orig_pointer
.clone
(),
),
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"call"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[
0
],
4
)),
nested_callee_orig_pointer
.clone
(),
),
]);
assert_eq!
(
location_to_data_map
,
expected_map
);
}
#[test]
fn
test_filter_location_to_data_map
()
{
let
(
mut
state
,
_
)
=
mock_arm32_fn_start_state
();
state
.memory
.add_abstract_object
(
AbstractIdentifier
::
mock
(
"callee_orig"
,
"r0"
,
4
),
ByteSize
::
new
(
4
),
None
,
);
state
.memory
.add_abstract_object
(
AbstractIdentifier
::
mock
(
"callee_orig_2"
,
"r0"
,
4
),
ByteSize
::
new
(
4
),
None
,
);
state
.memory
.add_abstract_object
(
AbstractIdentifier
::
mock
(
"callee_orig_3"
,
"r0"
,
4
),
ByteSize
::
new
(
4
),
None
,
);
let
mut
loc_to_data_map
=
BTreeMap
::
from
([
(
AbstractIdentifier
::
mock
(
"call"
,
"r0"
,
4
),
Data
::
mock_from_target_map
(
BTreeMap
::
from
([
(
AbstractIdentifier
::
mock
(
"callee"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
(
AbstractIdentifier
::
mock
(
"callee_orig"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
(
AbstractIdentifier
::
mock
(
"callee_orig_3"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
])),
),
(
AbstractIdentifier
::
mock
(
"call"
,
"r1"
,
4
),
Data
::
mock_from_target_map
(
BTreeMap
::
from
([
(
AbstractIdentifier
::
mock
(
"callee"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
(
AbstractIdentifier
::
mock
(
"callee_orig_2"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
])),
),
(
AbstractIdentifier
::
mock
(
"call"
,
"r2"
,
4
),
Data
::
mock_from_target_map
(
BTreeMap
::
from
([(
AbstractIdentifier
::
mock
(
"callee_orig_2"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
)])),
),
]);
state
.filter_location_to_pointer_data_map
(
&
mut
loc_to_data_map
);
let
expected_map
=
BTreeMap
::
from
([(
AbstractIdentifier
::
mock
(
"call"
,
"r0"
,
4
),
Data
::
mock_from_target_map
(
BTreeMap
::
from
([
(
AbstractIdentifier
::
mock
(
"callee"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
(
AbstractIdentifier
::
mock
(
"callee_orig"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
(
AbstractIdentifier
::
mock
(
"callee_orig_3"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
])),
)]);
assert_eq!
(
loc_to_data_map
,
expected_map
);
}
#[test]
fn
test_generate_target_objects_for_new_locations
()
{
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
(
mut
state
,
_
)
=
mock_arm32_fn_start_state
();
let
param_id
=
AbstractIdentifier
::
mock
(
"callee"
,
"r0"
,
4
);
let
callee_orig_id
=
AbstractIdentifier
::
mock
(
"callee_orig"
,
"r0"
,
4
);
let
callee_orig_2_id
=
AbstractIdentifier
::
mock
(
"callee_orig_2"
,
"r0"
,
4
);
state
.memory
.add_abstract_object
(
callee_orig_id
.clone
(),
ByteSize
::
new
(
4
),
None
);
state
.memory
.add_abstract_object
(
callee_orig_2_id
.clone
(),
ByteSize
::
new
(
4
),
None
);
state
.store_value
(
&
Data
::
from_target
(
param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
&
bitvec!
(
"0x42:4"
)
.into
(),
&
global_memory
,
)
.unwrap
();
state
.store_value
(
&
Data
::
from_target
(
callee_orig_id
.clone
(),
bitvec!
(
"0x4:4"
)
.into
()),
&
bitvec!
(
"0x24:4"
)
.into
(),
&
global_memory
,
)
.unwrap
();
let
loc_to_data_map
=
BTreeMap
::
from
([(
AbstractIdentifier
::
mock
(
"call"
,
"r0"
,
4
),
Data
::
mock_from_target_map
(
BTreeMap
::
from
([
(
param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
(
callee_orig_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
(
callee_orig_2_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
])),
)]);
let
loc_to_obj_map
=
state
.generate_target_objects_for_new_locations
(
&
loc_to_data_map
);
assert_eq!
(
loc_to_obj_map
.len
(),
1
);
let
object
=
&
loc_to_obj_map
[
&
AbstractIdentifier
::
mock
(
"call"
,
"r0"
,
4
)];
assert_eq!
(
object
.get_value
(
bitvec!
(
"0x0:4"
),
ByteSize
::
new
(
4
)),
Data
::
new_top
(
ByteSize
::
new
(
4
))
);
let
mut
merged_value
=
Data
::
new_top
(
ByteSize
::
new
(
4
));
merged_value
.set_absolute_value
(
Some
(
bitvec!
(
"0x24:4"
)
.into
()));
assert_eq!
(
object
.get_value
(
bitvec!
(
"0x4:4"
),
ByteSize
::
new
(
4
)),
merged_value
);
}
#[test]
fn
test_get_id_to_unified_id_replacement_map
()
{
let
cconv
=
CallingConvention
::
mock_arm32
();
let
(
mut
state
,
fn_sig
)
=
mock_arm32_fn_start_state
();
state
.minimize_before_return_instruction
(
&
fn_sig
,
&
cconv
);
let
location_to_data_map
=
BTreeMap
::
from
([(
AbstractIdentifier
::
mock
(
"call"
,
"r0"
,
4
),
Data
::
mock_from_target_map
(
BTreeMap
::
from
([
(
AbstractIdentifier
::
mock
(
"callee"
,
"r0"
,
4
),
bitvec!
(
"0x2:4"
)
.into
(),
),
(
AbstractIdentifier
::
mock
(
"callee_orig"
,
"r0"
,
4
),
bitvec!
(
"0x3:4"
)
.into
(),
),
(
AbstractIdentifier
::
mock
(
"callee_orig_2"
,
"r0"
,
4
),
bitvec!
(
"0x4:4"
)
.into
(),
),
])),
)]);
let
id_replacement_map
=
state
.get_id_to_unified_ids_replacement_map
(
&
location_to_data_map
);
let
merged_id
=
AbstractIdentifier
::
mock
(
"call"
,
"r0"
,
4
);
let
mut
merged_pointer
=
Data
::
from_target
(
merged_id
.clone
(),
bitvec!
(
"0x-3:4"
)
.into
());
merged_pointer
.set_contains_top_flag
();
let
mut
merged_pointer_2
=
Data
::
from_target
(
merged_id
.clone
(),
bitvec!
(
"0x-4:4"
)
.into
());
merged_pointer_2
.set_contains_top_flag
();
let
param_id
=
AbstractIdentifier
::
mock
(
"callee"
,
"r0"
,
4
);
let
expected_map
=
BTreeMap
::
from
([
(
AbstractIdentifier
::
mock
(
"callee_orig"
,
"r0"
,
4
),
merged_pointer
,
),
(
AbstractIdentifier
::
mock
(
"callee_orig_2"
,
"r0"
,
4
),
merged_pointer_2
,
),
(
param_id
.clone
(),
Data
::
from_target
(
param_id
,
bitvec!
(
"0x0:4"
)
.into
()),
),
]);
assert_eq!
(
id_replacement_map
,
expected_map
);
}
#[test]
fn
test_insert_pointers_to_unified_objects
()
{
let
call_tid
=
Tid
::
new
(
"call"
);
let
(
mut
state
,
_
)
=
mock_arm32_fn_start_state
();
let
param_id
=
AbstractIdentifier
::
mock
(
"callee"
,
"r0"
,
4
);
let
old_callee_orig_id
=
AbstractIdentifier
::
mock
(
"instr"
,
"r0"
,
4
);
let
old_callee_orig_id_2
=
AbstractIdentifier
::
mock
(
"instr_2"
,
"r0"
,
4
);
let
new_id
=
AbstractIdentifier
::
mock
(
"call"
,
"r0"
,
4
);
let
new_id_2
=
AbstractIdentifier
::
mock_nested
(
"call"
,
"r0:4"
,
&
[
0
],
4
);
state
.memory
.add_abstract_object
(
new_id
.clone
(),
ByteSize
::
new
(
4
),
None
);
state
.memory
.add_abstract_object
(
new_id_2
.clone
(),
ByteSize
::
new
(
4
),
None
);
let
location_to_data_map
=
BTreeMap
::
from
([
(
new_id
.clone
(),
Data
::
mock_from_target_map
(
BTreeMap
::
from
([
(
param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
(
old_callee_orig_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
])),
),
(
new_id_2
.clone
(),
Data
::
from_target
(
old_callee_orig_id_2
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
),
]);
state
.insert_pointers_to_unified_objects
(
&
location_to_data_map
,
&
call_tid
);
assert_eq!
(
state
.get_register
(
&
variable!
(
"r0:4"
)),
Data
::
mock_from_target_map
(
BTreeMap
::
from
([
(
param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
(
new_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
]))
);
assert_eq!
(
state
.memory
.get_object
(
&
new_id
)
.unwrap
()
.get_value
(
bitvec!
(
"0x0:4"
),
ByteSize
::
new
(
4
)),
Data
::
from_target
(
new_id_2
.clone
(),
bitvec!
(
"0x0:4"
)
.into
())
);
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/state/tests/mod.rs
View file @
f6ced95c
...
...
@@ -3,6 +3,8 @@ use super::*;
use
crate
::
analysis
::
pointer_inference
::
object
::
*
;
use
crate
::{
bitvec
,
def
,
expr
,
variable
};
mod
access_handling
;
mod
id_manipulation
;
mod
specialized_expressions
;
fn
bv
(
value
:
i64
)
->
ValueDomain
{
...
...
@@ -81,58 +83,6 @@ fn state() {
}
#[test]
fn
handle_store
()
{
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
mut
state
=
State
::
new
(
&
variable!
(
"RSP:8"
),
Tid
::
new
(
"time0"
),
BTreeSet
::
new
());
let
stack_id
=
new_id
(
"time0"
,
"RSP"
);
assert_eq!
(
state
.eval
(
&
expr!
(
"RSP:8"
)),
Data
::
from_target
(
stack_id
.clone
(),
bv
(
0
))
);
state
.handle_register_assign
(
&
variable!
(
"RSP:8"
),
&
expr!
(
"RSP:8 - 32:8"
));
assert_eq!
(
state
.eval
(
&
expr!
(
"RSP:8"
)),
Data
::
from_target
(
stack_id
.clone
(),
bv
(
-
32
))
);
state
.handle_register_assign
(
&
variable!
(
"RSP:8"
),
&
expr!
(
"RSP:8 + -8:8"
));
assert_eq!
(
state
.eval
(
&
expr!
(
"RSP:8"
)),
Data
::
from_target
(
stack_id
.clone
(),
bv
(
-
40
))
);
state
.handle_store
(
&
expr!
(
"RSP:8 + 8:8"
),
&
expr!
(
"1:8"
),
&
global_memory
)
.unwrap
();
state
.handle_store
(
&
expr!
(
"RSP:8 - 8:8"
),
&
expr!
(
"2:8"
),
&
global_memory
)
.unwrap
();
state
.handle_store
(
&
expr!
(
"RSP:8 + -16:8"
),
&
expr!
(
"3:8"
),
&
global_memory
)
.unwrap
();
state
.handle_register_assign
(
&
variable!
(
"RSP:8"
),
&
expr!
(
"RSP:8 - 4:8"
));
assert_eq!
(
state
.load_value
(
&
expr!
(
"RSP:8 + 12:8"
),
ByteSize
::
new
(
8
),
&
global_memory
)
.unwrap
(),
bv
(
1
)
.into
()
);
assert_eq!
(
state
.load_value
(
&
expr!
(
"RSP:8 - 4:8"
),
ByteSize
::
new
(
8
),
&
global_memory
)
.unwrap
(),
bv
(
2
)
.into
()
);
assert_eq!
(
state
.load_value
(
&
expr!
(
"RSP:8 + -12:8"
),
ByteSize
::
new
(
8
),
&
global_memory
)
.unwrap
(),
bv
(
3
)
.into
()
);
}
#[test]
fn
clear_parameters_on_the_stack_on_extern_calls
()
{
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
mut
state
=
State
::
new
(
&
variable!
(
"RSP:8"
),
Tid
::
new
(
"time0"
),
BTreeSet
::
new
());
...
...
@@ -226,61 +176,6 @@ fn reachable_ids_under_and_overapproximation() {
);
}
#[test]
fn
global_mem_access
()
{
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
mut
state
=
State
::
new
(
&
variable!
(
"RSP:8"
),
Tid
::
new
(
"func_tid"
),
BTreeSet
::
from
([
0x2000
]),
);
// global read-only address
let
address_expr
=
expr!
(
"0x1000:8"
);
assert_eq!
(
state
.load_value
(
&
address_expr
,
ByteSize
::
new
(
4
),
&
global_memory
)
.unwrap
(),
bitvec!
(
"0xb3b2b1b0:4"
)
.into
()
// note that we read in little-endian byte order
);
assert
!
(
state
.write_to_address
(
&
address_expr
,
&
DataDomain
::
new_top
(
ByteSize
::
new
(
4
)),
&
global_memory
)
.is_err
());
// global writeable address
let
address_expr
=
expr!
(
"0x2000:8"
);
assert_eq!
(
state
.load_value
(
&
address_expr
,
ByteSize
::
new
(
4
),
&
global_memory
)
.unwrap
(),
DataDomain
::
new_top
(
ByteSize
::
new
(
4
))
);
assert
!
(
state
.write_to_address
(
&
address_expr
,
&
bitvec!
(
"21:4"
)
.into
(),
&
global_memory
)
.is_ok
());
assert_eq!
(
state
.load_value
(
&
address_expr
,
ByteSize
::
new
(
4
),
&
global_memory
)
.unwrap
(),
bitvec!
(
"21:4"
)
.into
()
);
// invalid global address
let
address_expr
=
expr!
(
"0x3456:8"
);
assert
!
(
state
.load_value
(
&
address_expr
,
ByteSize
::
new
(
4
),
&
global_memory
)
.is_err
());
assert
!
(
state
.write_to_address
(
&
address_expr
,
&
DataDomain
::
new_top
(
ByteSize
::
new
(
4
)),
&
global_memory
)
.is_err
());
}
/// Test that value specialization does not introduce unintended widening hints.
/// This is a regression test for cases where pointer comparisons introduced two-sided bounds
/// (resulting in two-sided widenings) instead of one-sided bounds.
...
...
@@ -350,14 +245,38 @@ fn test_check_def_for_null_dereferences() {
#[test]
fn
from_fn_sig
()
{
let
fn_sig
=
FunctionSignature
::
mock_x64
();
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
full_access
=
AccessPattern
::
new_unknown_access
();
let
fn_sig
=
FunctionSignature
{
parameters
:
BTreeMap
::
from
([
(
AbstractLocation
::
mock
(
"RSI:8"
,
&
[],
8
),
full_access
),
(
AbstractLocation
::
mock
(
"RSI:8"
,
&
[
8
],
8
),
full_access
),
(
AbstractLocation
::
mock
(
"RDI:8"
,
&
[],
8
),
AccessPattern
::
new
()
.with_read_flag
(),
),
]),
global_parameters
:
BTreeMap
::
from
([
(
AbstractLocation
::
mock_global
(
0x2000
,
&
[],
8
),
full_access
),
(
AbstractLocation
::
mock_global
(
0x2000
,
&
[
0
],
8
),
full_access
),
]),
};
let
state
=
State
::
from_fn_sig
(
&
fn_sig
,
&
variable!
(
"RSP:8"
),
Tid
::
new
(
"func"
));
assert_eq!
(
state
.memory
.get_num_objects
(),
3
);
// The state should have 5 objects: The stack, the global memory space and 3 parameter objects.
assert_eq!
(
*
state
.memory
.get_object
(
&
new_id
(
"func"
,
"RSI"
))
.unwrap
(),
AbstractObject
::
new
(
None
,
ByteSize
::
new
(
8
))
state
.memory
.get_all_object_ids
(),
BTreeSet
::
from
([
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock
(
"RSP:8"
,
&
[],
8
)),
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock
(
"RSI:8"
,
&
[],
8
)),
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock
(
"RSI:8"
,
&
[
8
],
8
)),
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock_global
(
0x0
,
&
[],
8
)),
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock_global
(
0x2000
,
&
[
0
],
8
)
),
])
);
// Check that pointers have been correctly added to the state.
assert_eq!
(
state
.get_register
(
&
variable!
(
"RSP:8"
)),
Data
::
from_target
(
new_id
(
"func"
,
"RSP"
),
bv
(
0
)
.into
())
...
...
@@ -370,6 +289,32 @@ fn from_fn_sig() {
state
.get_register
(
&
variable!
(
"RSI:8"
)),
Data
::
from_target
(
new_id
(
"func"
,
"RSI"
),
bv
(
0
)
.into
())
);
assert_eq!
(
state
.eval_abstract_location
(
&
AbstractLocation
::
mock
(
"RSI:8"
,
&
[
8
],
8
),
&
global_memory
),
Data
::
from_target
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock
(
"RSI:8"
,
&
[
8
],
8
)),
bitvec!
(
"0x0:8"
)
.into
()
)
);
assert_eq!
(
state
.load_value_from_address
(
&
Data
::
from_target
(
state
.get_global_mem_id
()
.clone
(),
bitvec!
(
"0x2000:8"
)
.into
()
),
ByteSize
::
new
(
8
),
&
global_memory
)
.unwrap
(),
Data
::
from_target
(
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock_global
(
0x2000
,
&
[
0
],
8
)
),
bitvec!
(
"0x0:8"
)
.into
()
)
);
}
#[test]
...
...
@@ -411,3 +356,143 @@ fn add_param_object_from_callee() {
assert_eq!
(
value
.get_absolute_value
()
.unwrap
(),
&
bv
(
2
)
.into
());
assert
!
(
value
.contains_top
());
}
#[test]
fn
test_minimize_before_return_instruction
()
{
let
cconv
=
CallingConvention
::
mock_arm32
();
let
full_access
=
AccessPattern
::
new_unknown_access
();
let
deref_access
=
AccessPattern
::
new
()
.with_dereference_flag
();
let
fn_sig
=
FunctionSignature
{
parameters
:
BTreeMap
::
from
([
(
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
),
full_access
),
(
AbstractLocation
::
mock
(
"r0:4"
,
&
[
0
],
4
),
deref_access
),
(
AbstractLocation
::
mock
(
"r0:4"
,
&
[
0
,
0
],
4
),
full_access
),
]),
global_parameters
:
BTreeMap
::
from
([]),
};
let
mut
state
=
State
::
from_fn_sig
(
&
fn_sig
,
&
variable!
(
"sp:4"
),
Tid
::
new
(
"func"
));
state
.memory
.add_abstract_object
(
AbstractIdentifier
::
mock
(
"instr"
,
"r0"
,
4
),
ByteSize
::
new
(
4
),
None
,
);
state
.memory
.add_abstract_object
(
AbstractIdentifier
::
mock
(
"instr"
,
"r1"
,
4
),
ByteSize
::
new
(
4
),
None
,
);
state
.set_register
(
&
variable!
(
"r8:4"
),
bitvec!
(
"0x42:4"
)
.into
());
state
.set_register
(
&
variable!
(
"r0:4"
),
bitvec!
(
"0x42:4"
)
.into
());
state
.set_register
(
&
variable!
(
"r3:4"
),
Data
::
from_target
(
AbstractIdentifier
::
mock
(
"instr"
,
"r0"
,
4
),
bitvec!
(
"0x0:4"
)
.into
(),
),
);
state
.minimize_before_return_instruction
(
&
fn_sig
,
&
cconv
);
// non-return registers are cleared, but return registers remain
assert
!
(
state
.get_register
(
&
variable!
(
"r8:4"
))
.is_top
());
assert
!
(
!
state
.get_register
(
&
variable!
(
"r3:4"
))
.is_top
());
// immutable parameter objects are removed, but mutable parameter objects remain (even if no pointer to them remains)
assert
!
(
state
.memory
.get_object
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
)
))
.is_some
());
assert
!
(
state
.memory
.get_object
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[
0
],
4
)
))
.is_none
());
assert
!
(
state
.memory
.get_object
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"func"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[
0
,
0
],
4
)
))
.is_some
());
// The stack is removed
assert
!
(
state
.memory
.get_object
(
&
state
.stack_id
)
.is_none
());
// Unreferenced callee-originating objects are removed, but referenced ones remain
assert
!
(
state
.memory
.get_object
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"instr"
),
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
)
))
.is_some
());
assert
!
(
state
.memory
.get_object
(
&
AbstractIdentifier
::
new
(
Tid
::
new
(
"instr"
),
AbstractLocation
::
mock
(
"r1:4"
,
&
[],
4
)
))
.is_none
());
}
#[test]
fn
test_merge_mem_objects_with_unique_abstract_location
()
{
let
call_tid
=
Tid
::
new
(
"call"
);
let
global_memory
=
RuntimeMemoryImage
::
mock
();
let
cconv
=
CallingConvention
::
mock_arm32
();
let
full_access
=
AccessPattern
::
new_unknown_access
();
let
fn_sig
=
FunctionSignature
{
parameters
:
BTreeMap
::
from
([(
AbstractLocation
::
mock
(
"r0:4"
,
&
[],
4
),
full_access
)]),
global_parameters
:
BTreeMap
::
from
([(
AbstractLocation
::
mock_global
(
0x2000
,
&
[],
4
),
full_access
,
)]),
};
let
mut
state
=
State
::
from_fn_sig
(
&
fn_sig
,
&
variable!
(
"sp:4"
),
Tid
::
new
(
"callee"
));
let
param_id
=
AbstractIdentifier
::
mock
(
"callee"
,
"r0"
,
4
);
let
old_callee_orig_id
=
AbstractIdentifier
::
mock
(
"instr"
,
"r0"
,
4
);
let
old_callee_orig_id_2
=
AbstractIdentifier
::
mock
(
"instr_2"
,
"r0"
,
4
);
let
new_id
=
AbstractIdentifier
::
mock_nested
(
"call_param"
,
"r0:4"
,
&
[
0
],
4
);
state
.memory
.add_abstract_object
(
old_callee_orig_id
.clone
(),
ByteSize
::
new
(
4
),
None
);
state
.memory
.add_abstract_object
(
old_callee_orig_id_2
.clone
(),
ByteSize
::
new
(
4
),
None
);
// The pointer locations to callee_orig_id_2 will not be unique and thus removed from the state.
state
.set_register
(
&
variable!
(
"r1:4"
),
Data
::
from_target
(
old_callee_orig_id_2
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
);
state
.set_register
(
&
variable!
(
"r2:4"
),
Data
::
from_target
(
old_callee_orig_id_2
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
);
// This register should be cleared before computing return objects.
state
.set_register
(
&
variable!
(
"r8:4"
),
Data
::
from_target
(
old_callee_orig_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
);
state
.store_value
(
&
Data
::
from_target
(
param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
&
Data
::
from_target
(
old_callee_orig_id
,
bitvec!
(
"0x0:4"
)
.into
()),
&
global_memory
,
)
.unwrap
();
state
.minimize_before_return_instruction
(
&
fn_sig
,
&
cconv
);
state
.merge_mem_objects_with_unique_abstract_location
(
&
call_tid
);
let
mut
expected_state
=
State
::
from_fn_sig
(
&
fn_sig
,
&
variable!
(
"sp:4"
),
Tid
::
new
(
"callee"
));
expected_state
.minimize_before_return_instruction
(
&
fn_sig
,
&
cconv
);
expected_state
.memory
.add_abstract_object
(
new_id
.clone
(),
ByteSize
::
new
(
4
),
None
);
expected_state
.store_value
(
&
Data
::
from_target
(
param_id
.clone
(),
bitvec!
(
"0x0:4"
)
.into
()),
&
Data
::
from_target
(
new_id
,
bitvec!
(
"0x0:4"
)
.into
()),
&
global_memory
,
)
.unwrap
();
assert_eq!
(
state
,
expected_state
);
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/pointer_inference/vsa_result_impl.rs
View file @
f6ced95c
use
super
::
*
;
use
crate
::
analysis
::
vsa_results
::
VsaResult
;
use
crate
::
{
abstract_domain
::
AbstractLocation
,
analysis
::
vsa_results
::
VsaResult
}
;
/// Implementation of the [`VsaResult`] trait for providing other analyses with an easy-to-use interface
/// to use the value set and points-to analysis results of the pointer inference.
...
...
@@ -30,4 +30,15 @@ impl<'a> VsaResult for PointerInference<'a> {
.eval_parameter_arg
(
parameter
,
&
context
.project.runtime_memory_image
)
.ok
()
}
/// Evaluate the value of the given parameter at the given jump instruction.
fn
eval_parameter_location_at_call
(
&
self
,
jmp_tid
:
&
Tid
,
parameter
:
&
AbstractLocation
,
)
->
Option
<
Data
>
{
let
state
=
self
.states_at_tids
.get
(
jmp_tid
)
?
;
let
context
=
self
.computation
.get_context
()
.get_context
();
Some
(
state
.eval_abstract_location
(
parameter
,
&
context
.project.runtime_memory_image
))
}
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/string_abstraction/context/mod.rs
View file @
f6ced95c
...
...
@@ -44,7 +44,7 @@ pub struct Context<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<
/// The keys are of the form `(Def-TID, Current-Sub-TID)`
/// to distinguish the nodes for blocks contained in more than one function.
pub
block_start_node_map
:
HashMap
<
(
Tid
,
Tid
),
NodeIndex
>
,
/// A set containing a given [`Def`]
(crate::intermediate_representation::Def)
as the first `Def` of the block.
/// A set containing a given [`Def`] as the first `Def` of the block.
/// The keys are of the form `(Def-TID, Current-Sub-TID)`
/// to distinguish the nodes for blocks contained in more than one function.
pub
block_first_def_set
:
HashSet
<
(
Tid
,
Tid
)
>
,
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/vsa_results/mod.rs
View file @
f6ced95c
//! This module provides the [`VsaResult`] trait
//! which defines an interface for the results of analyses similar to a value set analysis.
use
crate
::
abstract_domain
::
AbstractLocation
;
use
crate
::
intermediate_representation
::{
Arg
,
Expression
};
use
crate
::
prelude
::
*
;
...
...
@@ -28,6 +29,13 @@ pub trait VsaResult {
/// Return the value of a parameter at the given jump instruction.
fn
eval_parameter_arg_at_call
(
&
self
,
jmp_tid
:
&
Tid
,
param
:
&
Arg
)
->
Option
<
Self
::
ValueDomain
>
;
/// Return the value of a parameter at the given jump instruction.
fn
eval_parameter_location_at_call
(
&
self
,
jmp_tid
:
&
Tid
,
param
:
&
AbstractLocation
,
)
->
Option
<
Self
::
ValueDomain
>
;
/// Evaluate the value of the given expression at the given jump instruction.
fn
eval_at_jmp
(
&
self
,
jmp_tid
:
&
Tid
,
expression
:
&
Expression
)
->
Option
<
Self
::
ValueDomain
>
;
}
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/checkers/cwe_119/context/bounds_computation.rs
View file @
f6ced95c
...
...
@@ -107,7 +107,7 @@ impl<'a> Context<'a> {
.function_signatures
.get
(
id
.get_tid
())
.unwrap
()
.get_stack_params_total_size
();
.get_stack_params_total_size
(
&
self
.project.stack_pointer_register
);
replace_if_smaller_bound
(
&
mut
upper_bound
,
BoundsMetadata
::
from_source
(
...
...
@@ -135,6 +135,8 @@ impl<'a> Context<'a> {
object_id
:
&
AbstractIdentifier
,
current_stack_frame_id
:
&
AbstractIdentifier
,
)
->
(
Option
<
BoundsMetadata
>
,
Option
<
BoundsMetadata
>
)
{
// FIXME: The malloc-tid-to-object-size-map check does not work anymore,
// because we do not use path hints in the PointerInference anymore.
if
self
.malloc_tid_to_object_size_map
.contains_key
(
object_id
.get_tid
())
...
...
@@ -153,7 +155,7 @@ impl<'a> Context<'a> {
.function_signatures
.get
(
object_id
.get_tid
())
.unwrap
()
.get_stack_params_total_size
();
.get_stack_params_total_size
(
&
self
.project.stack_pointer_register
);
(
None
,
Some
(
BoundsMetadata
::
new
(
stack_frame_upper_bound
)))
}
else
if
object_id
.get_tid
()
==
current_stack_frame_id
.get_tid
()
&&
object_id
.get_path_hints
()
.is_empty
()
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/checkers/cwe_119/context/mod.rs
View file @
f6ced95c
...
...
@@ -89,6 +89,8 @@ impl<'a> Context<'a> {
/// then the absolute value is used and unknown origins of the size value are ignored.
/// If more than one possible absolute value for the size is found then the minimum value for the size is returned.
pub
fn
compute_size_of_heap_object
(
&
self
,
object_id
:
&
AbstractIdentifier
)
->
BitvectorDomain
{
// FIXME: We use path hints, which are not longer provided by the PointerInference, to substitute some values.
// We either have to change that or make sure that we provide the path hints ourselves.
if
let
Some
(
object_size
)
=
self
.malloc_tid_to_object_size_map
.get
(
object_id
.get_tid
())
{
let
fn_tid_at_malloc_call
=
self
.call_to_caller_fn_map
[
object_id
.get_tid
()]
.clone
();
let
object_size
=
self
.recursively_substitute_param_values_context_sensitive
(
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/checkers/cwe_119/context/param_replacement.rs
View file @
f6ced95c
...
...
@@ -192,9 +192,10 @@ fn add_param_replacements_for_call(
.get
(
callee_tid
)
{
for
param_arg
in
fn_sig
.parameters
.keys
()
{
if
let
Some
(
param_value
)
=
vsa_results
.eval_parameter_arg_at_call
(
&
call
.tid
,
param_arg
)
if
let
Some
(
param_value
)
=
vsa_results
.eval_parameter_location_at_call
(
&
call
.tid
,
param_arg
)
{
let
param_id
=
AbstractIdentifier
::
from_arg
(
&
call
.tid
,
param_arg
);
let
param_id
=
AbstractIdentifier
::
new
(
call
.tid
.clone
(),
param_arg
.clone
()
);
replacement_map
.insert
(
param_id
,
param_value
);
}
}
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/checkers/cwe_119/mod.rs
View file @
f6ced95c
...
...
@@ -13,10 +13,13 @@
//!
//! The check uses the results of the [Pointer Inference analysis](`crate::analysis::pointer_inference`)
//! to check whether any memory accesses may point outside of the bounds of the corresponding memory objects.
//! For this the results of the Pointer Inference analysis are aggregated interprocedurally.
//! Additionally, the check uses a lightweight intraprocedural dataflow fixpoint computation
//! Additionally, the check uses a lightweight dataflow fixpoint computation
//! to ensure that for each memory object only the first access outside of its bounds is flagged as a CWE.
//!
//! Currently, the check is only partially interprocedural.
//! Bounds of parameter objects can be detected, but bounds of memory objects created in called functions
//! (other than the standard allocation functions) will not be detected.
//!
//! ## False Positives
//!
//! - Any analysis imprecision of the Pointer Inference analysis may lead to false positive results in this check.
...
...
@@ -40,6 +43,20 @@
//! this still may miss buffer overflows occuring in the called function.
//! - Right now the check only considers buffers on the stack or the heap, but not buffers in global memory.
//! Thus corresponding overflows of buffers in global memory are not detected.
//! - Since the check is only partially interprocedural at the moment,
//! it will miss object sizes of objects created in called functions.
//! For example, if allocations are wrapped in simple wrapper functions,
//! the analysis will miss overflows for corresponding objects, because it cannot determine their object sizes.
// FIXME: The current implementation uses path hints for memory object IDs to determine object sizes interprocedurally.
// But the number of path hint combinations can grow exponentially
// with the call depth to the actual allocation size of a callee-created object.
// This led to state explosion in the PointerInference and thus path hints are not longer provided by the PointerInference.
// But without the path hints that this analysis depended on, the check can only resolve sizes of parameter objects,
// but not of objects returned from called functions (other than the standard allocation functions).
// A future implementation needs a better way to determine object sizes interprocedurally,
// probably depending on several fixpoint computations to circumvent the state explosion problems
// that the old implementation is vulnerable to.
use
crate
::
analysis
::
pointer_inference
::
Data
;
use
crate
::
prelude
::
*
;
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/checkers/cwe_119/state.rs
View file @
f6ced95c
...
...
@@ -37,7 +37,7 @@ impl State {
};
let
stack_upper_bound
=
std
::
cmp
::
max
(
stack_upper_bound
,
function_sig
.get_stack_params_total_size
(),
function_sig
.get_stack_params_total_size
(
&
project
.stack_pointer_register
),
);
let
object_lower_bounds
=
BTreeMap
::
from
([(
stack_id
.clone
(),
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/checkers/cwe_416/context.rs
View file @
f6ced95c
...
...
@@ -112,7 +112,7 @@ impl<'a> Context<'a> {
if
access_pattern
.is_dereferenced
()
{
if
let
Some
(
arg_value
)
=
self
.pointer_inference
.eval_parameter_
arg
_at_call
(
call_tid
,
arg
)
.eval_parameter_
location
_at_call
(
call_tid
,
arg
)
{
if
let
Some
(
mut
warnings
)
=
state
.check_address_for_use_after_free
(
&
arg_value
)
{
warning_causes
.append
(
&
mut
warnings
);
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/checkers/cwe_416/mod.rs
View file @
f6ced95c
...
...
@@ -300,7 +300,7 @@ pub mod tests {
assert_eq!
(
processed_warnings
.len
(),
1
);
let
processed_cwe
=
processed_warnings
.iter
()
.next
()
.unwrap
();
assert_eq!
(
&
processed_cwe
.other
[
0
],
&
[
"Accessed ID object_origin_tid(->call_tid) @ RAX may have been freed before at free_tid."
.to_string
(),
"Accessed ID object_origin_tid(->call_tid) @ RAX
:i64
may have been freed before at free_tid."
.to_string
(),
"Relevant callgraph TIDs: [root_func_tid, call_tid]"
.to_string
(),
]);
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/intermediate_representation/term.rs
View file @
f6ced95c
...
...
@@ -31,6 +31,11 @@ impl Tid {
}
}
/// Returns true if the ID string ends with the provided suffix.
pub
fn
has_id_suffix
(
&
self
,
suffix
:
&
str
)
->
bool
{
self
.id
.ends_with
(
suffix
)
}
/// Generate the ID of a block starting at the given address.
///
/// Note that the block may not actually exist.
...
...
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/pipeline/mod.rs
View file @
f6ced95c
...
...
@@ -10,7 +10,7 @@ use crate::utils::log::LogMessage;
use
crate
::
utils
::{
binary
::
BareMetalConfig
,
ghidra
::
get_project_from_ghidra
};
use
std
::
path
::
Path
;
/// Disassemble the given binary and parse it to a [`Project`]
(crate::intermediate_representation::Project)
struct.
/// Disassemble the given binary and parse it to a [`Project`] struct.
///
/// If successful, returns the binary file (as a byte vector), the parsed project struct,
/// and a vector of log messages generated during the process.
...
...
This diff is collapsed.
Click to expand it.
test/src/lib.rs
View file @
f6ced95c
...
...
@@ -450,6 +450,7 @@ mod tests {
mark_architecture_skipped
(
&
mut
tests
,
"ppc64le"
);
// Ghidra generates mangled function names here for some reason.
mark_skipped
(
&
mut
tests
,
"x86"
,
"mingw32-gcc"
);
// TODO: Check reason for failure! Probably same as above?
mark_skipped
(
&
mut
tests
,
"x64"
,
"mingw32-gcc"
);
// We find an additional false positive in unrelated code.
for
test_case
in
tests
{
let
num_expected_occurences
=
1
;
...
...
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment