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
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
14 changed files
with
699 additions
and
7 deletions
+699
-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
+0
-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
+0
-0
call_handling.rs
...ib/src/analysis/function_signature/state/call_handling.rs
+0
-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
This diff is collapsed.
Click to expand it.
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
This diff is collapsed.
Click to expand it.
src/cwe_checker_lib/src/analysis/function_signature/state/call_handling.rs
0 → 100644
View file @
74976f0d
This diff is collapsed.
Click to expand it.
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