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
99042d01
Unverified
Commit
99042d01
authored
Aug 04, 2022
by
Enkelmann
Committed by
GitHub
Aug 04, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add libC function stubs to function signature analysis (#348)
parent
1e46589f
Show whitespace changes
Inline
Side-by-side
Showing
12 changed files
with
547 additions
and
75 deletions
+547
-75
access_pattern.rs
...ker_lib/src/analysis/function_signature/access_pattern.rs
+27
-0
context.rs
...we_checker_lib/src/analysis/function_signature/context.rs
+149
-6
tests.rs
...cker_lib/src/analysis/function_signature/context/tests.rs
+63
-0
mod.rs
src/cwe_checker_lib/src/analysis/function_signature/mod.rs
+1
-0
state.rs
src/cwe_checker_lib/src/analysis/function_signature/state.rs
+23
-1
call_handling.rs
...ib/src/analysis/function_signature/state/call_handling.rs
+3
-3
tests.rs
...hecker_lib/src/analysis/function_signature/state/tests.rs
+5
-11
stubs.rs
src/cwe_checker_lib/src/analysis/function_signature/stubs.rs
+233
-0
cwe_134.rs
src/cwe_checker_lib/src/checkers/cwe_134.rs
+1
-1
sub.rs
src/cwe_checker_lib/src/intermediate_representation/sub.rs
+1
-1
arguments.rs
src/cwe_checker_lib/src/utils/arguments.rs
+30
-31
tests.rs
src/cwe_checker_lib/src/utils/arguments/tests.rs
+11
-21
No files found.
src/cwe_checker_lib/src/analysis/function_signature/access_pattern.rs
View file @
99042d01
...
...
@@ -23,6 +23,33 @@ impl AccessPattern {
}
}
/// Generate a new `AccessPattern` object with all access flags set to true (to model unknown access).
pub
fn
new_unknown_access
()
->
Self
{
Self
{
dereferenced
:
true
,
read
:
true
,
mutably_dereferenced
:
true
,
}
}
/// Set the access flag for read access and return `self`.
pub
fn
with_read_flag
(
mut
self
)
->
Self
{
self
.read
=
true
;
self
}
/// Set the access flag for immutable pointer dereference and return `self`.
pub
fn
with_dereference_flag
(
mut
self
)
->
Self
{
self
.dereferenced
=
true
;
self
}
/// Set the access flag for pointer dereference with write access to the pointer target and return `self`.
pub
fn
with_mutably_dereferenced_flag
(
mut
self
)
->
Self
{
self
.mutably_dereferenced
=
true
;
self
}
/// Set the access flag for immutable pointer dereference.
pub
fn
set_dereference_flag
(
&
mut
self
)
{
self
.dereferenced
=
true
;
...
...
src/cwe_checker_lib/src/analysis/function_signature/context.rs
View file @
99042d01
use
crate
::
abstract_domain
::{
AbstractDomain
,
AbstractIdentifier
,
BitvectorDomain
,
DataDomain
};
use
crate
::
abstract_domain
::{
AbstractDomain
,
AbstractIdentifier
,
BitvectorDomain
,
DataDomain
,
TryToBitvec
,
};
use
crate
::
utils
::
arguments
;
use
crate
::{
analysis
::{
forward_interprocedural_fixpoint
,
graph
::
Graph
},
intermediate_representation
::
Project
,
...
...
@@ -10,12 +13,22 @@ use super::*;
pub
struct
Context
<
'a
>
{
graph
:
&
'a
Graph
<
'a
>
,
project
:
&
'a
Project
,
/// Parameter access patterns for stubbed extern symbols.
param_access_stubs
:
BTreeMap
<&
'static
str
,
Vec
<
AccessPattern
>>
,
/// Assigns to the name of a stubbed variadic symbol the index of its format string parameter
/// and the access pattern for all variadic parameters.
stubbed_variadic_symbols
:
BTreeMap
<&
'static
str
,
(
usize
,
AccessPattern
)
>
,
}
impl
<
'a
>
Context
<
'a
>
{
/// Generate a new context object.
pub
fn
new
(
project
:
&
'a
Project
,
graph
:
&
'a
Graph
<
'a
>
)
->
Self
{
Context
{
graph
,
project
}
Context
{
graph
,
project
,
param_access_stubs
:
stubs
::
generate_param_access_stubs
(),
stubbed_variadic_symbols
:
stubs
::
get_stubbed_variadic_symbols
(),
}
}
/// Compute the return values of a call and return them (without adding them to the caller state).
...
...
@@ -108,6 +121,133 @@ impl<'a> Context<'a> {
return_value
}
/// Handle a call to a specific extern symbol.
/// If function stubs exist for the symbol, then these are used to compute the effect of the call.
/// Else the [generic symbol handler](State::handle_generic_extern_symbol) is called.
fn
handle_extern_symbol_call
(
&
self
,
state
:
&
mut
State
,
extern_symbol
:
&
ExternSymbol
,
call_tid
:
&
Tid
,
)
{
let
cconv
=
self
.project
.get_calling_convention
(
extern_symbol
);
if
let
Some
(
param_access_list
)
=
self
.param_access_stubs
.get
(
extern_symbol
.name
.as_str
())
{
// Set access flags for parameter access
for
(
param
,
access_pattern
)
in
extern_symbol
.parameters
.iter
()
.zip
(
param_access_list
)
{
for
id
in
state
.eval_parameter_arg
(
param
)
.get_relative_values
()
.keys
()
{
state
.merge_access_pattern_of_id
(
id
,
access_pattern
);
}
}
if
self
.stubbed_variadic_symbols
.get
(
extern_symbol
.name
.as_str
())
.is_some
()
&&
self
.set_access_flags_for_variadic_parameters
(
state
,
extern_symbol
)
.is_none
()
{
self
.set_access_flags_for_generic_variadic_parameters
(
state
,
extern_symbol
);
}
let
return_val
=
stubs
::
compute_return_value_for_stubbed_function
(
self
.project
,
state
,
extern_symbol
,
call_tid
,
);
state
.clear_non_callee_saved_register
(
&
cconv
.callee_saved_register
);
state
.set_register
(
&
cconv
.integer_return_register
[
0
],
return_val
);
}
else
{
state
.handle_generic_extern_symbol
(
call_tid
,
extern_symbol
,
cconv
);
}
}
/// Merges the access patterns for all variadic parameters of the given symbol.
///
/// This function can only handle stubbed symbols where the number of variadic parameters can be parsed from a format string.
/// If the parsing of the variadic parameters failed for any reason
/// (e.g. because the format string could not be statically determined)
/// then this function does not modify any access patterns.
///
/// If the variadic access pattern contains the mutable dereference flag
/// then all variadic parameters are assumed to be pointers.
fn
set_access_flags_for_variadic_parameters
(
&
self
,
state
:
&
mut
State
,
extern_symbol
:
&
ExternSymbol
,
)
->
Option
<
()
>
{
let
(
format_string_index
,
variadic_access_pattern
)
=
self
.stubbed_variadic_symbols
.get
(
extern_symbol
.name
.as_str
())
?
;
let
format_string_address
=
state
.eval_parameter_arg
(
&
extern_symbol
.parameters
[
*
format_string_index
])
.get_if_absolute_value
()
.map
(|
value
|
value
.try_to_bitvec
()
.ok
())
??
;
let
format_string
=
arguments
::
parse_format_string_destination_and_return_content
(
format_string_address
,
&
self
.project.runtime_memory_image
,
)
.ok
()
?
;
let
mut
format_string_params
=
arguments
::
parse_format_string_parameters
(
&
format_string
,
&
self
.project.datatype_properties
,
)
.ok
()
?
;
if
variadic_access_pattern
.is_mutably_dereferenced
()
{
// All parameters are pointers to where values shall be written.
format_string_params
=
vec!
[
(
Datatype
::
Pointer
,
self
.project.stack_pointer_register.size
);
format_string_params
.len
()
];
}
let
format_string_args
=
arguments
::
calculate_parameter_locations
(
format_string_params
,
extern_symbol
,
self
.project
,
);
for
param
in
format_string_args
{
for
id
in
state
.eval_parameter_arg
(
&
param
)
.get_relative_values
()
.keys
()
{
state
.merge_access_pattern_of_id
(
id
,
variadic_access_pattern
);
}
}
Some
(())
}
/// Sets access patterns for variadic parameters
/// of a call to a variadic function with unknown number of variadic parameters.
/// This function assumes that all remaining integer parameter registers of the corresponding calling convention
/// are filled with variadic parameters,
/// but no variadic parameters are supplied as stack parameters.
fn
set_access_flags_for_generic_variadic_parameters
(
&
self
,
state
:
&
mut
State
,
extern_symbol
:
&
ExternSymbol
,
)
{
let
(
_
,
variadic_access_pattern
)
=
self
.stubbed_variadic_symbols
.get
(
extern_symbol
.name
.as_str
())
.unwrap
();
let
cconv
=
self
.project
.get_calling_convention
(
extern_symbol
);
if
extern_symbol
.parameters
.len
()
<
cconv
.integer_parameter_register
.len
()
{
for
index
in
[
extern_symbol
.parameters
.len
(),
cconv
.integer_parameter_register
.len
()
-
1
,
]
{
for
id
in
state
.get_register
(
&
cconv
.integer_parameter_register
[
index
])
.get_relative_values
()
.keys
()
{
state
.merge_access_pattern_of_id
(
id
,
variadic_access_pattern
);
}
}
}
}
}
impl
<
'a
>
forward_interprocedural_fixpoint
::
Context
<
'a
>
for
Context
<
'a
>
{
...
...
@@ -130,7 +270,11 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
}
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
);
let
value
=
new_state
.load_value
(
new_state
.eval
(
address
),
var
.size
,
Some
(
&
self
.project.runtime_memory_image
),
);
new_state
.set_register
(
var
,
value
);
}
Def
::
Store
{
address
,
value
}
=>
{
...
...
@@ -194,8 +338,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
}
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
);
self
.handle_extern_symbol_call
(
&
mut
new_state
,
extern_symbol
,
&
call
.tid
);
if
!
extern_symbol
.no_return
{
return
Some
(
new_state
);
}
...
...
@@ -206,7 +349,7 @@ impl<'a> forward_interprocedural_fixpoint::Context<'a> for Context<'a> {
}
_
=>
(),
}
// The call could not be properly handled, so we treat it as a dead end in the control flow graph.
// The call could not be properly handled
or is a non-returning function
, so we treat it as a dead end in the control flow graph.
None
}
...
...
src/cwe_checker_lib/src/analysis/function_signature/context/tests.rs
View file @
99042d01
...
...
@@ -49,3 +49,66 @@ fn test_compute_return_values_of_call() {
assert_eq!
(
return_values
.iter
()
.len
(),
3
);
assert_eq!
(
return_values
[
0
],
(
&
Variable
::
mock
(
"RAX"
,
8
),
expected_val
));
}
#[test]
fn
test_call_stub_handling
()
{
let
project
=
Project
::
mock_arm32
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
,
HashSet
::
new
());
let
context
=
Context
::
new
(
&
project
,
&
graph
);
// Test handling of malloc call
let
mut
state
=
State
::
new
(
&
Tid
::
new
(
"func"
),
&
project
.stack_pointer_register
,
project
.get_standard_calling_convention
()
.unwrap
(),
);
let
extern_symbol
=
ExternSymbol
::
mock_malloc_symbol_arm
();
let
call_tid
=
Tid
::
new
(
"call_malloc"
);
context
.handle_extern_symbol_call
(
&
mut
state
,
&
extern_symbol
,
&
call_tid
);
assert_eq!
(
state
.get_params_of_current_function
(),
vec!
[(
Arg
::
from_var
(
Variable
::
mock
(
"r0"
,
4
),
None
),
AccessPattern
::
new
()
.with_read_flag
()
)]
);
assert_eq!
(
state
.get_register
(
&
Variable
::
mock
(
"r0"
,
4
)),
DataDomain
::
from_target
(
AbstractIdentifier
::
mock
(
call_tid
,
"r0"
,
4
),
Bitvector
::
from_i32
(
0
)
.into
()
)
.merge
(
&
Bitvector
::
zero
(
ByteSize
::
new
(
4
)
.into
())
.into
())
);
// Test handling of sprintf call
let
mut
state
=
State
::
new
(
&
Tid
::
new
(
"func"
),
&
project
.stack_pointer_register
,
project
.get_standard_calling_convention
()
.unwrap
(),
);
// Set the format string param register to a pointer to the string 'cat %s %s %s %s'.
state
.set_register
(
&
Variable
::
mock
(
"r1"
,
4
),
Bitvector
::
from_i32
(
0x6000
)
.into
());
let
extern_symbol
=
ExternSymbol
::
mock_sprintf_symbol_arm
();
let
call_tid
=
Tid
::
new
(
"call_sprintf"
);
context
.handle_extern_symbol_call
(
&
mut
state
,
&
extern_symbol
,
&
call_tid
);
let
params
=
state
.get_params_of_current_function
();
assert_eq!
(
params
[
0
],
(
Arg
::
from_var
(
Variable
::
mock
(
"r0"
,
4
),
None
),
AccessPattern
::
new_unknown_access
()
)
);
assert_eq!
(
params
[
1
],
(
Arg
::
from_var
(
Variable
::
mock
(
"r2"
,
4
),
None
),
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
()
)
);
assert_eq!
(
params
.len
(),
5
);
}
src/cwe_checker_lib/src/analysis/function_signature/mod.rs
View file @
99042d01
...
...
@@ -42,6 +42,7 @@ mod state;
use
state
::
State
;
mod
access_pattern
;
pub
use
access_pattern
::
AccessPattern
;
mod
stubs
;
/// Generate the computation object for the fixpoint computation
/// and set the node values for all function entry nodes.
...
...
src/cwe_checker_lib/src/analysis/function_signature/state.rs
View file @
99042d01
...
...
@@ -99,9 +99,18 @@ impl State {
&
mut
self
,
address
:
DataDomain
<
BitvectorDomain
>
,
size
:
ByteSize
,
global_memory
:
Option
<&
RuntimeMemoryImage
>
,
)
->
DataDomain
<
BitvectorDomain
>
{
if
let
Some
(
stack_offset
)
=
self
.get_offset_if_exact_stack_pointer
(
&
address
)
{
self
.load_value_from_stack
(
stack_offset
,
size
)
}
else
if
let
(
Ok
(
global_address
),
Some
(
global_mem
))
=
(
address
.try_to_bitvec
(),
global_memory
)
{
if
let
Ok
(
Some
(
value
))
=
global_mem
.read
(
&
global_address
,
size
)
{
value
.into
()
}
else
{
DataDomain
::
new_top
(
size
)
}
}
else
{
DataDomain
::
new_top
(
size
)
}
...
...
@@ -220,6 +229,19 @@ impl State {
None
}
/// Merges the access pattern of the given abstract identifer in `self` with the provided access pattern.
///
/// Does not add the identifier to the list of tracked identifiers if it is not already tracked in `self`.
pub
fn
merge_access_pattern_of_id
(
&
mut
self
,
id
:
&
AbstractIdentifier
,
access_pattern
:
&
AccessPattern
,
)
{
if
let
Some
(
object
)
=
self
.tracked_ids
.get_mut
(
id
)
{
*
object
=
object
.merge
(
access_pattern
);
}
}
/// Evaluate the value of the given expression on the current state.
pub
fn
eval
(
&
self
,
expression
:
&
Expression
)
->
DataDomain
<
BitvectorDomain
>
{
match
expression
{
...
...
@@ -255,7 +277,7 @@ impl State {
}
=>
{
self
.set_deref_flag_for_input_ids_of_expression
(
address
);
let
address
=
self
.eval
(
address
);
self
.load_value
(
address
,
*
size
)
self
.load_value
(
address
,
*
size
,
None
)
}
}
}
...
...
src/cwe_checker_lib/src/analysis/function_signature/state/call_handling.rs
View file @
99042d01
...
...
@@ -5,15 +5,15 @@ impl State {
///
/// 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
(
pub
fn
handle_
generic_
extern_symbol
(
&
mut
self
,
call
:
&
Term
<
Jmp
>
,
call
_tid
:
&
Tid
,
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
);
self
.generate_return_values_for_call
(
&
input_ids
,
&
extern_symbol
.return_values
,
call_
tid
);
}
/// Handle a call to a completely unknown function
...
...
src/cwe_checker_lib/src/analysis/function_signature/state/tests.rs
View file @
99042d01
...
...
@@ -78,7 +78,7 @@ fn test_store_and_load_from_stack() {
state
.stack
.get
(
Bitvector
::
from_i32
(
-
4
),
ByteSize
::
new
(
4
)),
value
.clone
()
);
assert_eq!
(
state
.load_value
(
address
,
ByteSize
::
new
(
4
)),
value
);
assert_eq!
(
state
.load_value
(
address
,
ByteSize
::
new
(
4
)
,
None
),
value
);
// Load a parameter register and check that the parameter gets generated
let
address
=
DataDomain
::
from_target
(
mock_stack_id
(),
Bitvector
::
from_i32
(
4
)
.into
());
let
stack_param_id
=
mock_stack_param_id
(
4
,
4
);
...
...
@@ -86,7 +86,7 @@ fn test_store_and_load_from_stack() {
DataDomain
::
from_target
(
stack_param_id
.clone
(),
Bitvector
::
from_i32
(
0
)
.into
());
assert_eq!
(
state
.tracked_ids
.iter
()
.len
(),
6
);
assert_eq!
(
state
.load_value
(
address
.clone
(),
ByteSize
::
new
(
4
)),
state
.load_value
(
address
.clone
(),
ByteSize
::
new
(
4
)
,
None
),
stack_param
);
assert_eq!
(
state
.tracked_ids
.iter
()
.len
(),
7
);
...
...
@@ -108,7 +108,7 @@ fn test_load_unsized_from_stack() {
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
));
state
.load_value
(
address
,
ByteSize
::
new
(
4
)
,
None
);
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
());
...
...
@@ -145,18 +145,12 @@ fn test_extern_symbol_handling() {
let
mut
state
=
State
::
mock_arm32
();
let
extern_symbol
=
ExternSymbol
::
mock_arm32
(
"mock_symbol"
);
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
call_tid
=
Tid
::
new
(
"call_tid"
);
let
param_id
=
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"mock_fn"
),
&
Variable
::
mock
(
"r0"
,
4
));
let
return_val_id
=
AbstractIdentifier
::
from_var
(
Tid
::
new
(
"call_tid"
),
&
Variable
::
mock
(
"r0"
,
4
));
// Test extern symbol handling.
state
.handle_
extern_symbol
(
&
call
,
&
extern_symbol
,
&
cconv
);
state
.handle_
generic_extern_symbol
(
&
call_tid
,
&
extern_symbol
,
&
cconv
);
assert_eq!
(
state
.tracked_ids
...
...
src/cwe_checker_lib/src/analysis/function_signature/stubs.rs
0 → 100644
View file @
99042d01
use
super
::
State
;
use
crate
::
abstract_domain
::
AbstractDomain
;
use
crate
::
abstract_domain
::
BitvectorDomain
;
use
crate
::
abstract_domain
::
DataDomain
;
use
crate
::
abstract_domain
::
SizedDomain
;
use
crate
::
intermediate_representation
::
Project
;
use
crate
::{
analysis
::
function_signature
::
AccessPattern
,
intermediate_representation
::
ExternSymbol
,
prelude
::
*
,
};
use
std
::
collections
::
BTreeMap
;
/// Returns a map that maps the names of known extern functions to the access patterns for their parameters.
///
/// The access patterns are ordered in the same order as the parameters
/// (i.e. the first access pattern corresponds to the first parameter and so on).
pub
fn
generate_param_access_stubs
()
->
BTreeMap
<&
'static
str
,
Vec
<
AccessPattern
>>
{
let
read
=
||
AccessPattern
::
new
()
.with_read_flag
();
let
deref
=
||
{
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
()
};
let
deref_mut
=
||
{
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
()
.with_mutably_dereferenced_flag
()
};
BTreeMap
::
from
([
(
"abort"
,
vec!
[]),
(
"atoi"
,
vec!
[
deref
()]),
(
"bind"
,
vec!
[
read
(),
deref
(),
read
()]),
(
"calloc"
,
vec!
[
read
(),
read
()]),
(
"close"
,
vec!
[
read
()]),
(
"connect"
,
vec!
[
read
(),
deref
(),
read
()]),
(
"exit"
,
vec!
[
read
()]),
(
"fclose"
,
vec!
[
deref_mut
()]),
(
"fflush"
,
vec!
[
deref_mut
()]),
(
"fgets"
,
vec!
[
deref_mut
(),
read
(),
deref_mut
()]),
(
"fopen"
,
vec!
[
deref
(),
deref
()]),
(
"fork"
,
vec!
[]),
(
"fprintf"
,
vec!
[
deref_mut
(),
deref
()]),
(
"fputc"
,
vec!
[
read
(),
deref_mut
()]),
(
"fputs"
,
vec!
[
deref
(),
deref_mut
()]),
(
"fread"
,
vec!
[
deref_mut
(),
read
(),
read
(),
deref_mut
()]),
(
"free"
,
vec!
[
deref_mut
()]),
(
"fwrite"
,
vec!
[
deref
(),
read
(),
read
(),
deref_mut
()]),
(
"getenv"
,
vec!
[
deref
()]),
// FIXME: Not exactly allocating, but still returns a pointer to another memory region.
(
"getpid"
,
vec!
[]),
(
"getppid"
,
vec!
[]),
(
"gettimeofday"
,
vec!
[
deref_mut
(),
deref_mut
()]),
(
"kill"
,
vec!
[
read
(),
read
()]),
(
"localtime"
,
vec!
[
deref
()]),
// FIXME: The return value is a pointer to static storage.
(
"malloc"
,
vec!
[
read
()]),
(
"memcmp"
,
vec!
[
deref
(),
deref
(),
read
()]),
(
"memcpy"
,
vec!
[
deref_mut
(),
deref
(),
read
()]),
(
"memmove"
,
vec!
[
deref_mut
(),
deref
(),
read
()]),
(
"memset"
,
vec!
[
deref_mut
(),
read
(),
read
()]),
(
"open"
,
vec!
[
deref
(),
read
(),
read
()]),
(
"open64"
,
vec!
[
deref
(),
read
(),
read
()]),
(
"perror"
,
vec!
[
deref
()]),
(
"printf"
,
vec!
[
deref
()]),
(
"putchar"
,
vec!
[
read
()]),
(
"puts"
,
vec!
[
deref
()]),
(
"qsort"
,
vec!
[
deref_mut
(),
read
(),
read
(),
deref
()]),
(
"raise"
,
vec!
[]),
(
"read"
,
vec!
[
read
(),
deref_mut
(),
read
()]),
(
"realloc"
,
vec!
[
deref_mut
(),
read
()]),
(
"recv"
,
vec!
[
read
(),
deref_mut
(),
read
(),
read
()]),
(
"recvfrom"
,
vec!
[
read
(),
deref_mut
(),
read
(),
read
(),
deref_mut
(),
deref_mut
(),
],
),
(
"select"
,
vec!
[
read
(),
deref_mut
(),
deref_mut
(),
deref_mut
(),
deref
()],
),
(
"sendto"
,
vec!
[
read
(),
deref
(),
read
(),
read
(),
deref
(),
read
()],
),
(
"setsockopt"
,
vec!
[
read
(),
read
(),
read
(),
deref_mut
(),
read
()],
),
// FIXME: The deref_mut parameter may only be deref?
(
"signal"
,
vec!
[
read
(),
read
()]),
(
"sleep"
,
vec!
[
read
()]),
(
"snprintf"
,
vec!
[
deref_mut
(),
read
(),
deref
()]),
(
"socket"
,
vec!
[
read
(),
read
(),
read
()]),
(
"sprintf"
,
vec!
[
deref_mut
(),
deref
()]),
(
"sscanf"
,
vec!
[
deref
(),
deref
()]),
(
"strcasecmp"
,
vec!
[
deref
(),
deref
()]),
(
"strcat"
,
vec!
[
deref_mut
(),
deref
()]),
(
"strchr"
,
vec!
[
deref
(),
read
()]),
(
"strcmp"
,
vec!
[
deref
(),
deref
()]),
(
"strcpy"
,
vec!
[
deref_mut
(),
deref
()]),
(
"strdup"
,
vec!
[
deref
()]),
(
"strerror"
,
vec!
[
read
()]),
(
"strlen"
,
vec!
[
deref
()]),
(
"strncasecmp"
,
vec!
[
deref
(),
deref
(),
read
()]),
(
"strncat"
,
vec!
[
deref_mut
(),
deref
(),
read
()]),
(
"strncmp"
,
vec!
[
deref
(),
deref
(),
read
()]),
(
"strncpy"
,
vec!
[
deref_mut
(),
deref
(),
read
()]),
(
"strrchr"
,
vec!
[
deref
(),
read
()]),
(
"strstr"
,
vec!
[
deref
(),
deref
()]),
(
"strtol"
,
vec!
[
deref
(),
deref_mut
(),
read
()]),
// FIXME: We could specify the value written to the second parameter.
(
"strtoul"
,
vec!
[
deref
(),
deref_mut
(),
read
()]),
// FIXME: We could specify the value written to the second parameter.
(
"system"
,
vec!
[
deref
()]),
(
"time"
,
vec!
[
deref_mut
()]),
(
"unlink"
,
vec!
[
deref
()]),
(
"vfprintf"
,
vec!
[
deref_mut
(),
deref
(),
deref
()]),
(
"write"
,
vec!
[
read
(),
deref
(),
read
()]),
])
}
/// Return a map that maps names of stubbed variadic symbols to a tuple consisting of:
/// - the index of the format string parameter of the symbol
/// - the access pattern that the called symbols uses to access its variadic parameters.
/// Note that the access pattern may vary between variadic parameters,
/// e.g. some parameters may only be read and not derefenced by a call to `printf`.
/// But we still approximate all accesses by the the maximal possible access to these parameters.
pub
fn
get_stubbed_variadic_symbols
()
->
BTreeMap
<&
'static
str
,
(
usize
,
AccessPattern
)
>
{
let
deref
=
||
{
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
()
};
let
deref_mut
=
||
{
AccessPattern
::
new
()
.with_read_flag
()
.with_dereference_flag
()
.with_mutably_dereferenced_flag
()
};
BTreeMap
::
from
([
(
"fprintf"
,
(
1
,
deref
())),
(
"printf"
,
(
0
,
deref
())),
(
"snprintf"
,
(
2
,
deref
())),
(
"sprintf"
,
(
1
,
deref
())),
(
"sscanf"
,
(
1
,
deref_mut
())),
])
}
/// Compute the return value of a call to a known extern symbol from the given state.
///
/// Note that this function needs to be called before non-callee-saved registers are cleared from the state,
/// since the return value is usually computed out of the parameter values.
///
/// This function should only be called for symbols contained in the list returned by [generate_param_access_stubs],
/// since it assumes untracked return values (e.g. integers or void) for all not explicitly handled symbols.
pub
fn
compute_return_value_for_stubbed_function
(
project
:
&
Project
,
state
:
&
mut
State
,
extern_symbol
:
&
ExternSymbol
,
call_tid
:
&
Tid
,
)
->
DataDomain
<
BitvectorDomain
>
{
use
return_value_stubs
::
*
;
match
extern_symbol
.name
.as_str
()
{
"memcpy"
|
"memmove"
|
"memset"
|
"strcat"
|
"strcpy"
|
"strncat"
|
"strncpy"
=>
{
copy_param
(
state
,
extern_symbol
,
0
)
}
"fgets"
=>
or_null
(
copy_param
(
state
,
extern_symbol
,
0
)),
"calloc"
|
"fopen"
|
"malloc"
|
"strdup"
=>
{
or_null
(
new_mem_object_id
(
call_tid
,
&
extern_symbol
.return_values
[
0
]))
}
"realloc"
=>
or_null
(
copy_param
(
state
,
extern_symbol
,
0
)
.merge
(
&
new_mem_object_id
(
call_tid
,
&
extern_symbol
.return_values
[
0
],
)),
),
"strchr"
|
"strrchr"
|
"strstr"
=>
{
or_null
(
param_plus_unknown_offset
(
state
,
extern_symbol
,
0
))
}
_
=>
untracked
(
project
.stack_pointer_register.size
),
}
}
/// Helper functions for computing return values for extern symbol calls.
pub
mod
return_value_stubs
{
use
crate
::{
abstract_domain
::
AbstractIdentifier
,
intermediate_representation
::
Arg
};
use
super
::
*
;
/// An untracked value is just a `Top` value.
/// It is used for any non-pointer return values.
pub
fn
untracked
(
register_size
:
ByteSize
)
->
DataDomain
<
BitvectorDomain
>
{
DataDomain
::
new_top
(
register_size
)
}
/// A return value that is just a copy of a parameter.
pub
fn
copy_param
(
state
:
&
mut
State
,
extern_symbol
:
&
ExternSymbol
,
param_index
:
usize
,
)
->
DataDomain
<
BitvectorDomain
>
{
state
.eval_parameter_arg
(
&
extern_symbol
.parameters
[
param_index
])
}
/// A return value that contains a pointer to the start of a new memory object.
/// The ID of the memory object is given by the return register and the TID of the call instruction.
pub
fn
new_mem_object_id
(
call_tid
:
&
Tid
,
return_arg
:
&
Arg
)
->
DataDomain
<
BitvectorDomain
>
{
DataDomain
::
from_target
(
AbstractIdentifier
::
from_arg
(
call_tid
,
return_arg
),
Bitvector
::
zero
(
return_arg
.bytesize
()
.into
())
.into
(),
)
}
/// A return value that adds an unknown offset to a given parameter.
/// E.g. if the parameter is a pointer to a string,
/// this return value would describe a pointer to an offset inside the string.
pub
fn
param_plus_unknown_offset
(
state
:
&
mut
State
,
extern_symbol
:
&
ExternSymbol
,
param_index
:
usize
,
)
->
DataDomain
<
BitvectorDomain
>
{
let
param
=
state
.eval_parameter_arg
(
&
extern_symbol
.parameters
[
param_index
]);
param
.add_offset
(
&
BitvectorDomain
::
new_top
(
param
.bytesize
()))
}
/// The return value may also be zero in addition to its other possible values.
pub
fn
or_null
(
data
:
DataDomain
<
BitvectorDomain
>
)
->
DataDomain
<
BitvectorDomain
>
{
data
.merge
(
&
Bitvector
::
zero
(
data
.bytesize
()
.into
())
.into
())
}
}
src/cwe_checker_lib/src/checkers/cwe_134.rs
View file @
99042d01
...
...
@@ -218,7 +218,7 @@ pub mod tests {
#[test]
fn
test_locate_format_string
()
{
let
sprintf_symbol
=
ExternSymbol
::
mock_s
tring
();
let
sprintf_symbol
=
ExternSymbol
::
mock_s
printf_x64
();
let
project
=
mock_project
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
project
);
...
...
src/cwe_checker_lib/src/intermediate_representation/sub.rs
View file @
99042d01
...
...
@@ -375,7 +375,7 @@ mod tests {
}
}
pub
fn
mock_s
tring
()
->
Self
{
pub
fn
mock_s
printf_x64
()
->
Self
{
ExternSymbol
{
tid
:
Tid
::
new
(
"sprintf"
),
addresses
:
vec!
[
"UNKNOWN"
.to_string
()],
...
...
src/cwe_checker_lib/src/utils/arguments.rs
View file @
99042d01
...
...
@@ -2,8 +2,7 @@
use
crate
::
prelude
::
*
;
use
crate
::{
abstract_domain
::{
IntervalDomain
,
TryToBitvec
},
analysis
::
pointer_inference
::
State
as
PointerInferenceState
,
abstract_domain
::
TryToBitvec
,
analysis
::
pointer_inference
::
State
as
PointerInferenceState
,
intermediate_representation
::
*
,
};
use
regex
::
Regex
;
...
...
@@ -22,8 +21,9 @@ pub fn get_input_format_string(
.as_ref
()
.map
(|
param
|
param
.get_if_absolute_value
())
{
let
address
=
address
.try_to_bitvec
()
?
;
return
parse_format_string_destination_and_return_content
(
address
.clone
()
,
address
,
runtime_memory_image
,
);
}
...
...
@@ -42,19 +42,13 @@ pub fn get_input_format_string(
/// It checks whether the address points to another pointer in memory.
/// If so, it will use the target address of that pointer read the format string from memory.
pub
fn
parse_format_string_destination_and_return_content
(
address
:
IntervalDomain
,
address
:
Bitvector
,
runtime_memory_image
:
&
RuntimeMemoryImage
,
)
->
Result
<
String
,
Error
>
{
if
let
Ok
(
address_vector
)
=
address
.try_to_bitvec
()
{
return
match
runtime_memory_image
.read_string_until_null_terminator
(
&
address_vector
)
{
match
runtime_memory_image
.read_string_until_null_terminator
(
&
address
)
{
Ok
(
format_string
)
=>
Ok
(
format_string
.to_string
()),
Err
(
e
)
=>
Err
(
anyhow!
(
"{}"
,
e
)),
};
}
Err
(
anyhow!
(
"Could not translate format string address to bitvector."
))
}
/// Parses the format string parameters using a regex, determines their data types,
...
...
@@ -124,10 +118,8 @@ pub fn get_variable_parameters(
Ok
(
parameters
)
=>
{
return
Ok
(
calculate_parameter_locations
(
parameters
,
project
.get_calling_convention
(
extern_symbol
),
format_string_index
,
&
project
.stack_pointer_register
,
&
project
.cpu_architecture
,
extern_symbol
,
project
,
));
}
Err
(
e
)
=>
{
...
...
@@ -145,24 +137,31 @@ pub fn get_variable_parameters(
/// Calculates the register and stack positions of format string parameters.
/// The parameters are then returned as an argument vector for later tainting.
pub
fn
calculate_parameter_locations
(
parameters
:
Vec
<
(
Datatype
,
ByteSize
)
>
,
calling_convention
:
&
CallingConvention
,
format_string_index
:
usize
,
stack_register
:
&
Variable
,
cpu_arch
:
&
str
,
variadic_parameters
:
Vec
<
(
Datatype
,
ByteSize
)
>
,
extern_symbol
:
&
ExternSymbol
,
project
:
&
Project
,
)
->
Vec
<
Arg
>
{
let
calling_convention
=
project
.get_calling_convention
(
extern_symbol
);
let
mut
var_args
:
Vec
<
Arg
>
=
Vec
::
new
();
// The number of the remaining integer argument registers are calculated
// from the format string position since it is the last fixed argument.
let
mut
integer_arg_register_count
=
calling_convention
.integer_parameter_register
.len
()
-
(
format_string_index
+
1
);
let
mut
float_arg_register_count
=
calling_convention
.float_parameter_register
.len
();
let
mut
stack_offset
:
i64
=
match
cpu_arch
{
"x86"
|
"x86_32"
|
"x86_64"
=>
u64
::
from
(
stack
_register
.size
)
as
i64
,
let
mut
stack_offset
:
i64
=
match
project
.cpu_architecture
.as_str
()
{
"x86"
|
"x86_32"
|
"x86_64"
=>
u64
::
from
(
project
.stack_pointer
_register.size
)
as
i64
,
_
=>
0
,
};
let
mut
integer_arg_register_count
=
if
calling_convention
.integer_parameter_register
.len
()
>=
extern_symbol
.parameters
.len
()
{
calling_convention
.integer_parameter_register
.len
()
-
extern_symbol
.parameters
.len
()
}
else
{
for
param
in
extern_symbol
.parameters
.iter
()
{
if
let
Ok
(
offset
)
=
param
.eval_stack_offset
()
{
let
offset_after
=
offset
.try_to_u64
()
.unwrap
()
+
u64
::
from
(
param
.bytesize
());
stack_offset
=
std
::
cmp
::
max
(
stack_offset
,
offset_after
as
i64
);
}
}
0
};
for
(
data_type
,
size
)
in
parameters
.iter
()
{
for
(
data_type
,
size
)
in
variadic_
parameters
.iter
()
{
match
data_type
{
Datatype
::
Integer
|
Datatype
::
Pointer
|
Datatype
::
Char
=>
{
if
integer_arg_register_count
>
0
{
...
...
@@ -183,7 +182,7 @@ pub fn calculate_parameter_locations(
*
size
,
stack_offset
,
data_type
.clone
(),
stack
_register
,
&
project
.stack_pointer
_register
,
));
stack_offset
+=
u64
::
from
(
*
size
)
as
i64
}
...
...
@@ -204,7 +203,7 @@ pub fn calculate_parameter_locations(
*
size
,
stack_offset
,
data_type
.clone
(),
stack
_register
,
&
project
.stack_pointer
_register
,
));
stack_offset
+=
u64
::
from
(
*
size
)
as
i64
}
...
...
@@ -217,7 +216,7 @@ pub fn calculate_parameter_locations(
}
/// Creates a stack parameter given a size, stack offset and data type.
pub
fn
create_stack_arg
(
fn
create_stack_arg
(
size
:
ByteSize
,
stack_offset
:
i64
,
data_type
:
Datatype
,
...
...
@@ -231,7 +230,7 @@ pub fn create_stack_arg(
}
/// Creates a register parameter given a size, register name and data type.
pub
fn
create_register_arg
(
expr
:
Expression
,
data_type
:
Datatype
)
->
Arg
{
fn
create_register_arg
(
expr
:
Expression
,
data_type
:
Datatype
)
->
Arg
{
Arg
::
Register
{
expr
,
data_type
:
Some
(
data_type
),
...
...
src/cwe_checker_lib/src/utils/arguments/tests.rs
View file @
99042d01
use
crate
::
intermediate_representation
::{
Bitvector
,
Tid
};
use
crate
::{
abstract_domain
::
IntervalDomain
,
intermediate_representation
::{
Bitvector
,
Tid
},
};
use
super
::
*
;
...
...
@@ -10,7 +13,7 @@ fn mock_pi_state() -> PointerInferenceState {
/// Tests extraction of format string parameters '/dev/sd%c%d' and 'cat %s'.
fn
test_get_variable_parameters
()
{
let
mut
pi_state
=
mock_pi_state
();
let
sprintf_symbol
=
ExternSymbol
::
mock_s
tring
();
let
sprintf_symbol
=
ExternSymbol
::
mock_s
printf_x64
();
let
mut
format_string_index_map
:
HashMap
<
String
,
usize
>
=
HashMap
::
new
();
format_string_index_map
.insert
(
"sprintf"
.to_string
(),
1
);
let
global_address
=
Bitvector
::
from_str_radix
(
16
,
"5000"
)
.unwrap
();
...
...
@@ -68,7 +71,7 @@ fn test_get_variable_parameters() {
fn
test_get_input_format_string
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_state
=
mock_pi_state
();
let
sprintf_symbol
=
ExternSymbol
::
mock_s
tring
();
let
sprintf_symbol
=
ExternSymbol
::
mock_s
printf_x64
();
let
global_address
=
Bitvector
::
from_str_radix
(
16
,
"3002"
)
.unwrap
();
pi_state
.set_register
(
...
...
@@ -85,8 +88,7 @@ fn test_get_input_format_string() {
#[test]
fn
test_parse_format_string_destination_and_return_content
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
string_address_vector
=
Bitvector
::
from_str_radix
(
16
,
"3002"
)
.unwrap
();
let
string_address
=
IntervalDomain
::
new
(
string_address_vector
.clone
(),
string_address_vector
);
let
string_address
=
Bitvector
::
from_str_radix
(
16
,
"3002"
)
.unwrap
();
assert_eq!
(
"Hello World"
,
...
...
@@ -154,8 +156,8 @@ fn test_parse_format_string_parameters() {
#[test]
/// Tests tracking of parameters according to format string
fn
test_calculate_parameter_locations
()
{
let
cconv
=
CallingConvention
::
mock_x64
();
let
format_string_index
:
usize
=
1
;
let
project
=
Project
::
mock_x64
();
let
extern_symbol
=
ExternSymbol
::
mock_sprintf_x64
()
;
let
mut
parameters
:
Vec
<
(
Datatype
,
ByteSize
)
>
=
Vec
::
new
();
parameters
.push
((
"d"
.to_string
()
.into
(),
ByteSize
::
new
(
8
)));
parameters
.push
((
"f"
.to_string
()
.into
(),
ByteSize
::
new
(
16
)));
...
...
@@ -183,13 +185,7 @@ fn test_calculate_parameter_locations() {
// Test Case 1: The string parameter is still written in the RCX register since 'f' is contained in the float register.
assert_eq!
(
expected_args
,
calculate_parameter_locations
(
parameters
.clone
(),
&
cconv
,
format_string_index
,
&
Variable
::
mock
(
"RSP"
,
8
),
"x86_64"
)
calculate_parameter_locations
(
parameters
.clone
(),
&
extern_symbol
,
&
project
,)
);
parameters
.push
((
"s"
.to_string
()
.into
(),
ByteSize
::
new
(
8
)));
...
...
@@ -213,13 +209,7 @@ fn test_calculate_parameter_locations() {
// Test Case 2: Three further string parameter does not fit into the registers anymore and one is written into the stack.
assert_eq!
(
expected_args
,
calculate_parameter_locations
(
parameters
,
&
cconv
,
format_string_index
,
&
Variable
::
mock
(
"RSP"
,
8
),
"x86_64"
)
calculate_parameter_locations
(
parameters
,
&
extern_symbol
,
&
project
)
);
}
...
...
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