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
74976f0d
Unverified
Commit
74976f0d
authored
Dec 16, 2021
by
Enkelmann
Committed by
GitHub
Dec 16, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
implement FunctionSignature analysis (#267)
parent
e5631994
Show whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
1559 additions
and
7 deletions
+1559
-7
acceptance-tests.yml
.github/workflows/acceptance-tests.yml
+6
-4
data.rs
src/cwe_checker_lib/src/abstract_domain/data.rs
+6
-0
identifier.rs
src/cwe_checker_lib/src/abstract_domain/identifier.rs
+14
-0
access_pattern.rs
...ker_lib/src/analysis/function_signature/access_pattern.rs
+106
-0
context.rs
...we_checker_lib/src/analysis/function_signature/context.rs
+263
-0
tests.rs
...cker_lib/src/analysis/function_signature/context/tests.rs
+44
-0
mod.rs
src/cwe_checker_lib/src/analysis/function_signature/mod.rs
+229
-0
state.rs
src/cwe_checker_lib/src/analysis/function_signature/state.rs
+375
-0
call_handling.rs
...ib/src/analysis/function_signature/state/call_handling.rs
+222
-0
tests.rs
...hecker_lib/src/analysis/function_signature/state/tests.rs
+182
-0
mod.rs
src/cwe_checker_lib/src/analysis/mod.rs
+1
-0
context.rs
src/cwe_checker_lib/src/checkers/cwe_476/context.rs
+10
-2
sub.rs
src/cwe_checker_lib/src/intermediate_representation/sub.rs
+90
-1
variable.rs
...e_checker_lib/src/intermediate_representation/variable.rs
+11
-0
No files found.
.github/workflows/acceptance-tests.yml
View file @
74976f0d
...
...
@@ -8,6 +8,8 @@ on:
env
:
CARGO_TERM_COLOR
:
always
GHIDRA_RELEASE_TAG
:
Ghidra_10.1_build
GHIDRA_VERSION
:
ghidra_10.1_PUBLIC_20211210
jobs
:
...
...
@@ -28,10 +30,10 @@ jobs:
architecture
:
x64
-
name
:
Install Ghidra
run
:
|
curl -fSL https://www.ghidra-sre.org/ghidra_9.2.1_PUBLIC_20201215.zip -o ghidra
.zip
unzip -
q ghidra
.zip
mv ghidra
_9.2.1_PUBLIC
/opt/ghidra
rm
ghidra
.zip
wget https://github.com/NationalSecurityAgency/ghidra/releases/download/${GHIDRA_RELEASE_TAG}/${GHIDRA_VERSION}
.zip
unzip -
d ghidra ${GHIDRA_VERSION}
.zip
mv ghidra
/ghidra_*
/opt/ghidra
rm
${GHIDRA_VERSION}
.zip
-
uses
:
actions-rs/toolchain@v1
with
:
profile
:
minimal
...
...
src/cwe_checker_lib/src/abstract_domain/data.rs
View file @
74976f0d
...
...
@@ -121,6 +121,12 @@ impl<T: RegisterDomain> DataDomain<T> {
self
.contains_top_values
=
true
;
}
/// Indicate that the domain does not contain any `Top` values
/// in addition to the contained absolute and relative values.
pub
fn
unset_contains_top_flag
(
&
mut
self
)
{
self
.contains_top_values
=
false
;
}
/// Return a new value representing a variable plus an offset,
/// where the variable is represented by the given abstract ID.
pub
fn
from_target
(
id
:
AbstractIdentifier
,
offset
:
T
)
->
Self
{
...
...
src/cwe_checker_lib/src/abstract_domain/identifier.rs
View file @
74976f0d
...
...
@@ -43,6 +43,15 @@ impl AbstractIdentifier {
AbstractIdentifier
(
Arc
::
new
(
AbstractIdentifierData
{
time
,
location
}))
}
/// Create a new abstract identifier where the abstract location is a register.
/// Panics if the register is a temporary register.
pub
fn
new_from_var
(
time
:
Tid
,
variable
:
&
Variable
)
->
AbstractIdentifier
{
AbstractIdentifier
(
Arc
::
new
(
AbstractIdentifierData
{
time
,
location
:
AbstractLocation
::
from_var
(
variable
)
.unwrap
(),
}))
}
/// Get the register associated to the abstract location.
/// Panics if the abstract location is a memory location and not a register.
pub
fn
unwrap_register
(
&
self
)
->
&
Variable
{
...
...
@@ -56,6 +65,11 @@ impl AbstractIdentifier {
pub
fn
get_tid
(
&
self
)
->
&
Tid
{
&
self
.time
}
/// Get the location component of the abstract ID
pub
fn
get_location
(
&
self
)
->
&
AbstractLocation
{
&
self
.location
}
}
impl
std
::
fmt
::
Display
for
AbstractIdentifier
{
...
...
src/cwe_checker_lib/src/analysis/function_signature/access_pattern.rs
0 → 100644
View file @
74976f0d
use
crate
::{
abstract_domain
::
AbstractDomain
,
prelude
::
*
};
use
std
::
fmt
::
Display
;
/// Access flags to track different kind of access/usage patterns of a variable.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Clone,
Copy)]
pub
struct
AccessPattern
{
/// The variable was used in the computation of a pointer that was dereferenced for reading a value.
dereferenced
:
bool
,
/// The variable was accessed to compute some nontrivial value
/// or the value was stored in some location.
read
:
bool
,
/// The variable was used in the computation of a pointer that was dereferenced for writing a value.
mutably_dereferenced
:
bool
,
}
impl
AccessPattern
{
/// Generate a new `AccessPattern` object with none of the access flags set.
pub
fn
new
()
->
Self
{
Self
{
dereferenced
:
false
,
read
:
false
,
mutably_dereferenced
:
false
,
}
}
/// Set the access flag for immutable pointer dereference.
pub
fn
set_dereference_flag
(
&
mut
self
)
{
self
.dereferenced
=
true
;
}
/// Set the access flag for read access.
pub
fn
set_read_flag
(
&
mut
self
)
{
self
.read
=
true
;
}
/// Set the access flag for pointer dereference (with write access to the target of the pointer).
pub
fn
set_mutably_dereferenced_flag
(
&
mut
self
)
{
self
.mutably_dereferenced
=
true
;
}
/// Set all access flags to indicate that any kind of access to the variable may have occured.
pub
fn
set_unknown_access_flags
(
&
mut
self
)
{
self
.read
=
true
;
self
.dereferenced
=
true
;
self
.mutably_dereferenced
=
true
;
}
/// Returns true if any of the access flags is set.
pub
fn
is_accessed
(
&
self
)
->
bool
{
self
.read
||
self
.dereferenced
||
self
.mutably_dereferenced
}
/// Returns true if the dereferenced or mutably dereferenced access flag is set.
pub
fn
is_dereferenced
(
&
self
)
->
bool
{
self
.dereferenced
||
self
.mutably_dereferenced
}
/// Returns true if the mutably dereferenced access flag is set.
pub
fn
is_mutably_dereferenced
(
&
self
)
->
bool
{
self
.mutably_dereferenced
}
}
impl
Default
for
AccessPattern
{
fn
default
()
->
Self
{
Self
::
new
()
}
}
impl
AbstractDomain
for
AccessPattern
{
/// An access flag in the merged `AccessPattern` object is set
/// if it is set in at least one of the input objects.
fn
merge
(
&
self
,
other
:
&
Self
)
->
Self
{
AccessPattern
{
dereferenced
:
self
.dereferenced
||
other
.dereferenced
,
read
:
self
.read
||
other
.read
,
mutably_dereferenced
:
self
.mutably_dereferenced
||
other
.mutably_dereferenced
,
}
}
/// Returns true if all of the access flags are set.
fn
is_top
(
&
self
)
->
bool
{
self
.read
&&
self
.dereferenced
&&
self
.mutably_dereferenced
}
}
impl
Display
for
AccessPattern
{
fn
fmt
(
&
self
,
f
:
&
mut
std
::
fmt
::
Formatter
<
'_
>
)
->
std
::
fmt
::
Result
{
if
self
.read
{
write!
(
f
,
"r"
)
?
;
}
else
{
write!
(
f
,
"-"
)
?
;
}
if
self
.dereferenced
{
write!
(
f
,
"d"
)
?
;
}
else
{
write!
(
f
,
"-"
)
?
;
}
if
self
.mutably_dereferenced
{
write!
(
f
,
"w"
)
?
;
}
else
{
write!
(
f
,
"-"
)
?
;
}
Ok
(())
}
}
src/cwe_checker_lib/src/analysis/function_signature/context.rs
0 → 100644
View file @
74976f0d
use
crate
::
abstract_domain
::{
AbstractDomain
,
AbstractIdentifier
,
BitvectorDomain
,
DataDomain
};
use
crate
::{
analysis
::{
forward_interprocedural_fixpoint
,
graph
::
Graph
},
intermediate_representation
::
Project
,
};
use
super
::
*
;
/// The context struct for the fixpoint algorithm.
pub
struct
Context
<
'a
>
{
graph
:
&
'a
Graph
<
'a
>
,
project
:
&
'a
Project
,
}
impl
<
'a
>
Context
<
'a
>
{
/// Generate a new context object.
pub
fn
new
(
project
:
&
'a
Project
,
graph
:
&
'a
Graph
<
'a
>
)
->
Self
{
Context
{
graph
,
project
}
}
/// 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.
fn
compute_return_values_of_call
<
'cconv
>
(
&
self
,
caller_state
:
&
mut
State
,
callee_state
:
&
State
,
calling_convention
:
&
'cconv
CallingConvention
,
call
:
&
Term
<
Jmp
>
,
)
->
Vec
<
(
&
'cconv
Variable
,
DataDomain
<
BitvectorDomain
>
)
>
{
let
mut
return_value_list
=
Vec
::
new
();
for
return_register
in
&
calling_convention
.integer_return_register
{
let
return_value
=
self
.compute_return_register_value_of_call
(
caller_state
,
callee_state
,
return_register
,
call
,
);
return_value_list
.push
((
return_register
,
return_value
));
}
for
return_expr
in
&
calling_convention
.float_return_register
{
for
return_register
in
return_expr
.input_vars
()
{
let
return_value
=
self
.compute_return_register_value_of_call
(
caller_state
,
callee_state
,
return_register
,
call
,
);
return_value_list
.push
((
return_register
,
return_value
));
}
}
return_value_list
}
/// 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.
fn
compute_return_register_value_of_call
(
&
self
,
caller_state
:
&
mut
State
,
callee_state
:
&
State
,
return_register
:
&
Variable
,
call
:
&
Term
<
Jmp
>
,
)
->
DataDomain
<
BitvectorDomain
>
{
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
let
Some
(
param_arg
)
=
callee_state
.get_arg_corresponding_to_id
(
callee_id
)
{
let
param_value
=
caller_state
.eval_parameter_arg
(
&
param_arg
);
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
(),
param_offset
.clone
()
+
callee_offset
.clone
(),
);
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
::
new_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
();
}
return_value
}
}
impl
<
'a
>
forward_interprocedural_fixpoint
::
Context
<
'a
>
for
Context
<
'a
>
{
type
Value
=
State
;
fn
get_graph
(
&
self
)
->
&
Graph
<
'a
>
{
self
.graph
}
fn
merge
(
&
self
,
state_left
:
&
State
,
state_right
:
&
State
)
->
State
{
state_left
.merge
(
state_right
)
}
fn
update_def
(
&
self
,
state
:
&
State
,
def
:
&
Term
<
Def
>
)
->
Option
<
State
>
{
let
mut
new_state
=
state
.clone
();
match
&
def
.term
{
Def
::
Assign
{
var
,
value
}
=>
{
new_state
.set_read_flag_for_input_ids_of_expression
(
value
);
new_state
.set_register
(
var
,
state
.eval
(
value
));
}
Def
::
Load
{
var
,
address
}
=>
{
new_state
.set_deref_flag_for_input_ids_of_expression
(
address
);
let
value
=
new_state
.load_value
(
new_state
.eval
(
address
),
var
.size
);
new_state
.set_register
(
var
,
value
);
}
Def
::
Store
{
address
,
value
}
=>
{
new_state
.set_mutable_deref_flag_for_input_ids_of_expression
(
address
);
if
state
.get_offset_if_exact_stack_pointer
(
&
state
.eval
(
address
))
.is_some
()
{
// 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).
new_state
.set_read_flag_for_input_ids_of_nontrivial_expression
(
value
);
}
else
{
new_state
.set_read_flag_for_input_ids_of_expression
(
value
);
}
new_state
.write_value
(
new_state
.eval
(
address
),
new_state
.eval
(
value
));
}
}
Some
(
new_state
)
}
fn
update_jump
(
&
self
,
state
:
&
State
,
jump
:
&
Term
<
Jmp
>
,
_untaken_conditional
:
Option
<&
Term
<
Jmp
>>
,
_target
:
&
Term
<
Blk
>
,
)
->
Option
<
State
>
{
let
mut
new_state
=
state
.clone
();
match
&
jump
.term
{
Jmp
::
BranchInd
(
address
)
|
Jmp
::
Return
(
address
)
=>
{
new_state
.set_read_flag_for_input_ids_of_expression
(
address
);
}
Jmp
::
CBranch
{
condition
,
..
}
=>
{
new_state
.set_read_flag_for_input_ids_of_expression
(
condition
);
}
_
=>
(),
}
Some
(
new_state
)
}
fn
update_call
(
&
self
,
_state
:
&
State
,
_call
:
&
Term
<
Jmp
>
,
_target
:
&
crate
::
analysis
::
graph
::
Node
,
)
->
Option
<
State
>
{
// No knowledge is transferred from the caller to the callee.
None
}
fn
update_call_stub
(
&
self
,
state
:
&
State
,
call
:
&
Term
<
Jmp
>
)
->
Option
<
State
>
{
let
mut
new_state
=
state
.clone
();
match
&
call
.term
{
Jmp
::
CallInd
{
target
,
..
}
=>
{
new_state
.set_read_flag_for_input_ids_of_expression
(
target
);
if
let
Some
(
cconv
)
=
self
.project
.get_standard_calling_convention
()
{
new_state
.handle_unknown_function_stub
(
call
,
cconv
);
return
Some
(
new_state
);
}
}
Jmp
::
Call
{
target
,
..
}
=>
{
if
let
Some
(
extern_symbol
)
=
self
.project.program.term.extern_symbols
.get
(
target
)
{
let
cconv
=
self
.project
.get_calling_convention
(
extern_symbol
);
new_state
.handle_extern_symbol
(
call
,
extern_symbol
,
cconv
);
if
!
extern_symbol
.no_return
{
return
Some
(
new_state
);
}
}
else
if
let
Some
(
cconv
)
=
self
.project
.get_standard_calling_convention
()
{
new_state
.handle_unknown_function_stub
(
call
,
cconv
);
return
Some
(
new_state
);
}
}
_
=>
(),
}
// The call could not be properly handled, so we treat it as a dead end in the control flow graph.
None
}
fn
update_return
(
&
self
,
state
:
Option
<&
State
>
,
state_before_call
:
Option
<&
State
>
,
call_term
:
&
Term
<
Jmp
>
,
_return_term
:
&
Term
<
Jmp
>
,
)
->
Option
<
State
>
{
if
state
.is_none
()
||
state_before_call
.is_none
()
{
return
None
;
}
let
calling_convention
=
match
self
.project
.get_standard_calling_convention
()
{
Some
(
cconv
)
=>
cconv
,
None
=>
return
None
,
};
let
old_state
=
state_before_call
.unwrap
();
let
callee_state
=
state
.unwrap
();
let
mut
new_state
=
old_state
.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
);
// Compute values for return register (but do not add them to `new_state` yet)
let
return_value_list
=
self
.compute_return_values_of_call
(
&
mut
new_state
,
callee_state
,
calling_convention
,
call_term
,
);
// From now on the operations on new_state are allowed to modify register values.
// Only retain callee-saved register from the caller register values.
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
{
new_state
.set_register
(
var
,
value
);
}
Some
(
new_state
)
}
fn
specialize_conditional
(
&
self
,
state
:
&
State
,
condition
:
&
Expression
,
_block_before_condition
:
&
Term
<
Blk
>
,
_is_true
:
bool
,
)
->
Option
<
State
>
{
let
mut
new_state
=
state
.clone
();
new_state
.set_read_flag_for_input_ids_of_expression
(
condition
);
Some
(
new_state
)
}
}
#[cfg(test)]
pub
mod
tests
;
src/cwe_checker_lib/src/analysis/function_signature/context/tests.rs
0 → 100644
View file @
74976f0d
use
super
::
*
;
use
std
::
collections
::
HashSet
;
#[test]
fn
test_compute_return_values_of_call
()
{
let
project
=
Project
::
mock_empty
();
let
cconv
=
CallingConvention
::
mock
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
,
HashSet
::
new
());
let
context
=
Context
::
new
(
&
project
,
&
graph
);
let
mut
caller_state
=
State
::
mock_x64
(
"caller"
);
let
mut
callee_state
=
State
::
mock_x64
(
"callee"
);
let
call
=
Term
{
tid
:
Tid
::
new
(
"call_tid"
),
term
:
Jmp
::
Call
{
target
:
Tid
::
new
(
"callee"
),
return_
:
Some
(
Tid
::
new
(
"return_tid"
)),
},
};
// Test returning a value of unknown origin (since RAX does not contain a reference to the input register).
let
return_values
=
context
.compute_return_values_of_call
(
&
mut
caller_state
,
&
callee_state
,
&
cconv
,
&
call
);
let
expected_val
=
DataDomain
::
from_target
(
AbstractIdentifier
::
new_from_var
(
Tid
::
new
(
"call_tid"
),
&
Variable
::
mock
(
"RAX"
,
8
)),
Bitvector
::
from_i64
(
0
)
.into
(),
);
assert_eq!
(
return_values
.iter
()
.len
(),
1
);
assert_eq!
(
return_values
[
0
],
(
&
Variable
::
mock
(
"RAX"
,
8
),
expected_val
));
// Test returning a known value.
let
param_ref
=
DataDomain
::
from_target
(
AbstractIdentifier
::
new_from_var
(
Tid
::
new
(
"callee"
),
&
Variable
::
mock
(
"RDI"
,
8
)),
Bitvector
::
from_i64
(
0
)
.into
(),
);
callee_state
.set_register
(
&
Variable
::
mock
(
"RAX"
,
8
),
param_ref
);
let
expected_val
=
DataDomain
::
from_target
(
AbstractIdentifier
::
new_from_var
(
Tid
::
new
(
"caller"
),
&
Variable
::
mock
(
"RDI"
,
8
)),
Bitvector
::
from_i64
(
0
)
.into
(),
);
let
return_values
=
context
.compute_return_values_of_call
(
&
mut
caller_state
,
&
callee_state
,
&
cconv
,
&
call
);
assert_eq!
(
return_values
.iter
()
.len
(),
1
);
assert_eq!
(
return_values
[
0
],
(
&
Variable
::
mock
(
"RAX"
,
8
),
expected_val
));
}
src/cwe_checker_lib/src/analysis/function_signature/mod.rs
0 → 100644
View file @
74976f0d
//! A fixpoint algorithm computing parameters of functions and their access patterns.
//!
//! The fixpoint algorithm tracks the values of registers and the stack,
//! although only stack accesses with known, constant offset are processed.
//! 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).
//!
//! 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.
//! * Only registers that are potential parameter registers in the standard calling convention
//! of the CPU architecture are considered as potential parameter registers.
//! For functions that use other registers
//! than those in the standard calling convention for parameter passing
//! the results of this analysis will be wrong.
//! * Parameters that are used as input values for variadic functions (e.g. sprintf) may be missed
//! since detection of variadic function parameters is not yet implemented for this analysis.
//! * 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.
use
crate
::
abstract_domain
::
AbstractDomain
;
use
crate
::
analysis
::
fixpoint
::
Computation
;
use
crate
::
analysis
::
forward_interprocedural_fixpoint
::
create_computation
;
use
crate
::
analysis
::
forward_interprocedural_fixpoint
::
GeneralizedContext
;
use
crate
::
analysis
::
graph
::
*
;
use
crate
::
analysis
::
interprocedural_fixpoint_generic
::
NodeValue
;
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
state
::
State
;
mod
access_pattern
;
pub
use
access_pattern
::
AccessPattern
;
/// Generate the computation object for the fixpoint computation
/// and set the node values for all function entry nodes.
fn
generate_fixpoint_computation
<
'a
>
(
project
:
&
'a
Project
,
graph
:
&
'a
Graph
,
)
->
Computation
<
GeneralizedContext
<
'a
,
Context
<
'a
>>>
{
let
context
=
Context
::
new
(
project
,
graph
);
let
mut
computation
=
create_computation
(
context
,
None
);
// Set the node values for all function entry nodes.
for
node
in
graph
.node_indices
()
{
if
let
Node
::
BlkStart
(
block
,
sub
)
=
graph
[
node
]
{
if
let
Some
(
entry_block
)
=
sub
.term.blocks
.get
(
0
)
{
if
entry_block
.tid
==
block
.tid
{
// The node of a function entry point
computation
.set_node_value
(
node
,
NodeValue
::
Value
(
State
::
new
(
&
sub
.tid
,
&
project
.stack_pointer_register
,
project
.get_standard_calling_convention
()
.unwrap
(),
)),
)
}
}
}
}
computation
}
/// Extract the function signatures from the computed fixpoint.
///
/// This function needs to merge the signatures at all nodes corresponding to a function
/// to ensure that parameter accesses on non-returning execution paths of a function
/// are also recognized in the function signature.
fn
extract_fn_signatures_from_fixpoint
<
'a
>
(
project
:
&
'a
Project
,
graph
:
&
'a
Graph
,
fixpoint
:
Computation
<
GeneralizedContext
<
'a
,
Context
<
'a
>>>
,
)
->
BTreeMap
<
Tid
,
FunctionSignature
>
{
let
mut
fn_sig_map
:
BTreeMap
<
Tid
,
FunctionSignature
>
=
project
.program
.term
.subs
.keys
()
.map
(|
tid
|
(
tid
.clone
(),
FunctionSignature
::
new
()))
.collect
();
for
node
in
graph
.node_indices
()
{
match
fixpoint
.get_node_value
(
node
)
{
None
=>
(),
Some
(
NodeValue
::
Value
(
state
))
=>
{
let
fn_sig
=
fn_sig_map
.get_mut
(
state
.get_current_function_tid
())
.unwrap
();
fn_sig
.merge_with_fn_sig_of_state
(
state
);
}
Some
(
NodeValue
::
CallFlowCombinator
{
call_stub
,
interprocedural_flow
,
})
=>
{
if
let
Some
(
state
)
=
call_stub
{
let
fn_sig
=
fn_sig_map
.get_mut
(
state
.get_current_function_tid
())
.unwrap
();
fn_sig
.merge_with_fn_sig_of_state
(
state
);
}
if
let
Some
(
state
)
=
interprocedural_flow
{
let
fn_sig
=
fn_sig_map
.get_mut
(
state
.get_current_function_tid
())
.unwrap
();
fn_sig
.merge_with_fn_sig_of_state
(
state
);
}
}
}
}
fn_sig_map
}
/// Compute the function signatures for all functions in the project.
///
/// Returns a map from the function TIDs to their signatures,
/// and a list of log messages recorded during the computation of the signatures.
///
/// For more information on the used algorithm see the module-level documentation.
pub
fn
compute_function_signatures
<
'a
>
(
project
:
&
'a
Project
,
graph
:
&
'a
Graph
,
)
->
(
Vec
<
LogMessage
>
,
BTreeMap
<
Tid
,
FunctionSignature
>
)
{
let
mut
computation
=
generate_fixpoint_computation
(
project
,
graph
);
computation
.compute_with_max_steps
(
100
);
let
mut
fn_sig_map
=
extract_fn_signatures_from_fixpoint
(
project
,
graph
,
computation
);
// Sanitize the parameters
let
mut
logs
=
Vec
::
new
();
for
(
fn_tid
,
fn_sig
)
in
fn_sig_map
.iter_mut
()
{
if
fn_sig
.sanitize
(
project
)
.is_err
()
{
logs
.push
(
LogMessage
::
new_error
(
"Function parameters are not properly sanitized"
)
.location
(
fn_tid
.clone
())
.source
(
"Function Signature Analysis"
),
);
}
}
(
logs
,
fn_sig_map
)
}
/// The signature of a function.
/// Currently only contains information on the parameters of a function and their access patterns.
#[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
>
,
}
impl
FunctionSignature
{
/// Generate an empty function signature.
pub
fn
new
()
->
Self
{
Self
{
parameters
:
HashMap
::
new
(),
}
}
/// Merge the parameter list of `self` with the given parameter list.
fn
merge_parameter_list
(
&
mut
self
,
params
:
&
[(
Arg
,
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
);
}
}
}
/// Merge the function signature with the signature extracted from the given state.
fn
merge_with_fn_sig_of_state
(
&
mut
self
,
state
:
&
State
)
{
let
params
=
state
.get_params_of_current_function
();
self
.merge_parameter_list
(
&
params
);
}
/// Sanitize the function signature:
/// * Remove the return address from the list of stack parameters for x86-based architectures.
/// * Check for unaligned stack parameters or stack parameters that are not pointer-sized
/// and return an error message if one is found.
/// This may indicate an error in the analysis
/// as no proper sanitation pass is implemented for such cases yet.
fn
sanitize
(
&
mut
self
,
project
:
&
Project
)
->
Result
<
(),
Error
>
{
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
);
}
_
=>
(),
}
self
.check_for_unaligned_stack_params
(
&
project
.stack_pointer_register
)
}
/// Return an error if an 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
)
->
Result
<
(),
Error
>
{
for
arg
in
self
.parameters
.keys
()
{
if
let
Arg
::
Stack
{
size
,
..
}
=
arg
{
if
*
size
!=
stack_register
.size
{
return
Err
(
anyhow!
(
"Unexpected stack parameter size"
));
}
if
let
Ok
(
offset
)
=
arg
.eval_stack_offset
(
stack_register
)
{
if
offset
.try_to_u64
()
?
%
u64
::
from
(
stack_register
.size
)
!=
0
{
return
Err
(
anyhow!
(
"Unexpected stack parameter alignment"
));
}
}
}
}
Ok
(())
}
}
impl
Default
for
FunctionSignature
{
fn
default
()
->
Self
{
Self
::
new
()
}
}
src/cwe_checker_lib/src/analysis/function_signature/state.rs
0 → 100644
View file @
74976f0d
use
std
::
collections
::
BTreeMap
;
use
std
::
collections
::
BTreeSet
;
use
crate
::
abstract_domain
::
*
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
prelude
::
*
;
use
super
::
AccessPattern
;
/// Methods of [`State`] related to handling call instructions.
mod
call_handling
;
/// The state tracks knowledge about known register values,
/// known values on the stack, and access patterns of tracked variables.
///
/// The values and access patterns are tracked as upper bounds.
/// For example, if some access flag for a variable is set, then the variable may have been accessed,
/// but it does not have to be accessed in the current state.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Clone)]
pub
struct
State
{
/// Known register values.
register
:
DomainMap
<
Variable
,
DataDomain
<
BitvectorDomain
>
,
MergeTopStrategy
>
,
/// The abstract ID representing the stack of the current function.
stack_id
:
AbstractIdentifier
,
/// The content of the current stack frame.
stack
:
MemRegion
<
DataDomain
<
BitvectorDomain
>>
,
/// Maps each tracked ID to an [`AccessPattern`], which tracks known access patterns to the object.
tracked_ids
:
DomainMap
<
AbstractIdentifier
,
AccessPattern
,
UnionMergeStrategy
>
,
}
impl
State
{
/// Generate a new state corresponding to the function start state for the given function TID.
///
/// Only registers that are parameter registers in the given calling convention are added to the tracked IDs.
pub
fn
new
(
func_tid
:
&
Tid
,
stack_register
:
&
Variable
,
calling_convention
:
&
CallingConvention
,
)
->
State
{
let
mut
register_map
=
BTreeMap
::
new
();
let
mut
tracked_ids
=
BTreeMap
::
new
();
// Generate tracked IDs for all parameters and also add them to the register map
for
var
in
calling_convention
.get_all_parameter_register
()
{
let
id
=
AbstractIdentifier
::
new_from_var
(
func_tid
.clone
(),
var
);
let
value
=
DataDomain
::
from_target
(
id
.clone
(),
Bitvector
::
zero
(
var
.size
.into
())
.into
());
register_map
.insert
(
var
.clone
(),
value
);
if
var
!=
stack_register
{
tracked_ids
.insert
(
id
,
AccessPattern
::
new
());
}
}
// Generate all stack-related objects
let
stack_id
=
AbstractIdentifier
::
new_from_var
(
func_tid
.clone
(),
stack_register
);
let
stack_value
=
DataDomain
::
from_target
(
stack_id
.clone
(),
Bitvector
::
zero
(
stack_register
.size
.into
())
.into
(),
);
register_map
.insert
(
stack_register
.clone
(),
stack_value
);
let
stack
=
MemRegion
::
new
(
stack_register
.size
);
State
{
register
:
DomainMap
::
from
(
register_map
),
stack_id
,
stack
,
tracked_ids
:
DomainMap
::
from
(
tracked_ids
),
}
}
/// Get the value of the given register in the current state.
pub
fn
get_register
(
&
self
,
register
:
&
Variable
)
->
DataDomain
<
BitvectorDomain
>
{
self
.register
.get
(
register
)
.cloned
()
.unwrap_or_else
(||
DataDomain
::
new_top
(
register
.size
))
}
/// Set the value of the given register in the current state.
pub
fn
set_register
(
&
mut
self
,
register
:
&
Variable
,
value
:
DataDomain
<
BitvectorDomain
>
)
{
if
value
.is_top
()
{
self
.register
.remove
(
register
);
}
else
{
self
.register
.insert
(
register
.clone
(),
value
);
}
}
/// Get the TID of the function that this state belongs to.
pub
fn
get_current_function_tid
(
&
self
)
->
&
Tid
{
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
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
let
Some
(
stack_offset
)
=
self
.get_offset_if_exact_stack_pointer
(
&
address
)
{
self
.load_value_from_stack
(
stack_offset
,
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
(
&
mut
self
,
stack_offset
:
Bitvector
,
size
:
ByteSize
,
)
->
Option
<
AbstractIdentifier
>
{
assert
!
(
!
stack_offset
.sign_bit
()
.to_bool
());
let
stack_pos
=
AbstractLocation
::
from_stack_position
(
self
.stack_id
.unwrap_register
(),
stack_offset
.try_to_i64
()
.unwrap
(),
size
,
);
let
param_id
=
AbstractIdentifier
::
new
(
self
.stack_id
.get_tid
()
.clone
(),
stack_pos
);
if
self
.tracked_ids
.contains_key
(
&
param_id
)
{
None
}
else
{
self
.tracked_ids
.insert
(
param_id
.clone
(),
AccessPattern
::
new
());
Some
(
param_id
)
}
}
/// 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
}
/// Evaluate the value of the given expression on the current state.
pub
fn
eval
(
&
self
,
expression
:
&
Expression
)
->
DataDomain
<
BitvectorDomain
>
{
match
expression
{
Expression
::
Var
(
var
)
=>
self
.get_register
(
var
),
Expression
::
Const
(
bitvector
)
=>
bitvector
.clone
()
.into
(),
Expression
::
BinOp
{
op
,
lhs
,
rhs
}
=>
self
.eval
(
lhs
)
.bin_op
(
*
op
,
&
self
.eval
(
rhs
)),
Expression
::
UnOp
{
op
,
arg
}
=>
self
.eval
(
arg
)
.un_op
(
*
op
),
Expression
::
Cast
{
op
,
size
,
arg
}
=>
self
.eval
(
arg
)
.cast
(
*
op
,
*
size
),
Expression
::
Unknown
{
description
:
_
,
size
,
}
=>
DataDomain
::
new_top
(
*
size
),
Expression
::
Subpiece
{
low_byte
,
size
,
arg
,
}
=>
self
.eval
(
arg
)
.subpiece
(
*
low_byte
,
*
size
),
}
}
/// Evaluate the value of the given parameter on the current state.
///
/// Note that this may alter the state
/// since stack parameters of the argument may access stack parameters of the the current stack frame,
/// which may need to be generated first.
pub
fn
eval_parameter_arg
(
&
mut
self
,
parameter
:
&
Arg
)
->
DataDomain
<
BitvectorDomain
>
{
match
parameter
{
Arg
::
Register
{
expr
,
data_type
:
_
}
=>
self
.eval
(
expr
),
Arg
::
Stack
{
address
,
size
,
data_type
:
_
,
}
=>
{
self
.set_deref_flag_for_input_ids_of_expression
(
address
);
let
address
=
self
.eval
(
address
);
self
.load_value
(
address
,
*
size
)
}
}
}
/// 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.
///
/// [`Expression::Var`] accesses also happen when writing a callee-saved register to the stack.
/// This function can be used to prevent accidentially flagging callee-saved registers as input registers.
pub
fn
set_read_flag_for_input_ids_of_nontrivial_expression
(
&
mut
self
,
expression
:
&
Expression
,
)
{
match
expression
{
Expression
::
Var
(
_
)
=>
(),
_
=>
self
.set_read_flag_for_input_ids_of_expression
(
expression
),
}
}
/// Set the read flag for every ID that may be referenced when computing the value of the expression.
pub
fn
set_read_flag_for_input_ids_of_expression
(
&
mut
self
,
expression
:
&
Expression
)
{
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
)
{
object
.set_read_flag
();
}
}
}
}
/// Set the read and dereferenced flag for every ID
/// 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
)
{
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
)
{
object
.set_read_flag
();
object
.set_dereference_flag
();
}
}
}
}
/// Set the read and mutably dereferenced flag for every 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
()
{
for
id
in
self
.get_register
(
register
)
.referenced_ids
()
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
object
.set_read_flag
();
object
.set_mutably_dereferenced_flag
();
}
}
}
}
}
impl
AbstractDomain
for
State
{
/// Merge two states
fn
merge
(
&
self
,
other
:
&
Self
)
->
Self
{
let
stack_id
=
self
.stack_id
.clone
();
let
stack
=
self
.stack
.merge
(
&
other
.stack
);
State
{
register
:
self
.register
.merge
(
&
other
.register
),
stack_id
,
stack
,
tracked_ids
:
self
.tracked_ids
.merge
(
&
other
.tracked_ids
),
}
}
/// The state does not have an explicit `Top` element.
fn
is_top
(
&
self
)
->
bool
{
false
}
}
impl
State
{
/// Generate a compact JSON-representation of the state for pretty printing.
#[allow(dead_code)]
pub
fn
to_json_compact
(
&
self
)
->
serde_json
::
Value
{
let
mut
json_map
=
serde_json
::
Map
::
new
();
json_map
.insert
(
"Stack-ID"
.to_string
(),
serde_json
::
Value
::
String
(
format!
(
"{}"
,
self
.stack_id
)),
);
let
regs
=
self
.register
.iter
()
.map
(|(
var
,
value
)|
(
format!
(
"{}"
,
var
),
value
.to_json_compact
()))
.collect
();
json_map
.insert
(
"Register"
.to_string
(),
serde_json
::
Value
::
Object
(
regs
));
let
access_patterns
=
self
.tracked_ids
.iter
()
.map
(|(
id
,
pattern
)|
{
(
format!
(
"{}"
,
id
),
serde_json
::
Value
::
String
(
format!
(
"{}"
,
pattern
)),
)
})
.collect
();
json_map
.insert
(
"Tracked IDs"
.to_string
(),
serde_json
::
Value
::
Object
(
access_patterns
),
);
let
stack
=
self
.stack
.iter
()
.map
(|(
index
,
value
)|
(
format!
(
"{}"
,
*
index
),
value
.to_json_compact
()))
.collect
();
json_map
.insert
(
"Stack"
.to_string
(),
serde_json
::
Value
::
Object
(
stack
));
serde_json
::
Value
::
Object
(
json_map
)
}
}
#[cfg(test)]
mod
tests
;
src/cwe_checker_lib/src/analysis/function_signature/state/call_handling.rs
0 → 100644
View file @
74976f0d
use
super
::
*
;
impl
State
{
/// Handle a call to an extern symbol.
///
/// Marks every possible input ID as accessed and writes to every return register a value
/// that may point to any of the input IDs.
pub
fn
handle_extern_symbol
(
&
mut
self
,
call
:
&
Term
<
Jmp
>
,
extern_symbol
:
&
ExternSymbol
,
calling_convention
:
&
CallingConvention
,
)
{
let
input_ids
=
self
.collect_input_ids_of_call
(
&
extern_symbol
.parameters
);
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
);
}
/// Handle a call to a completely unknown function
/// by assuming that every input register of the given calling convention is an input
/// and every integer return register of the calling convention is an output.
///
/// Marks every possible input ID as accessed and writes to every return register a value
/// that may point to any of the input IDs.
pub
fn
handle_unknown_function_stub
(
&
mut
self
,
call
:
&
Term
<
Jmp
>
,
calling_convention
:
&
CallingConvention
,
)
{
let
mut
parameters
=
generate_args_from_registers
(
&
calling_convention
.integer_parameter_register
);
for
float_param
in
&
calling_convention
.float_parameter_register
{
parameters
.push
(
Arg
::
Register
{
expr
:
float_param
.clone
(),
data_type
:
None
,
});
}
let
mut
return_register
=
generate_args_from_registers
(
&
calling_convention
.integer_return_register
);
for
float_return_register
in
&
calling_convention
.float_return_register
{
return_register
.push
(
Arg
::
Register
{
expr
:
float_return_register
.clone
(),
data_type
:
None
,
});
}
let
input_ids
=
self
.collect_input_ids_of_call
(
&
parameters
);
self
.clear_non_callee_saved_register
(
&
calling_convention
.callee_saved_register
);
self
.generate_return_values_for_call
(
&
input_ids
,
&
return_register
,
&
call
.tid
);
}
/// Get all input IDs referenced in the parameters of a call.
/// Marks every input ID as accessed (with access flags for unknown access)
/// and generates stack parameter IDs for the current function if necessary.
fn
collect_input_ids_of_call
(
&
mut
self
,
parameters
:
&
[
Arg
])
->
BTreeSet
<
AbstractIdentifier
>
{
let
mut
input_ids
=
BTreeSet
::
new
();
for
input_param
in
parameters
{
for
(
id
,
offset
)
in
self
.eval_parameter_arg
(
input_param
)
.get_relative_values
()
.iter
()
{
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
*
id
==
self
.stack_id
{
if
let
Ok
(
offset
)
=
offset
.try_to_bitvec
()
{
let
value
=
self
.load_unsized_value_from_stack
(
offset
);
for
id
in
value
.get_relative_values
()
.keys
()
{
input_ids
.insert
(
id
.clone
());
}
}
}
}
}
// Mark every input ID as accessed
for
id
in
&
input_ids
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
object
.set_unknown_access_flags
();
}
}
input_ids
}
/// Delete the content of all non-callee-saved registers from the state.
pub
fn
clear_non_callee_saved_register
(
&
mut
self
,
callee_saved
:
&
[
Variable
])
{
self
.register
.retain
(|
var
,
_
|
callee_saved
.contains
(
var
));
}
/// Fill every return register that might be a pointer with a value that may point to any input ID
/// or to an output 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 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_output_relative_values
:
BTreeMap
<
AbstractIdentifier
,
BitvectorDomain
>
=
input_ids
.iter
()
.map
(|
id
|
(
id
.clone
(),
BitvectorDomain
::
new_top
(
generic_pointer_size
)))
.collect
();
let
mut
generic_output
=
DataDomain
::
new_top
(
generic_pointer_size
);
generic_output
.set_relative_values
(
generic_output_relative_values
);
for
output_arg
in
return_args
{
if
let
Arg
::
Register
{
expr
:
Expression
::
Var
(
var
),
data_type
:
_
,
}
=
output_arg
{
if
var
.size
==
generic_pointer_size
{
let
specific_target
=
DataDomain
::
from_target
(
AbstractIdentifier
::
new_from_var
(
call_tid
.clone
(),
var
),
Bitvector
::
zero
(
var
.size
.into
())
.into
(),
);
let
output
=
generic_output
.merge
(
&
specific_target
);
self
.set_register
(
var
,
output
);
}
}
}
}
/// Return a list of parameter arguments and their associated object signatures for the current 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
)
>
{
let
mut
params
=
Vec
::
new
();
for
(
id
,
access_pattern
)
in
self
.tracked_ids
.iter
()
{
if
id
.get_tid
()
==
self
.get_current_function_tid
()
{
if
access_pattern
.is_accessed
()
{
params
.push
((
generate_arg_from_abstract_id
(
id
),
*
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
((
generate_arg_from_abstract_id
(
id
),
access_pattern
));
}
}
}
params
}
/// 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`.
///
/// If a parameter is a pointer to the stack frame of self, it is dereferenced
/// to set the access patterns of the target.
/// Note that this may create new stack parameter objects for self.
pub
fn
merge_parameter_access
(
&
mut
self
,
params
:
&
[(
Arg
,
AccessPattern
)])
{
for
(
parameter
,
call_access_pattern
)
in
params
{
for
(
id
,
offset
)
in
self
.eval_parameter_arg
(
parameter
)
.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
();
}
}
}
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
),
);
}
}
}
}
}
}
/// 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
()
{
Some
(
generate_arg_from_abstract_id
(
id
))
}
else
{
None
}
}
}
/// Generate register arguments from a list of registers.
fn
generate_args_from_registers
(
registers
:
&
[
Variable
])
->
Vec
<
Arg
>
{
registers
.iter
()
.map
(|
var
|
Arg
::
from_var
(
var
.clone
(),
None
))
.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.
/// Panics if the location contains a second level of indirection.
fn
generate_arg_from_abstract_id
(
id
:
&
AbstractIdentifier
)
->
Arg
{
match
id
.get_location
()
{
AbstractLocation
::
Register
(
var
)
=>
Arg
::
from_var
(
var
.clone
(),
None
),
AbstractLocation
::
Pointer
(
var
,
mem_location
)
=>
match
mem_location
{
AbstractMemoryLocation
::
Location
{
offset
,
size
}
=>
Arg
::
Stack
{
address
:
Expression
::
Var
(
var
.clone
())
.plus_const
(
*
offset
),
size
:
*
size
,
data_type
:
None
,
},
AbstractMemoryLocation
::
Pointer
{
..
}
=>
{
panic!
(
"Memory location is not a stack offset."
)
}
},
}
}
src/cwe_checker_lib/src/analysis/function_signature/state/tests.rs
0 → 100644
View file @
74976f0d
use
super
::
*
;
impl
State
{
/// Generate a mock state for an ARM-32 state.
pub
fn
mock
()
->
State
{
State
::
new
(
&
Tid
::
new
(
"mock_fn"
),
&
Variable
::
mock
(
"sp"
,
4
),
&
CallingConvention
::
mock_standard_arm_32
(),
)
}
/// Generate a mock state for an x64 state.
pub
fn
mock_x64
(
tid_name
:
&
str
)
->
State
{
State
::
new
(
&
Tid
::
new
(
tid_name
),
&
Variable
::
mock
(
"RSP"
,
8
),
&
CallingConvention
::
mock
(),
)
}
}
/// Mock an abstract ID representing the stack.
fn
mock_stack_id
()
->
AbstractIdentifier
{
AbstractIdentifier
::
new_from_var
(
Tid
::
new
(
"mock_fn"
),
&
Variable
::
mock
(
"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
();
// Test the generated stack
assert_eq!
(
&
state
.stack_id
,
&
mock_stack_id
());
assert_eq!
(
state
.stack
.iter
()
.len
(),
0
);
// Assert that the register values are as expected
assert_eq!
(
state
.register
.len
(),
9
);
// 8 parameters plus stack pointer
assert_eq!
(
state
.get_register
(
&
Variable
::
mock
(
"sp"
,
4
)),
DataDomain
::
from_target
(
mock_stack_id
(),
Bitvector
::
zero
(
ByteSize
::
new
(
4
)
.into
())
.into
()
)
);
// Check the generated tracked IDs
assert_eq!
(
state
.tracked_ids
.len
(),
8
);
for
(
id
,
access_pattern
)
in
state
.tracked_ids
.iter
()
{
assert_eq!
(
state
.get_register
(
id
.unwrap_register
()),
DataDomain
::
from_target
(
id
.clone
(),
Bitvector
::
zero
(
ByteSize
::
new
(
4
)
.into
())
.into
())
);
assert_eq!
(
access_pattern
,
&
AccessPattern
::
new
());
}
}
#[test]
fn
test_store_and_load_from_stack
()
{
let
mut
state
=
State
::
mock
();
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
Bitvector
::
from_i32
(
-
4
)
.into
());
let
value
:
DataDomain
<
BitvectorDomain
>
=
Bitvector
::
from_i32
(
0
)
.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
(
Bitvector
::
from_i32
(
-
4
),
ByteSize
::
new
(
4
)),
value
.clone
()
);
assert_eq!
(
state
.load_value
(
address
,
ByteSize
::
new
(
4
)),
value
);
// Load a parameter register and check that the parameter gets generated
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
Bitvector
::
from_i32
(
4
)
.into
());
let
stack_param_id
=
mock_stack_param_id
(
4
,
4
);
let
stack_param
=
DataDomain
::
from_target
(
stack_param_id
.clone
(),
Bitvector
::
from_i32
(
0
)
.into
());
assert_eq!
(
state
.tracked_ids
.iter
()
.len
(),
8
);
assert_eq!
(
state
.load_value
(
address
.clone
(),
ByteSize
::
new
(
4
)),
stack_param
);
assert_eq!
(
state
.tracked_ids
.iter
()
.len
(),
9
);
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
();
// Load an existing stack param (generated by a sized load at the same address)
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
Bitvector
::
from_i32
(
0
)
.into
());
let
stack_param_id
=
mock_stack_param_id
(
0
,
4
);
let
stack_param
=
DataDomain
::
from_target
(
stack_param_id
.clone
(),
Bitvector
::
from_i32
(
0
)
.into
());
state
.load_value
(
address
,
ByteSize
::
new
(
4
));
let
unsized_load
=
state
.load_unsized_value_from_stack
(
Bitvector
::
from_i32
(
0
));
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
(),
Bitvector
::
from_i8
(
0
)
.into
());
let
unsized_load
=
state
.load_unsized_value_from_stack
(
Bitvector
::
from_i32
(
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
(
Bitvector
::
from_i32
(
-
4
));
assert_eq!
(
unsized_load
,
DataDomain
::
new_top
(
ByteSize
::
new
(
1
)));
}
#[test]
fn
test_eval
()
{
let
mut
state
=
State
::
mock
();
// Test the eval method
let
expr
=
Expression
::
Var
(
Variable
::
mock
(
"sp"
,
4
))
.plus_const
(
42
);
assert_eq!
(
state
.eval
(
&
expr
),
DataDomain
::
from_target
(
mock_stack_id
(),
Bitvector
::
from_i32
(
42
)
.into
())
);
// Test the eval_parameter_arg method
let
arg
=
Arg
::
from_var
(
Variable
::
mock
(
"sp"
,
4
),
None
);
assert_eq!
(
state
.eval_parameter_arg
(
&
arg
),
DataDomain
::
from_target
(
mock_stack_id
(),
Bitvector
::
from_i32
(
0
)
.into
())
);
}
#[test]
fn
test_extern_symbol_handling
()
{
let
mut
state
=
State
::
mock
();
let
extern_symbol
=
ExternSymbol
::
mock_arm32
();
let
cconv
=
CallingConvention
::
mock_arm32
();
let
call
=
Term
{
tid
:
Tid
::
new
(
"call_tid"
),
term
:
Jmp
::
Call
{
target
:
extern_symbol
.tid
.clone
(),
return_
:
Some
(
Tid
::
new
(
"return_tid"
)),
},
};
let
param_id
=
AbstractIdentifier
::
new_from_var
(
Tid
::
new
(
"mock_fn"
),
&
Variable
::
mock
(
"r0"
,
4
));
let
return_val_id
=
AbstractIdentifier
::
new_from_var
(
Tid
::
new
(
"call_tid"
),
&
Variable
::
mock
(
"r0"
,
4
));
// Test extern symbol handling.
state
.handle_extern_symbol
(
&
call
,
&
extern_symbol
,
&
cconv
);
assert_eq!
(
state
.tracked_ids
.get
(
&
param_id
)
.unwrap
()
.is_mutably_dereferenced
(),
true
);
let
return_val
=
state
.get_register
(
&
Variable
::
mock
(
"r0"
,
4
));
assert_eq!
(
return_val
.get_relative_values
()
.iter
()
.len
(),
2
);
assert_eq!
(
return_val
.get_relative_values
()
.get
(
&
param_id
)
.unwrap
(),
&
BitvectorDomain
::
new_top
(
ByteSize
::
new
(
4
))
);
assert_eq!
(
return_val
.get_relative_values
()
.get
(
&
param_id
)
.unwrap
(),
&
BitvectorDomain
::
new_top
(
ByteSize
::
new
(
4
))
);
assert_eq!
(
return_val
.get_relative_values
()
.get
(
&
return_val_id
)
.unwrap
(),
&
Bitvector
::
from_i32
(
0
)
.into
()
);
}
src/cwe_checker_lib/src/analysis/mod.rs
View file @
74976f0d
...
...
@@ -5,6 +5,7 @@ pub mod backward_interprocedural_fixpoint;
pub
mod
dead_variable_elimination
;
pub
mod
fixpoint
;
pub
mod
forward_interprocedural_fixpoint
;
pub
mod
function_signature
;
pub
mod
graph
;
pub
mod
interprocedural_fixpoint_generic
;
pub
mod
pointer_inference
;
...
...
src/cwe_checker_lib/src/checkers/cwe_476/context.rs
View file @
74976f0d
...
...
@@ -446,7 +446,11 @@ mod tests {
let
(
mut
state
,
_pi_state
)
=
State
::
mock_with_pi_state
();
assert_eq!
(
context
.check_parameters_for_taint
(
&
state
,
&
ExternSymbol
::
mock
(),
NodeIndex
::
new
(
0
)),
context
.check_parameters_for_taint
(
&
state
,
&
ExternSymbol
::
mock_x64
(),
NodeIndex
::
new
(
0
)
),
false
);
...
...
@@ -455,7 +459,11 @@ mod tests {
Taint
::
Tainted
(
ByteSize
::
new
(
8
)),
);
assert_eq!
(
context
.check_parameters_for_taint
(
&
state
,
&
ExternSymbol
::
mock
(),
NodeIndex
::
new
(
0
)),
context
.check_parameters_for_taint
(
&
state
,
&
ExternSymbol
::
mock_x64
(),
NodeIndex
::
new
(
0
)
),
true
);
}
...
...
src/cwe_checker_lib/src/intermediate_representation/sub.rs
View file @
74976f0d
...
...
@@ -52,6 +52,46 @@ impl Arg {
Arg
::
Stack
{
data_type
,
..
}
=>
data_type
.clone
(),
}
}
/// If the argument is a stack argument,
/// return its offset relative to the current stack register value.
/// Return an error for register arguments or if the offset could not be computed.
pub
fn
eval_stack_offset
(
&
self
,
stack_register
:
&
Variable
)
->
Result
<
Bitvector
,
Error
>
{
let
expression
=
match
self
{
Arg
::
Register
{
..
}
=>
return
Err
(
anyhow!
(
"The argument is not a stack argument."
)),
Arg
::
Stack
{
address
,
..
}
=>
address
,
};
Self
::
eval_stack_offset_expression
(
expression
,
stack_register
)
}
/// If the given expression computes a constant offset to the given stack register,
/// then return the offset.
/// Else return an error.
fn
eval_stack_offset_expression
(
expression
:
&
Expression
,
stack_register
:
&
Variable
,
)
->
Result
<
Bitvector
,
Error
>
{
match
expression
{
Expression
::
Var
(
var
)
=>
{
if
var
==
stack_register
{
Ok
(
Bitvector
::
zero
(
var
.size
.into
()))
}
else
{
Err
(
anyhow!
(
"Input register is not the stack register"
))
}
}
Expression
::
Const
(
bitvec
)
=>
Ok
(
bitvec
.clone
()),
Expression
::
BinOp
{
op
,
lhs
,
rhs
}
=>
{
let
lhs
=
Self
::
eval_stack_offset_expression
(
lhs
,
stack_register
)
?
;
let
rhs
=
Self
::
eval_stack_offset_expression
(
rhs
,
stack_register
)
?
;
lhs
.bin_op
(
*
op
,
&
rhs
)
}
Expression
::
UnOp
{
op
,
arg
}
=>
{
let
arg
=
Self
::
eval_stack_offset_expression
(
arg
,
stack_register
)
?
;
arg
.un_op
(
*
op
)
}
_
=>
Err
(
anyhow!
(
"Expression type not supported for argument values"
)),
}
}
}
/// An extern symbol represents a funtion that is dynamically linked from another binary.
...
...
@@ -131,6 +171,30 @@ pub struct CallingConvention {
pub
callee_saved_register
:
Vec
<
Variable
>
,
}
impl
CallingConvention
{
/// Return a list of all parameter registers of the calling convention.
/// For parameters, where only a part of a register is the actual parameter,
/// the parameter register is approximated by the (larger) base register.
pub
fn
get_all_parameter_register
(
&
self
)
->
Vec
<&
Variable
>
{
let
mut
register_list
:
Vec
<&
Variable
>
=
self
.integer_parameter_register
.iter
()
.collect
();
for
float_param_expr
in
self
.float_parameter_register
.iter
()
{
register_list
.append
(
&
mut
float_param_expr
.input_vars
());
}
register_list
}
/// Return a list of all return registers of the calling convention.
/// For return register, where only a part of a register is the actual return register,
/// the return register is approximated by the (larger) base register.
pub
fn
get_all_return_register
(
&
self
)
->
Vec
<&
Variable
>
{
let
mut
register_list
:
Vec
<&
Variable
>
=
self
.integer_return_register
.iter
()
.collect
();
for
float_param_expr
in
self
.float_return_register
.iter
()
{
register_list
.append
(
&
mut
float_param_expr
.input_vars
());
}
register_list
}
}
#[cfg(test)]
mod
tests
{
use
super
::
*
;
...
...
@@ -159,6 +223,17 @@ mod tests {
}
}
pub
fn
mock_arm32
()
->
CallingConvention
{
CallingConvention
{
name
:
"__stdcall"
.to_string
(),
// so that the mock is useable as standard calling convention in tests
integer_parameter_register
:
vec!
[
Variable
::
mock
(
"r0"
,
4
)],
float_parameter_register
:
vec!
[
Expression
::
Var
(
Variable
::
mock
(
"d0"
,
8
))],
integer_return_register
:
vec!
[
Variable
::
mock
(
"r0"
,
4
)],
float_return_register
:
vec!
[],
callee_saved_register
:
vec!
[
Variable
::
mock
(
"r4"
,
4
)],
}
}
pub
fn
mock_with_parameter_registers
(
integer_parameter_register
:
Vec
<
Variable
>
,
float_parameter_register
:
Vec
<
Variable
>
,
...
...
@@ -209,7 +284,7 @@ mod tests {
}
impl
ExternSymbol
{
pub
fn
mock
()
->
ExternSymbol
{
pub
fn
mock
_x64
()
->
ExternSymbol
{
ExternSymbol
{
tid
:
Tid
::
new
(
"mock_symbol"
),
addresses
:
vec!
[
"UNKNOWN"
.to_string
()],
...
...
@@ -222,6 +297,20 @@ mod tests {
}
}
pub
fn
mock_arm32
()
->
ExternSymbol
{
// There is also the mock_standard_arm32() method. Only on of the two should exist!
ExternSymbol
{
tid
:
Tid
::
new
(
"mock_symbol"
),
addresses
:
vec!
[
"UNKNOWN"
.to_string
()],
name
:
"mock_symbol"
.to_string
(),
calling_convention
:
Some
(
"__stdcall"
.to_string
()),
parameters
:
vec!
[
Arg
::
mock_register
(
"r0"
,
4
)],
return_values
:
vec!
[
Arg
::
mock_register
(
"r0"
,
4
)],
no_return
:
false
,
has_var_args
:
false
,
}
}
pub
fn
mock_string
()
->
Self
{
ExternSymbol
{
tid
:
Tid
::
new
(
"sprintf"
),
...
...
src/cwe_checker_lib/src/intermediate_representation/variable.rs
View file @
74976f0d
use
super
::
ByteSize
;
use
crate
::
prelude
::
*
;
use
std
::
fmt
::
Display
;
/// A variable represents a register with a known size and name.
///
...
...
@@ -19,6 +20,16 @@ pub struct Variable {
pub
is_temp
:
bool
,
}
impl
Display
for
Variable
{
fn
fmt
(
&
self
,
f
:
&
mut
std
::
fmt
::
Formatter
<
'_
>
)
->
std
::
fmt
::
Result
{
write!
(
f
,
"{}:{}"
,
self
.name
,
self
.size
.as_bit_length
())
?
;
if
self
.is_temp
{
write!
(
f
,
"(temp)"
)
?
;
}
Ok
(())
}
}
#[cfg(test)]
mod
tests
{
use
super
::
*
;
...
...
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