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
6810c1f8
Unverified
Commit
6810c1f8
authored
Jun 07, 2021
by
Melvin Klimke
Committed by
GitHub
Jun 07, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor parameter detection (#186)
parent
c6a2741b
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
511 additions
and
537 deletions
+511
-537
parameter_detection.rs
...er_lib/src/checkers/cwe_78/context/parameter_detection.rs
+21
-214
tests.rs
.../src/checkers/cwe_78/context/parameter_detection/tests.rs
+2
-323
arguments.rs
src/cwe_checker_lib/src/utils/arguments.rs
+229
-0
tests.rs
src/cwe_checker_lib/src/utils/arguments/tests.rs
+258
-0
mod.rs
src/cwe_checker_lib/src/utils/mod.rs
+1
-0
No files found.
src/cwe_checker_lib/src/checkers/cwe_78/context/parameter_detection.rs
View file @
6810c1f8
use
petgraph
::
graph
::
NodeIndex
;
use
regex
::
Regex
;
use
crate
::
intermediate_representation
::
Arg
;
use
crate
::{
analysis
::
pointer_inference
::
State
as
PointerInferenceState
,
checkers
::
cwe_476
::
Taint
,
};
use
crate
::{
abstract_domain
::{
DataDomain
,
IntervalDomain
,
TryToBitvec
},
analysis
::{
backward_interprocedural_fixpoint
::
Context
as
_
,
interprocedural_fixpoint_generic
::
NodeValue
,
},
intermediate_representation
::{
Arg
,
ByteSize
,
CallingConvention
,
ExternSymbol
,
Variable
},
};
use
crate
::{
analysis
::
pointer_inference
::
State
as
PointerInferenceState
,
checkers
::
cwe_476
::
Taint
,
intermediate_representation
::
ExternSymbol
,
utils
::
arguments
,
};
use
super
::{
Context
,
State
};
...
...
@@ -74,7 +74,13 @@ impl<'a> Context<'a> {
.pointer_inference_results
.get_node_value
(
call_source_node
)
{
let
parameters
=
self
.get_variable_number_parameters
(
pi_state
,
user_input_symbol
);
let
parameters
=
arguments
::
get_variable_number_parameters
(
self
.project
,
pi_state
,
user_input_symbol
,
&
self
.symbol_maps.format_string_index
,
self
.runtime_memory_image
,
);
if
!
parameters
.is_empty
()
{
match
user_input_symbol
.name
.as_str
()
{
"scanf"
|
"__isoc99_scanf"
=>
{
...
...
@@ -181,7 +187,7 @@ impl<'a> Context<'a> {
let
mut
new_state
=
state
.clone
();
// Check whether the return register is tainted before the call
// If so, taint the parameter registers and memory addresses of possible stack parameters
let
return_registers
=
Context
::
get_return_registers_from_symbol
(
symbol
);
let
return_registers
=
arguments
::
get_return_registers_from_symbol
(
symbol
);
if
new_state
.check_return_registers_for_taint
(
return_registers
)
{
new_state
.remove_non_callee_saved_taint
(
symbol
.get_calling_convention
(
self
.project
));
if
let
Some
(
NodeValue
::
Value
(
pi_state
))
=
self
...
...
@@ -195,18 +201,6 @@ impl<'a> Context<'a> {
new_state
}
/// Returns all return registers of a symbol as a vector of strings.
fn
get_return_registers_from_symbol
(
symbol
:
&
ExternSymbol
)
->
Vec
<
String
>
{
symbol
.return_values
.iter
()
.filter_map
(|
ret
|
match
ret
{
Arg
::
Register
(
var
)
=>
Some
(
var
.name
.clone
()),
_
=>
None
,
})
.collect
::
<
Vec
<
String
>>
()
}
/// This function taints the registers and stack positions of the parameter pointers for string functions
/// such as sprintf, snprintf, etc.
/// The size parameter is ignored if available (e.g. snprintf, strncat etc.).
...
...
@@ -228,7 +222,13 @@ impl<'a> Context<'a> {
if
self
.is_relevant_string_function_call
(
string_symbol
,
pi_state
,
&
mut
new_state
)
{
let
mut
parameters
=
string_symbol
.parameters
.clone
();
if
string_symbol
.has_var_args
{
parameters
=
self
.get_variable_number_parameters
(
pi_state
,
string_symbol
);
parameters
=
arguments
::
get_variable_number_parameters
(
self
.project
,
pi_state
,
string_symbol
,
&
self
.symbol_maps.format_string_index
,
self
.runtime_memory_image
,
);
}
self
.taint_function_parameters
(
&
mut
new_state
,
pi_state
,
parameters
);
}
...
...
@@ -274,199 +274,6 @@ impl<'a> Context<'a> {
}
}
}
/// Parses the input format string for the corresponding string function.
pub
fn
get_input_format_string
(
&
self
,
pi_state
:
&
PointerInferenceState
,
extern_symbol
:
&
ExternSymbol
,
format_string_index
:
usize
,
)
->
String
{
if
let
Some
(
format_string
)
=
extern_symbol
.parameters
.get
(
format_string_index
)
{
if
let
Ok
(
address
)
=
pi_state
.eval_parameter_arg
(
format_string
,
&
self
.project.stack_pointer_register
,
self
.runtime_memory_image
,
)
{
self
.parse_format_string_destination_and_return_content
(
address
)
}
else
{
panic!
(
"Could not parse target address of format string pointer."
);
}
}
else
{
panic!
(
"No format string parameter at specified index {} for function {}"
,
format_string_index
,
extern_symbol
.name
);
}
}
/// Parses the destiniation address of the 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
(
&
self
,
address
:
DataDomain
<
IntervalDomain
>
,
)
->
String
{
if
let
Ok
(
address_vector
)
=
address
.try_to_bitvec
()
{
match
self
.runtime_memory_image
.read_string_until_null_terminator
(
&
address_vector
)
{
Ok
(
format_string
)
=>
format_string
.to_string
(),
Err
(
e
)
=>
panic!
(
"{}"
,
e
),
}
}
else
{
panic!
(
"Could not translate format string address to bitvector."
);
}
}
/// Parses the format string parameters using a regex, determines their data types,
/// and calculates their positions (register or memory).
pub
fn
parse_format_string_parameters
(
&
self
,
format_string
:
&
str
)
->
Vec
<
(
String
,
ByteSize
)
>
{
let
re
=
Regex
::
new
(
r
#
"
%\
d{0,2}([c,C,d,i,o,u,x,X,e,E,f,F,g,G,a,A,n,p,s,S])"
#
)
.expect
(
"No valid regex!"
);
re
.captures_iter
(
format_string
)
.map
(|
cap
|
{
(
cap
[
1
]
.to_string
(),
self
.map_format_specifier_to_bytesize
(
cap
[
1
]
.to_string
()),
)
})
.collect
()
}
/// Maps a given format specifier to the bytesize of its corresponding data type.
pub
fn
map_format_specifier_to_bytesize
(
&
self
,
specifier
:
String
)
->
ByteSize
{
if
Context
::
is_integer
(
&
specifier
)
{
return
self
.project.datatype_properties.integer_size
;
}
if
Context
::
is_float
(
&
specifier
)
{
return
self
.project.datatype_properties.double_size
;
}
if
Context
::
is_pointer
(
&
specifier
)
{
return
self
.project.datatype_properties.pointer_size
;
}
panic!
(
"Unknown format specifier."
)
}
/// Returns an argument vector of detected variable parameters if they are of type string.
pub
fn
get_variable_number_parameters
(
&
self
,
pi_state
:
&
PointerInferenceState
,
extern_symbol
:
&
ExternSymbol
,
)
->
Vec
<
Arg
>
{
let
format_string_index
=
match
self
.symbol_maps
.format_string_index
.get
(
&
extern_symbol
.name
)
{
Some
(
index
)
=>
*
index
,
None
=>
panic!
(
"External Symbol does not contain a format string parameter."
),
};
let
format_string
=
self
.get_input_format_string
(
pi_state
,
extern_symbol
,
format_string_index
);
let
parameters
=
self
.parse_format_string_parameters
(
format_string
.as_str
());
if
parameters
.iter
()
.any
(|(
specifier
,
_
)|
Context
::
is_string
(
specifier
))
{
return
self
.calculate_parameter_locations
(
parameters
,
extern_symbol
.get_calling_convention
(
self
.project
),
format_string_index
,
);
}
vec!
[]
}
/// 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
(
&
self
,
parameters
:
Vec
<
(
String
,
ByteSize
)
>
,
calling_convention
:
&
CallingConvention
,
format_string_index
:
usize
,
)
->
Vec
<
Arg
>
{
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
=
0
;
for
(
type_name
,
size
)
in
parameters
.iter
()
{
if
Context
::
is_integer
(
type_name
)
||
Context
::
is_pointer
(
type_name
)
{
if
integer_arg_register_count
>
0
{
if
Context
::
is_string
(
type_name
)
{
let
register_name
=
calling_convention
.integer_parameter_register
[
calling_convention
.integer_parameter_register
.len
()
-
integer_arg_register_count
]
.clone
();
var_args
.push
(
Context
::
create_string_register_arg
(
self
.project
.get_pointer_bytesize
(),
register_name
,
));
}
integer_arg_register_count
-
=
1
;
}
else
{
if
Context
::
is_string
(
type_name
)
{
var_args
.push
(
Context
::
create_string_stack_arg
(
*
size
,
stack_offset
));
}
stack_offset
+=
u64
::
from
(
*
size
)
as
i64
}
}
else
if
float_arg_register_count
>
0
{
float_arg_register_count
-
=
1
;
}
else
{
stack_offset
+=
u64
::
from
(
*
size
)
as
i64
;
}
}
var_args
}
/// Creates a string stack parameter given a size and stack offset.
pub
fn
create_string_stack_arg
(
size
:
ByteSize
,
stack_offset
:
i64
)
->
Arg
{
Arg
::
Stack
{
offset
:
stack_offset
,
size
,
}
}
/// Creates a string register parameter given a register name.
pub
fn
create_string_register_arg
(
size
:
ByteSize
,
register_name
:
String
)
->
Arg
{
Arg
::
Register
(
Variable
{
name
:
register_name
,
size
,
is_temp
:
false
,
})
}
/// Checks whether the format specifier is of type int.
pub
fn
is_integer
(
specifier
:
&
str
)
->
bool
{
matches!
(
specifier
,
"d"
|
"i"
|
"o"
|
"x"
|
"X"
|
"u"
|
"c"
|
"C"
)
}
/// Checks whether the format specifier is of type pointer.
pub
fn
is_pointer
(
specifier
:
&
str
)
->
bool
{
matches!
(
specifier
,
"s"
|
"S"
|
"n"
|
"p"
)
}
/// Checks whether the format specifier is of type float.
pub
fn
is_float
(
specifier
:
&
str
)
->
bool
{
matches!
(
specifier
,
"f"
|
"F"
|
"e"
|
"E"
|
"a"
|
"A"
|
"g"
|
"G"
)
}
/// Checks whether the format specifier is a string pointer
/// or a string.
pub
fn
is_string
(
specifier
:
&
str
)
->
bool
{
matches!
(
specifier
,
"s"
|
"S"
)
}
}
#[cfg(test)]
...
...
src/cwe_checker_lib/src/checkers/cwe_78/context/parameter_detection/tests.rs
View file @
6810c1f8
use
petgraph
::
graph
::
NodeIndex
;
use
crate
::
abstract_domain
::{
DataDomain
,
IntervalDomain
,
PointerDomain
};
use
crate
::
analysis
::
pointer_inference
::{
Data
,
PointerInference
as
PointerInferenceComputation
};
use
crate
::
intermediate_representation
::{
Arg
,
BinOpType
,
Bitvector
,
ByteSize
,
Expression
,
ExternSymbol
,
Tid
,
Variable
,
};
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
crate
::{
abstract_domain
::{
DataDomain
,
IntervalDomain
,
PointerDomain
},
intermediate_representation
::
CallingConvention
,
};
use
crate
::{
analysis
::
pointer_inference
::{
Data
,
PointerInference
as
PointerInferenceComputation
},
intermediate_representation
::
DatatypeProperties
,
};
use
crate
::{
checkers
::
cwe_476
::
Taint
,
utils
::
log
::
CweWarning
};
use
super
::
super
::
tests
::{
bv
,
Setup
};
...
...
@@ -475,318 +469,3 @@ fn test_is_user_input_symbol() {
assert
!
(
context
.is_user_input_symbol
(
&
scanf_symbol
));
assert
!
(
!
context
.is_user_input_symbol
(
&
memcpy_symbol
));
}
#[test]
fn
test_get_return_registers_from_symbol
()
{
assert_eq!
(
vec!
[
"RAX"
],
Context
::
get_return_registers_from_symbol
(
&
ExternSymbol
::
mock_string
())
);
}
#[test]
fn
test_get_input_format_string
()
{
let
mut
setup
=
Setup
::
new
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
setup
.project.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
,
&
graph
);
pi_results
.compute
();
let
sprintf_symbol
=
ExternSymbol
::
mock_string
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
HashMap
::
new
(),
HashMap
::
new
(),
&
pi_results
,
&
mem_image
,
);
let
global_address
=
Bitvector
::
from_str_radix
(
16
,
"3002"
)
.unwrap
();
setup
.pi_state
.set_register
(
&
Variable
::
mock
(
"RSI"
,
8
as
u64
),
DataDomain
::
Value
(
IntervalDomain
::
new
(
global_address
.clone
(),
global_address
)),
);
assert_eq!
(
"Hello World"
,
context
.get_input_format_string
(
&
setup
.pi_state
,
&
sprintf_symbol
,
1
)
);
}
#[test]
fn
test_parse_format_string_destination_and_return_content
()
{
let
setup
=
Setup
::
new
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
setup
.project.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
,
&
graph
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
HashMap
::
new
(),
HashMap
::
new
(),
&
pi_results
,
&
mem_image
,
);
// Test Case 2: Global memory location contains string itself.
let
string_address_vector
=
Bitvector
::
from_str_radix
(
16
,
"3002"
)
.unwrap
();
let
string_address
=
DataDomain
::
Value
(
IntervalDomain
::
new
(
string_address_vector
.clone
(),
string_address_vector
,
));
assert_eq!
(
"Hello World"
,
context
.parse_format_string_destination_and_return_content
(
string_address
)
);
}
#[test]
fn
test_parse_format_string_parameter
()
{
let
setup
=
Setup
::
new
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
setup
.project.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
,
&
graph
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
HashMap
::
new
(),
HashMap
::
new
(),
&
pi_results
,
&
mem_image
,
);
let
test_cases
:
Vec
<&
str
>
=
vec!
[
"
%s
\
"
%
s
\
"
%s
"
,
"ifconfig eth0 add 3ffe:501:ffff:101:2
%02x
:
%02x
ff:fe
%02x
:
%02x%02x
/64"
,
"/dev/sd
%c%d
"
,
"
%s
: Unable to open
\'
%s
\'
, errno=
%d
\n
"
,
];
let
properties
=
DatatypeProperties
::
mock
();
let
expected_outputs
:
Vec
<
Vec
<
(
String
,
ByteSize
)
>>
=
vec!
[
vec!
[
(
"s"
.to_string
(),
properties
.pointer_size
),
(
"s"
.to_string
(),
properties
.pointer_size
),
(
"s"
.to_string
(),
properties
.pointer_size
),
],
vec!
[
(
"x"
.to_string
(),
properties
.integer_size
),
(
"x"
.to_string
(),
properties
.integer_size
),
(
"x"
.to_string
(),
properties
.integer_size
),
(
"x"
.to_string
(),
properties
.integer_size
),
(
"x"
.to_string
(),
properties
.integer_size
),
],
vec!
[
(
"c"
.to_string
(),
properties
.integer_size
),
(
"d"
.to_string
(),
properties
.integer_size
),
],
vec!
[
(
"s"
.to_string
(),
properties
.pointer_size
),
(
"s"
.to_string
(),
properties
.pointer_size
),
(
"d"
.to_string
(),
properties
.integer_size
),
],
];
for
(
case
,
output
)
in
test_cases
.into_iter
()
.zip
(
expected_outputs
.into_iter
())
{
assert_eq!
(
output
,
context
.parse_format_string_parameters
(
case
));
}
}
#[test]
fn
test_map_format_specifier_to_bytesize
()
{
let
setup
=
Setup
::
new
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
setup
.project.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
,
&
graph
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
HashMap
::
new
(),
HashMap
::
new
(),
&
pi_results
,
&
mem_image
,
);
assert_eq!
(
ByteSize
::
new
(
8
),
context
.map_format_specifier_to_bytesize
(
"s"
.to_string
())
);
assert_eq!
(
ByteSize
::
new
(
8
),
context
.map_format_specifier_to_bytesize
(
"f"
.to_string
())
);
assert_eq!
(
ByteSize
::
new
(
4
),
context
.map_format_specifier_to_bytesize
(
"d"
.to_string
())
);
}
#[test]
#[should_panic]
fn
test_map_invalid_format_specifier_to_bytesize
()
{
let
setup
=
Setup
::
new
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
setup
.project.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
,
&
graph
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
HashMap
::
new
(),
HashMap
::
new
(),
&
pi_results
,
&
mem_image
,
);
context
.map_format_specifier_to_bytesize
(
"w"
.to_string
());
}
#[test]
fn
test_get_variable_number_parameters
()
{
let
mut
setup
=
Setup
::
new
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
setup
.project.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
,
&
graph
);
pi_results
.compute
();
let
sprintf_symbol
=
ExternSymbol
::
mock_string
();
let
mut
format_string_index
:
HashMap
<
String
,
usize
>
=
HashMap
::
new
();
format_string_index
.insert
(
"sprintf"
.to_string
(),
0
);
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
HashMap
::
new
(),
format_string_index
,
&
pi_results
,
&
mem_image
,
);
let
global_address
=
Bitvector
::
from_str_radix
(
16
,
"5000"
)
.unwrap
();
setup
.pi_state
.set_register
(
&
Variable
::
mock
(
"RDI"
,
8
as
u64
),
DataDomain
::
Value
(
IntervalDomain
::
new
(
global_address
.clone
(),
global_address
)),
);
let
mut
output
:
Vec
<
Arg
>
=
Vec
::
new
();
assert_eq!
(
output
,
context
.get_variable_number_parameters
(
&
setup
.pi_state
,
&
sprintf_symbol
)
);
output
.push
(
Arg
::
Stack
{
offset
:
0
,
size
:
ByteSize
::
new
(
8
),
});
let
global_address
=
Bitvector
::
from_str_radix
(
16
,
"500c"
)
.unwrap
();
setup
.pi_state
.set_register
(
&
Variable
::
mock
(
"RDI"
,
8
as
u64
),
DataDomain
::
Value
(
IntervalDomain
::
new
(
global_address
.clone
(),
global_address
)),
);
assert_eq!
(
output
,
context
.get_variable_number_parameters
(
&
setup
.pi_state
,
&
sprintf_symbol
)
);
}
#[test]
fn
test_calculate_parameter_locations
()
{
let
setup
=
Setup
::
new
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
setup
.project.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
,
&
graph
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
HashMap
::
new
(),
HashMap
::
new
(),
&
pi_results
,
&
mem_image
,
);
let
cconv
=
CallingConvention
::
mock_with_parameter_registers
(
vec!
[
"RDI"
.to_string
(),
"RSI"
.to_string
(),
"R8"
.to_string
(),
"R9"
.to_string
(),
],
vec!
[
"XMM0"
.to_string
()],
);
let
format_string_index
:
usize
=
1
;
let
mut
parameters
:
Vec
<
(
String
,
ByteSize
)
>
=
Vec
::
new
();
parameters
.push
((
"d"
.to_string
(),
ByteSize
::
new
(
4
)));
parameters
.push
((
"f"
.to_string
(),
ByteSize
::
new
(
8
)));
parameters
.push
((
"s"
.to_string
(),
ByteSize
::
new
(
4
)));
let
mut
expected_args
=
vec!
[
Arg
::
Register
(
Variable
::
mock
(
"R9"
,
ByteSize
::
new
(
8
)))];
// Test Case 1: The string parameter is still written in the R9 register since 'f' is contained in the float register.
assert_eq!
(
expected_args
,
context
.calculate_parameter_locations
(
parameters
.clone
(),
&
cconv
,
format_string_index
)
);
parameters
.push
((
"s"
.to_string
(),
ByteSize
::
new
(
4
)));
expected_args
.push
(
Arg
::
Stack
{
offset
:
0
,
size
:
ByteSize
::
new
(
4
),
});
// Test Case 2: A second string parameter does not fit into the registers anymore and is written into the stack.
assert_eq!
(
expected_args
,
context
.calculate_parameter_locations
(
parameters
,
&
cconv
,
format_string_index
)
);
}
#[test]
fn
test_create_string_stack_arg
()
{
assert_eq!
(
Arg
::
Stack
{
size
:
ByteSize
::
new
(
8
),
offset
:
8
,
},
Context
::
create_string_stack_arg
(
ByteSize
::
new
(
8
),
8
),
)
}
#[test]
fn
test_create_string_register_arg
()
{
assert_eq!
(
Arg
::
Register
(
Variable
::
mock
(
"R9"
,
ByteSize
::
new
(
8
))),
Context
::
create_string_register_arg
(
ByteSize
::
new
(
8
),
"R9"
.to_string
()),
);
}
#[test]
fn
test_is_integer
()
{
assert
!
(
Context
::
is_integer
(
"d"
));
assert
!
(
Context
::
is_integer
(
"i"
));
assert
!
(
!
Context
::
is_integer
(
"f"
));
}
#[test]
fn
test_is_pointer
()
{
assert
!
(
Context
::
is_pointer
(
"s"
));
assert
!
(
Context
::
is_pointer
(
"S"
));
assert
!
(
Context
::
is_pointer
(
"n"
));
assert
!
(
Context
::
is_pointer
(
"p"
));
assert
!
(
!
Context
::
is_pointer
(
"g"
));
}
#[test]
fn
test_is_float
()
{
assert
!
(
Context
::
is_float
(
"f"
));
assert
!
(
Context
::
is_float
(
"A"
));
assert
!
(
!
Context
::
is_float
(
"s"
));
}
#[test]
fn
test_is_string
()
{
assert
!
(
Context
::
is_string
(
"s"
));
assert
!
(
Context
::
is_string
(
"S"
));
assert
!
(
!
Context
::
is_string
(
"g"
));
}
src/cwe_checker_lib/src/utils/arguments.rs
0 → 100644
View file @
6810c1f8
//! Handles argument detection by parsing format string arguments during a function call. (e.g. sprintf)
use
std
::
collections
::
HashMap
;
use
regex
::
Regex
;
use
crate
::{
abstract_domain
::{
DataDomain
,
IntervalDomain
,
TryToBitvec
},
analysis
::
pointer_inference
::
State
as
PointerInferenceState
,
intermediate_representation
::{
Arg
,
ByteSize
,
CallingConvention
,
DatatypeProperties
,
ExternSymbol
,
Project
,
Variable
,
},
};
use
super
::
binary
::
RuntimeMemoryImage
;
/// Returns all return registers of a symbol as a vector of strings.
pub
fn
get_return_registers_from_symbol
(
symbol
:
&
ExternSymbol
)
->
Vec
<
String
>
{
symbol
.return_values
.iter
()
.filter_map
(|
ret
|
match
ret
{
Arg
::
Register
(
var
)
=>
Some
(
var
.name
.clone
()),
_
=>
None
,
})
.collect
::
<
Vec
<
String
>>
()
}
/// Parses the input format string for the corresponding string function.
pub
fn
get_input_format_string
(
pi_state
:
&
PointerInferenceState
,
extern_symbol
:
&
ExternSymbol
,
format_string_index
:
usize
,
stack_pointer_register
:
&
Variable
,
runtime_memory_image
:
&
RuntimeMemoryImage
,
)
->
String
{
if
let
Some
(
format_string
)
=
extern_symbol
.parameters
.get
(
format_string_index
)
{
if
let
Ok
(
address
)
=
pi_state
.eval_parameter_arg
(
format_string
,
&
stack_pointer_register
,
runtime_memory_image
,
)
{
parse_format_string_destination_and_return_content
(
address
,
runtime_memory_image
)
}
else
{
panic!
(
"Could not parse target address of format string pointer."
);
}
}
else
{
panic!
(
"No format string parameter at specified index {} for function {}"
,
format_string_index
,
extern_symbol
.name
);
}
}
/// Parses the destiniation address of the 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
:
DataDomain
<
IntervalDomain
>
,
runtime_memory_image
:
&
RuntimeMemoryImage
,
)
->
String
{
if
let
Ok
(
address_vector
)
=
address
.try_to_bitvec
()
{
match
runtime_memory_image
.read_string_until_null_terminator
(
&
address_vector
)
{
Ok
(
format_string
)
=>
format_string
.to_string
(),
Err
(
e
)
=>
panic!
(
"{}"
,
e
),
}
}
else
{
panic!
(
"Could not translate format string address to bitvector."
);
}
}
/// Parses the format string parameters using a regex, determines their data types,
/// and calculates their positions (register or memory).
pub
fn
parse_format_string_parameters
(
format_string
:
&
str
,
datatype_properties
:
&
DatatypeProperties
,
)
->
Vec
<
(
String
,
ByteSize
)
>
{
let
re
=
Regex
::
new
(
r
#
"
%\
d{0,2}([c,C,d,i,o,u,x,X,e,E,f,F,g,G,a,A,n,p,s,S])"
#
)
.expect
(
"No valid regex!"
);
re
.captures_iter
(
format_string
)
.map
(|
cap
|
{
(
cap
[
1
]
.to_string
(),
map_format_specifier_to_bytesize
(
datatype_properties
,
cap
[
1
]
.to_string
()),
)
})
.collect
()
}
/// Maps a given format specifier to the bytesize of its corresponding data type.
pub
fn
map_format_specifier_to_bytesize
(
datatype_properties
:
&
DatatypeProperties
,
specifier
:
String
,
)
->
ByteSize
{
if
is_integer
(
&
specifier
)
{
return
datatype_properties
.integer_size
;
}
if
is_float
(
&
specifier
)
{
return
datatype_properties
.double_size
;
}
if
is_pointer
(
&
specifier
)
{
return
datatype_properties
.pointer_size
;
}
panic!
(
"Unknown format specifier."
)
}
/// Returns an argument vector of detected variable parameters if they are of type string.
pub
fn
get_variable_number_parameters
(
project
:
&
Project
,
pi_state
:
&
PointerInferenceState
,
extern_symbol
:
&
ExternSymbol
,
format_string_index_map
:
&
HashMap
<
String
,
usize
>
,
runtime_memory_image
:
&
RuntimeMemoryImage
,
)
->
Vec
<
Arg
>
{
let
format_string_index
=
match
format_string_index_map
.get
(
&
extern_symbol
.name
)
{
Some
(
index
)
=>
*
index
,
None
=>
panic!
(
"External Symbol does not contain a format string parameter."
),
};
let
format_string
=
get_input_format_string
(
pi_state
,
extern_symbol
,
format_string_index
,
&
project
.stack_pointer_register
,
runtime_memory_image
,
);
let
parameters
=
parse_format_string_parameters
(
format_string
.as_str
(),
&
project
.datatype_properties
);
if
parameters
.iter
()
.any
(|(
specifier
,
_
)|
is_string
(
specifier
))
{
return
calculate_parameter_locations
(
project
,
parameters
,
extern_symbol
.get_calling_convention
(
project
),
format_string_index
,
);
}
vec!
[]
}
/// 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
(
project
:
&
Project
,
parameters
:
Vec
<
(
String
,
ByteSize
)
>
,
calling_convention
:
&
CallingConvention
,
format_string_index
:
usize
,
)
->
Vec
<
Arg
>
{
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
=
0
;
for
(
type_name
,
size
)
in
parameters
.iter
()
{
if
is_integer
(
type_name
)
||
is_pointer
(
type_name
)
{
if
integer_arg_register_count
>
0
{
if
is_string
(
type_name
)
{
let
register_name
=
calling_convention
.integer_parameter_register
[
calling_convention
.integer_parameter_register
.len
()
-
integer_arg_register_count
]
.clone
();
var_args
.push
(
create_string_register_arg
(
project
.get_pointer_bytesize
(),
register_name
,
));
}
integer_arg_register_count
-
=
1
;
}
else
{
if
is_string
(
type_name
)
{
var_args
.push
(
create_string_stack_arg
(
*
size
,
stack_offset
));
}
stack_offset
+=
u64
::
from
(
*
size
)
as
i64
}
}
else
if
float_arg_register_count
>
0
{
float_arg_register_count
-
=
1
;
}
else
{
stack_offset
+=
u64
::
from
(
*
size
)
as
i64
;
}
}
var_args
}
/// Creates a string stack parameter given a size and stack offset.
pub
fn
create_string_stack_arg
(
size
:
ByteSize
,
stack_offset
:
i64
)
->
Arg
{
Arg
::
Stack
{
offset
:
stack_offset
,
size
,
}
}
/// Creates a string register parameter given a register name.
pub
fn
create_string_register_arg
(
size
:
ByteSize
,
register_name
:
String
)
->
Arg
{
Arg
::
Register
(
Variable
{
name
:
register_name
,
size
,
is_temp
:
false
,
})
}
/// Checks whether the format specifier is of type int.
pub
fn
is_integer
(
specifier
:
&
str
)
->
bool
{
matches!
(
specifier
,
"d"
|
"i"
|
"o"
|
"x"
|
"X"
|
"u"
|
"c"
|
"C"
)
}
/// Checks whether the format specifier is of type pointer.
pub
fn
is_pointer
(
specifier
:
&
str
)
->
bool
{
matches!
(
specifier
,
"s"
|
"S"
|
"n"
|
"p"
)
}
/// Checks whether the format specifier is of type float.
pub
fn
is_float
(
specifier
:
&
str
)
->
bool
{
matches!
(
specifier
,
"f"
|
"F"
|
"e"
|
"E"
|
"a"
|
"A"
|
"g"
|
"G"
)
}
/// Checks whether the format specifier is a string pointer
/// or a string.
pub
fn
is_string
(
specifier
:
&
str
)
->
bool
{
matches!
(
specifier
,
"s"
|
"S"
)
}
#[cfg(test)]
mod
tests
;
src/cwe_checker_lib/src/utils/arguments/tests.rs
0 → 100644
View file @
6810c1f8
use
crate
::
intermediate_representation
::{
Bitvector
,
Tid
};
use
super
::
*
;
fn
mock_pi_state
()
->
PointerInferenceState
{
PointerInferenceState
::
new
(
&
Variable
::
mock
(
"RSP"
,
8
as
u64
),
Tid
::
new
(
"func"
))
}
#[test]
fn
test_get_return_registers_from_symbol
()
{
assert_eq!
(
vec!
[
"RAX"
],
get_return_registers_from_symbol
(
&
ExternSymbol
::
mock_string
())
);
}
#[test]
fn
test_get_variable_number_parameters
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_state
=
mock_pi_state
();
let
sprintf_symbol
=
ExternSymbol
::
mock_string
();
let
mut
format_string_index_map
:
HashMap
<
String
,
usize
>
=
HashMap
::
new
();
format_string_index_map
.insert
(
"sprintf"
.to_string
(),
0
);
let
global_address
=
Bitvector
::
from_str_radix
(
16
,
"5000"
)
.unwrap
();
pi_state
.set_register
(
&
Variable
::
mock
(
"RDI"
,
8
as
u64
),
DataDomain
::
Value
(
IntervalDomain
::
new
(
global_address
.clone
(),
global_address
)),
);
let
mut
project
=
Project
::
mock_empty
();
let
cconv
=
CallingConvention
::
mock_with_parameter_registers
(
vec!
[
"RDI"
.to_string
()],
vec!
[
"XMM0"
.to_string
()],
);
project
.calling_conventions
=
vec!
[
cconv
];
let
mut
output
:
Vec
<
Arg
>
=
Vec
::
new
();
assert_eq!
(
output
,
get_variable_number_parameters
(
&
project
,
&
pi_state
,
&
sprintf_symbol
,
&
format_string_index_map
,
&
mem_image
,
)
);
output
.push
(
Arg
::
Stack
{
offset
:
0
,
size
:
ByteSize
::
new
(
8
),
});
let
global_address
=
Bitvector
::
from_str_radix
(
16
,
"500c"
)
.unwrap
();
pi_state
.set_register
(
&
Variable
::
mock
(
"RDI"
,
8
as
u64
),
DataDomain
::
Value
(
IntervalDomain
::
new
(
global_address
.clone
(),
global_address
)),
);
assert_eq!
(
output
,
get_variable_number_parameters
(
&
project
,
&
pi_state
,
&
sprintf_symbol
,
&
format_string_index_map
,
&
mem_image
,
)
);
}
#[test]
fn
test_get_input_format_string
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_state
=
mock_pi_state
();
let
sprintf_symbol
=
ExternSymbol
::
mock_string
();
let
global_address
=
Bitvector
::
from_str_radix
(
16
,
"3002"
)
.unwrap
();
pi_state
.set_register
(
&
Variable
::
mock
(
"RSI"
,
8
as
u64
),
DataDomain
::
Value
(
IntervalDomain
::
new
(
global_address
.clone
(),
global_address
)),
);
assert_eq!
(
"Hello World"
,
get_input_format_string
(
&
pi_state
,
&
sprintf_symbol
,
1
,
&
Variable
::
mock
(
"RSP"
,
8
as
u64
),
&
mem_image
)
);
}
#[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
=
DataDomain
::
Value
(
IntervalDomain
::
new
(
string_address_vector
.clone
(),
string_address_vector
,
));
assert_eq!
(
"Hello World"
,
parse_format_string_destination_and_return_content
(
string_address
,
&
mem_image
)
);
}
#[test]
fn
test_parse_format_string_parameters
()
{
let
test_cases
:
Vec
<&
str
>
=
vec!
[
"
%s
\
"
%
s
\
"
%s
"
,
"ifconfig eth0 add 3ffe:501:ffff:101:2
%02x
:
%02x
ff:fe
%02x
:
%02x%02x
/64"
,
"/dev/sd
%c%d
"
,
"
%s
: Unable to open
\'
%s
\'
, errno=
%d
\n
"
,
];
let
properties
=
DatatypeProperties
::
mock
();
let
expected_outputs
:
Vec
<
Vec
<
(
String
,
ByteSize
)
>>
=
vec!
[
vec!
[
(
"s"
.to_string
(),
properties
.pointer_size
),
(
"s"
.to_string
(),
properties
.pointer_size
),
(
"s"
.to_string
(),
properties
.pointer_size
),
],
vec!
[
(
"x"
.to_string
(),
properties
.integer_size
),
(
"x"
.to_string
(),
properties
.integer_size
),
(
"x"
.to_string
(),
properties
.integer_size
),
(
"x"
.to_string
(),
properties
.integer_size
),
(
"x"
.to_string
(),
properties
.integer_size
),
],
vec!
[
(
"c"
.to_string
(),
properties
.integer_size
),
(
"d"
.to_string
(),
properties
.integer_size
),
],
vec!
[
(
"s"
.to_string
(),
properties
.pointer_size
),
(
"s"
.to_string
(),
properties
.pointer_size
),
(
"d"
.to_string
(),
properties
.integer_size
),
],
];
for
(
case
,
output
)
in
test_cases
.into_iter
()
.zip
(
expected_outputs
.into_iter
())
{
assert_eq!
(
output
,
parse_format_string_parameters
(
case
,
&
properties
));
}
}
#[test]
fn
test_map_format_specifier_to_bytesize
()
{
let
properties
=
DatatypeProperties
::
mock
();
assert_eq!
(
ByteSize
::
new
(
8
),
map_format_specifier_to_bytesize
(
&
properties
,
"s"
.to_string
())
);
assert_eq!
(
ByteSize
::
new
(
8
),
map_format_specifier_to_bytesize
(
&
properties
,
"f"
.to_string
())
);
assert_eq!
(
ByteSize
::
new
(
4
),
map_format_specifier_to_bytesize
(
&
properties
,
"d"
.to_string
())
);
}
#[test]
#[should_panic]
fn
test_map_invalid_format_specifier_to_bytesize
()
{
let
properties
=
DatatypeProperties
::
mock
();
map_format_specifier_to_bytesize
(
&
properties
,
"w"
.to_string
());
}
#[test]
fn
test_calculate_parameter_locations
()
{
let
project
=
Project
::
mock_empty
();
let
cconv
=
CallingConvention
::
mock_with_parameter_registers
(
vec!
[
"RDI"
.to_string
(),
"RSI"
.to_string
(),
"R8"
.to_string
(),
"R9"
.to_string
(),
],
vec!
[
"XMM0"
.to_string
()],
);
let
format_string_index
:
usize
=
1
;
let
mut
parameters
:
Vec
<
(
String
,
ByteSize
)
>
=
Vec
::
new
();
parameters
.push
((
"d"
.to_string
(),
ByteSize
::
new
(
4
)));
parameters
.push
((
"f"
.to_string
(),
ByteSize
::
new
(
8
)));
parameters
.push
((
"s"
.to_string
(),
ByteSize
::
new
(
4
)));
let
mut
expected_args
=
vec!
[
Arg
::
Register
(
Variable
::
mock
(
"R9"
,
ByteSize
::
new
(
8
)))];
// Test Case 1: The string parameter is still written in the R9 register since 'f' is contained in the float register.
assert_eq!
(
expected_args
,
calculate_parameter_locations
(
&
project
,
parameters
.clone
(),
&
cconv
,
format_string_index
)
);
parameters
.push
((
"s"
.to_string
(),
ByteSize
::
new
(
4
)));
expected_args
.push
(
Arg
::
Stack
{
offset
:
0
,
size
:
ByteSize
::
new
(
4
),
});
// Test Case 2: A second string parameter does not fit into the registers anymore and is written into the stack.
assert_eq!
(
expected_args
,
calculate_parameter_locations
(
&
project
,
parameters
,
&
cconv
,
format_string_index
)
);
}
#[test]
fn
test_create_string_stack_arg
()
{
assert_eq!
(
Arg
::
Stack
{
size
:
ByteSize
::
new
(
8
),
offset
:
8
,
},
create_string_stack_arg
(
ByteSize
::
new
(
8
),
8
),
)
}
#[test]
fn
test_create_string_register_arg
()
{
assert_eq!
(
Arg
::
Register
(
Variable
::
mock
(
"R9"
,
ByteSize
::
new
(
8
))),
create_string_register_arg
(
ByteSize
::
new
(
8
),
"R9"
.to_string
()),
);
}
#[test]
fn
test_is_integer
()
{
assert
!
(
is_integer
(
"d"
));
assert
!
(
is_integer
(
"i"
));
assert
!
(
!
is_integer
(
"f"
));
}
#[test]
fn
test_is_pointer
()
{
assert
!
(
is_pointer
(
"s"
));
assert
!
(
is_pointer
(
"S"
));
assert
!
(
is_pointer
(
"n"
));
assert
!
(
is_pointer
(
"p"
));
assert
!
(
!
is_pointer
(
"g"
));
}
#[test]
fn
test_is_string
()
{
assert
!
(
is_string
(
"s"
));
assert
!
(
is_string
(
"S"
));
assert
!
(
!
is_string
(
"g"
));
}
#[test]
fn
test_is_float
()
{
assert
!
(
is_float
(
"f"
));
assert
!
(
is_float
(
"A"
));
assert
!
(
!
is_float
(
"s"
));
}
src/cwe_checker_lib/src/utils/mod.rs
View file @
6810c1f8
//! This module contains various utility modules and helper functions.
pub
mod
arguments
;
pub
mod
binary
;
pub
mod
graph_utils
;
pub
mod
log
;
...
...
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