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
d926369b
Unverified
Commit
d926369b
authored
4 years ago
by
Melvin Klimke
Committed by
GitHub
4 years ago
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
add check for CWE 78: OS Command Injection (#130)
parent
9a414bab
Show whitespace changes
Inline
Side-by-side
Showing
19 changed files
with
3069 additions
and
13 deletions
+3069
-13
main.rs
caller/src/main.rs
+2
-1
mem_region.rs
cwe_checker_rs/src/abstract_domain/mem_region.rs
+5
-0
mock_context.rs
...nalysis/backward_interprocedural_fixpoint/mock_context.rs
+6
-1
mod.rs
..._rs/src/analysis/backward_interprocedural_fixpoint/mod.rs
+20
-8
mod.rs
cwe_checker_rs/src/analysis/pointer_inference/state/mod.rs
+1
-1
checkers.rs
cwe_checker_rs/src/checkers.rs
+1
-0
cwe_476.rs
cwe_checker_rs/src/checkers/cwe_476.rs
+2
-2
cwe_78.rs
cwe_checker_rs/src/checkers/cwe_78.rs
+204
-0
mod.rs
cwe_checker_rs/src/checkers/cwe_78/context/mod.rs
+623
-0
tests.rs
cwe_checker_rs/src/checkers/cwe_78/context/tests.rs
+1036
-0
mod.rs
cwe_checker_rs/src/checkers/cwe_78/state/mod.rs
+457
-0
tests.rs
cwe_checker_rs/src/checkers/cwe_78/state/tests.rs
+542
-0
builder.rs
..._rs/src/intermediate_representation/expression/builder.rs
+48
-0
term.rs
cwe_checker_rs/src/intermediate_representation/term.rs
+2
-0
builder.rs
...hecker_rs/src/intermediate_representation/term/builder.rs
+57
-0
lib.rs
cwe_checker_rs/src/lib.rs
+1
-0
config.json
src/config.json
+16
-0
cwe_78.c
test/artificial_samples/cwe_78.c
+14
-0
lib.rs
test/src/lib.rs
+32
-0
No files found.
caller/src/main.rs
View file @
d926369b
...
@@ -149,7 +149,8 @@ fn run_with_ghidra(args: CmdlineArgs) {
...
@@ -149,7 +149,8 @@ fn run_with_ghidra(args: CmdlineArgs) {
let
mut
analysis_results
=
AnalysisResults
::
new
(
&
binary
,
&
runtime_memory_image
,
&
project
);
let
mut
analysis_results
=
AnalysisResults
::
new
(
&
binary
,
&
runtime_memory_image
,
&
project
);
let
modules_depending_on_pointer_inference
=
vec!
[
"CWE243"
,
"CWE367"
,
"CWE476"
,
"Memory"
];
let
modules_depending_on_pointer_inference
=
vec!
[
"CWE78"
,
"CWE243"
,
"CWE367"
,
"CWE476"
,
"Memory"
];
let
pointer_inference_results
=
if
modules
let
pointer_inference_results
=
if
modules
.iter
()
.iter
()
.any
(|
module
|
modules_depending_on_pointer_inference
.contains
(
&
module
.name
))
.any
(|
module
|
modules_depending_on_pointer_inference
.contains
(
&
module
.name
))
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/abstract_domain/mem_region.rs
View file @
d926369b
...
@@ -200,6 +200,11 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
...
@@ -200,6 +200,11 @@ impl<T: AbstractDomain + SizedDomain + HasTop + std::fmt::Debug> MemRegionData<T
self
.values
.values
()
self
.values
.values
()
}
}
/// Get the map of all elements including their offset into the memory region.
pub
fn
entry_map
(
&
self
)
->
&
BTreeMap
<
i64
,
T
>
{
&
self
.values
}
/// Get an iterator over all values in the memory region for in-place manipulation.
/// Get an iterator over all values in the memory region for in-place manipulation.
/// Note that one can changes values to *Top* using the iterator.
/// Note that one can changes values to *Top* using the iterator.
/// These values should be removed from the memory region using `clear_top_values()`.
/// These values should be removed from the memory region using `clear_top_values()`.
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/analysis/backward_interprocedural_fixpoint/mock_context.rs
View file @
d926369b
...
@@ -88,6 +88,7 @@ impl<'a> crate::analysis::backward_interprocedural_fixpoint::Context<'a> for Con
...
@@ -88,6 +88,7 @@ impl<'a> crate::analysis::backward_interprocedural_fixpoint::Context<'a> for Con
&
self
,
&
self
,
target_value
:
Option
<&
u64
>
,
target_value
:
Option
<&
u64
>
,
return_value
:
Option
<&
u64
>
,
return_value
:
Option
<&
u64
>
,
_caller_sub
:
&
Term
<
Sub
>
,
_call
:
&
Term
<
Jmp
>
,
_call
:
&
Term
<
Jmp
>
,
_return_
:
&
Term
<
Jmp
>
,
_return_
:
&
Term
<
Jmp
>
,
)
->
Option
<
u64
>
{
)
->
Option
<
u64
>
{
...
@@ -105,7 +106,11 @@ impl<'a> crate::analysis::backward_interprocedural_fixpoint::Context<'a> for Con
...
@@ -105,7 +106,11 @@ impl<'a> crate::analysis::backward_interprocedural_fixpoint::Context<'a> for Con
}
}
/// Simply copy the value
/// Simply copy the value
fn
split_return_stub
(
&
self
,
combined_value
:
&
u64
)
->
Option
<
u64
>
{
fn
split_return_stub
(
&
self
,
combined_value
:
&
u64
,
_returned_from_sub
:
&
Term
<
Sub
>
,
)
->
Option
<
u64
>
{
Some
(
*
combined_value
)
Some
(
*
combined_value
)
}
}
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/analysis/backward_interprocedural_fixpoint/mod.rs
View file @
d926369b
...
@@ -59,6 +59,7 @@ pub trait Context<'a> {
...
@@ -59,6 +59,7 @@ pub trait Context<'a> {
&
self
,
&
self
,
target_value
:
Option
<&
Self
::
Value
>
,
target_value
:
Option
<&
Self
::
Value
>
,
return_value
:
Option
<&
Self
::
Value
>
,
return_value
:
Option
<&
Self
::
Value
>
,
caller_sub
:
&
Term
<
Sub
>
,
call
:
&
Term
<
Jmp
>
,
call
:
&
Term
<
Jmp
>
,
return_
:
&
Term
<
Jmp
>
,
return_
:
&
Term
<
Jmp
>
,
)
->
Option
<
Self
::
Value
>
;
)
->
Option
<
Self
::
Value
>
;
...
@@ -71,7 +72,11 @@ pub trait Context<'a> {
...
@@ -71,7 +72,11 @@ pub trait Context<'a> {
/// Transition function for return stub split.
/// Transition function for return stub split.
/// Has access to the value at the ReturnCombine node and
/// Has access to the value at the ReturnCombine node and
/// decides which data is transferred along the Return Stub Edge.
/// decides which data is transferred along the Return Stub Edge.
fn
split_return_stub
(
&
self
,
combined_value
:
&
Self
::
Value
)
->
Option
<
Self
::
Value
>
;
fn
split_return_stub
(
&
self
,
combined_value
:
&
Self
::
Value
,
returned_from_sub
:
&
Term
<
Sub
>
,
)
->
Option
<
Self
::
Value
>
;
/// Transition function for calls to functions not contained in the binary.
/// Transition function for calls to functions not contained in the binary.
/// The corresponding edge goes from the callsite to the returned-to block.
/// The corresponding edge goes from the callsite to the returned-to block.
...
@@ -165,10 +170,16 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
...
@@ -165,10 +170,16 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
}),
}),
// The user has the ability to split the node value at the BlkStart return node
// The user has the ability to split the node value at the BlkStart return node
// to only send specific data along the ReturnStub Edge to the last BlkEnd node called subroutine
// to only send specific data along the ReturnStub Edge to the last BlkEnd node called subroutine
Edge
::
CRReturnStub
=>
self
Edge
::
CRReturnStub
=>
{
.context
// The subroutine term from which the program returns
.split_return_stub
(
node_value
.unwrap_value
())
let
returned_from_sub
=
match
graph
.node_weight
(
end_node
)
{
.map
(
NodeValue
::
Value
),
Some
(
Node
::
BlkEnd
{
0
:
_
,
1
:
sub_term
})
=>
sub_term
,
_
=>
panic!
(
"Malformed Control flow graph"
),
};
self
.context
.split_return_stub
(
node_value
.unwrap_value
(),
returned_from_sub
)
.map
(
NodeValue
::
Value
)
}
// The CallCombine Edge merges the values coming in from the CallStub Edge and Call Edge
// The CallCombine Edge merges the values coming in from the CallStub Edge and Call Edge
// It also gives the user access to the call and return term.
// It also gives the user access to the call and return term.
...
@@ -178,17 +189,18 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
...
@@ -178,17 +189,18 @@ impl<'a, T: Context<'a>> GeneralFPContext for GeneralizedContext<'a, T> {
call_stub
,
call_stub
,
interprocedural_flow
,
interprocedural_flow
,
}
=>
{
}
=>
{
let
call_block
=
match
graph
.node_weight
(
start_node
)
{
let
(
call_block
,
caller_sub
)
=
match
graph
.node_weight
(
start_node
)
{
Some
(
Node
::
CallSource
{
Some
(
Node
::
CallSource
{
source
:
(
call_block
,
..
),
source
:
(
call_block
,
call_sub
),
target
:
_
,
target
:
_
,
})
=>
call_block
,
})
=>
(
call_block
,
call_sub
)
,
_
=>
panic!
(
"Malformed Control flow graph"
),
_
=>
panic!
(
"Malformed Control flow graph"
),
};
};
let
call_term
=
&
call_block
.term.jmps
[
0
];
let
call_term
=
&
call_block
.term.jmps
[
0
];
match
self
.context
.update_callsite
(
match
self
.context
.update_callsite
(
interprocedural_flow
.as_ref
(),
interprocedural_flow
.as_ref
(),
call_stub
.as_ref
(),
call_stub
.as_ref
(),
caller_sub
,
call_term
,
call_term
,
return_term
,
return_term
,
)
{
)
{
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/analysis/pointer_inference/state/mod.rs
View file @
d926369b
...
@@ -17,7 +17,7 @@ pub struct State {
...
@@ -17,7 +17,7 @@ pub struct State {
/// The list of all known memory objects.
/// The list of all known memory objects.
pub
memory
:
AbstractObjectList
,
pub
memory
:
AbstractObjectList
,
/// The abstract identifier of the current stack frame.
/// The abstract identifier of the current stack frame.
/// It points to the
to the
base of the stack frame, i.e. only negative offsets point into the current stack frame.
/// It points to the base of the stack frame, i.e. only negative offsets point into the current stack frame.
pub
stack_id
:
AbstractIdentifier
,
pub
stack_id
:
AbstractIdentifier
,
/// All known IDs of caller stack frames.
/// All known IDs of caller stack frames.
/// Note that these IDs are named after the callsite,
/// Note that these IDs are named after the callsite,
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers.rs
View file @
d926369b
...
@@ -8,4 +8,5 @@ pub mod cwe_467;
...
@@ -8,4 +8,5 @@ pub mod cwe_467;
pub
mod
cwe_476
;
pub
mod
cwe_476
;
pub
mod
cwe_560
;
pub
mod
cwe_560
;
pub
mod
cwe_676
;
pub
mod
cwe_676
;
pub
mod
cwe_78
;
pub
mod
cwe_782
;
pub
mod
cwe_782
;
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_476.rs
View file @
d926369b
...
@@ -50,8 +50,8 @@ use std::collections::HashMap;
...
@@ -50,8 +50,8 @@ use std::collections::HashMap;
mod
state
;
mod
state
;
use
state
::
*
;
use
state
::
*
;
mod
taint
;
pub
mod
taint
;
use
taint
::
*
;
pub
use
taint
::
*
;
mod
context
;
mod
context
;
use
context
::
*
;
use
context
::
*
;
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_78.rs
0 → 100644
View file @
d926369b
//! This module implements a check for CWE-78: Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').
//!
//! The software constructs all or part of an OS command using externally-influenced input from an upstream component,
//! but it does not neutralize or incorrectly neutralizes special elements that could modify the intended OS command
//! when it is sent to a downstream component.
//!
//! See <https://cwe.mitre.org/data/definitions/78.html> for a detailed description.
//!
//! ## How the check works
//!
//! Using backward dataflow analysis we search for an executation path from a system call parameter (string) to an user input
//! to identify possible command injections.
//!
//! To find relevant string related functions, such as sprintf, it is assumed that the first input parameter points
//! to the memory position that will be used as the return location. (e.g. char *strcat(char *dest, const char *src)
//! where 'char *dest' will contain the return value)
//!
//! For instance:
//! ...
//! MOV RAX, qword ptr [RBP + local_10]
//! MOV RDI, RAX // RDI is the first input parameter for the strcat call and it points to [RBP + local_10]
//! CALL strcat
//! MOV RAX, qword ptr [RBP + local_10] // In the backwards analysis [RBP + local_10] will be tainted and it contains the return value
//! ...
//!
//! ### Symbols configurable in config.json
//!
//! The symbols are the functions which
//! 1. make system calls (e.g. system)
//! 2. manipulate strings (e.g. sprintf, strcat, memcpy, etc.)
//! 3. take user input (e.g. scanf)
//!
//! ## False Positives
//!
//! - The input comes from the user but proper sanitization was not detected by the analysis even though it exists.
//! - The input comes from the user but the format string's input format could not be distinguished as non-string input.
//!
//! ## False Negatives
//!
//! - Missing Taints due to lost track of pointer targets
//! - Non tracked function parameters cause incomplete taints that could miss possible dangerous inputs
use
std
::
collections
::
HashMap
;
use
crate
::{
analysis
::{
backward_interprocedural_fixpoint
::{
create_computation
,
Context
as
_
},
graph
::{
self
,
Edge
,
Node
},
interprocedural_fixpoint_generic
::
NodeValue
,
},
intermediate_representation
::{
Jmp
,
Project
,
Sub
},
prelude
::
*
,
utils
::
log
::{
CweWarning
,
LogMessage
},
AnalysisResults
,
CweModule
,
};
use
petgraph
::{
graph
::
NodeIndex
,
visit
::
EdgeRef
};
mod
state
;
use
state
::
*
;
mod
context
;
use
context
::
*
;
pub
static
CWE_MODULE
:
CweModule
=
CweModule
{
name
:
"CWE78"
,
version
:
"0.1"
,
run
:
check_cwe
,
};
/// The configuration struct
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone)]
pub
struct
Config
{
/// The names of the system call symbols
system_symbols
:
Vec
<
String
>
,
/// The names of the string manipulating symbols
string_symbols
:
Vec
<
String
>
,
/// The name of the user input symbols
user_input_symbols
:
Vec
<
String
>
,
}
/// This check searches for system calls and sets their parameters as taint source if available.
/// Then the fixpoint computation is executed and its result may generate cwe warnings if
/// the parameters can be tracked back to user inputs
pub
fn
check_cwe
(
analysis_results
:
&
AnalysisResults
,
cwe_params
:
&
serde_json
::
Value
,
)
->
(
Vec
<
LogMessage
>
,
Vec
<
CweWarning
>
)
{
let
project
=
analysis_results
.project
;
let
pointer_inference_results
=
analysis_results
.pointer_inference
.unwrap
();
let
(
cwe_sender
,
cwe_receiver
)
=
crossbeam_channel
::
unbounded
();
let
config
:
Config
=
serde_json
::
from_value
(
cwe_params
.clone
())
.unwrap
();
let
system_symbols
=
crate
::
utils
::
symbol_utils
::
get_symbol_map
(
project
,
&
config
.system_symbols
[
..
]);
let
string_symbols
=
crate
::
utils
::
symbol_utils
::
get_symbol_map
(
project
,
&
config
.string_symbols
[
..
]);
let
user_input_symbols
=
crate
::
utils
::
symbol_utils
::
get_symbol_map
(
project
,
&
config
.user_input_symbols
[
..
]);
let
general_context
=
Context
::
new
(
project
,
analysis_results
.runtime_memory_image
,
&
pointer_inference_results
,
string_symbols
,
user_input_symbols
,
cwe_sender
,
);
let
entry_sub_to_entry_node_map
=
get_entry_sub_to_entry_node_map
(
project
,
&
general_context
);
for
edge
in
general_context
.get_pi_graph
()
.edge_references
()
{
if
let
Edge
::
ExternCallStub
(
jmp
)
=
edge
.weight
()
{
if
let
Jmp
::
Call
{
target
,
..
}
=
&
jmp
.term
{
if
let
Some
(
symbol
)
=
system_symbols
.get
(
target
)
{
let
node
=
edge
.source
();
let
current_sub
=
match
general_context
.get_pi_graph
()[
node
]
{
Node
::
BlkEnd
(
_blk
,
sub
)
=>
sub
,
_
=>
panic!
(),
};
let
mut
context
=
general_context
.clone
();
context
.set_taint_source
(
jmp
,
&
symbol
.name
,
current_sub
);
let
pi_state_at_taint_source
=
match
pointer_inference_results
.get_node_value
(
node
)
{
Some
(
NodeValue
::
Value
(
val
))
=>
Some
(
val
.clone
()),
_
=>
None
,
};
let
mut
computation
=
create_computation
(
context
.clone
(),
None
);
computation
.set_node_value
(
node
,
NodeValue
::
Value
(
State
::
new
(
symbol
,
&
project
.stack_pointer_register
,
pi_state_at_taint_source
.as_ref
(),
current_sub
,
)),
);
computation
.compute_with_max_steps
(
100
);
for
(
sub_name
,
node_index
)
in
entry_sub_to_entry_node_map
.iter
()
{
if
let
Some
(
node_weight
)
=
computation
.get_node_value
(
*
node_index
)
{
let
state
=
node_weight
.unwrap_value
();
if
!
state
.is_empty
()
{
context
.generate_cwe_warning
(
sub_name
);
}
}
}
}
}
}
}
let
mut
cwe_warnings
=
HashMap
::
new
();
for
cwe
in
cwe_receiver
.try_iter
()
{
match
&
cwe
.addresses
[
..
]
{
[
taint_source_address
,
..
]
=>
cwe_warnings
.insert
(
taint_source_address
.clone
(),
cwe
),
_
=>
panic!
(),
};
}
let
cwe_warnings
=
cwe_warnings
.into_iter
()
.map
(|(
_
,
cwe
)|
cwe
)
.collect
();
(
Vec
::
new
(),
cwe_warnings
)
}
/// Returns a map from subroutine names to their corresponding start node index
fn
get_entry_sub_to_entry_node_map
(
project
:
&
Project
,
context
:
&
Context
,
)
->
HashMap
<
String
,
NodeIndex
>
{
let
mut
entry_sub_to_entry_blocks_map
=
HashMap
::
new
();
let
subs
:
HashMap
<
Tid
,
&
Term
<
Sub
>>
=
project
.program
.term
.subs
.iter
()
.map
(|
sub
|
(
sub
.tid
.clone
(),
sub
))
.collect
();
for
sub_tid
in
project
.program.term.entry_points
.iter
()
{
if
let
Some
(
sub
)
=
subs
.get
(
sub_tid
)
{
if
let
Some
(
entry_block
)
=
sub
.term.blocks
.get
(
0
)
{
entry_sub_to_entry_blocks_map
.insert
(
(
sub_tid
.clone
(),
sub
.term.name
.clone
()),
entry_block
.tid
.clone
(),
);
}
}
}
let
mut
tid_to_graph_indices_map
=
HashMap
::
new
();
for
node
in
context
.get_graph
()
.node_indices
()
{
if
let
graph
::
Node
::
BlkStart
(
block
,
sub
)
=
context
.get_graph
()[
node
]
{
tid_to_graph_indices_map
.insert
((
block
.tid
.clone
(),
sub
.tid
.clone
()),
node
);
}
}
entry_sub_to_entry_blocks_map
.into_iter
()
.filter_map
(|((
sub_tid
,
name
),
block_tid
)|
{
if
let
Some
(
start_node_index
)
=
tid_to_graph_indices_map
.get
(
&
(
block_tid
,
sub_tid
))
{
Some
((
name
,
*
start_node_index
))
}
else
{
None
}
})
.collect
()
}
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_78/context/mod.rs
0 → 100644
View file @
d926369b
use
std
::{
collections
::{
HashMap
,
HashSet
},
sync
::
Arc
,
};
use
petgraph
::{
graph
::
NodeIndex
,
visit
::
IntoNodeReferences
};
use
super
::{
state
::
State
,
CWE_MODULE
};
use
crate
::{
abstract_domain
::
AbstractDomain
,
analysis
::{
forward_interprocedural_fixpoint
::
Context
as
PiContext
,
graph
::{
Graph
,
Node
},
interprocedural_fixpoint_generic
::
NodeValue
,
pointer_inference
::
PointerInference
as
PointerInferenceComputation
,
pointer_inference
::
State
as
PointerInferenceState
,
},
checkers
::
cwe_476
::
Taint
,
intermediate_representation
::
*
,
utils
::{
binary
::
RuntimeMemoryImage
,
log
::
CweWarning
},
};
#[derive(Clone)]
pub
struct
Context
<
'a
>
{
/// A pointer to the corresponding project struct.
project
:
&
'a
Project
,
/// A pointer to the representation of the runtime memory image.
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
/// The reversed control flow graph for the analysis
graph
:
Graph
<
'a
>
,
/// A pointer to the results of the pointer inference analysis.
/// They are used to determine the targets of pointers to memory,
/// which in turn is used to keep track of taint on the stack or on the heap.
pub
pointer_inference_results
:
&
'a
PointerInferenceComputation
<
'a
>
,
/// A map to get the node index of the `BlkStart` node containing a given [`Def`] as the last `Def` of the block.
/// The keys are of the form `(Def-TID, Current-Sub-TID)`
/// to distinguish the nodes for blocks contained in more than one function.
block_start_last_def_map
:
Arc
<
HashMap
<
(
Tid
,
Tid
),
NodeIndex
>>
,
/// A set containing a given [`Def`] as the first `Def` of the block.
/// The keys are of the form `(Def-TID, Current-Sub-TID)`
/// to distinguish the nodes for blocks contained in more than one function.
block_first_def_set
:
Arc
<
HashSet
<
(
Tid
,
Tid
)
>>
,
/// Maps the TID of an extern symbol to the extern symbol struct.
extern_symbol_map
:
Arc
<
HashMap
<
Tid
,
&
'a
ExternSymbol
>>
,
/// Maps the TID of an extern string related symbol to the corresponding extern symbol struct.
string_symbol_map
:
Arc
<
HashMap
<
Tid
,
&
'a
ExternSymbol
>>
,
/// Maps the TID of an extern symbol that take input from the user to the corresponding extern symbol struct.
user_input_symbol_map
:
Arc
<
HashMap
<
Tid
,
&
'a
ExternSymbol
>>
,
/// A map to get the node index of the `BlkEnd` node containing a given [`Jmp`].
/// The keys are of the form `(Jmp-TID, Current-Sub-TID)`
/// to distinguish the nodes for blocks contained in more than one function.
jmp_to_blk_end_node_map
:
Arc
<
HashMap
<
(
Tid
,
Tid
),
NodeIndex
>>
,
/// The call whose parameter values are the sources for taint for the analysis.
pub
taint_source
:
Option
<&
'a
Term
<
Jmp
>>
,
/// The subroutine from which the taint source originates
pub
taint_source_sub
:
Option
<&
'a
Term
<
Sub
>>
,
/// The name of the function, whose parameter values are the taint sources.
pub
taint_source_name
:
Option
<
String
>
,
/// A channel where found CWE hits can be sent to.
cwe_collector
:
crossbeam_channel
::
Sender
<
CweWarning
>
,
}
impl
<
'a
>
Context
<
'a
>
{
/// Creates a new context for the CWE 78 taint analysis.
pub
fn
new
(
project
:
&
'a
Project
,
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
pointer_inference_results
:
&
'a
PointerInferenceComputation
<
'a
>
,
string_symbols
:
HashMap
<
Tid
,
&
'a
ExternSymbol
>
,
user_input_symbols
:
HashMap
<
Tid
,
&
'a
ExternSymbol
>
,
cwe_collector
:
crossbeam_channel
::
Sender
<
CweWarning
>
,
)
->
Self
{
let
mut
block_first_def_set
=
HashSet
::
new
();
let
mut
block_start_last_def_map
=
HashMap
::
new
();
let
mut
extern_symbol_map
=
HashMap
::
new
();
for
symbol
in
project
.program.term.extern_symbols
.iter
()
{
extern_symbol_map
.insert
(
symbol
.tid
.clone
(),
symbol
);
}
let
mut
jmp_to_blk_end_node_map
=
HashMap
::
new
();
let
graph
=
pointer_inference_results
.get_graph
();
for
(
node_id
,
node
)
in
graph
.node_references
()
{
match
node
{
Node
::
BlkStart
(
block
,
sub
)
=>
match
block
.term.defs
.len
()
{
0
=>
(),
num_of_defs
=>
{
let
first_def
=
block
.term.defs
.get
(
0
)
.unwrap
();
let
last_def
=
block
.term.defs
.get
(
num_of_defs
-
1
)
.unwrap
();
block_first_def_set
.insert
((
first_def
.tid
.clone
(),
sub
.tid
.clone
()));
block_start_last_def_map
.insert
((
last_def
.tid
.clone
(),
sub
.tid
.clone
()),
node_id
);
}
},
Node
::
BlkEnd
(
block
,
sub
)
=>
{
for
jmp
in
block
.term.jmps
.iter
()
{
jmp_to_blk_end_node_map
.insert
((
jmp
.tid
.clone
(),
sub
.tid
.clone
()),
node_id
);
}
}
_
=>
(),
}
}
let
mut
cwe_78_graph
=
graph
.clone
();
cwe_78_graph
.reverse
();
Context
{
project
,
runtime_memory_image
,
graph
:
cwe_78_graph
,
pointer_inference_results
,
block_start_last_def_map
:
Arc
::
new
(
block_start_last_def_map
),
block_first_def_set
:
Arc
::
new
(
block_first_def_set
),
extern_symbol_map
:
Arc
::
new
(
extern_symbol_map
),
string_symbol_map
:
Arc
::
new
(
string_symbols
),
user_input_symbol_map
:
Arc
::
new
(
user_input_symbols
),
jmp_to_blk_end_node_map
:
Arc
::
new
(
jmp_to_blk_end_node_map
),
taint_source
:
None
,
taint_source_sub
:
None
,
taint_source_name
:
None
,
cwe_collector
,
}
}
/// Generates the CWE Warning for the CWE 78 check
pub
fn
generate_cwe_warning
(
&
self
,
sub_name
:
&
str
)
{
let
source
=
self
.taint_source
.unwrap
();
let
name
=
self
.taint_source_name
.clone
()
.unwrap
();
let
description
:
String
=
format!
(
"(Potential OS Command Injection) Input for call to {} is not properly sanitized in function {} ({})"
,
name
,
sub_name
,
source
.tid.address
,
);
let
cwe_warning
=
CweWarning
::
new
(
String
::
from
(
CWE_MODULE
.name
),
String
::
from
(
CWE_MODULE
.version
),
description
,
)
.addresses
(
vec!
[
source
.tid.address
.clone
()])
.tids
(
vec!
[
format!
(
"{}"
,
source
.tid
)])
.symbols
(
vec!
[
String
::
from
(
sub_name
)])
.other
(
vec!
[
vec!
[
String
::
from
(
"OS Command Injection"
),
name
]]);
let
_
=
self
.cwe_collector
.send
(
cwe_warning
);
}
/// Set the taint source and the current function for the analysis.
pub
fn
set_taint_source
(
&
mut
self
,
taint_source
:
&
'a
Term
<
Jmp
>
,
taint_source_symbol_name
:
&
str
,
taint_source_sub
:
&
'a
Term
<
Sub
>
,
)
{
self
.taint_source
=
Some
(
taint_source
);
self
.taint_source_sub
=
Some
(
taint_source_sub
);
self
.taint_source_name
=
Some
(
taint_source_symbol_name
.to_string
());
}
/// Returns the pointer inference graph
pub
fn
get_pi_graph
(
&
self
)
->
&
Graph
<
'a
>
{
self
.pointer_inference_results
.get_graph
()
}
/// 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.)
pub
fn
taint_string_function_parameters
(
&
self
,
state
:
&
State
,
string_symbol
:
&
ExternSymbol
,
call_source_node
:
NodeIndex
,
)
->
State
{
let
mut
new_state
=
state
.clone
();
if
let
Some
(
NodeValue
::
Value
(
pi_state
))
=
self
.pointer_inference_results
.get_node_value
(
call_source_node
)
{
// Check whether the parameter points to a tainted memory target
// Since the first parameter of these string functions is also the return parameter,
// this will serve as an indicator whether the function call is relevant to the taint analysis.
let
relevant_fuction_call
=
if
let
Some
(
param
)
=
string_symbol
.parameters
.get
(
0
)
{
self
.first_param_points_to_memory_taint
(
pi_state
,
&
mut
new_state
,
param
)
}
else
{
panic!
(
"Missing parameters for string related function!"
);
};
if
relevant_fuction_call
{
for
parameter
in
string_symbol
.parameters
.iter
()
{
match
parameter
{
Arg
::
Register
(
var
)
=>
{
new_state
.set_register_taint
(
var
,
Taint
::
Tainted
(
var
.size
))
}
Arg
::
Stack
{
size
,
..
}
=>
{
if
let
Ok
(
address
)
=
pi_state
.eval_parameter_arg
(
parameter
,
&
self
.project.stack_pointer_register
,
self
.runtime_memory_image
,
)
{
new_state
.save_taint_to_memory
(
&
address
,
Taint
::
Tainted
(
*
size
))
}
}
}
}
}
}
new_state
}
/// Checks whether the firt parameter of a string related function points to a taint.
/// If so, removes the taint at the target memory.
pub
fn
first_param_points_to_memory_taint
(
&
self
,
pi_state
:
&
PointerInferenceState
,
state
:
&
mut
State
,
parameter
:
&
Arg
,
)
->
bool
{
if
let
Ok
(
address
)
=
pi_state
.eval_parameter_arg
(
parameter
,
&
self
.project.stack_pointer_register
,
self
.runtime_memory_image
,
)
{
if
state
.check_if_address_points_to_taint
(
address
.clone
(),
pi_state
)
{
state
.remove_mem_taint_at_target
(
&
address
);
return
true
;
}
}
false
}
/// This function taints the registers and stack positions of the parameter pointers of external functions
/// If the function is one of the specified string functions, the processing of the call is transferred to
/// the string function processor
pub
fn
taint_generic_function_parameters_and_remove_non_callee_saved
(
&
self
,
state
:
&
State
,
symbol
:
&
ExternSymbol
,
call_source_node
:
NodeIndex
,
)
->
State
{
let
mut
new_state
=
state
.clone
();
// Check if the extern symbol is a string symbol, since the return register is not tainted for these.
// Instead, is has to be checked whether the first function parameter points to a tainted memory address
if
self
.string_symbol_map
.get
(
&
symbol
.tid
)
.is_some
()
{
new_state
.remove_non_callee_saved_taint
(
symbol
.get_calling_convention
(
self
.project
));
new_state
=
self
.taint_string_function_parameters
(
&
new_state
,
symbol
,
call_source_node
);
}
else
{
// 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
=
symbol
.return_values
.iter
()
.filter_map
(|
ret
|
match
ret
{
Arg
::
Register
(
var
)
=>
Some
(
var
.name
.clone
()),
_
=>
None
,
})
.collect
::
<
Vec
<
String
>>
();
if
new_state
.check_return_registers_for_taint
(
return_registers
)
{
new_state
.remove_non_callee_saved_taint
(
symbol
.get_calling_convention
(
self
.project
));
// TODO: Parameter detection since targets of input parameters are the return locations
// Taint memory for string inputs
if
self
.user_input_symbol_map
.get
(
&
symbol
.tid
)
.is_some
()
{
self
.generate_cwe_warning
(
&
new_state
.get_current_sub
()
.as_ref
()
.unwrap
()
.term.name
,
);
}
return
self
.taint_parameters
(
&
new_state
,
symbol
.parameters
.clone
(),
call_source_node
,
);
}
}
new_state
}
/// Taints a stack parameter given a size and an offset
pub
fn
taint_stack_parameters
(
&
self
,
state
:
State
,
call_source_node
:
NodeIndex
,
offset
:
i64
,
size
:
ByteSize
,
)
->
State
{
let
mut
new_state
=
state
;
if
let
Some
(
NodeValue
::
Value
(
pi_state
))
=
self
.pointer_inference_results
.get_node_value
(
call_source_node
)
{
let
address_exp
=
Expression
::
Var
(
self
.project.stack_pointer_register
.clone
())
.plus_const
(
offset
);
if
let
Ok
(
address
)
=
pi_state
.eval
(
&
address_exp
)
{
new_state
.save_taint_to_memory
(
&
address
,
Taint
::
Tainted
(
size
));
}
}
new_state
}
/// Iterates over the given parameters of a function and returns an updated state
pub
fn
taint_parameters
(
&
self
,
state
:
&
State
,
parameters
:
Vec
<
Arg
>
,
call_source_node
:
NodeIndex
,
)
->
State
{
let
mut
new_state
=
state
.clone
();
for
parameter
in
parameters
{
match
parameter
{
Arg
::
Register
(
param
)
=>
{
new_state
.set_register_taint
(
&
param
,
Taint
::
Tainted
(
param
.size
))
}
Arg
::
Stack
{
offset
,
size
}
=>
{
new_state
=
self
.taint_stack_parameters
(
new_state
.clone
(),
call_source_node
,
offset
,
size
,
);
}
}
}
new_state
}
/// Checks whether the current def term is the last def term
/// of its corresponding block and if so, returns the node index of the BlkStart node.
pub
fn
get_blk_start_node_if_last_def
(
&
self
,
state
:
&
State
,
def
:
&
Term
<
Def
>
,
)
->
Option
<
NodeIndex
>
{
if
let
Some
(
sub
)
=
state
.get_current_sub
()
{
if
let
Some
(
node
)
=
self
.block_start_last_def_map
.get
(
&
(
def
.tid
.clone
(),
sub
.tid
.clone
()))
{
return
Some
(
*
node
);
}
}
else
{
panic!
(
"Missing current Sub."
);
}
None
}
/// Creates a map from def terms to their corresponding pointer inference states
/// by taking the pointer inference state of the BlkStart node and updating it
/// for each def term in the block.
pub
fn
create_pi_def_map
(
&
self
,
block_start_node
:
NodeIndex
,
)
->
Option
<
HashMap
<
Tid
,
PointerInferenceState
>>
{
if
let
Some
(
block_node
)
=
self
.get_pi_graph
()
.node_weight
(
block_start_node
)
{
if
let
Some
(
pi_value
)
=
self
.pointer_inference_results
.get_node_value
(
block_start_node
)
{
let
mut
pi_def_map
:
HashMap
<
Tid
,
PointerInferenceState
>
=
HashMap
::
new
();
let
pi_context
=
self
.pointer_inference_results
.get_context
();
let
mut
new_pi_state
=
Some
(
pi_value
.unwrap_value
()
.clone
());
for
def
in
block_node
.get_block
()
.term.defs
.iter
()
{
// Add the pi state to the map that is available after the def was executed
// If no state is available after the update_def() call, none is added
if
new_pi_state
.is_none
()
{
break
;
}
new_pi_state
=
pi_context
.update_def
(
&
new_pi_state
.unwrap
(),
def
);
if
let
Some
(
new_state
)
=
new_pi_state
.clone
()
{
pi_def_map
.insert
(
def
.tid
.clone
(),
new_state
);
}
}
return
Some
(
pi_def_map
);
}
}
else
{
panic!
(
"Unexpected node index for BlkStart Node."
);
}
None
}
/// Handles assignment and load definition updates
pub
fn
handle_assign_and_load
(
&
self
,
state
:
State
,
def
:
&
Term
<
Def
>
,
var
:
&
Variable
,
input
:
&
Expression
,
)
->
State
{
let
mut
new_state
=
state
;
if
let
Some
(
taint
)
=
new_state
.get_register_taint
(
var
)
{
if
taint
.is_tainted
()
{
new_state
.set_expression_taint_and_store_constants
(
&
def
.tid
,
var
,
input
,
&
self
.project.stack_pointer_register
,
)
}
}
new_state
}
/// Gets the BlkEnd node of an external function call
pub
fn
get_source_node
(
&
self
,
state
:
&
State
,
call_source
:
&
Tid
)
->
NodeIndex
{
let
blk_end_node_id
=
self
.jmp_to_blk_end_node_map
.get
(
&
(
call_source
.clone
(),
state
.get_current_sub
()
.as_ref
()
.unwrap
()
.tid
.clone
(),
));
if
let
Some
(
blk_end_node
)
=
blk_end_node_id
{
*
blk_end_node
}
else
{
panic!
(
"Malformed Control Flow Graph."
);
}
}
/// Updates the target state at the callsite by removing non parameter register taints
/// and by merging callee saved register taints from the return state if available
pub
fn
update_target_state_for_callsite
(
&
self
,
return_state
:
Option
<&
State
>
,
target_state
:
Option
<&
State
>
,
caller_sub
:
&
Term
<
Sub
>
,
)
->
Option
<
State
>
{
if
let
Some
(
target
)
=
target_state
{
let
mut
new_state
=
target
.clone
();
new_state
.remove_non_parameter_taints_for_generic_function
(
self
.project
);
new_state
.set_current_sub
(
caller_sub
);
if
let
Some
(
return_
)
=
return_state
{
new_state
.merge_callee_saved_taints_from_return_state
(
return_
,
self
.project
.get_standard_calling_convention
(),
);
}
return
Some
(
new_state
);
}
None
}
}
impl
<
'a
>
crate
::
analysis
::
backward_interprocedural_fixpoint
::
Context
<
'a
>
for
Context
<
'a
>
{
type
Value
=
State
;
/// Get the underlying graph of the fixpoint computation
fn
get_graph
(
&
self
)
->
&
Graph
<
'a
>
{
&
self
.graph
}
/// Merge two states
fn
merge
(
&
self
,
state1
:
&
State
,
state2
:
&
State
)
->
State
{
state1
.merge
(
state2
)
}
/// Updates State according to side effects of the definition
fn
update_def
(
&
self
,
state
:
&
State
,
def
:
&
Term
<
Def
>
)
->
Option
<
State
>
{
if
state
.is_empty
()
{
// Without taint there is nothing to propagate.
return
None
;
}
let
mut
new_state
=
state
.clone
();
// Check whether the def is the last def of a block and if so, create the
// Def Pi Map
if
let
Some
(
blk_start_node
)
=
self
.get_blk_start_node_if_last_def
(
&
new_state
,
def
)
{
new_state
.set_pi_def_map
(
self
.create_pi_def_map
(
blk_start_node
));
}
match
&
def
.term
{
Def
::
Assign
{
var
,
value
:
input
}
|
Def
::
Load
{
var
,
address
:
input
,
}
=>
new_state
=
self
.handle_assign_and_load
(
new_state
,
def
,
var
,
input
),
Def
::
Store
{
address
,
value
}
=>
new_state
.taint_value_to_be_stored
(
&
def
.tid
,
address
,
value
,
&
self
.project.stack_pointer_register
,
),
}
// Check whether the current def term is the first of the block and if so, remove
// the pi_def_map for the current state to save memory
if
self
.block_first_def_set
.get
(
&
(
def
.tid
.clone
(),
new_state
.get_current_sub
()
.as_ref
()
.unwrap
()
.tid
.clone
(),
))
.is_some
()
{
new_state
.set_pi_def_map
(
None
);
}
Some
(
new_state
)
}
/// Either returns a copy of the input state when there is no conditional
/// Or merges both incoming states from the branch and conditional branch
fn
update_jumpsite
(
&
self
,
state_after_jump
:
&
State
,
_jump
:
&
Term
<
Jmp
>
,
_untaken_conditional
:
Option
<&
Term
<
Jmp
>>
,
_jumpsite
:
&
Term
<
Blk
>
,
)
->
Option
<
State
>
{
Some
(
state_after_jump
.clone
())
}
/// The specific execution is dependent on the existence of a return and target state
/// If there is no return state and the taint source is not in the callee, none is returned.
/// If there is no return state and the taint source is in the callee, the target state is copied.
/// If there is a return state and no target state, non callee saved registers are removed and the
/// updated return state is let through.
/// If there is a return state and a target state, non parameter taints are removed from the target state
/// and the remaining taints plus the callee saved taints from the return state are combined in a new state
fn
update_callsite
(
&
self
,
target_state
:
Option
<&
State
>
,
return_state
:
Option
<&
State
>
,
caller_sub
:
&
Term
<
Sub
>
,
_call
:
&
Term
<
Jmp
>
,
_return_
:
&
Term
<
Jmp
>
,
)
->
Option
<
State
>
{
// Return state is present
if
let
Some
(
return_
)
=
return_state
{
// Update the target state if there is one. Otherwise clone the return state and
// remove all non callee saved register taints
let
new_state
=
self
.update_target_state_for_callsite
(
return_state
,
target_state
,
caller_sub
);
if
new_state
.is_none
()
{
let
mut
new_state
=
return_
.clone
();
if
let
Some
(
calling_conv
)
=
self
.project
.get_standard_calling_convention
()
{
new_state
.remove_non_callee_saved_taint
(
calling_conv
);
}
return
Some
(
new_state
);
}
return
new_state
;
// No return state: check for taint source
}
else
{
// If the called subroutine contains the taint source, update the target state if there is one.
// Otherwise return None.
if
let
Some
(
source_sub
)
=
self
.taint_source_sub
{
if
source_sub
.tid
==
caller_sub
.tid
{
return
self
.update_target_state_for_callsite
(
return_state
,
target_state
,
caller_sub
,
);
}
}
}
None
}
/// Simply sends a copy of the state after the call return to the callsite
/// Will be used at the callsite to restore non-volatile registers
fn
split_call_stub
(
&
self
,
combined_state
:
&
State
)
->
Option
<
State
>
{
Some
(
combined_state
.clone
())
}
/// Removes all register taints except for possible return register taints
fn
split_return_stub
(
&
self
,
combined_state
:
&
State
,
returned_from_sub
:
&
Term
<
Sub
>
,
)
->
Option
<
State
>
{
let
mut
new_state
=
combined_state
.clone
();
if
let
Some
(
calling_conv
)
=
self
.project
.get_standard_calling_convention
()
{
let
return_registers
:
HashSet
<
String
>
=
calling_conv
.return_register
.iter
()
.cloned
()
.collect
();
new_state
.remove_all_except_return_register_taints
(
return_registers
);
}
new_state
.set_current_sub
(
returned_from_sub
);
Some
(
new_state
)
}
/// Check whether the extern call is direct and if so, taint the extern symbol parameters and
/// remove non callee saved registers.
fn
update_call_stub
(
&
self
,
state_after_call
:
&
State
,
call
:
&
Term
<
Jmp
>
)
->
Option
<
State
>
{
if
state_after_call
.is_empty
()
{
return
None
;
}
let
mut
new_state
=
state_after_call
.clone
();
match
&
call
.term
{
Jmp
::
Call
{
target
,
..
}
=>
{
let
source_node
=
self
.get_source_node
(
&
new_state
,
&
call
.tid
);
if
let
Some
(
extern_symbol
)
=
self
.extern_symbol_map
.get
(
target
)
{
new_state
=
self
.taint_generic_function_parameters_and_remove_non_callee_saved
(
&
new_state
,
extern_symbol
,
source_node
,
)
}
else
{
panic!
(
"Extern symbol not found."
);
}
}
_
=>
panic!
(
"Malformed control flow graph encountered."
),
}
Some
(
new_state
)
}
/// Just returns a copy of the input state.
fn
specialize_conditional
(
&
self
,
state
:
&
State
,
_condition
:
&
Expression
,
_is_true
:
bool
,
)
->
Option
<
State
>
{
Some
(
state
.clone
())
}
}
#[cfg(test)]
mod
tests
;
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_78/context/tests.rs
0 → 100644
View file @
d926369b
use
super
::
*
;
use
crate
::
analysis
::
backward_interprocedural_fixpoint
::
Context
as
BackwardContext
;
use
crate
::{
abstract_domain
::{
BitvectorDomain
,
DataDomain
,
PointerDomain
,
SizedDomain
},
analysis
::
pointer_inference
::{
Data
,
State
as
PointerInferenceState
},
bil
::
Bitvector
,
intermediate_representation
::{
Expression
,
Variable
},
};
// TODO: change actual mock function for blocks to receive a TID parameter and then remove this function
fn
mock_block
(
tid
:
&
str
)
->
Term
<
Blk
>
{
Term
{
tid
:
Tid
::
new
(
tid
),
term
:
Blk
{
defs
:
Vec
::
new
(),
jmps
:
Vec
::
new
(),
},
}
}
fn
bv
(
value
:
i64
)
->
BitvectorDomain
{
BitvectorDomain
::
Value
(
Bitvector
::
from_i64
(
value
))
}
impl
ExternSymbol
{
fn
mock_string
()
->
Self
{
ExternSymbol
{
tid
:
Tid
::
new
(
"sprintf"
),
addresses
:
vec!
[
"UNKNOWN"
.to_string
()],
name
:
"sprintf"
.to_string
(),
calling_convention
:
Some
(
"__stdcall"
.to_string
()),
parameters
:
vec!
[
Arg
::
mock_register
(
"RDI"
),
Arg
::
mock_register
(
"RSI"
)],
return_values
:
vec!
[
Arg
::
mock_register
(
"RAX"
)],
no_return
:
false
,
}
}
}
struct
Setup
{
project
:
Project
,
state
:
State
,
pi_state
:
PointerInferenceState
,
string_sym
:
ExternSymbol
,
taint_source
:
Term
<
Jmp
>
,
base_eight_offset
:
DataDomain
<
BitvectorDomain
>
,
base_sixteen_offset
:
DataDomain
<
BitvectorDomain
>
,
}
impl
Setup
{
fn
new
()
->
Self
{
let
(
state
,
pi_state
)
=
State
::
mock_with_pi_state
();
let
stack_id
=
pi_state
.stack_id
.clone
();
let
taint_source
=
Term
{
tid
:
Tid
::
new
(
"taint_source"
),
term
:
Jmp
::
Call
{
target
:
Tid
::
new
(
"system"
),
return_
:
None
,
},
};
let
mut
project
=
Project
::
mock_empty
();
let
mut
sub
=
Sub
::
mock
(
"func"
);
let
mut
block1
=
mock_block
(
"block1"
);
let
block2
=
mock_block
(
"block2"
);
let
def1
=
Def
::
assign
(
"def1"
,
Variable
::
mock
(
"RBP"
,
8
as
u64
),
Expression
::
var
(
"RSP"
),
);
let
def2
=
Def
::
assign
(
"def2"
,
Variable
::
mock
(
"RDI"
,
8
as
u64
),
Expression
::
var
(
"RBP"
)
.plus_const
(
-
8
),
);
let
jump
=
Jmp
::
call
(
"call_string"
,
"sprintf"
,
Some
(
"block2"
));
block1
.term.defs
.push
(
def1
);
block1
.term.defs
.push
(
def2
);
block1
.term.jmps
.push
(
jump
.clone
());
sub
.term.blocks
.push
(
block1
);
sub
.term.blocks
.push
(
block2
);
project
.program
.term
.extern_symbols
.push
(
ExternSymbol
::
mock_string
());
project
.program
.term
.extern_symbols
.push
(
ExternSymbol
::
mock
());
project
.program.term.subs
.push
(
sub
);
project
.program.term.entry_points
.push
(
Tid
::
new
(
"func"
));
project
.calling_conventions
.push
(
CallingConvention
::
mock
());
Setup
{
project
,
state
,
pi_state
,
string_sym
:
ExternSymbol
::
mock_string
(),
taint_source
,
base_eight_offset
:
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
-
8
))),
base_sixteen_offset
:
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
-
16
))),
}
}
}
impl
<
'a
>
Context
<
'a
>
{
fn
mock
(
project
:
&
'a
Project
,
string_symbols
:
HashMap
<
Tid
,
&
'a
ExternSymbol
>
,
pi_results
:
&
'a
PointerInferenceComputation
<
'a
>
,
mem_image
:
&
'a
RuntimeMemoryImage
,
)
->
Self
{
let
(
cwe_sender
,
_
)
=
crossbeam_channel
::
unbounded
();
Context
::
new
(
project
,
mem_image
,
pi_results
,
string_symbols
,
HashMap
::
new
(),
cwe_sender
,
)
}
}
#[test]
fn
setting_taint_source
()
{
let
setup
=
Setup
::
new
();
let
current_sub
=
Sub
::
mock
(
"func"
);
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
context
.set_taint_source
(
&
setup
.taint_source
,
&
String
::
from
(
"system"
),
&
current_sub
);
assert_eq!
(
context
.taint_source
,
Some
(
&
setup
.taint_source
));
assert_eq!
(
context
.taint_source_name
,
Some
(
String
::
from
(
"system"
)));
assert_eq!
(
context
.taint_source_sub
,
Some
(
&
current_sub
));
}
#[test]
fn
tainting_string_function_parameters
()
{
let
mut
setup
=
Setup
::
new
();
let
rbp_reg
=
Variable
::
mock
(
"RBP"
,
8
as
u64
);
// callee saved -> will point to RSP
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
// parameter 1 -> will point to RBP - 8
let
rsi_reg
=
Variable
::
mock
(
"RSI"
,
8
as
u64
);
// parameter 2
setup
.state
.set_register_taint
(
&
rbp_reg
,
Taint
::
Tainted
(
rbp_reg
.size
));
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
setup
.state
.save_taint_to_memory
(
&
setup
.base_sixteen_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
node_id
=
context
.jmp_to_blk_end_node_map
.get
(
&
(
Tid
::
new
(
"call_string"
),
Tid
::
new
(
"func"
)))
.unwrap
();
let
new_state
=
context
.taint_string_function_parameters
(
&
setup
.state
,
&
setup
.string_sym
,
*
node_id
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_sixteen_offset
,
&
setup
.pi_state
),
true
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
&
setup
.pi_state
),
false
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
rsi_reg
),
Some
(
&
Taint
::
Tainted
(
rsi_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
rbp_reg
),
Some
(
&
Taint
::
Tainted
(
rbp_reg
.size
))
);
}
#[test]
fn
first_param_pointing_to_memory_taint
()
{
let
mut
setup
=
Setup
::
new
();
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
setup
.pi_state
.set_register
(
&
rdi_reg
,
setup
.base_eight_offset
.clone
());
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
arg
=
Arg
::
Register
(
rdi_reg
);
assert_eq!
(
context
.first_param_points_to_memory_taint
(
&
setup
.pi_state
,
&
mut
setup
.state
,
&
arg
),
true
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
&
setup
.pi_state
),
false
);
}
#[test]
fn
tainting_generic_function_parameters_and_removing_non_callee_saved
()
{
let
mut
setup
=
Setup
::
new
();
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
let
rbp_reg
=
Variable
::
mock
(
"RBP"
,
8
as
u64
);
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
rsi_reg
=
Variable
::
mock
(
"RSI"
,
8
as
u64
);
let
rax_reg
=
Variable
::
mock
(
"RAX"
,
8
as
u64
);
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
setup
.state
.set_register_taint
(
&
rbp_reg
,
Taint
::
Tainted
(
rbp_reg
.size
));
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
let
mut
string_syms
:
HashMap
<
Tid
,
&
ExternSymbol
>
=
HashMap
::
new
();
string_syms
.insert
(
Tid
::
new
(
"sprintf"
),
&
setup
.string_sym
);
let
context
=
Context
::
mock
(
&
setup
.project
,
string_syms
,
&
pi_results
,
&
mem_image
);
let
node_id
=
context
.jmp_to_blk_end_node_map
.get
(
&
(
Tid
::
new
(
"call_string"
),
Tid
::
new
(
"func"
)))
.unwrap
();
// Test Case 1: String Symbol
let
mut
new_state
=
context
.taint_generic_function_parameters_and_remove_non_callee_saved
(
&
setup
.state
,
&
ExternSymbol
::
mock_string
(),
node_id
.clone
(),
);
// Parameter
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
rsi_reg
),
Some
(
&
Taint
::
Tainted
(
rsi_reg
.size
))
);
// Callee Saved
assert_eq!
(
new_state
.get_register_taint
(
&
rbp_reg
),
Some
(
&
Taint
::
Tainted
(
rbp_reg
.size
))
);
// Non Callee Saved
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rax_reg
),
None
);
new_state
.remove_all_register_taints
();
new_state
.set_register_taint
(
&
rbp_reg
,
Taint
::
Tainted
(
rbp_reg
.size
));
new_state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
new_state
.set_register_taint
(
&
rax_reg
,
Taint
::
Tainted
(
rax_reg
.size
));
// Test Case 2: Other Extern Symbol
new_state
=
context
.taint_generic_function_parameters_and_remove_non_callee_saved
(
&
new_state
,
&
ExternSymbol
::
mock
(),
node_id
.clone
(),
);
// Parameter
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
// Callee Saved
assert_eq!
(
new_state
.get_register_taint
(
&
rbp_reg
),
Some
(
&
Taint
::
Tainted
(
rbp_reg
.size
))
);
// Non Callee Saved
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rax_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rsi_reg
),
None
);
// TODO: add test for scanf when parameter detection is implemented
}
#[test]
fn
tainting_stack_parameters
()
{
let
setup
=
Setup
::
new
();
let
offset
=
4
as
i64
;
let
size
=
ByteSize
::
new
(
8
);
let
stack_id
=
setup
.pi_state.stack_id
.clone
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
call_source_node
=
context
.jmp_to_blk_end_node_map
.get
(
&
(
Tid
::
new
(
"call_string"
),
Tid
::
new
(
"func"
)))
.unwrap
();
let
new_state
=
context
.taint_stack_parameters
(
setup
.state
,
call_source_node
.clone
(),
offset
,
size
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
4
))),
&
setup
.pi_state
),
true
);
}
#[test]
fn
tainting_parameters
()
{
let
setup
=
Setup
::
new
();
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
rsi_reg
=
Variable
::
mock
(
"RSI"
,
8
as
u64
);
let
params
=
vec!
[
Arg
::
Register
(
rdi_reg
.clone
()),
Arg
::
Register
(
rsi_reg
.clone
()),
Arg
::
Stack
{
offset
:
4
,
size
:
ByteSize
::
new
(
8
),
},
];
let
stack_id
=
setup
.pi_state.stack_id
.clone
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
call_source_node
=
context
.jmp_to_blk_end_node_map
.get
(
&
(
Tid
::
new
(
"call_string"
),
Tid
::
new
(
"func"
)))
.unwrap
();
let
new_state
=
context
.taint_parameters
(
&
setup
.state
,
params
,
call_source_node
.clone
());
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
rsi_reg
),
Some
(
&
Taint
::
Tainted
(
rsi_reg
.size
))
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
4
))),
&
setup
.pi_state
),
true
);
}
#[test]
fn
creating_pi_def_map
()
{
let
setup
=
Setup
::
new
();
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
def1
=
Tid
::
new
(
"def1"
);
let
def2
=
Tid
::
new
(
"def2"
);
let
stack_id
=
setup
.pi_state.stack_id
.clone
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
current_sub
=
setup
.project.program.term.subs
.get
(
0
)
.unwrap
();
let
start_node
=
context
.block_start_last_def_map
.get
(
&
(
def2
.clone
(),
current_sub
.tid
.clone
()))
.unwrap
();
let
pi_def_map
=
context
.create_pi_def_map
(
start_node
.clone
())
.unwrap
();
for
(
def_tid
,
pi_state
)
in
pi_def_map
.iter
()
{
if
*
def_tid
==
def1
{
assert_eq!
(
pi_state
.get_register
(
&
rdi_reg
)
.unwrap
(),
Data
::
new_top
(
rdi_reg
.size
)
);
}
else
if
*
def_tid
==
def2
{
assert_eq!
(
pi_state
.get_register
(
&
rdi_reg
)
.unwrap
(),
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
-
8
)))
);
}
}
}
#[test]
fn
getting_blk_start_node_if_last_def
()
{
let
mut
setup
=
Setup
::
new
();
let
def1
=
Def
::
assign
(
"def1"
,
Variable
::
mock
(
"RBP"
,
8
as
u64
),
Expression
::
var
(
"RSP"
),
);
let
def2
=
Def
::
assign
(
"def2"
,
Variable
::
mock
(
"RDI"
,
8
as
u64
),
Expression
::
var
(
"RBP"
)
.plus_const
(
-
8
),
);
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
current_sub
=
setup
.project.program.term.subs
.get
(
0
)
.unwrap
();
setup
.state
.set_current_sub
(
current_sub
);
let
start_node
=
context
.block_start_last_def_map
.get
(
&
(
def2
.tid
.clone
(),
current_sub
.tid
.clone
()))
.unwrap
();
assert_eq!
(
context
.get_blk_start_node_if_last_def
(
&
setup
.state
,
&
def1
),
None
);
assert_eq!
(
context
.get_blk_start_node_if_last_def
(
&
setup
.state
,
&
def2
),
Some
(
start_node
.clone
())
);
}
#[test]
fn
getting_source_node
()
{
let
mut
setup
=
Setup
::
new
();
let
call_tid
=
Tid
::
new
(
"call_string"
);
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
current_sub
=
setup
.project.program.term.subs
.get
(
0
)
.unwrap
();
setup
.state
.set_current_sub
(
current_sub
);
let
blk_end_node_id
=
context
.jmp_to_blk_end_node_map
.get
(
&
(
call_tid
.clone
(),
current_sub
.tid
.clone
()))
.unwrap
();
assert_eq!
(
context
.get_source_node
(
&
setup
.state
,
&
call_tid
),
*
blk_end_node_id
);
}
#[test]
fn
updating_target_state_for_callsite
()
{
let
mut
setup
=
Setup
::
new
();
let
caller_sub
=
Sub
::
mock
(
"caller"
);
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
let
rbp_reg
=
Variable
::
mock
(
"RBP"
,
8
as
u64
);
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
mut
return_state
=
setup
.state
.clone
();
// Test Case 1: No target state
assert_eq!
(
context
.update_target_state_for_callsite
(
None
,
None
,
&
caller_sub
),
None
);
// Test Case 2: Target state but no return state
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
setup
.state
.set_register_taint
(
&
rdi_reg
,
Taint
::
Tainted
(
rdi_reg
.size
));
let
new_state
=
context
.update_target_state_for_callsite
(
None
,
Some
(
&
setup
.state
),
&
caller_sub
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
assert_eq!
(
*
new_state
.get_current_sub
()
.as_ref
()
.unwrap
(),
caller_sub
);
// Test Case 3: Target state and return state
return_state
.set_register_taint
(
&
rbp_reg
,
Taint
::
Tainted
(
rbp_reg
.size
));
let
new_state
=
context
.update_target_state_for_callsite
(
Some
(
&
return_state
),
Some
(
&
setup
.state
),
&
caller_sub
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
rbp_reg
),
Some
(
&
Taint
::
Tainted
(
rbp_reg
.size
))
);
assert_eq!
(
*
new_state
.get_current_sub
()
.as_ref
()
.unwrap
(),
caller_sub
);
}
#[test]
fn
handling_assign_and_load
()
{
let
mut
setup
=
Setup
::
new
();
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
mock_assign_register
=
Def
::
assign
(
"assign"
,
Variable
::
mock
(
"R9"
,
8
as
u64
),
Expression
::
var
(
"RDI"
),
);
let
mock_assign_stack
=
Def
::
assign
(
"stack_assign"
,
Variable
::
mock
(
"R9"
,
8
as
u64
),
Expression
::
var
(
"RSP"
),
);
let
mock_load
=
Def
::
load
(
"load"
,
Variable
::
mock
(
"R9"
,
8
as
u64
),
Expression
::
var
(
"RDI"
),
);
let
mut
pi_map
:
HashMap
<
Tid
,
PointerInferenceState
>
=
HashMap
::
new
();
let
stack_id
=
setup
.pi_state.stack_id
.clone
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
current_sub
=
setup
.project.program.term.subs
.get
(
0
)
.unwrap
();
setup
.state
.set_current_sub
(
current_sub
);
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
let
mut
new_state
=
context
.update_def
(
&
setup
.state
,
&
mock_assign_register
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
// Test Case: None State
new_state
.remove_all_register_taints
();
assert_eq!
(
context
.update_def
(
&
new_state
,
&
mock_assign_register
),
None
);
// Test Case: Assign RSP Register
pi_map
.insert
(
Tid
::
new
(
"stack_assign"
),
setup
.pi_state
.clone
());
new_state
.set_pointer_inference_map
(
pi_map
.clone
());
new_state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
new_state
=
context
.update_def
(
&
new_state
,
&
mock_assign_stack
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
0
))),
&
setup
.pi_state
),
true
);
// Test Case: Load
new_state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
new_state
=
context
.update_def
(
&
new_state
,
&
mock_load
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
}
#[test]
fn
updating_def
()
{
let
mut
setup
=
Setup
::
new
();
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
mock_assign_register
=
Def
::
assign
(
"assign"
,
Variable
::
mock
(
"R9"
,
8
as
u64
),
Expression
::
var
(
"RDI"
),
);
let
mock_assign_stack
=
Def
::
assign
(
"stack_assign"
,
Variable
::
mock
(
"R9"
,
8
as
u64
),
Expression
::
var
(
"RSP"
),
);
let
mock_load
=
Def
::
load
(
"load"
,
Variable
::
mock
(
"R9"
,
8
as
u64
),
Expression
::
var
(
"RDI"
),
);
let
mock_store
=
Def
::
store
(
"store"
,
Expression
::
var
(
"R9"
),
Expression
::
var
(
"RDI"
));
let
mut
pi_map
:
HashMap
<
Tid
,
PointerInferenceState
>
=
HashMap
::
new
();
let
stack_id
=
setup
.pi_state.stack_id
.clone
();
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
current_sub
=
setup
.project.program.term.subs
.get
(
0
)
.unwrap
();
setup
.state
.set_current_sub
(
current_sub
);
// Test Case: Assign R9 Register
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
let
mut
new_state
=
context
.update_def
(
&
setup
.state
,
&
mock_assign_register
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
// Test Case: None State
new_state
.remove_all_register_taints
();
assert_eq!
(
context
.update_def
(
&
new_state
,
&
mock_assign_register
),
None
);
// Test Case: Assign RSP Register
pi_map
.insert
(
Tid
::
new
(
"stack_assign"
),
setup
.pi_state
.clone
());
new_state
.set_pointer_inference_map
(
pi_map
.clone
());
new_state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
new_state
=
context
.update_def
(
&
new_state
,
&
mock_assign_stack
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
0
))),
&
setup
.pi_state
),
true
);
// Test Case: Load
new_state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
new_state
=
context
.update_def
(
&
new_state
,
&
mock_load
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
// Test Case: Store
new_state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
setup
.pi_state
.set_register
(
&
r9_reg
,
setup
.base_eight_offset
.clone
());
new_state
.set_pointer_inference_state_for_def
(
Some
(
setup
.pi_state
.clone
()),
&
Tid
::
new
(
"store"
));
new_state
=
context
.update_def
(
&
new_state
,
&
mock_store
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
&
setup
.pi_state
,),
false
);
}
#[test]
fn
updating_jumpsite
()
{
let
mut
setup
=
Setup
::
new
();
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
mut
new_state
=
context
.update_jumpsite
(
&
setup
.state
,
&
Jmp
::
branch
(
"jump"
,
"block2"
),
Some
(
&
Jmp
::
branch
(
"jump"
,
"block2"
)),
&
Blk
::
mock
(),
)
.unwrap
();
let
mut
pi_map
:
HashMap
<
Tid
,
PointerInferenceState
>
=
HashMap
::
new
();
pi_map
.insert
(
Tid
::
new
(
"initial"
),
setup
.pi_state
);
new_state
.set_pointer_inference_map
(
pi_map
);
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
Some
(
&
Taint
::
Tainted
(
r9_reg
.size
))
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
new_state
.get_pointer_inference_state_at_def
(
&
Tid
::
new
(
"initial"
))
.unwrap
()
),
true
);
}
#[test]
fn
updating_callsite
()
{
let
mut
setup
=
Setup
::
new
();
let
mut
return_state
:
Option
<&
State
>
=
None
;
let
mut
target_state
:
Option
<&
State
>
=
None
;
let
jump_term
=
Jmp
::
call
(
"call_string"
,
"sprintf"
,
Some
(
"block2"
));
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
let
rbp_reg
=
Variable
::
mock
(
"RBP"
,
8
as
u64
);
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
rax_reg
=
Variable
::
mock
(
"RAX"
,
8
as
u64
);
let
caller_sub
=
Sub
::
mock
(
"caller"
);
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
// Test Case: No return state
assert_eq!
(
context
.update_callsite
(
target_state
,
return_state
,
&
caller_sub
,
&
jump_term
,
&
jump_term
),
None
);
// Test Case: Return state but no target state
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
setup
.state
.set_register_taint
(
&
rbp_reg
,
Taint
::
Tainted
(
rbp_reg
.size
));
let
cloned_state
=
setup
.state
.clone
();
return_state
=
Some
(
&
cloned_state
);
let
mut
new_state
=
context
.update_callsite
(
target_state
,
return_state
,
&
caller_sub
,
&
jump_term
,
&
jump_term
,
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rbp_reg
),
Some
(
&
Taint
::
Tainted
(
rbp_reg
.size
))
);
// Test Case: Return and target state
setup
.state
.remove_all_register_taints
();
setup
.state
.set_register_taint
(
&
rdi_reg
,
Taint
::
Tainted
(
rdi_reg
.size
));
setup
.state
.set_register_taint
(
&
rax_reg
,
Taint
::
Tainted
(
rax_reg
.size
));
target_state
=
Some
(
&
setup
.state
);
new_state
=
context
.update_callsite
(
target_state
,
return_state
,
&
caller_sub
,
&
jump_term
,
&
jump_term
,
)
.unwrap
();
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rax_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rbp_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rbp_reg
.size
))
);
}
#[test]
fn
splitting_call_stub
()
{
let
mut
setup
=
Setup
::
new
();
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
mut
new_state
=
context
.split_call_stub
(
&
setup
.state
)
.unwrap
();
// Set pi_state to check for memory pointers
let
mut
pi_map
:
HashMap
<
Tid
,
PointerInferenceState
>
=
HashMap
::
new
();
pi_map
.insert
(
Tid
::
new
(
"initial"
),
setup
.pi_state
);
new_state
.set_pointer_inference_map
(
pi_map
);
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
Some
(
&
Taint
::
Tainted
(
r9_reg
.size
))
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
new_state
.get_pointer_inference_state_at_def
(
&
Tid
::
new
(
"initial"
))
.unwrap
()
),
true
);
}
#[test]
fn
splitting_return_stub
()
{
let
mut
setup
=
Setup
::
new
();
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
let
rax_reg
=
Variable
::
mock
(
"RAX"
,
8
as
u64
);
let
called_sub
=
Sub
::
mock
(
"called"
);
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
setup
.state
.set_register_taint
(
&
rax_reg
,
Taint
::
Tainted
(
rax_reg
.size
));
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
// Set pi_state to check for memory pointers
let
mut
new_state
=
context
.split_return_stub
(
&
setup
.state
,
&
called_sub
)
.unwrap
();
let
mut
pi_map
:
HashMap
<
Tid
,
PointerInferenceState
>
=
HashMap
::
new
();
pi_map
.insert
(
Tid
::
new
(
"initial"
),
setup
.pi_state
);
new_state
.set_pointer_inference_map
(
pi_map
);
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
assert_eq!
(
new_state
.get_register_taint
(
&
rax_reg
),
Some
(
&
Taint
::
Tainted
(
rax_reg
.size
))
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
new_state
.get_pointer_inference_state_at_def
(
&
Tid
::
new
(
"initial"
))
.unwrap
()
),
true
);
}
#[test]
fn
updating_call_stub
()
{
let
mut
setup
=
Setup
::
new
();
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
// non callee saved
let
rbp_reg
=
Variable
::
mock
(
"RBP"
,
8
as
u64
);
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
rsi_reg
=
Variable
::
mock
(
"RSI"
,
8
as
u64
);
let
mock_call
=
Jmp
::
call
(
"call_string"
,
"sprintf"
,
Some
(
"block2"
));
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
setup
.state
.set_register_taint
(
&
rbp_reg
,
Taint
::
Tainted
(
rbp_reg
.size
));
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
setup
.state
.save_taint_to_memory
(
&
setup
.base_sixteen_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
mut
string_symbols
:
HashMap
<
Tid
,
&
ExternSymbol
>
=
HashMap
::
new
();
let
sprintf
=
&
ExternSymbol
::
mock_string
();
string_symbols
.insert
(
Tid
::
new
(
"sprintf"
),
sprintf
);
let
context
=
Context
::
mock
(
&
setup
.project
,
string_symbols
,
&
pi_results
,
&
mem_image
);
let
current_sub
=
Sub
::
mock
(
"func"
);
setup
.state
.set_current_sub
(
&
current_sub
);
let
new_state
=
context
.update_call_stub
(
&
setup
.state
,
&
mock_call
)
.unwrap
();
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_sixteen_offset
,
&
setup
.pi_state
),
true
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
&
setup
.pi_state
),
false
);
assert_eq!
(
new_state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
rsi_reg
),
Some
(
&
Taint
::
Tainted
(
rsi_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
rbp_reg
),
Some
(
&
Taint
::
Tainted
(
rbp_reg
.size
))
);
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
None
);
}
#[test]
fn
specializing_conditional
()
{
let
mut
setup
=
Setup
::
new
();
let
r9_reg
=
Variable
::
mock
(
"R9"
,
8
as
u64
);
setup
.state
.set_register_taint
(
&
r9_reg
,
Taint
::
Tainted
(
r9_reg
.size
));
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
setup
.project
,
&
mem_image
);
pi_results
.compute
();
let
context
=
Context
::
mock
(
&
setup
.project
,
HashMap
::
new
(),
&
pi_results
,
&
mem_image
);
let
mut
new_state
=
context
.split_call_stub
(
&
setup
.state
)
.unwrap
();
let
mut
pi_map
:
HashMap
<
Tid
,
PointerInferenceState
>
=
HashMap
::
new
();
pi_map
.insert
(
Tid
::
new
(
"initial"
),
setup
.pi_state
);
new_state
.set_pointer_inference_map
(
pi_map
);
assert_eq!
(
new_state
.get_register_taint
(
&
r9_reg
),
Some
(
&
Taint
::
Tainted
(
r9_reg
.size
))
);
assert_eq!
(
new_state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
new_state
.get_pointer_inference_state_at_def
(
&
Tid
::
new
(
"initial"
))
.unwrap
()
),
true
);
}
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_78/state/mod.rs
0 → 100644
View file @
d926369b
use
std
::
collections
::{
HashMap
,
HashSet
};
use
crate
::{
abstract_domain
::{
AbstractDomain
,
AbstractIdentifier
,
BitvectorDomain
,
MemRegion
,
SizedDomain
,
},
analysis
::
pointer_inference
::{
Data
,
State
as
PointerInferenceState
},
checkers
::
cwe_476
::
Taint
,
intermediate_representation
::{
Arg
,
CallingConvention
,
Expression
,
ExternSymbol
,
Project
,
Sub
,
Variable
,
},
prelude
::
*
,
};
#[derive(Serialize,
Deserialize,
Debug,
Eq,
Clone)]
pub
struct
State
{
/// The set of currently tainted registers.
register_taint
:
HashMap
<
Variable
,
Taint
>
,
/// The Taint contained in memory objects
memory_taint
:
HashMap
<
AbstractIdentifier
,
MemRegion
<
Taint
>>
,
/// The set of addresses in the binary where string constants reside
string_constants
:
HashSet
<
Bitvector
>
,
/// A map from Def Tids to their corresponding pointer inference state.
/// The pointer inference states are calculated in a forward manner
/// from the BlkStart node when entering a BlkEnd node through a jump.
#[serde(skip_serializing)]
pi_def_map
:
Option
<
HashMap
<
Tid
,
PointerInferenceState
>>
,
/// Holds the currently analyzed subroutine term
current_sub
:
Option
<
Term
<
Sub
>>
,
}
impl
PartialEq
for
State
{
/// Two states are equal if the same values are tainted in both states.
///
/// The equality operator ignores the `pi_def_map` field,
/// since it only denotes an intermediate value.
fn
eq
(
&
self
,
other
:
&
Self
)
->
bool
{
self
.register_taint
==
other
.register_taint
&&
self
.memory_taint
==
other
.memory_taint
&&
self
.string_constants
==
other
.string_constants
}
}
impl
AbstractDomain
for
State
{
/// Merge two states.
/// Any value tainted in at least one input state is also tainted in the merged state.
///
/// The used algorithm for merging the taints contained in memory regions is unsound
/// when merging taints that intersect only partially.
/// However, this should not have an effect in practice,
/// since these values are usually unsound and unused by the program anyway.
fn
merge
(
&
self
,
other
:
&
Self
)
->
Self
{
let
mut
register_taint
=
self
.register_taint
.clone
();
for
(
var
,
other_taint
)
in
other
.register_taint
.iter
()
{
if
let
Some
(
taint
)
=
self
.register_taint
.get
(
var
)
{
register_taint
.insert
(
var
.clone
(),
taint
.merge
(
other_taint
));
}
else
{
register_taint
.insert
(
var
.clone
(),
*
other_taint
);
}
}
let
mut
memory_taint
=
self
.memory_taint
.clone
();
for
(
tid
,
other_mem_region
)
in
other
.memory_taint
.iter
()
{
if
let
Some
(
mem_region
)
=
memory_taint
.get_mut
(
tid
)
{
for
(
index
,
taint
)
in
other_mem_region
.iter
()
{
mem_region
.insert_at_byte_index
(
*
taint
,
*
index
);
// Unsound in theory for partially intersecting taints. Should not matter in practice.
}
}
else
{
memory_taint
.insert
(
tid
.clone
(),
other_mem_region
.clone
());
}
}
let
constants
=
self
.string_constants
.clone
();
constants
.union
(
&
other
.string_constants
);
State
{
register_taint
,
memory_taint
,
string_constants
:
constants
,
pi_def_map
:
None
,
// At nodes this intermediate value can be safely forgotten.
current_sub
:
self
.current_sub
.clone
(),
}
}
/// The state has no explicit Top element.
fn
is_top
(
&
self
)
->
bool
{
false
}
}
impl
State
{
/// Get a new state in which only the parameter values of the given extern symbol are tainted.
pub
fn
new
(
taint_source
:
&
ExternSymbol
,
stack_pointer_register
:
&
Variable
,
pi_state
:
Option
<&
PointerInferenceState
>
,
current_sub
:
&
Term
<
Sub
>
,
)
->
State
{
let
mut
state
=
State
{
register_taint
:
HashMap
::
new
(),
memory_taint
:
HashMap
::
new
(),
string_constants
:
HashSet
::
new
(),
pi_def_map
:
None
,
current_sub
:
Some
(
current_sub
.clone
()),
};
for
parameter
in
taint_source
.parameters
.iter
()
{
match
parameter
{
Arg
::
Register
(
var
)
=>
{
state
.register_taint
.insert
(
var
.clone
(),
Taint
::
Tainted
(
var
.size
));
}
Arg
::
Stack
{
offset
,
size
}
=>
{
if
let
Some
(
pi_state
)
=
pi_state
{
let
address_exp
=
Expression
::
Var
(
stack_pointer_register
.clone
())
.plus_const
(
*
offset
);
if
let
Ok
(
address
)
=
pi_state
.eval
(
&
address_exp
)
{
state
.save_taint_to_memory
(
&
address
,
Taint
::
Tainted
(
*
size
));
}
}
}
}
}
state
}
/// Mark the value at the given address with the given taint.
///
/// If the address points to more than one object,
/// we merge the taint object with the object at the targets,
/// possibly tainting all possible targets.
pub
fn
save_taint_to_memory
(
&
mut
self
,
address
:
&
Data
,
taint
:
Taint
)
{
if
let
Data
::
Pointer
(
pointer
)
=
address
{
if
pointer
.targets
()
.len
()
==
1
{
for
(
mem_id
,
offset
)
in
pointer
.targets
()
.iter
()
{
if
let
BitvectorDomain
::
Value
(
position
)
=
offset
{
if
let
Some
(
mem_region
)
=
self
.memory_taint
.get_mut
(
mem_id
)
{
mem_region
.add
(
taint
,
position
.clone
());
}
else
{
let
mut
mem_region
=
MemRegion
::
new
(
address
.bytesize
());
mem_region
.add
(
taint
,
position
.clone
());
self
.memory_taint
.insert
(
mem_id
.clone
(),
mem_region
);
}
}
}
}
else
{
for
(
mem_id
,
offset
)
in
pointer
.targets
()
.iter
()
{
if
let
BitvectorDomain
::
Value
(
position
)
=
offset
{
if
let
Some
(
mem_region
)
=
self
.memory_taint
.get_mut
(
mem_id
)
{
let
old_taint
=
mem_region
.get
(
position
.clone
(),
taint
.bytesize
());
mem_region
.add
(
old_taint
.merge
(
&
taint
),
position
.clone
());
}
else
{
let
mut
mem_region
=
MemRegion
::
new
(
address
.bytesize
());
mem_region
.add
(
taint
,
position
.clone
());
self
.memory_taint
.insert
(
mem_id
.clone
(),
mem_region
);
}
}
}
}
}
}
/// Returns the sub of the currently analysed nodes.
pub
fn
get_current_sub
(
&
self
)
->
&
Option
<
Term
<
Sub
>>
{
&
self
.current_sub
}
/// Set the current sub to locate the analysis.
pub
fn
set_current_sub
(
&
mut
self
,
current_sub
:
&
Term
<
Sub
>
)
{
self
.current_sub
=
Some
(
current_sub
.clone
());
}
/// Sets the pointer inference to definition map for the current state.
pub
fn
set_pi_def_map
(
&
mut
self
,
pi_def_map
:
Option
<
HashMap
<
Tid
,
PointerInferenceState
>>
)
{
self
.pi_def_map
=
pi_def_map
;
}
/// Gets the taint state of a register if there is one.
pub
fn
get_register_taint
(
&
self
,
var
:
&
Variable
)
->
Option
<&
Taint
>
{
self
.register_taint
.get
(
var
)
}
/// Returns an iterator over currently tainted registers.
pub
fn
get_register_taints
(
&
self
)
->
std
::
collections
::
hash_map
::
Iter
<
Variable
,
Taint
>
{
self
.register_taint
.iter
()
}
/// Gets the string constant saved at the given address and saves it to the string constants field.
pub
fn
evaluate_constant
(
&
mut
self
,
constant
:
Bitvector
)
{
// TODO: check whether the constant is a valid memory address in the binary
// If so, get the string constant at that memory address and save it in the state
self
.string_constants
.insert
(
constant
);
}
/// Taints input registers and evaluates constant memory addresses for simple assignments
/// and taints memory if a pointer is overwritten.
/// The taint on the result register is removed.
pub
fn
set_expression_taint_and_store_constants
(
&
mut
self
,
def_tid
:
&
Tid
,
result
:
&
Variable
,
expression
:
&
Expression
,
stack_pointer_register
:
&
Variable
,
)
{
self
.remove_register_taint
(
result
);
match
expression
{
Expression
::
Const
(
constant
)
=>
self
.evaluate_constant
(
constant
.clone
()),
Expression
::
Var
(
var
)
=>
self
.taint_variable_input
(
var
,
stack_pointer_register
,
def_tid
),
Expression
::
BinOp
{
..
}
=>
{
if
let
Some
(
pid_map
)
=
self
.pi_def_map
.as_ref
()
{
if
let
Some
(
pi_state
)
=
pid_map
.get
(
def_tid
)
{
if
let
Ok
(
address
)
=
pi_state
.get_register
(
result
)
{
self
.save_taint_to_memory
(
&
address
,
Taint
::
Tainted
(
result
.size
));
}
}
}
}
Expression
::
UnOp
{
arg
,
..
}
|
Expression
::
Cast
{
arg
,
..
}
|
Expression
::
Subpiece
{
arg
,
..
}
=>
{
self
.taint_def_input_register
(
arg
,
stack_pointer_register
,
def_tid
)
}
_
=>
(),
}
}
/// Taints the input register of a store instruction and removes the memory taint at the target address.
pub
fn
taint_value_to_be_stored
(
&
mut
self
,
def_tid
:
&
Tid
,
target
:
&
Expression
,
value
:
&
Expression
,
stack_pointer_register
:
&
Variable
,
)
{
if
let
Some
(
pid_map
)
=
self
.pi_def_map
.as_ref
()
{
if
let
Some
(
pi_state
)
=
pid_map
.get
(
def_tid
)
{
if
let
Ok
(
address
)
=
pi_state
.eval
(
target
)
{
if
self
.check_if_address_points_to_taint
(
address
.clone
(),
&
pi_state
)
{
self
.taint_def_input_register
(
value
,
stack_pointer_register
,
def_tid
);
self
.remove_mem_taint_at_target
(
&
address
);
}
}
}
}
}
/// Taints all input register of an expression.
pub
fn
taint_def_input_register
(
&
mut
self
,
expr
:
&
Expression
,
stack_pointer_register
:
&
Variable
,
def_tid
:
&
Tid
,
)
{
match
expr
{
// TODO: Distinguish integer constants from global addresses in evaluate constant
Expression
::
Const
(
constant
)
=>
self
.evaluate_constant
(
constant
.clone
()),
Expression
::
Var
(
var
)
=>
self
.taint_variable_input
(
var
,
stack_pointer_register
,
def_tid
),
Expression
::
BinOp
{
lhs
,
rhs
,
..
}
=>
{
self
.taint_def_input_register
(
lhs
,
stack_pointer_register
,
def_tid
);
self
.taint_def_input_register
(
rhs
,
stack_pointer_register
,
def_tid
);
}
Expression
::
UnOp
{
arg
,
..
}
|
Expression
::
Cast
{
arg
,
..
}
|
Expression
::
Subpiece
{
arg
,
..
}
=>
{
self
.taint_def_input_register
(
arg
,
stack_pointer_register
,
def_tid
)
}
_
=>
(),
}
}
/// Either taints the input register or a memory position if it is the stack pointer register.
pub
fn
taint_variable_input
(
&
mut
self
,
var
:
&
Variable
,
stack_pointer_register
:
&
Variable
,
def_tid
:
&
Tid
,
)
{
if
var
.name
==
stack_pointer_register
.name
{
if
let
Some
(
pid_map
)
=
self
.pi_def_map
.as_ref
()
{
if
let
Some
(
pi_state
)
=
pid_map
.get
(
def_tid
)
{
if
let
Ok
(
address
)
=
pi_state
.get_register
(
stack_pointer_register
)
{
self
.save_taint_to_memory
(
&
address
,
Taint
::
Tainted
(
stack_pointer_register
.size
),
);
}
}
}
}
else
{
self
.set_register_taint
(
var
,
Taint
::
Tainted
(
var
.size
));
}
}
/// Remove the taint in the specified memory regions at the specified offsets.
pub
fn
remove_mem_taint_at_target
(
&
mut
self
,
address
:
&
Data
)
{
if
let
Data
::
Pointer
(
pointer
)
=
address
{
for
(
mem_id
,
offset
)
in
pointer
.targets
()
.iter
()
{
if
let
(
Some
(
mem_region
),
BitvectorDomain
::
Value
(
position
))
=
(
self
.memory_taint
.get_mut
(
mem_id
),
offset
.clone
())
{
if
let
Some
(
taint
)
=
mem_region
.get_unsized
(
position
.clone
())
{
mem_region
.remove
(
position
,
Bitvector
::
from_u64
(
u64
::
from
(
taint
.bytesize
())));
}
}
}
}
}
/// Set the taint of a register.
pub
fn
set_register_taint
(
&
mut
self
,
register
:
&
Variable
,
taint
:
Taint
)
{
if
taint
.is_top
()
{
self
.register_taint
.remove
(
register
);
}
else
{
self
.register_taint
.insert
(
register
.clone
(),
taint
);
}
}
/// Removes a specified register taint
pub
fn
remove_register_taint
(
&
mut
self
,
register
:
&
Variable
)
{
self
.register_taint
.remove
(
register
);
}
/// Return true if the memory object with the given ID contains a tainted value.
pub
fn
check_mem_id_for_taint
(
&
self
,
id
:
&
AbstractIdentifier
)
->
bool
{
if
let
Some
(
mem_object
)
=
self
.memory_taint
.get
(
&
id
)
{
for
elem
in
mem_object
.values
()
{
if
elem
.is_tainted
()
{
return
true
;
}
}
}
false
}
/// If the given address points to the stack,
/// return true if and only if the value at that stack position is tainted.
/// If the given address points to a non-stack memory object,
/// return true if the memory object contains any tainted value (at any position).
pub
fn
check_if_address_points_to_taint
(
&
self
,
address
:
Data
,
pi_state
:
&
PointerInferenceState
,
)
->
bool
{
use
crate
::
analysis
::
pointer_inference
::
object
::
ObjectType
;
if
let
Data
::
Pointer
(
pointer
)
=
address
{
for
(
target
,
offset
)
in
pointer
.targets
()
{
if
let
Ok
(
Some
(
ObjectType
::
Stack
))
=
pi_state
.memory
.get_object_type
(
target
)
{
// Only check if the value at the address is tainted
if
let
(
Some
(
mem_object
),
BitvectorDomain
::
Value
(
target_offset
))
=
(
self
.memory_taint
.get
(
target
),
offset
)
{
if
let
Some
(
taint
)
=
mem_object
.get_unsized
(
target_offset
.clone
())
{
if
taint
.is_tainted
()
{
return
true
;
}
}
}
}
else
{
// Check whether the memory object contains any taint.
if
self
.check_mem_id_for_taint
(
target
)
{
return
true
;
}
}
}
}
false
}
/// Removes all taints of registers that are not generic function parameters.
/// Since we don't know the actual calling convention of the call,
/// we approximate the parameters with all parameter registers of the standard calling convention of the project.
pub
fn
remove_non_parameter_taints_for_generic_function
(
&
mut
self
,
project
:
&
Project
)
{
if
let
Some
(
calling_conv
)
=
project
.get_standard_calling_convention
()
{
let
register_names
:
HashSet
<
String
>
=
calling_conv
.parameter_register
.iter
()
.cloned
()
.collect
();
let
taints
=
self
.register_taint
.clone
();
for
(
register
,
_
)
in
taints
.iter
()
{
if
register_names
.get
(
&
register
.name
)
.is_none
()
{
self
.register_taint
.remove
(
&
register
);
}
}
}
}
/// Remove the taint from all registers not contained in the callee-saved register list of the given calling convention.
pub
fn
remove_non_callee_saved_taint
(
&
mut
self
,
calling_conv
:
&
CallingConvention
)
{
self
.register_taint
=
self
.register_taint
.iter
()
.filter_map
(|(
register
,
taint
)|
{
if
calling_conv
.callee_saved_register
.iter
()
.any
(|
callee_saved_reg
|
register
.name
==
*
callee_saved_reg
)
{
Some
((
register
.clone
(),
*
taint
))
}
else
{
None
}
})
.collect
();
}
/// Remove all register taints except for the return register taints if available
/// This clears the state on the return stub edge
pub
fn
remove_all_except_return_register_taints
(
&
mut
self
,
return_registers
:
HashSet
<
String
>
)
{
let
tainted
=
self
.register_taint
.clone
();
for
(
register
,
_taint
)
in
tainted
{
if
return_registers
.get
(
&
register
.name
)
.is_none
()
{
self
.register_taint
.remove
(
&
register
);
}
}
}
/// Check whether `self` contains any taint at all.
pub
fn
is_empty
(
&
self
)
->
bool
{
self
.memory_taint
.is_empty
()
&&
self
.register_taint
.is_empty
()
}
/// Checks whether the return registers are contained in the current tainted registers
pub
fn
check_return_registers_for_taint
(
&
self
,
register_list
:
Vec
<
String
>
)
->
bool
{
// Check whether a register contains taint
for
(
register
,
taint
)
in
&
self
.register_taint
{
if
register_list
.iter
()
.any
(|
reg_name
|
*
reg_name
==
register
.name
)
&&
!
taint
.is_top
()
{
return
true
;
}
}
false
}
/// Merges callee saved register taints into the current state
pub
fn
merge_callee_saved_taints_from_return_state
(
&
mut
self
,
return_state
:
&
State
,
calling_convention
:
Option
<&
CallingConvention
>
,
)
{
if
let
Some
(
calling_conv
)
=
calling_convention
{
let
callee_saved_registers
:
HashSet
<
String
>
=
calling_conv
.callee_saved_register
.iter
()
.cloned
()
.collect
();
for
(
variable
,
taint
)
in
return_state
.get_register_taints
()
{
if
callee_saved_registers
.get
(
&
variable
.name
)
.is_some
()
{
self
.set_register_taint
(
variable
,
*
taint
);
}
}
}
}
}
#[cfg(test)]
mod
tests
;
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_78/state/tests.rs
0 → 100644
View file @
d926369b
use
crate
::{
abstract_domain
::{
DataDomain
,
PointerDomain
},
intermediate_representation
::
CastOpType
,
};
use
super
::
*
;
fn
extern_symbol
(
name
:
&
str
,
return_args
:
Vec
<
Arg
>
)
->
ExternSymbol
{
ExternSymbol
{
tid
:
Tid
::
new
(
name
.to_string
()),
addresses
:
vec!
[],
name
:
name
.into
(),
calling_convention
:
None
,
parameters
:
Vec
::
new
(),
return_values
:
return_args
,
no_return
:
false
,
}
}
fn
bv
(
value
:
i64
)
->
BitvectorDomain
{
BitvectorDomain
::
Value
(
Bitvector
::
from_i64
(
value
))
}
impl
State
{
pub
fn
mock_with_pi_state
()
->
(
State
,
PointerInferenceState
)
{
let
arg
=
Arg
::
Register
(
Variable
::
mock
(
"RAX"
,
8
as
u64
));
let
pi_state
=
PointerInferenceState
::
new
(
&
Variable
::
mock
(
"RSP"
,
8
as
u64
),
Tid
::
new
(
"func"
));
let
symbol
=
extern_symbol
(
"system"
,
vec!
[
arg
]);
let
current_sub
=
Sub
::
mock
(
"current"
);
let
mut
state
=
State
::
new
(
&
symbol
,
&
Variable
::
mock
(
"RSP"
,
8
as
u64
),
Some
(
&
pi_state
),
&
current_sub
,
);
state
.pi_def_map
=
Some
(
HashMap
::
new
());
(
state
,
pi_state
)
}
pub
fn
set_pointer_inference_state_for_def
(
&
mut
self
,
pi_state
:
Option
<
PointerInferenceState
>
,
def_tid
:
&
Tid
,
)
{
if
let
Some
(
pi_state
)
=
pi_state
{
if
let
Some
(
pid_map
)
=
self
.pi_def_map
.as_mut
()
{
pid_map
.insert
(
def_tid
.clone
(),
pi_state
);
}
}
}
pub
fn
set_pointer_inference_map
(
&
mut
self
,
pi_state_map
:
HashMap
<
Tid
,
PointerInferenceState
>
)
{
self
.pi_def_map
=
Some
(
pi_state_map
);
}
pub
fn
get_pointer_inference_state_at_def
(
&
self
,
def_tid
:
&
Tid
,
)
->
Option
<&
PointerInferenceState
>
{
if
let
Some
(
pid_map
)
=
self
.pi_def_map
.as_ref
()
{
return
pid_map
.get
(
def_tid
);
}
None
}
pub
fn
remove_all_register_taints
(
&
mut
self
)
{
self
.register_taint
=
HashMap
::
new
();
}
}
struct
Setup
{
state
:
State
,
pi_state
:
PointerInferenceState
,
rdi
:
Variable
,
rsi
:
Variable
,
rsp
:
Variable
,
constant
:
Bitvector
,
def_tid
:
Tid
,
stack_pointer
:
DataDomain
<
BitvectorDomain
>
,
base_eight_offset
:
DataDomain
<
BitvectorDomain
>
,
base_sixteen_offset
:
DataDomain
<
BitvectorDomain
>
,
}
impl
Setup
{
fn
new
()
->
Self
{
let
(
state
,
pi_state
)
=
State
::
mock_with_pi_state
();
let
stack_id
=
pi_state
.stack_id
.clone
();
Setup
{
state
,
pi_state
,
rdi
:
Variable
::
mock
(
"RDI"
,
8
as
u64
),
rsi
:
Variable
::
mock
(
"RSI"
,
8
as
u64
),
rsp
:
Variable
::
mock
(
"RSP"
,
8
as
u64
),
constant
:
Bitvector
::
from_str_radix
(
16
,
"ffcc00"
)
.unwrap
(),
def_tid
:
Tid
::
new
(
"def"
),
stack_pointer
:
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
0
))),
base_eight_offset
:
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
-
8
))),
base_sixteen_offset
:
Data
::
Pointer
(
PointerDomain
::
new
(
stack_id
.clone
(),
bv
(
-
16
))),
}
}
}
#[test]
fn
setting_expression_and_constants
()
{
let
mut
setup
=
Setup
::
new
();
setup
.pi_state
.set_register
(
&
setup
.rdi
,
setup
.base_eight_offset
.clone
());
setup
.state
.set_pointer_inference_state_for_def
(
Some
(
setup
.pi_state
.clone
()),
&
setup
.def_tid
);
// Test Case 1: Constants
let
copy_const_expr
=
Expression
::
const_from_apint
(
setup
.constant
.clone
());
setup
.state
.set_register_taint
(
&
setup
.rdi
,
Taint
::
Tainted
(
setup
.rdi.size
));
setup
.state
.set_expression_taint_and_store_constants
(
&
setup
.def_tid
,
&
setup
.rdi
,
&
copy_const_expr
,
&
setup
.rsp
,
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rdi
),
None
);
assert_eq!
(
setup
.state.string_constants
.len
(),
1
);
assert_eq!
(
setup
.state.string_constants
.get
(
&
setup
.constant
),
Some
(
&
setup
.constant
)
);
// Test Case 2: Variables
let
copy_var_expr
=
Expression
::
var
(
"RSI"
);
setup
.state
.set_register_taint
(
&
setup
.rdi
,
Taint
::
Tainted
(
setup
.rdi.size
));
setup
.state
.set_expression_taint_and_store_constants
(
&
setup
.def_tid
,
&
setup
.rdi
,
&
copy_var_expr
,
&
setup
.rsp
,
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rdi
),
None
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rsi
),
Some
(
&
Taint
::
Tainted
(
setup
.rsi.size
))
);
// Test Case 2.5: Stack Pointer Assignment
let
stack_expression
=
Expression
::
var
(
"RSP"
);
setup
.state
.set_register_taint
(
&
setup
.rdi
,
Taint
::
Tainted
(
setup
.rdi.size
));
setup
.state
.set_expression_taint_and_store_constants
(
&
setup
.def_tid
,
&
setup
.rdi
,
&
stack_expression
,
&
setup
.rsp
,
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rdi
),
None
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.stack_pointer
,
&
setup
.pi_state
),
true
);
// Test Case 3: Bin Ops
let
bin_op_expr
=
Expression
::
var
(
"RBP"
)
.plus_const
(
-
8
);
setup
.state
.set_register_taint
(
&
setup
.rdi
,
Taint
::
Tainted
(
setup
.rdi.size
));
setup
.state
.set_expression_taint_and_store_constants
(
&
setup
.def_tid
,
&
setup
.rdi
,
&
bin_op_expr
,
&
setup
.rsp
,
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rdi
),
None
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
&
setup
.pi_state
),
true
);
// Test Case 4: Any other Expression
let
cast_expr
=
Expression
::
var
(
"RDI"
)
.subpiece
(
ByteSize
::
new
(
0
),
ByteSize
::
new
(
4
))
.cast
(
CastOpType
::
IntZExt
);
setup
.state
.set_register_taint
(
&
setup
.rdi
,
Taint
::
Tainted
(
setup
.rdi.size
));
setup
.state
.set_expression_taint_and_store_constants
(
&
setup
.def_tid
,
&
setup
.rdi
,
&
cast_expr
,
&
setup
.rsp
,
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rdi
),
Some
(
&
Taint
::
Tainted
(
setup
.rdi.size
))
);
}
#[test]
fn
tainting_values_to_be_stored
()
{
let
mut
setup
=
Setup
::
new
();
let
stack_pointer
=
Variable
::
mock
(
"RSP"
,
8
as
u64
);
// Test Case: Memory target is tainted. --> Taint the input register
setup
.pi_state
.set_register
(
&
setup
.rdi
,
setup
.base_eight_offset
.clone
());
setup
.state
.set_pointer_inference_state_for_def
(
Some
(
setup
.pi_state
.clone
()),
&
setup
.def_tid
);
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
setup
.state
.taint_value_to_be_stored
(
&
setup
.def_tid
,
&
Expression
::
var
(
"RDI"
),
&
Expression
::
var
(
"RSI"
),
&
stack_pointer
,
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
&
setup
.pi_state
),
false
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rsi
),
Some
(
&
Taint
::
Tainted
(
setup
.rsi.size
))
);
// Test Case: Memory target is not tainted. --> Do nothing
setup
.state.register_taint
.remove
(
&
setup
.rsi
);
setup
.pi_state
.set_register
(
&
setup
.rdi
,
setup
.base_sixteen_offset
.clone
());
setup
.state
.set_pointer_inference_state_for_def
(
Some
(
setup
.pi_state
.clone
()),
&
setup
.def_tid
);
setup
.state
.taint_value_to_be_stored
(
&
setup
.def_tid
,
&
Expression
::
var
(
"RDI"
),
&
Expression
::
var
(
"RSI"
),
&
stack_pointer
,
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rsi
),
None
);
}
#[test]
fn
tainting_def_input_register
()
{
let
mut
setup
=
Setup
::
new
();
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
stack_pointer
=
Variable
::
mock
(
"RSP"
,
8
as
u64
);
setup
.state
.set_pointer_inference_state_for_def
(
Some
(
setup
.pi_state
.clone
()),
&
setup
.def_tid
);
// Test Case 1: Variable input
setup
.state
.taint_def_input_register
(
&
Expression
::
var
(
"RDI"
),
&
stack_pointer
,
&
setup
.def_tid
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
// Test Case 2: Stack Pointer input
setup
.state
.taint_def_input_register
(
&
Expression
::
var
(
"RSP"
),
&
stack_pointer
,
&
setup
.def_tid
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.stack_pointer
.clone
(),
&
setup
.pi_state
),
true
);
setup
.state
.remove_all_register_taints
();
// Test Case 3: Bin Op Input
setup
.state
.taint_def_input_register
(
&
Expression
::
var
(
"RDI"
)
.plus_const
(
8
),
&
stack_pointer
,
&
setup
.def_tid
,
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
setup
.state
.remove_all_register_taints
();
// Test Case 4: Cast Op Input
setup
.state
.taint_def_input_register
(
&
Expression
::
var
(
"RDI"
)
.cast
(
CastOpType
::
IntZExt
),
&
stack_pointer
,
&
setup
.def_tid
,
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
}
#[test]
fn
tainting_variable_input
()
{
let
mut
setup
=
Setup
::
new
();
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
let
stack_pointer
=
Variable
::
mock
(
"RSP"
,
8
as
u64
);
setup
.state
.set_pointer_inference_state_for_def
(
Some
(
setup
.pi_state
.clone
()),
&
setup
.def_tid
);
// Test Case 1: Register input
setup
.state
.taint_variable_input
(
&
rdi_reg
,
&
stack_pointer
,
&
setup
.def_tid
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
rdi_reg
),
Some
(
&
Taint
::
Tainted
(
rdi_reg
.size
))
);
// Test Case 2: Stack Pointer input
setup
.state
.taint_variable_input
(
&
stack_pointer
,
&
stack_pointer
,
&
setup
.def_tid
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.stack_pointer
.clone
(),
&
setup
.pi_state
),
true
);
}
#[test]
fn
removing_memory_taint_at_target
()
{
let
mut
setup
=
Setup
::
new
();
// Test Case: Memory was tainted and taint is removed
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
.clone
(),
&
setup
.pi_state
),
true
);
setup
.state
.remove_mem_taint_at_target
(
&
setup
.base_eight_offset
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
&
setup
.pi_state
),
false
);
// Test Case: Memory was not tainted and nothing happens
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_sixteen_offset
.clone
(),
&
setup
.pi_state
),
false
);
setup
.state
.remove_mem_taint_at_target
(
&
setup
.base_sixteen_offset
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_sixteen_offset
,
&
setup
.pi_state
),
false
);
}
#[test]
fn
saving_taint_to_memory
()
{
let
mut
setup
=
Setup
::
new
();
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
.clone
(),
&
setup
.pi_state
),
false
);
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
.clone
(),
&
setup
.pi_state
),
true
);
}
#[test]
fn
removing_non_parameter_taints_for_generic_function
()
{
let
mut
setup
=
Setup
::
new
();
let
mut
mock_project
=
Project
::
mock_empty
();
mock_project
.calling_conventions
.push
(
CallingConvention
::
mock
());
setup
.state
.set_register_taint
(
&
setup
.rdi
,
Taint
::
Tainted
(
setup
.rdi.size
));
setup
.state
.set_register_taint
(
&
setup
.rsi
,
Taint
::
Tainted
(
setup
.rsi.size
));
setup
.state
.remove_non_parameter_taints_for_generic_function
(
&
mock_project
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rdi
),
Some
(
&
Taint
::
Tainted
(
setup
.rdi.size
))
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rsi
),
None
);
}
#[test]
fn
removing_non_callee_saved_taint
()
{
let
mut
setup
=
Setup
::
new
();
let
cconv
=
CallingConvention
::
mock
();
let
rbp_reg
=
Variable
::
mock
(
"RBP"
,
8
as
u64
);
setup
.state
.set_register_taint
(
&
setup
.rdi
,
Taint
::
Tainted
(
setup
.rsi.size
));
setup
.state
.set_register_taint
(
&
rbp_reg
,
Taint
::
Tainted
(
rbp_reg
.size
));
setup
.state
.remove_non_callee_saved_taint
(
&
cconv
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rdi
),
None
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
rbp_reg
),
Some
(
&
Taint
::
Tainted
(
rbp_reg
.size
))
);
}
#[test]
fn
removing_all_but_return
()
{
let
mut
setup
=
Setup
::
new
();
let
mut
return_regs
:
HashSet
<
String
>
=
HashSet
::
new
();
return_regs
.insert
(
"RAX"
.to_string
());
let
rax_reg
=
Variable
::
mock
(
"RAX"
,
8
as
u64
);
setup
.state
.set_register_taint
(
&
setup
.rdi
,
Taint
::
Tainted
(
setup
.rsi.size
));
setup
.state
.set_register_taint
(
&
rax_reg
,
Taint
::
Tainted
(
rax_reg
.size
));
setup
.state
.remove_all_except_return_register_taints
(
return_regs
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
setup
.rdi
),
None
);
assert_eq!
(
setup
.state
.get_register_taint
(
&
rax_reg
),
Some
(
&
Taint
::
Tainted
(
rax_reg
.size
))
);
}
#[test]
fn
checking_if_address_points_to_taint
()
{
let
mut
setup
=
Setup
::
new
();
setup
.state
.save_taint_to_memory
(
&
setup
.base_eight_offset
,
Taint
::
Tainted
(
ByteSize
::
new
(
8
)));
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_eight_offset
,
&
setup
.pi_state
),
true
);
assert_eq!
(
setup
.state
.check_if_address_points_to_taint
(
setup
.base_sixteen_offset
,
&
setup
.pi_state
),
false
);
}
#[test]
fn
checking_return_registers_for_taint
()
{
let
mut
setup
=
Setup
::
new
();
let
rax_reg
=
Variable
::
mock
(
"RAX"
,
8
as
u64
);
let
rdi_reg
=
Variable
::
mock
(
"RDI"
,
8
as
u64
);
// Test Case: Empty Taint
assert_eq!
(
setup
.state
.check_return_registers_for_taint
(
vec!
[
"RAX"
.to_string
()]),
false
);
// Test Case: No return register tainted
setup
.state
.set_register_taint
(
&
rdi_reg
,
Taint
::
Tainted
(
rdi_reg
.size
));
assert_eq!
(
setup
.state
.check_return_registers_for_taint
(
vec!
[
"RAX"
.to_string
()]),
false
);
// Test Case: Return register tainted
setup
.state
.set_register_taint
(
&
rax_reg
,
Taint
::
Tainted
(
rax_reg
.size
));
assert_eq!
(
setup
.state
.check_return_registers_for_taint
(
vec!
[
"RAX"
.to_string
()]),
true
);
}
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/intermediate_representation/expression/builder.rs
View file @
d926369b
#
[
cfg
(
test
)]
use
apint
::
ApInt
;
#[cfg(test)]
use
super
::{
CastOpType
,
Variable
};
use
super
::{
BinOpType
,
Expression
};
use
super
::{
BinOpType
,
Expression
};
use
crate
::
prelude
::
*
;
use
crate
::
prelude
::
*
;
/// ## Helper functions for building expressions
/// ## Helper functions for building expressions
impl
Expression
{
impl
Expression
{
/// Shortcut for creating a constant expression from an i64 value
#[cfg(test)]
pub
fn
const_from_i64
(
value
:
i64
)
->
Expression
{
Expression
::
Const
(
Bitvector
::
from_i64
(
value
))
}
/// Shortcut for creating a constant expression from an apint value (e.g. copy of global address)
#[cfg(test)]
pub
fn
const_from_apint
(
value
:
ApInt
)
->
Expression
{
Expression
::
Const
(
value
)
}
/// Shortcut for creating a variable expression
#[cfg(test)]
pub
fn
var
(
name
:
&
str
)
->
Expression
{
Expression
::
Var
(
Variable
{
name
:
name
.into
(),
size
:
ByteSize
::
new
(
8
),
is_temp
:
false
,
})
}
/// Shortcut for creating a cast expression
#[cfg(test)]
pub
fn
cast
(
self
,
op
:
CastOpType
)
->
Expression
{
Expression
::
Cast
{
op
,
size
:
ByteSize
::
new
(
8
),
arg
:
Box
::
new
(
self
),
}
}
/// Shortcut for creating a subpiece expression
#[cfg(test)]
pub
fn
subpiece
(
self
,
low_byte
:
ByteSize
,
size
:
ByteSize
)
->
Expression
{
Expression
::
Subpiece
{
low_byte
,
size
,
arg
:
Box
::
new
(
self
),
}
}
/// Shortcut for creating an `IntAdd`-expression
/// Shortcut for creating an `IntAdd`-expression
pub
fn
plus
(
self
,
rhs
:
Expression
)
->
Expression
{
pub
fn
plus
(
self
,
rhs
:
Expression
)
->
Expression
{
Expression
::
BinOp
{
Expression
::
BinOp
{
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/intermediate_representation/term.rs
View file @
d926369b
...
@@ -3,6 +3,8 @@ use crate::prelude::*;
...
@@ -3,6 +3,8 @@ use crate::prelude::*;
use
crate
::
utils
::
log
::
LogMessage
;
use
crate
::
utils
::
log
::
LogMessage
;
use
std
::
collections
::
HashSet
;
use
std
::
collections
::
HashSet
;
pub
mod
builder
;
/// A term identifier consisting of an ID string (which is required to be unique)
/// A term identifier consisting of an ID string (which is required to be unique)
/// and an address to indicate where the term is located.
/// and an address to indicate where the term is located.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone,
PartialOrd,
Ord)]
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone,
PartialOrd,
Ord)]
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/intermediate_representation/term/builder.rs
0 → 100644
View file @
d926369b
#
[
cfg
(
test
)]
use
crate
::
intermediate_representation
::{
Expression
,
Variable
};
#[cfg(test)]
use
super
::{
Def
,
Jmp
,
Term
,
Tid
};
/// ## Helper functions for building defs
#[cfg(test)]
impl
Def
{
/// Shortcut for creating a assign def
pub
fn
assign
(
tid
:
&
str
,
var
:
Variable
,
value
:
Expression
)
->
Term
<
Def
>
{
Term
{
tid
:
Tid
::
new
(
tid
),
term
:
Def
::
Assign
{
var
,
value
},
}
}
/// Shortcut for creating a load def
pub
fn
load
(
tid
:
&
str
,
var
:
Variable
,
address
:
Expression
)
->
Term
<
Def
>
{
Term
{
tid
:
Tid
::
new
(
tid
),
term
:
Def
::
Load
{
var
,
address
},
}
}
/// Shortcut for creating a store def
pub
fn
store
(
tid
:
&
str
,
address
:
Expression
,
value
:
Expression
)
->
Term
<
Def
>
{
Term
{
tid
:
Tid
::
new
(
tid
),
term
:
Def
::
Store
{
address
,
value
},
}
}
}
/// ## Helper functions for building jmps
#[cfg(test)]
impl
Jmp
{
/// Shortcut for creating a call
pub
fn
call
(
tid
:
&
str
,
target_tid
:
&
str
,
return_tid
:
Option
<&
str
>
)
->
Term
<
Jmp
>
{
let
return_tid
=
return_tid
.map
(|
tid_name
|
Tid
::
new
(
tid_name
));
Term
{
tid
:
Tid
::
new
(
tid
),
term
:
Jmp
::
Call
{
target
:
Tid
::
new
(
target_tid
),
return_
:
return_tid
,
},
}
}
/// Shortcut for creating a branch
pub
fn
branch
(
tid
:
&
str
,
target_tid
:
&
str
)
->
Term
<
Jmp
>
{
Term
{
tid
:
Tid
::
new
(
tid
),
term
:
Jmp
::
Branch
(
Tid
::
new
(
target_tid
)),
}
}
}
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/lib.rs
View file @
d926369b
...
@@ -55,6 +55,7 @@ impl std::fmt::Display for CweModule {
...
@@ -55,6 +55,7 @@ impl std::fmt::Display for CweModule {
/// Get a list of all known analysis modules.
/// Get a list of all known analysis modules.
pub
fn
get_modules
()
->
Vec
<&
'static
CweModule
>
{
pub
fn
get_modules
()
->
Vec
<&
'static
CweModule
>
{
vec!
[
vec!
[
&
crate
::
checkers
::
cwe_78
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_190
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_190
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_215
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_215
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_243
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_243
::
CWE_MODULE
,
...
...
This diff is collapsed.
Click to expand it.
src/config.json
View file @
d926369b
{
{
"CWE78"
:
{
"system_symbols"
:
[
"system"
,
"execl"
],
"string_symbols"
:
[
"sprintf"
,
"snprintf"
,
"strcat"
,
"strncat"
],
"user_input_symbols"
:
[
"scanf"
,
"__isoc99_scanf"
]
},
"CWE190"
:
{
"CWE190"
:
{
"symbols"
:
[
"symbols"
:
[
"xmalloc"
,
"xmalloc"
,
...
...
This diff is collapsed.
Click to expand it.
test/artificial_samples/cwe_78.c
0 → 100644
View file @
d926369b
#include <string.h>
#include <stdlib.h>
int
constant_system
()
{
system
(
"ls"
);
}
int
main
(
int
argc
,
char
**
argv
)
{
char
*
dest
=
"usr/bin/cat "
;
strcat
(
dest
,
argv
[
1
]);
system
(
dest
);
constant_system
();
return
0
;
}
This diff is collapsed.
Click to expand it.
test/src/lib.rs
View file @
d926369b
...
@@ -170,6 +170,38 @@ mod tests {
...
@@ -170,6 +170,38 @@ mod tests {
#[test]
#[test]
#[ignore]
#[ignore]
fn
cwe_78
()
{
let
mut
error_log
=
Vec
::
new
();
let
mut
tests
=
all_test_cases
(
"cwe_78"
,
"CWE78"
);
// Ghidra does not recognize all extern function calls in the disassembly step for MIPS.
// Needs own control flow graph analysis to be fixed.
mark_architecture_skipped
(
&
mut
tests
,
"mips64"
);
mark_architecture_skipped
(
&
mut
tests
,
"mips64el"
);
mark_architecture_skipped
(
&
mut
tests
,
"mips"
);
mark_architecture_skipped
(
&
mut
tests
,
"mipsel"
);
mark_architecture_skipped
(
&
mut
tests
,
"ppc64"
);
// Ghidra generates mangled function names here for some reason.
mark_architecture_skipped
(
&
mut
tests
,
"ppc64le"
);
// Ghidra generates mangled function names here for some reason.
mark_skipped
(
&
mut
tests
,
"x86"
,
"clang"
);
// Return value detection insufficient for x86
mark_compiler_skipped
(
&
mut
tests
,
"mingw32-gcc"
);
// Pointer Inference returns insufficient results for PE
for
test_case
in
tests
{
let
num_expected_occurences
=
1
;
if
let
Err
(
error
)
=
test_case
.run_test
(
"[CWE78]"
,
num_expected_occurences
)
{
error_log
.push
((
test_case
.get_filepath
(),
error
));
}
}
if
!
error_log
.is_empty
()
{
print_errors
(
error_log
);
panic!
();
}
}
#[test]
#[ignore]
fn
cwe_190
()
{
fn
cwe_190
()
{
let
mut
error_log
=
Vec
::
new
();
let
mut
error_log
=
Vec
::
new
();
let
mut
tests
=
all_test_cases
(
"cwe_190"
,
"CWE190"
);
let
mut
tests
=
all_test_cases
(
"cwe_190"
,
"CWE190"
);
...
...
This diff is collapsed.
Click to expand it.
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