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
a16e6589
Unverified
Commit
a16e6589
authored
Nov 11, 2022
by
Enkelmann
Committed by
GitHub
Nov 11, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add global memory tracking to FunctionSignature analysis (#358)
parent
0359ea16
Show whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
738 additions
and
66 deletions
+738
-66
identifier.rs
src/cwe_checker_lib/src/abstract_domain/identifier.rs
+40
-3
callgraph.rs
src/cwe_checker_lib/src/analysis/callgraph.rs
+75
-0
context.rs
...we_checker_lib/src/analysis/function_signature/context.rs
+101
-27
tests.rs
...cker_lib/src/analysis/function_signature/context/tests.rs
+23
-0
global_var_propagation.rs
...src/analysis/function_signature/global_var_propagation.rs
+298
-0
mod.rs
src/cwe_checker_lib/src/analysis/function_signature/mod.rs
+31
-6
state.rs
src/cwe_checker_lib/src/analysis/function_signature/state.rs
+61
-8
call_handling.rs
...ib/src/analysis/function_signature/state/call_handling.rs
+55
-21
tests.rs
...hecker_lib/src/analysis/function_signature/state/tests.rs
+33
-1
mod.rs
src/cwe_checker_lib/src/analysis/mod.rs
+1
-0
jmp.rs
src/cwe_checker_lib/src/intermediate_representation/jmp.rs
+20
-0
No files found.
src/cwe_checker_lib/src/abstract_domain/identifier.rs
View file @
a16e6589
...
@@ -86,6 +86,11 @@ impl AbstractIdentifier {
...
@@ -86,6 +86,11 @@ impl AbstractIdentifier {
AbstractIdentifier
::
new
(
time
.clone
(),
location
)
AbstractIdentifier
::
new
(
time
.clone
(),
location
)
}
}
/// Create an abstract identifier from an address into global memory.
pub
fn
from_global_address
(
time
:
&
Tid
,
address
:
&
Bitvector
)
->
AbstractIdentifier
{
AbstractIdentifier
::
new
(
time
.clone
(),
AbstractLocation
::
from_global_address
(
address
))
}
/// Create a new abstract identifier
/// Create a new abstract identifier
/// by pushing the given path hint to the array of path hints of `self`.
/// by pushing the given path hint to the array of path hints of `self`.
/// Returns an error if the path hint is already contained in the path hints of `self`.
/// Returns an error if the path hint is already contained in the path hints of `self`.
...
@@ -106,11 +111,13 @@ impl AbstractIdentifier {
...
@@ -106,11 +111,13 @@ impl AbstractIdentifier {
}
}
/// Get the register associated to the abstract location.
/// Get the register associated to the abstract location.
/// Panics if the abstract location is
a memory location and not a register
.
/// Panics if the abstract location is
not a register but a memory location
.
pub
fn
unwrap_register
(
&
self
)
->
&
Variable
{
pub
fn
unwrap_register
(
&
self
)
->
&
Variable
{
match
&
self
.location
{
match
&
self
.location
{
AbstractLocation
::
Register
(
var
)
=>
var
,
AbstractLocation
::
Register
(
var
)
=>
var
,
AbstractLocation
::
Pointer
(
_
,
_
)
=>
panic!
(
"Abstract location is not a register."
),
AbstractLocation
::
GlobalAddress
{
..
}
|
AbstractLocation
::
GlobalPointer
(
_
,
_
)
|
AbstractLocation
::
Pointer
(
_
,
_
)
=>
panic!
(
"Abstract location is not a register."
),
}
}
}
}
...
@@ -153,18 +160,36 @@ impl std::fmt::Display for AbstractIdentifier {
...
@@ -153,18 +160,36 @@ impl std::fmt::Display for AbstractIdentifier {
pub
enum
AbstractLocation
{
pub
enum
AbstractLocation
{
/// The location is given by a register.
/// The location is given by a register.
Register
(
Variable
),
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.
/// The location is in memory.
/// One needs to follow the pointer in the given register
/// One needs to follow the pointer in the given register
/// and then follow the abstract memory location inside the pointed to memory object
/// and then follow the abstract memory location inside the pointed to memory object
/// to find the actual memory location.
/// to find the actual memory location.
Pointer
(
Variable
,
AbstractMemoryLocation
),
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
{
impl
std
::
fmt
::
Display
for
AbstractLocation
{
fn
fmt
(
&
self
,
formatter
:
&
mut
std
::
fmt
::
Formatter
)
->
std
::
fmt
::
Result
{
fn
fmt
(
&
self
,
formatter
:
&
mut
std
::
fmt
::
Formatter
)
->
std
::
fmt
::
Result
{
match
self
{
match
self
{
Self
::
Register
(
var
)
=>
write!
(
formatter
,
"{}"
,
var
.name
),
Self
::
Register
(
var
)
=>
write!
(
formatter
,
"{}"
,
var
.name
),
Self
::
GlobalAddress
{
address
,
size
:
_
}
=>
write!
(
formatter
,
"0x{:x}"
,
address
),
Self
::
Pointer
(
var
,
location
)
=>
write!
(
formatter
,
"{}->{}"
,
var
.name
,
location
),
Self
::
Pointer
(
var
,
location
)
=>
write!
(
formatter
,
"{}->{}"
,
var
.name
,
location
),
Self
::
GlobalPointer
(
address
,
location
)
=>
{
write!
(
formatter
,
"0x{:x}->{}"
,
address
,
location
)
}
}
}
}
}
}
}
...
@@ -193,11 +218,23 @@ impl AbstractLocation {
...
@@ -193,11 +218,23 @@ impl AbstractLocation {
AbstractLocation
::
Pointer
(
stack_register
.clone
(),
stack_pos
)
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.
/// Get the bytesize of the value represented by the abstract location.
pub
fn
bytesize
(
&
self
)
->
ByteSize
{
pub
fn
bytesize
(
&
self
)
->
ByteSize
{
match
self
{
match
self
{
Self
::
Register
(
var
)
=>
var
.size
,
Self
::
Register
(
var
)
=>
var
.size
,
Self
::
Pointer
(
_pointer_var
,
mem_location
)
=>
mem_location
.bytesize
(),
Self
::
GlobalAddress
{
size
,
..
}
=>
*
size
,
Self
::
Pointer
(
_
,
mem_location
)
|
Self
::
GlobalPointer
(
_
,
mem_location
)
=>
{
mem_location
.bytesize
()
}
}
}
}
}
}
}
...
...
src/cwe_checker_lib/src/analysis/callgraph.rs
0 → 100644
View file @
a16e6589
//! Generate call graphs out of a program term.
use
std
::
collections
::
HashMap
;
use
crate
::
intermediate_representation
::
*
;
use
petgraph
::
graph
::
DiGraph
;
/// The graph type of a call graph
pub
type
CallGraph
<
'a
>
=
DiGraph
<
Tid
,
&
'a
Term
<
Jmp
>>
;
/// Generate a call graph for the given program.
///
/// The nodes of the returned graph correspond to the TIDs of functions in the program.
/// Edges are jump terms of call operations.
///
/// Note that calls to external symbols are not represented in the graph,
/// i.e. there are neither nodes nor edges representing (calls to) external symbols in the graph.
/// Also, there are currently no edges for indirect calls,
/// because a corresponding analysis for resolving indirect calls is not implemented yet.
pub
fn
get_program_callgraph
(
program
:
&
Term
<
Program
>
)
->
CallGraph
{
let
mut
callgraph
=
CallGraph
::
new
();
let
mut
tid_to_node_index_map
=
HashMap
::
new
();
for
sub_tid
in
program
.term.subs
.keys
()
{
let
node_index
=
callgraph
.add_node
(
sub_tid
.clone
());
tid_to_node_index_map
.insert
(
sub_tid
.clone
(),
node_index
);
}
for
sub
in
program
.term.subs
.values
()
{
let
source_index
=
tid_to_node_index_map
.get
(
&
sub
.tid
)
.unwrap
();
for
block
in
&
sub
.term.blocks
{
for
jump
in
&
block
.term.jmps
{
if
let
Jmp
::
Call
{
target
,
..
}
=
&
jump
.term
{
if
let
Some
(
target_index
)
=
tid_to_node_index_map
.get
(
target
)
{
callgraph
.add_edge
(
*
source_index
,
*
target_index
,
jump
);
}
}
}
}
}
callgraph
}
#[cfg(test)]
pub
mod
tests
{
use
super
::
*
;
#[test]
fn
test_get_program_callgraph
()
{
// Create a program with 2 functions and one call between them
let
mut
project
=
Project
::
mock_x64
();
let
mut
caller
=
Sub
::
mock
(
"caller"
);
let
callee
=
Sub
::
mock
(
"callee"
);
let
call
=
Jmp
::
Call
{
target
:
Tid
::
new
(
"callee"
),
return_
:
None
,
};
let
mut
call_block
=
Blk
::
mock
();
call_block
.term.jmps
.push
(
Term
{
tid
:
Tid
::
new
(
"call"
),
term
:
call
,
});
caller
.term.blocks
.push
(
call_block
);
project
.program.term.subs
.insert
(
Tid
::
new
(
"caller"
),
caller
);
project
.program.term.subs
.insert
(
Tid
::
new
(
"callee"
),
callee
);
// Test correctness of the call graph
let
callgraph
=
get_program_callgraph
(
&
project
.program
);
assert_eq!
(
callgraph
.node_indices
()
.len
(),
2
);
assert_eq!
(
callgraph
.edge_indices
()
.len
(),
1
);
let
(
start
,
end
)
=
callgraph
.edge_endpoints
(
callgraph
.edge_indices
()
.next
()
.unwrap
())
.unwrap
();
assert_eq!
(
callgraph
[
start
],
Tid
::
new
(
"caller"
));
assert_eq!
(
callgraph
[
end
],
Tid
::
new
(
"callee"
));
}
}
src/cwe_checker_lib/src/analysis/function_signature/context.rs
View file @
a16e6589
use
crate
::
abstract_domain
::{
use
crate
::
abstract_domain
::{
AbstractDomain
,
AbstractIdentifier
,
BitvectorDomain
,
DataDomain
,
TryToBitvec
,
AbstractDomain
,
AbstractIdentifier
,
AbstractLocation
,
BitvectorDomain
,
DataDomain
,
SizedDomain
,
TryToBitvec
,
};
};
use
crate
::
utils
::
arguments
;
use
crate
::
utils
::
arguments
;
use
crate
::{
use
crate
::{
...
@@ -95,6 +96,8 @@ impl<'a> Context<'a> {
...
@@ -95,6 +96,8 @@ impl<'a> Context<'a> {
for
(
callee_id
,
callee_offset
)
in
callee_value
.get_relative_values
()
{
for
(
callee_id
,
callee_offset
)
in
callee_value
.get_relative_values
()
{
if
let
Some
(
param_arg
)
=
callee_state
.get_arg_corresponding_to_id
(
callee_id
)
{
if
let
Some
(
param_arg
)
=
callee_state
.get_arg_corresponding_to_id
(
callee_id
)
{
let
param_value
=
caller_state
.eval_parameter_arg
(
&
param_arg
);
let
param_value
=
caller_state
.eval_parameter_arg
(
&
param_arg
);
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
()
{
if
param_value
.contains_top
()
||
param_value
.get_absolute_value
()
.is_some
()
{
return_value
.set_contains_top_flag
()
return_value
.set_contains_top_flag
()
}
}
...
@@ -135,7 +138,10 @@ impl<'a> Context<'a> {
...
@@ -135,7 +138,10 @@ impl<'a> Context<'a> {
if
let
Some
(
param_access_list
)
=
self
.param_access_stubs
.get
(
extern_symbol
.name
.as_str
())
{
if
let
Some
(
param_access_list
)
=
self
.param_access_stubs
.get
(
extern_symbol
.name
.as_str
())
{
// Set access flags for parameter access
// Set access flags for parameter access
for
(
param
,
access_pattern
)
in
extern_symbol
.parameters
.iter
()
.zip
(
param_access_list
)
{
for
(
param
,
access_pattern
)
in
extern_symbol
.parameters
.iter
()
.zip
(
param_access_list
)
{
for
id
in
state
.eval_parameter_arg
(
param
)
.get_relative_values
()
.keys
()
{
let
param_value
=
state
.eval_parameter_arg
(
param
);
let
param_value
=
state
.substitute_global_mem_address
(
param_value
,
&
self
.project.runtime_memory_image
);
for
id
in
param_value
.get_relative_values
()
.keys
()
{
state
.merge_access_pattern_of_id
(
id
,
access_pattern
);
state
.merge_access_pattern_of_id
(
id
,
access_pattern
);
}
}
}
}
...
@@ -158,7 +164,12 @@ impl<'a> Context<'a> {
...
@@ -158,7 +164,12 @@ impl<'a> Context<'a> {
state
.clear_non_callee_saved_register
(
&
cconv
.callee_saved_register
);
state
.clear_non_callee_saved_register
(
&
cconv
.callee_saved_register
);
state
.set_register
(
&
cconv
.integer_return_register
[
0
],
return_val
);
state
.set_register
(
&
cconv
.integer_return_register
[
0
],
return_val
);
}
else
{
}
else
{
state
.handle_generic_extern_symbol
(
call_tid
,
extern_symbol
,
cconv
);
state
.handle_generic_extern_symbol
(
call_tid
,
extern_symbol
,
cconv
,
&
self
.project.runtime_memory_image
,
);
}
}
}
}
...
@@ -179,10 +190,13 @@ impl<'a> Context<'a> {
...
@@ -179,10 +190,13 @@ impl<'a> Context<'a> {
let
(
format_string_index
,
variadic_access_pattern
)
=
self
let
(
format_string_index
,
variadic_access_pattern
)
=
self
.stubbed_variadic_symbols
.stubbed_variadic_symbols
.get
(
extern_symbol
.name
.as_str
())
?
;
.get
(
extern_symbol
.name
.as_str
())
?
;
let
format_string_address
=
state
let
format_string_address
=
.eval_parameter_arg
(
&
extern_symbol
.parameters
[
*
format_string_index
])
state
.eval_parameter_arg
(
&
extern_symbol
.parameters
[
*
format_string_index
]);
// TODO: potential problem: What if the address is now an abstract ID? And how do we handle format strings in writeable memory anyway?
.get_if_absolute_value
()
let
format_string_address
=
state
.substitute_global_mem_address
(
.map
(|
value
|
value
.try_to_bitvec
()
.ok
())
??
;
format_string_address
,
&
self
.project.runtime_memory_image
,
);
let
format_string_address
=
self
.get_global_mem_address
(
&
format_string_address
)
?
;
let
format_string
=
arguments
::
parse_format_string_destination_and_return_content
(
let
format_string
=
arguments
::
parse_format_string_destination_and_return_content
(
format_string_address
,
format_string_address
,
&
self
.project.runtime_memory_image
,
&
self
.project.runtime_memory_image
,
...
@@ -207,11 +221,10 @@ impl<'a> Context<'a> {
...
@@ -207,11 +221,10 @@ impl<'a> Context<'a> {
self
.project
,
self
.project
,
);
);
for
param
in
format_string_args
{
for
param
in
format_string_args
{
for
id
in
state
let
param_value
=
state
.eval_parameter_arg
(
&
param
);
.eval_parameter_arg
(
&
param
)
let
param_value
=
state
.get_relative_values
()
.substitute_global_mem_address
(
param_value
,
&
self
.project.runtime_memory_image
);
.keys
()
for
id
in
param_value
.get_relative_values
()
.keys
()
{
{
state
.merge_access_pattern_of_id
(
id
,
variadic_access_pattern
);
state
.merge_access_pattern_of_id
(
id
,
variadic_access_pattern
);
}
}
}
}
...
@@ -238,16 +251,54 @@ impl<'a> Context<'a> {
...
@@ -238,16 +251,54 @@ impl<'a> Context<'a> {
extern_symbol
.parameters
.len
(),
extern_symbol
.parameters
.len
(),
cconv
.integer_parameter_register
.len
()
-
1
,
cconv
.integer_parameter_register
.len
()
-
1
,
]
{
]
{
for
id
in
state
let
param
=
state
.get_register
(
&
cconv
.integer_parameter_register
[
index
]);
.get_register
(
&
cconv
.integer_parameter_register
[
index
])
let
param
=
.get_relative_values
()
state
.substitute_global_mem_address
(
param
,
&
self
.project.runtime_memory_image
);
.keys
()
for
id
in
param
.get_relative_values
()
.keys
()
{
{
state
.merge_access_pattern_of_id
(
id
,
variadic_access_pattern
);
state
.merge_access_pattern_of_id
(
id
,
variadic_access_pattern
);
}
}
}
}
}
}
}
}
/// If the given data is either an absolute value or a unique relative value, where the corresponding abstract ID denotes a global memory address,
/// then return the resulting global memory address.
/// If the resulting constant value does not denote a global address then `None` is returned.
///
/// If the data may denote more than one value, then also return `None`.
fn
get_global_mem_address
(
&
self
,
data
:
&
DataDomain
<
BitvectorDomain
>
)
->
Option
<
Bitvector
>
{
if
let
Some
((
id
,
offset
))
=
data
.get_if_unique_target
()
{
// Check if the relative value is a global memory address (in writeable memory)
if
let
AbstractLocation
::
GlobalAddress
{
address
,
size
:
_
}
=
id
.get_location
()
{
if
let
Ok
(
offset_bitvec
)
=
offset
.try_to_bitvec
()
{
let
mut
global_address
=
Bitvector
::
from_u64
(
*
address
)
.into_truncate
(
offset
.bytesize
())
.ok
()
?
;
global_address
+=
&
offset_bitvec
;
if
self
.project
.runtime_memory_image
.is_global_memory_address
(
&
global_address
)
{
return
Some
(
global_address
);
}
}
}
}
else
{
// Global addresses in read-only memory are still handled as absolute values.
let
global_address
=
data
.get_if_absolute_value
()
.map
(|
value
|
value
.try_to_bitvec
()
.ok
())
??
;
if
self
.project
.runtime_memory_image
.is_global_memory_address
(
&
global_address
)
{
return
Some
(
global_address
);
}
}
None
}
}
}
impl
<
'a
>
forward_interprocedural_fixpoint
::
Context
<
'a
>
for
Context
<
'a
>
{
impl
<
'a
>
forward_interprocedural_fixpoint
::
Context
<
'a
>
for
Context
<
'a
>
{
...
@@ -266,12 +317,21 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
...
@@ -266,12 +317,21 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
match
&
def
.term
{
match
&
def
.term
{
Def
::
Assign
{
var
,
value
}
=>
{
Def
::
Assign
{
var
,
value
}
=>
{
new_state
.set_read_flag_for_input_ids_of_expression
(
value
);
new_state
.set_read_flag_for_input_ids_of_expression
(
value
);
new_state
.set_register
(
var
,
state
.eval
(
value
));
let
value
=
new_state
.substitute_global_mem_address
(
state
.eval
(
value
),
&
self
.project.runtime_memory_image
,
);
new_state
.set_register
(
var
,
value
);
}
}
Def
::
Load
{
var
,
address
}
=>
{
Def
::
Load
{
var
,
address
}
=>
{
new_state
.set_deref_flag_for_input_ids_of_expression
(
address
);
new_state
.set_deref_flag_for_input_ids_of_expression
(
address
);
let
address
=
new_state
.substitute_global_mem_address
(
state
.eval
(
address
),
&
self
.project.runtime_memory_image
,
);
new_state
.set_deref_flag_for_contained_ids
(
&
address
);
let
value
=
new_state
.load_value
(
let
value
=
new_state
.load_value
(
new_state
.eval
(
address
)
,
address
,
var
.size
,
var
.size
,
Some
(
&
self
.project.runtime_memory_image
),
Some
(
&
self
.project.runtime_memory_image
),
);
);
...
@@ -279,17 +339,23 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
...
@@ -279,17 +339,23 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
}
}
Def
::
Store
{
address
,
value
}
=>
{
Def
::
Store
{
address
,
value
}
=>
{
new_state
.set_mutable_deref_flag_for_input_ids_of_expression
(
address
);
new_state
.set_mutable_deref_flag_for_input_ids_of_expression
(
address
);
if
state
let
address
=
new_state
.substitute_global_mem_address
(
.get_offset_if_exact_stack_pointer
(
&
state
.eval
(
address
))
state
.eval
(
address
),
.is_some
()
&
self
.project.runtime_memory_image
,
{
);
new_state
.set_deref_mut_flag_for_contained_ids
(
&
address
);
if
state
.get_offset_if_exact_stack_pointer
(
&
address
)
.is_some
()
{
// Only flag inputs of non-trivial expressions as accessed to prevent flagging callee-saved registers as parameters.
// Only flag inputs of non-trivial expressions as accessed to prevent flagging callee-saved registers as parameters.
// Sometimes parameter registers are callee-saved (for no apparent reason).
// Sometimes parameter registers are callee-saved (for no apparent reason).
new_state
.set_read_flag_for_input_ids_of_nontrivial_expression
(
value
);
new_state
.set_read_flag_for_input_ids_of_nontrivial_expression
(
value
);
}
else
{
}
else
{
new_state
.set_read_flag_for_input_ids_of_expression
(
value
);
new_state
.set_read_flag_for_input_ids_of_expression
(
value
);
}
}
new_state
.write_value
(
new_state
.eval
(
address
),
new_state
.eval
(
value
));
let
value
=
new_state
.substitute_global_mem_address
(
state
.eval
(
value
),
&
self
.project.runtime_memory_image
,
);
new_state
.write_value
(
address
,
value
);
}
}
}
}
Some
(
new_state
)
Some
(
new_state
)
...
@@ -332,7 +398,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
...
@@ -332,7 +398,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
Jmp
::
CallInd
{
target
,
..
}
=>
{
Jmp
::
CallInd
{
target
,
..
}
=>
{
new_state
.set_read_flag_for_input_ids_of_expression
(
target
);
new_state
.set_read_flag_for_input_ids_of_expression
(
target
);
if
let
Some
(
cconv
)
=
self
.project
.get_standard_calling_convention
()
{
if
let
Some
(
cconv
)
=
self
.project
.get_standard_calling_convention
()
{
new_state
.handle_unknown_function_stub
(
call
,
cconv
);
new_state
.handle_unknown_function_stub
(
call
,
cconv
,
&
self
.project.runtime_memory_image
,
);
return
Some
(
new_state
);
return
Some
(
new_state
);
}
}
}
}
...
@@ -343,7 +413,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
...
@@ -343,7 +413,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
return
Some
(
new_state
);
return
Some
(
new_state
);
}
}
}
else
if
let
Some
(
cconv
)
=
self
.project
.get_standard_calling_convention
()
{
}
else
if
let
Some
(
cconv
)
=
self
.project
.get_standard_calling_convention
()
{
new_state
.handle_unknown_function_stub
(
call
,
cconv
);
new_state
.handle_unknown_function_stub
(
call
,
cconv
,
&
self
.project.runtime_memory_image
,
);
return
Some
(
new_state
);
return
Some
(
new_state
);
}
}
}
}
...
@@ -373,7 +447,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
...
@@ -373,7 +447,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
let
mut
new_state
=
old_state
.clone
();
let
mut
new_state
=
old_state
.clone
();
// Merge parameter access patterns with the access patterns from the callee.
// Merge parameter access patterns with the access patterns from the callee.
let
parameters
=
callee_state
.get_params_of_current_function
();
let
parameters
=
callee_state
.get_params_of_current_function
();
new_state
.merge_parameter_access
(
&
parameters
);
new_state
.merge_parameter_access
(
&
parameters
,
&
self
.project.runtime_memory_image
);
// Compute values for return register (but do not add them to `new_state` yet)
// Compute values for return register (but do not add them to `new_state` yet)
let
return_value_list
=
self
.compute_return_values_of_call
(
let
return_value_list
=
self
.compute_return_values_of_call
(
&
mut
new_state
,
&
mut
new_state
,
...
...
src/cwe_checker_lib/src/analysis/function_signature/context/tests.rs
View file @
a16e6589
...
@@ -112,3 +112,26 @@ fn test_call_stub_handling() {
...
@@ -112,3 +112,26 @@ fn test_call_stub_handling() {
);
);
assert_eq!
(
params
.len
(),
5
);
assert_eq!
(
params
.len
(),
5
);
}
}
#[test]
fn
test_get_global_mem_address
()
{
let
project
=
Project
::
mock_arm32
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
,
HashSet
::
new
());
let
context
=
Context
::
new
(
&
project
,
&
graph
);
// Check global address from abstract ID
let
global_address_id
:
DataDomain
<
BitvectorDomain
>
=
DataDomain
::
from_target
(
AbstractIdentifier
::
from_global_address
(
&
Tid
::
new
(
"fn_tid"
),
&
Bitvector
::
from_i32
(
0x2000
)),
Bitvector
::
from_i32
(
0x2
)
.into
(),
);
let
result
=
context
.get_global_mem_address
(
&
global_address_id
);
assert_eq!
(
result
,
Some
(
Bitvector
::
from_i32
(
0x2002
)));
// Check global address from absolute value
let
global_address_const
=
Bitvector
::
from_i32
(
0x2003
)
.into
();
let
result
=
context
.get_global_mem_address
(
&
global_address_const
);
assert_eq!
(
result
,
Some
(
Bitvector
::
from_i32
(
0x2003
)));
// Check global address not returned if it may not be unique
let
value
=
global_address_id
.merge
(
&
global_address_const
);
let
result
=
context
.get_global_mem_address
(
&
value
);
assert
!
(
result
.is_none
());
}
src/cwe_checker_lib/src/analysis/function_signature/global_var_propagation.rs
0 → 100644
View file @
a16e6589
//! This module implements propagation of global variables via two fixpoint algorithms on the call graph.
//! For more details see [`propagate_globals`].
use
super
::
AccessPattern
;
use
super
::
FunctionSignature
;
use
crate
::
abstract_domain
::
AbstractDomain
;
use
crate
::
abstract_domain
::
DomainMap
;
use
crate
::
abstract_domain
::
UnionMergeStrategy
;
use
crate
::
analysis
::
callgraph
::
get_program_callgraph
;
use
crate
::
analysis
::
callgraph
::
CallGraph
;
use
crate
::
analysis
::
fixpoint
::{
Computation
,
Context
};
use
crate
::
intermediate_representation
::
*
;
use
std
::
collections
::
BTreeMap
;
use
std
::
collections
::
HashSet
;
/// The context object for propagating known global variables top-down in the call graph.
struct
KnownGlobalsContext
<
'a
>
{
/// The call graph of the program.
graph
:
&
'a
CallGraph
<
'a
>
,
}
impl
<
'a
>
KnownGlobalsContext
<
'a
>
{
/// Create a new context object.
fn
new
(
graph
:
&
'a
CallGraph
<
'a
>
)
->
Self
{
KnownGlobalsContext
{
graph
}
}
}
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
>
;
/// Get the call graph corresponding to the context object.
fn
get_graph
(
&
self
)
->
&
CallGraph
<
'a
>
{
self
.graph
}
/// 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
>
{
let
mut
result
=
set1
.clone
();
for
address
in
set2
{
result
.insert
(
*
address
);
}
result
}
/// We always propagate all known addresses of global variables along the edges of the call graph.
fn
update_edge
(
&
self
,
globals
:
&
HashSet
<
u64
>
,
_edge
:
petgraph
::
stable_graph
::
EdgeIndex
,
)
->
Option
<
HashSet
<
u64
>>
{
Some
(
globals
.clone
())
}
}
/// For each function in the call graph,
/// compute the set of global addresses that are known to the function itself
/// or at least one function that calls this function (either directly or indirectly).
///
/// This is computed via a fixpoint algorithm on the call graph of the program,
/// where known addresses of global variables are propagated top-down along the edges of the call graph.
fn
propagate_known_globals_top_down
(
project
:
&
Project
,
fn_sigs
:
&
BTreeMap
<
Tid
,
FunctionSignature
>
,
)
->
BTreeMap
<
Tid
,
HashSet
<
u64
>>
{
let
graph
=
get_program_callgraph
(
&
project
.program
);
let
context
=
KnownGlobalsContext
::
new
(
&
graph
);
let
mut
computation
=
Computation
::
new
(
context
,
None
);
// Set the start values of all nodes
for
node
in
graph
.node_indices
()
{
let
fn_tid
=
&
graph
[
node
];
let
fn_sig
=
&
fn_sigs
[
fn_tid
];
let
globals
=
fn_sig
.global_parameters
.keys
()
.cloned
()
.collect
();
computation
.set_node_value
(
node
,
globals
);
}
// Propagate top-down in the call graph
computation
.compute_with_max_steps
(
100
);
// Generate results map
let
mut
results
=
BTreeMap
::
new
();
for
node
in
graph
.node_indices
()
{
let
fn_tid
=
&
graph
[
node
];
let
propagated_globals
=
computation
.get_node_value
(
node
)
.unwrap
();
results
.insert
(
fn_tid
.clone
(),
propagated_globals
.clone
());
}
results
}
/// The context object for propagating the access patterns of global variables in the call graph.
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
>>
,
}
impl
<
'a
>
GlobalsPropagationContext
<
'a
>
{
/// Create a new [`GlobalsPropagationContext`] object.
fn
new
(
graph
:
&
'a
CallGraph
<
'a
>
,
known_globals
:
&
'a
BTreeMap
<
Tid
,
HashSet
<
u64
>>
)
->
Self
{
GlobalsPropagationContext
{
graph
,
known_globals
,
}
}
}
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
/// to the corresponding access pattern of the global variable.
type
NodeValue
=
DomainMap
<
u64
,
AccessPattern
,
UnionMergeStrategy
>
;
/// Get the (reversed!) call graph corresponding to the program
fn
get_graph
(
&
self
)
->
&
CallGraph
<
'a
>
{
self
.graph
}
/// Merge two maps of known globals by merging the corresponding access patterns.
fn
merge
(
&
self
,
globals1
:
&
Self
::
NodeValue
,
globals2
:
&
Self
::
NodeValue
)
->
Self
::
NodeValue
{
globals1
.merge
(
globals2
)
}
/// Propagate the access patterns of global variables along the edges of the reversed call graph.
///
/// Access patterns are propagated from callees to callers,
/// but only for those global variables, that are also known to the caller.
fn
update_edge
(
&
self
,
callee_globals
:
&
Self
::
NodeValue
,
edge
:
petgraph
::
stable_graph
::
EdgeIndex
,
)
->
Option
<
Self
::
NodeValue
>
{
let
(
_
,
target_node
)
=
self
.graph
.edge_endpoints
(
edge
)
.unwrap
();
let
target_tid
=
&
self
.graph
[
target_node
];
let
caller_known_globals
=
&
self
.known_globals
[
target_tid
];
let
caller_globals
:
Self
::
NodeValue
=
callee_globals
.iter
()
.filter_map
(|(
address
,
access_pattern
)|
{
if
caller_known_globals
.contains
(
address
)
&&
access_pattern
.is_accessed
()
{
Some
((
*
address
,
*
access_pattern
))
}
else
{
None
}
})
.collect
();
Some
(
caller_globals
)
}
}
/// Propagate the access patterns of global variables bottom-up in the call graph.
///
/// Only those global variables (and their access patterns) are propagated,
/// 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
>>
,
fn_sigs
:
&
mut
BTreeMap
<
Tid
,
FunctionSignature
>
,
)
{
// To propagate bottom-up, we have to reverse the edges in the callgraph
let
mut
graph
=
get_program_callgraph
(
&
project
.program
);
graph
.reverse
();
let
context
=
GlobalsPropagationContext
::
new
(
&
graph
,
known_globals
);
let
mut
computation
=
Computation
::
new
(
context
,
None
);
// Set start values for all nodes
for
node
in
graph
.node_indices
()
{
let
fn_tid
=
&
graph
[
node
];
let
fn_sig
=
&
fn_sigs
[
fn_tid
];
let
globals
=
fn_sig
.global_parameters
.iter
()
.filter_map
(|(
address
,
access_pattern
)|
{
if
access_pattern
.is_accessed
()
{
Some
((
*
address
,
*
access_pattern
))
}
else
{
None
}
})
.collect
();
computation
.set_node_value
(
node
,
globals
);
}
// Compute the fixpoint
computation
.compute_with_max_steps
(
100
);
if
!
computation
.has_stabilized
()
{
panic!
(
"Global parameter propagation algorithm did not stabilize."
)
}
// Add the propagated globals to the function signatures
for
node
in
graph
.node_indices
()
{
let
fn_tid
=
&
graph
[
node
];
let
propagated_globals
=
computation
.get_node_value
(
node
)
.unwrap
();
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
)
.and_modify
(|
access_pattern
|
{
*
access_pattern
=
access_pattern
.merge
(
propagated_access_pattern
);
})
.or_insert
(
*
propagated_access_pattern
);
}
}
}
/// Propagate the access patterns of global variables along the edges of the call graph of the given project.
///
/// The propagation works as follows:
/// Global variables and their access patterns are only propagated from callees to callers
/// and only if some function upwards in the call-stack also accesses the corresponding variable.
/// As usual, access patterns are merged if the caller also may access a global variable.
///
/// This propagation scheme is optimized for usage with other bottom-up analyses:
/// - If some callee of a function accesses the same global variable as the function itself,
/// then we need to propagate the corresponding access pattern to the function.
/// This ensures that the function knows which callees may modify the value of the global variable.
/// - If two callees of a function access a global variable,
/// then there is no information flow on the value of the global variable between the callees in a proper bottom-up analysis.
/// But if the function itself (or any of its callers) do not access the global variable,
/// then there is no benefit in tracking its value for the function itself.
/// Thus, the global variable should not be propagated to the function in such a case.
pub
fn
propagate_globals
(
project
:
&
Project
,
fn_sigs
:
&
mut
BTreeMap
<
Tid
,
FunctionSignature
>
)
{
let
known_globals
=
propagate_known_globals_top_down
(
project
,
fn_sigs
);
propagate_globals_bottom_up
(
project
,
&
known_globals
,
fn_sigs
);
}
#[cfg(test)]
pub
mod
tests
{
use
std
::
collections
::
HashMap
;
use
super
::
*
;
#[test]
fn
test_globals_propagation
()
{
let
mut
project
=
Project
::
mock_arm32
();
// Add 3 functions, so that the call graph will look like this:
// main -> callee1 -> callee2
let
mut
func
=
Sub
::
mock
(
"main"
);
let
mut
call_blk
=
Blk
::
mock_with_tid
(
"main_blk"
);
let
call
=
Jmp
::
mock_call
(
"callee1"
,
None
);
call_blk
.term.jmps
.push
(
call
);
func
.term.blocks
.push
(
call_blk
);
project
.program.term.subs
.insert
(
Tid
::
new
(
"main"
),
func
);
let
mut
func
=
Sub
::
mock
(
"callee1"
);
let
mut
call_blk
=
Blk
::
mock_with_tid
(
"callee1_blk"
);
let
call
=
Jmp
::
mock_call
(
"callee2"
,
None
);
call_blk
.term.jmps
.push
(
call
);
func
.term.blocks
.push
(
call_blk
);
project
.program.term.subs
.insert
(
Tid
::
new
(
"callee1"
),
func
);
let
func
=
Sub
::
mock
(
"callee2"
);
project
.program.term.subs
.insert
(
Tid
::
new
(
"callee2"
),
func
);
// Add one global var that is known to main and callee2
// and another that is only known to callee1
let
mut
sig_main
=
FunctionSignature
::
new
();
sig_main
.global_parameters
.insert
(
1000
,
AccessPattern
::
new
()
.with_read_flag
());
let
mut
sig_callee1
=
FunctionSignature
::
new
();
sig_callee1
.global_parameters
.insert
(
2000
,
AccessPattern
::
new
()
.with_dereference_flag
());
let
mut
sig_callee2
=
FunctionSignature
::
new
();
sig_callee2
.global_parameters
.insert
(
1000
,
AccessPattern
::
new_unknown_access
());
let
mut
fn_sigs
=
BTreeMap
::
from
([
(
Tid
::
new
(
"main"
),
sig_main
),
(
Tid
::
new
(
"callee1"
),
sig_callee1
),
(
Tid
::
new
(
"callee2"
),
sig_callee2
),
]);
// Propagate globals
propagate_globals
(
&
project
,
&
mut
fn_sigs
);
// Check propagation results
assert_eq!
(
&
fn_sigs
[
&
Tid
::
new
(
"main"
)]
.global_parameters
,
&
HashMap
::
from
([(
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
())
])
);
assert_eq!
(
&
fn_sigs
[
&
Tid
::
new
(
"callee2"
)]
.global_parameters
,
&
HashMap
::
from
([(
1000
,
AccessPattern
::
new_unknown_access
())])
);
}
}
src/cwe_checker_lib/src/analysis/function_signature/mod.rs
View file @
a16e6589
...
@@ -4,6 +4,7 @@
...
@@ -4,6 +4,7 @@
//! although only stack accesses with known, constant offset are processed.
//! although only stack accesses with known, constant offset are processed.
//! Accesses to potential function parameters are collected together with the type of the access
//! Accesses to potential function parameters are collected together with the type of the access
//! (is the value read, dereferenced for read access or dereferenced for write access).
//! (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.
//!
//!
//! Known limitations of the analysis:
//! Known limitations of the analysis:
//! * The analysis is an overapproximation in the sense that it may generate more input parameters
//! * The analysis is an overapproximation in the sense that it may generate more input parameters
...
@@ -13,8 +14,9 @@
...
@@ -13,8 +14,9 @@
//! For functions that use other registers
//! For functions that use other registers
//! than those in the standard calling convention for parameter passing
//! than those in the standard calling convention for parameter passing
//! the results of this analysis will be wrong.
//! the results of this analysis will be wrong.
//! * Parameters that are used as input values for variadic functions (e.g. sprintf) may be missed
//! * Parameters that are used as input values for variadic functions may be missed.
//! since detection of variadic function parameters is not yet implemented for this analysis.
//! 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
//! * 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.
//! then a duplicate stack parameter may be generated.
//! A proper sanitation for this case is not yet implemented,
//! A proper sanitation for this case is not yet implemented,
...
@@ -42,6 +44,8 @@ mod state;
...
@@ -42,6 +44,8 @@ mod state;
use
state
::
State
;
use
state
::
State
;
mod
access_pattern
;
mod
access_pattern
;
pub
use
access_pattern
::
AccessPattern
;
pub
use
access_pattern
::
AccessPattern
;
mod
global_var_propagation
;
use
global_var_propagation
::
propagate_globals
;
pub
mod
stubs
;
pub
mod
stubs
;
/// Generate the computation object for the fixpoint computation
/// Generate the computation object for the fixpoint computation
...
@@ -152,6 +156,8 @@ pub fn compute_function_signatures<'a>(
...
@@ -152,6 +156,8 @@ pub fn compute_function_signatures<'a>(
);
);
}
}
}
}
// Propagate globals in bottom-up direction in the call graph
propagate_globals
(
project
,
&
mut
fn_sig_map
);
(
fn_sig_map
,
logs
)
(
fn_sig_map
,
logs
)
}
}
...
@@ -162,6 +168,9 @@ pub fn compute_function_signatures<'a>(
...
@@ -162,6 +168,9 @@ pub fn compute_function_signatures<'a>(
pub
struct
FunctionSignature
{
pub
struct
FunctionSignature
{
/// The parameters of the function together with their access patterns.
/// The parameters of the function together with their access patterns.
pub
parameters
:
HashMap
<
Arg
,
AccessPattern
>
,
pub
parameters
:
HashMap
<
Arg
,
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
>
,
}
}
impl
FunctionSignature
{
impl
FunctionSignature
{
...
@@ -169,6 +178,7 @@ impl FunctionSignature {
...
@@ -169,6 +178,7 @@ impl FunctionSignature {
pub
fn
new
()
->
Self
{
pub
fn
new
()
->
Self
{
Self
{
Self
{
parameters
:
HashMap
::
new
(),
parameters
:
HashMap
::
new
(),
global_parameters
:
HashMap
::
new
(),
}
}
}
}
...
@@ -186,8 +196,12 @@ impl FunctionSignature {
...
@@ -186,8 +196,12 @@ impl FunctionSignature {
stack_params_total_size
stack_params_total_size
}
}
/// Merge the parameter list of `self` with the given parameter list.
/// Merge the parameter list and the global parameter list of `self` with the given lists.
fn
merge_parameter_list
(
&
mut
self
,
params
:
&
[(
Arg
,
AccessPattern
)])
{
fn
merge_parameter_lists
(
&
mut
self
,
params
:
&
[(
Arg
,
AccessPattern
)],
global_params
:
&
[(
u64
,
AccessPattern
)],
)
{
for
(
arg
,
sig_new
)
in
params
{
for
(
arg
,
sig_new
)
in
params
{
if
let
Some
(
sig_self
)
=
self
.parameters
.get_mut
(
arg
)
{
if
let
Some
(
sig_self
)
=
self
.parameters
.get_mut
(
arg
)
{
*
sig_self
=
sig_self
.merge
(
sig_new
);
*
sig_self
=
sig_self
.merge
(
sig_new
);
...
@@ -195,12 +209,20 @@ impl FunctionSignature {
...
@@ -195,12 +209,20 @@ impl FunctionSignature {
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
);
}
}
}
}
/// Merge the function signature with the signature extracted from the given state.
/// Merge the function signature with the signature extracted from the given state.
fn
merge_with_fn_sig_of_state
(
&
mut
self
,
state
:
&
State
)
{
fn
merge_with_fn_sig_of_state
(
&
mut
self
,
state
:
&
State
)
{
let
params
=
state
.get_params_of_current_function
();
let
params
=
state
.get_params_of_current_function
();
self
.merge_parameter_list
(
&
params
);
let
global_params
=
state
.get_global_mem_params_of_current_function
();
self
.merge_parameter_lists
(
&
params
,
&
global_params
);
}
}
/// Sanitize the function signature:
/// Sanitize the function signature:
...
@@ -269,7 +291,10 @@ pub mod tests {
...
@@ -269,7 +291,10 @@ pub mod tests {
write_access_pattern
,
write_access_pattern
,
),
),
]);
]);
FunctionSignature
{
parameters
}
FunctionSignature
{
parameters
,
global_parameters
:
HashMap
::
new
(),
}
}
}
}
}
}
}
src/cwe_checker_lib/src/analysis/function_signature/state.rs
View file @
a16e6589
...
@@ -195,6 +195,14 @@ impl State {
...
@@ -195,6 +195,14 @@ impl State {
}
}
}
}
/// Add an abstract ID to the set of tracked IDs if it is not already tracked.
/// No access flags are set if the ID was not already tracked.
pub
fn
add_id_to_tracked_ids
(
&
mut
self
,
id
:
&
AbstractIdentifier
)
{
if
self
.tracked_ids
.get
(
id
)
.is_none
()
{
self
.tracked_ids
.insert
(
id
.clone
(),
AccessPattern
::
new
());
}
}
/// Get the value located at a positive stack offset.
/// Get the value located at a positive stack offset.
///
///
/// If no corresponding stack parameter ID exists for the value,
/// If no corresponding stack parameter ID exists for the value,
...
@@ -308,30 +316,75 @@ impl State {
...
@@ -308,30 +316,75 @@ impl State {
}
}
}
}
/// Set the read and dereferenced flag for every ID
/// Set the read and dereferenced flag for every
tracked
ID
/// that may be referenced when computing the value of the expression.
/// that may be referenced when computing the value of the expression.
pub
fn
set_deref_flag_for_input_ids_of_expression
(
&
mut
self
,
expression
:
&
Expression
)
{
pub
fn
set_deref_flag_for_input_ids_of_expression
(
&
mut
self
,
expression
:
&
Expression
)
{
for
register
in
expression
.input_vars
()
{
for
register
in
expression
.input_vars
()
{
for
id
in
self
.get_register
(
register
)
.referenced_ids
()
{
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
()
{
self
.set_deref_mut_flag_for_contained_ids
(
&
self
.get_register
(
register
));
}
}
/// 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
()
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
object
.set_read_flag
();
object
.set_read_flag
();
object
.set_dereference_flag
();
object
.set_dereference_flag
();
}
}
}
}
}
}
}
/// Set the read and mutably dereferenced flag for every ID
/// Set the read and mutably dereferenced flag for every tracked ID contained in the given value.
/// that may be referenced when computing the value of the expression.
pub
fn
set_deref_mut_flag_for_contained_ids
(
&
mut
self
,
value
:
&
DataDomain
<
BitvectorDomain
>
)
{
pub
fn
set_mutable_deref_flag_for_input_ids_of_expression
(
&
mut
self
,
expression
:
&
Expression
)
{
for
id
in
value
.referenced_ids
()
{
for
register
in
expression
.input_vars
()
{
for
id
in
self
.get_register
(
register
)
.referenced_ids
()
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
object
.set_read_flag
();
object
.set_read_flag
();
object
.set_mutably_dereferenced_flag
();
object
.set_mutably_dereferenced_flag
();
}
}
}
}
}
}
/// If the absolute value part of the given value might represent an address into writeable global memory
/// then substitute it by a relative value relative to a new global memory ID.
///
/// The generated ID will be also added to the tracked IDs of `self`.
/// However, no access flags will be set for the newly generated ID.
pub
fn
substitute_global_mem_address
(
&
mut
self
,
mut
value
:
DataDomain
<
BitvectorDomain
>
,
global_memory
:
&
RuntimeMemoryImage
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
value
.bytesize
()
!=
self
.stack_id
.bytesize
()
{
// Only pointer-sized values can represent global addresses.
return
value
;
}
else
if
let
Some
(
absolute_value
)
=
value
.get_absolute_value
()
{
if
let
Ok
(
bitvec
)
=
absolute_value
.try_to_bitvec
()
{
if
let
Ok
(
true
)
=
global_memory
.is_address_writeable
(
&
bitvec
)
{
// The absolute value might be a pointer to global memory.
let
global_id
=
AbstractIdentifier
::
from_global_address
(
self
.get_current_function_tid
(),
&
bitvec
,
);
// Add the ID to the set of tracked IDs for the state.
self
.add_id_to_tracked_ids
(
&
global_id
);
// Convert the absolute value to a relative value (relative the new global ID).
value
=
value
.merge
(
&
DataDomain
::
from_target
(
global_id
,
Bitvector
::
zero
(
value
.bytesize
()
.into
())
.into
(),
));
value
.set_absolute_value
(
None
);
}
}
}
value
}
}
}
}
...
...
src/cwe_checker_lib/src/analysis/function_signature/state/call_handling.rs
View file @
a16e6589
...
@@ -10,8 +10,9 @@ impl State {
...
@@ -10,8 +10,9 @@ impl State {
call_tid
:
&
Tid
,
call_tid
:
&
Tid
,
extern_symbol
:
&
ExternSymbol
,
extern_symbol
:
&
ExternSymbol
,
calling_convention
:
&
CallingConvention
,
calling_convention
:
&
CallingConvention
,
global_memory
:
&
RuntimeMemoryImage
,
)
{
)
{
let
input_ids
=
self
.collect_input_ids_of_call
(
&
extern_symbol
.parameters
);
let
input_ids
=
self
.collect_input_ids_of_call
(
&
extern_symbol
.parameters
,
global_memory
);
self
.clear_non_callee_saved_register
(
&
calling_convention
.callee_saved_register
);
self
.clear_non_callee_saved_register
(
&
calling_convention
.callee_saved_register
);
self
.generate_return_values_for_call
(
&
input_ids
,
&
extern_symbol
.return_values
,
call_tid
);
self
.generate_return_values_for_call
(
&
input_ids
,
&
extern_symbol
.return_values
,
call_tid
);
}
}
...
@@ -26,6 +27,7 @@ impl State {
...
@@ -26,6 +27,7 @@ impl State {
&
mut
self
,
&
mut
self
,
call
:
&
Term
<
Jmp
>
,
call
:
&
Term
<
Jmp
>
,
calling_convention
:
&
CallingConvention
,
calling_convention
:
&
CallingConvention
,
global_memory
:
&
RuntimeMemoryImage
,
)
{
)
{
let
mut
parameters
=
let
mut
parameters
=
generate_args_from_registers
(
&
calling_convention
.integer_parameter_register
);
generate_args_from_registers
(
&
calling_convention
.integer_parameter_register
);
...
@@ -43,22 +45,24 @@ impl State {
...
@@ -43,22 +45,24 @@ impl State {
data_type
:
None
,
data_type
:
None
,
});
});
}
}
let
input_ids
=
self
.collect_input_ids_of_call
(
&
parameters
);
let
input_ids
=
self
.collect_input_ids_of_call
(
&
parameters
,
global_memory
);
self
.clear_non_callee_saved_register
(
&
calling_convention
.callee_saved_register
);
self
.clear_non_callee_saved_register
(
&
calling_convention
.callee_saved_register
);
self
.generate_return_values_for_call
(
&
input_ids
,
&
return_register
,
&
call
.tid
);
self
.generate_return_values_for_call
(
&
input_ids
,
&
return_register
,
&
call
.tid
);
}
}
/// Get all input IDs referenced in the parameters of a call.
/// Get all input IDs referenced in the parameters of a call.
/// Marks every input ID as accessed (with access flags for unknown access)
/// Marks every input ID as accessed (with access flags for unknown access).
/// and generates stack parameter IDs for the current function if necessary.
/// Also generates stack parameter IDs and global memory IDs for the current function if necessary.
fn
collect_input_ids_of_call
(
&
mut
self
,
parameters
:
&
[
Arg
])
->
BTreeSet
<
AbstractIdentifier
>
{
fn
collect_input_ids_of_call
(
&
mut
self
,
parameters
:
&
[
Arg
],
global_memory
:
&
RuntimeMemoryImage
,
)
->
BTreeSet
<
AbstractIdentifier
>
{
let
mut
input_ids
=
BTreeSet
::
new
();
let
mut
input_ids
=
BTreeSet
::
new
();
for
input_param
in
parameters
{
for
input_param
in
parameters
{
for
(
id
,
offset
)
in
self
let
param
=
self
.eval_parameter_arg
(
input_param
);
.eval_parameter_arg
(
input_param
)
let
param
=
self
.substitute_global_mem_address
(
param
,
global_memory
);
.get_relative_values
()
for
(
id
,
offset
)
in
param
.get_relative_values
()
{
.iter
()
{
input_ids
.insert
(
id
.clone
());
input_ids
.insert
(
id
.clone
());
// If the relative value points to the stack we also have to collect all IDs contained in the pointed-to value.
// If the relative value points to the stack we also have to collect all IDs contained in the pointed-to value.
if
*
id
==
self
.stack_id
{
if
*
id
==
self
.stack_id
{
...
@@ -131,29 +135,55 @@ impl State {
...
@@ -131,29 +135,55 @@ impl State {
let
mut
params
=
Vec
::
new
();
let
mut
params
=
Vec
::
new
();
for
(
id
,
access_pattern
)
in
self
.tracked_ids
.iter
()
{
for
(
id
,
access_pattern
)
in
self
.tracked_ids
.iter
()
{
if
id
.get_tid
()
==
self
.get_current_function_tid
()
{
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
()
{
if
access_pattern
.is_accessed
()
{
params
.push
((
generate_arg_from_abstract_id
(
id
)
,
*
access_pattern
));
params
.push
((
param_arg
,
*
access_pattern
));
}
else
if
matches!
(
id
.get_location
(),
&
AbstractLocation
::
Pointer
{
..
})
{
}
else
if
matches!
(
id
.get_location
(),
&
AbstractLocation
::
Pointer
{
..
})
{
// This is a stack parameter.
// 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.
// 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
;
let
mut
access_pattern
=
*
access_pattern
;
access_pattern
.set_read_flag
();
access_pattern
.set_read_flag
();
params
.push
((
generate_arg_from_abstract_id
(
id
),
access_pattern
));
params
.push
((
param_arg
,
access_pattern
));
}
}
}
}
}
}
}
params
params
}
}
/// 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
)
>
{
let
mut
global_params
=
Vec
::
new
();
for
(
id
,
access_pattern
)
in
self
.tracked_ids
.iter
()
{
if
id
.get_tid
()
==
self
.get_current_function_tid
()
&&
access_pattern
.is_accessed
()
{
match
id
.get_location
()
{
AbstractLocation
::
GlobalPointer
(
address
,
_
)
|
AbstractLocation
::
GlobalAddress
{
address
,
..
}
=>
{
global_params
.push
((
*
address
,
*
access_pattern
));
}
AbstractLocation
::
Pointer
(
_
,
_
)
|
AbstractLocation
::
Register
(
_
)
=>
(),
}
}
}
global_params
}
/// Merges the access patterns of callee parameters with those of the caller (represented by `self`).
/// Merges the access patterns of callee parameters with those of the caller (represented by `self`).
/// The result represents the access patterns after returning to the caller and is written to `self`.
/// The result represents the access patterns after returning to the caller and is written to `self`.
///
///
/// If a parameter is a pointer to the stack frame of self, it is dereferenced
/// If a parameter is a pointer to the stack frame of self, it is dereferenced
/// to set the access patterns of the target.
/// to set the access patterns of the target.
/// Note that this may create new stack parameter objects for self.
/// Note that this may create new stack parameter objects for self.
pub
fn
merge_parameter_access
(
&
mut
self
,
params
:
&
[(
Arg
,
AccessPattern
)])
{
pub
fn
merge_parameter_access
(
&
mut
self
,
params
:
&
[(
Arg
,
AccessPattern
)],
global_memory
:
&
RuntimeMemoryImage
,
)
{
for
(
parameter
,
call_access_pattern
)
in
params
{
for
(
parameter
,
call_access_pattern
)
in
params
{
for
(
id
,
offset
)
in
self
.eval_parameter_arg
(
parameter
)
.get_relative_values
()
{
let
param_value
=
self
.eval_parameter_arg
(
parameter
);
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
)
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
*
object
=
object
.merge
(
call_access_pattern
);
*
object
=
object
.merge
(
call_access_pattern
);
}
}
...
@@ -188,7 +218,7 @@ impl State {
...
@@ -188,7 +218,7 @@ impl State {
/// then return an argument object corresponding to the parameter.
/// then return an argument object corresponding to the parameter.
pub
fn
get_arg_corresponding_to_id
(
&
self
,
id
:
&
AbstractIdentifier
)
->
Option
<
Arg
>
{
pub
fn
get_arg_corresponding_to_id
(
&
self
,
id
:
&
AbstractIdentifier
)
->
Option
<
Arg
>
{
if
id
.get_tid
()
==
self
.stack_id
.get_tid
()
{
if
id
.get_tid
()
==
self
.stack_id
.get_tid
()
{
Some
(
generate_arg_from_abstract_id
(
id
)
)
generate_param_arg_from_abstract_id
(
id
)
.ok
(
)
}
else
{
}
else
{
None
None
}
}
...
@@ -205,19 +235,23 @@ fn generate_args_from_registers(registers: &[Variable]) -> Vec<Arg> {
...
@@ -205,19 +235,23 @@ fn generate_args_from_registers(registers: &[Variable]) -> Vec<Arg> {
/// Generate an argument representing the location in the given abstract ID.
/// 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.
/// If the location is a pointer, it is assumed that the pointer points to the stack.
/// Panics if the location contains a second level of indirection.
/// Returns an error if the location contains a second level of indirection
fn
generate_arg_from_abstract_id
(
id
:
&
AbstractIdentifier
)
->
Arg
{
/// 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
()
{
match
id
.get_location
()
{
AbstractLocation
::
Register
(
var
)
=>
Arg
::
from_var
(
var
.clone
(),
None
),
AbstractLocation
::
Register
(
var
)
=>
Ok
(
Arg
::
from_var
(
var
.clone
(),
None
)
),
AbstractLocation
::
Pointer
(
var
,
mem_location
)
=>
match
mem_location
{
AbstractLocation
::
Pointer
(
var
,
mem_location
)
=>
match
mem_location
{
AbstractMemoryLocation
::
Location
{
offset
,
size
}
=>
Arg
::
Stack
{
AbstractMemoryLocation
::
Location
{
offset
,
size
}
=>
Ok
(
Arg
::
Stack
{
address
:
Expression
::
Var
(
var
.clone
())
.plus_const
(
*
offset
),
address
:
Expression
::
Var
(
var
.clone
())
.plus_const
(
*
offset
),
size
:
*
size
,
size
:
*
size
,
data_type
:
None
,
data_type
:
None
,
},
}
)
,
AbstractMemoryLocation
::
Pointer
{
..
}
=>
{
AbstractMemoryLocation
::
Pointer
{
..
}
=>
{
panic!
(
"Memory location is not a stack offset."
)
Err
(
anyhow!
(
"Memory location is not a stack offset."
)
)
}
}
},
},
AbstractLocation
::
GlobalAddress
{
..
}
|
AbstractLocation
::
GlobalPointer
(
_
,
_
)
=>
{
Err
(
anyhow!
(
"Global values are not parameters."
))
}
}
}
}
}
src/cwe_checker_lib/src/analysis/function_signature/state/tests.rs
View file @
a16e6589
...
@@ -150,7 +150,12 @@ fn test_extern_symbol_handling() {
...
@@ -150,7 +150,12 @@ fn test_extern_symbol_handling() {
let
return_val_id
=
let
return_val_id
=
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"call_tid"
),
&
Variable
::
mock
(
"r0"
,
4
));
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"call_tid"
),
&
Variable
::
mock
(
"r0"
,
4
));
// Test extern symbol handling.
// Test extern symbol handling.
state
.handle_generic_extern_symbol
(
&
call_tid
,
&
extern_symbol
,
&
cconv
);
state
.handle_generic_extern_symbol
(
&
call_tid
,
&
extern_symbol
,
&
cconv
,
&
RuntimeMemoryImage
::
mock
(),
);
assert_eq!
(
assert_eq!
(
state
state
.tracked_ids
.tracked_ids
...
@@ -177,3 +182,30 @@ fn test_extern_symbol_handling() {
...
@@ -177,3 +182,30 @@ fn test_extern_symbol_handling() {
&
Bitvector
::
from_i32
(
0
)
.into
()
&
Bitvector
::
from_i32
(
0
)
.into
()
);
);
}
}
#[test]
fn
test_substitute_global_mem_address
()
{
let
mut
state
=
State
::
mock_arm32
();
let
global_memory
=
RuntimeMemoryImage
::
mock
();
// Test that addresses into non-writeable memory do not get substituted.
let
global_address
:
DataDomain
<
BitvectorDomain
>
=
Bitvector
::
from_i32
(
0x1000
)
.into
();
let
substituted_address
=
state
.substitute_global_mem_address
(
global_address
.clone
(),
&
global_memory
);
assert_eq!
(
global_address
,
substituted_address
);
// Test substitution for addresses into writeable global memory.
let
global_address
:
DataDomain
<
BitvectorDomain
>
=
Bitvector
::
from_i32
(
0x2000
)
.into
();
let
substituted_address
=
state
.substitute_global_mem_address
(
global_address
,
&
global_memory
);
let
expected_global_id
=
AbstractIdentifier
::
from_global_address
(
state
.get_current_function_tid
(),
&
Bitvector
::
from_i32
(
0x2000
),
);
assert_eq!
(
state
.tracked_ids
.get
(
&
expected_global_id
),
Some
(
&
AccessPattern
::
new
())
);
assert_eq!
(
substituted_address
,
DataDomain
::
from_target
(
expected_global_id
,
Bitvector
::
from_i32
(
0
)
.into
())
);
}
src/cwe_checker_lib/src/analysis/mod.rs
View file @
a16e6589
...
@@ -2,6 +2,7 @@
...
@@ -2,6 +2,7 @@
//! as well as analyses depending on these modules.
//! as well as analyses depending on these modules.
pub
mod
backward_interprocedural_fixpoint
;
pub
mod
backward_interprocedural_fixpoint
;
pub
mod
callgraph
;
pub
mod
dead_variable_elimination
;
pub
mod
dead_variable_elimination
;
pub
mod
expression_propagation
;
pub
mod
expression_propagation
;
pub
mod
fixpoint
;
pub
mod
fixpoint
;
...
...
src/cwe_checker_lib/src/intermediate_representation/jmp.rs
View file @
a16e6589
...
@@ -92,3 +92,23 @@ impl fmt::Display for Jmp {
...
@@ -92,3 +92,23 @@ impl fmt::Display for Jmp {
}
}
}
}
}
}
#[cfg(test)]
pub
mod
tests
{
use
super
::
*
;
impl
Jmp
{
/// Create a mock call to a TID with the given `target` and `return_`
/// as the names of the target and return TIDs.
pub
fn
mock_call
(
target
:
&
str
,
return_
:
Option
<&
str
>
)
->
Term
<
Jmp
>
{
let
call
=
Jmp
::
Call
{
target
:
Tid
::
new
(
target
.to_string
()),
return_
:
return_
.map
(|
tid_name
|
Tid
::
new
(
tid_name
)),
};
Term
{
tid
:
Tid
::
new
(
format!
(
"call_{}"
,
target
.to_string
())),
term
:
call
,
}
}
}
}
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