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
170f44b1
Unverified
Commit
170f44b1
authored
May 23, 2022
by
van den Bosch
Committed by
GitHub
May 23, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Refactor runtime memory image (#324)
parent
576c3dd9
Hide whitespace changes
Inline
Side-by-side
Showing
37 changed files
with
512 additions
and
567 deletions
+512
-567
main.rs
src/caller/src/main.rs
+6
-7
id_manipulation.rs
...src/analysis/pointer_inference/context/id_manipulation.rs
+1
-1
mod.rs
...checker_lib/src/analysis/pointer_inference/context/mod.rs
+19
-17
trait_impls.rs
...lib/src/analysis/pointer_inference/context/trait_impls.rs
+5
-3
mod.rs
src/cwe_checker_lib/src/analysis/pointer_inference/mod.rs
+5
-1
cwe_helpers.rs
...src/analysis/pointer_inference/object_list/cwe_helpers.rs
+2
-0
mod.rs
...ker_lib/src/analysis/pointer_inference/object_list/mod.rs
+1
-1
access_handling.rs
...b/src/analysis/pointer_inference/state/access_handling.rs
+0
-2
mod.rs
...e_checker_lib/src/analysis/pointer_inference/state/mod.rs
+0
-1
tests.rs
...checker_lib/src/analysis/pointer_inference/state/tests.rs
+0
-1
vsa_result_impl.rs
...ker_lib/src/analysis/pointer_inference/vsa_result_impl.rs
+1
-1
mod.rs
...hecker_lib/src/analysis/string_abstraction/context/mod.rs
+0
-6
symbol_calls.rs
...b/src/analysis/string_abstraction/context/symbol_calls.rs
+3
-2
memcpy.rs
...nalysis/string_abstraction/context/symbol_calls/memcpy.rs
+2
-2
scanf.rs
...analysis/string_abstraction/context/symbol_calls/scanf.rs
+13
-10
sprintf.rs
...alysis/string_abstraction/context/symbol_calls/sprintf.rs
+3
-4
strcat.rs
...nalysis/string_abstraction/context/symbol_calls/strcat.rs
+3
-2
tests.rs
...analysis/string_abstraction/context/symbol_calls/tests.rs
+0
-1
tests.rs
...cker_lib/src/analysis/string_abstraction/context/tests.rs
+0
-2
trait_impls.rs
...ib/src/analysis/string_abstraction/context/trait_impls.rs
+3
-3
tests.rs
.../analysis/string_abstraction/context/trait_impls/tests.rs
+1
-3
mod.rs
src/cwe_checker_lib/src/analysis/string_abstraction/mod.rs
+3
-16
mod.rs
..._checker_lib/src/analysis/string_abstraction/state/mod.rs
+1
-2
cwe_134.rs
src/cwe_checker_lib/src/checkers/cwe_134.rs
+3
-4
cwe_467.rs
src/cwe_checker_lib/src/checkers/cwe_467.rs
+6
-17
cwe_476.rs
src/cwe_checker_lib/src/checkers/cwe_476.rs
+1
-6
context.rs
src/cwe_checker_lib/src/checkers/cwe_476/context.rs
+7
-18
cwe_560.rs
src/cwe_checker_lib/src/checkers/cwe_560.rs
+4
-11
cwe_78.rs
src/cwe_checker_lib/src/checkers/cwe_78.rs
+5
-2
mod.rs
src/cwe_checker_lib/src/intermediate_representation/mod.rs
+2
-0
project.rs
...we_checker_lib/src/intermediate_representation/project.rs
+5
-2
runtime_memory_image.rs
...b/src/intermediate_representation/runtime_memory_image.rs
+400
-0
lib.rs
src/cwe_checker_lib/src/lib.rs
+2
-10
term.rs
src/cwe_checker_lib/src/pcode/term.rs
+2
-0
arguments.rs
src/cwe_checker_lib/src/utils/arguments.rs
+1
-3
tests.rs
src/cwe_checker_lib/src/utils/arguments/tests.rs
+0
-3
binary.rs
src/cwe_checker_lib/src/utils/binary.rs
+2
-403
No files found.
src/caller/src/main.rs
View file @
170f44b1
...
...
@@ -4,7 +4,8 @@
extern
crate
cwe_checker_lib
;
// Needed for the docstring-link to work
use
cwe_checker_lib
::
analysis
::
graph
;
use
cwe_checker_lib
::
utils
::
binary
::{
BareMetalConfig
,
RuntimeMemoryImage
};
use
cwe_checker_lib
::
intermediate_representation
::
RuntimeMemoryImage
;
use
cwe_checker_lib
::
utils
::
binary
::
BareMetalConfig
;
use
cwe_checker_lib
::
utils
::
log
::{
print_all_messages
,
LogLevel
};
use
cwe_checker_lib
::
utils
::{
get_ghidra_plugin_path
,
read_config_file
};
use
cwe_checker_lib
::
AnalysisResults
;
...
...
@@ -159,6 +160,9 @@ fn run_with_ghidra(args: &CmdlineArgs) {
// so that other analyses do not have to adjust their addresses.
runtime_memory_image
.add_global_memory_offset
(
project
.program.term.address_base_offset
);
}
project
.runtime_memory_image
=
runtime_memory_image
;
// Generate the control flow graph of the program
let
extern_sub_tids
=
project
.program
...
...
@@ -169,12 +173,7 @@ fn run_with_ghidra(args: &CmdlineArgs) {
.collect
();
let
control_flow_graph
=
graph
::
get_program_cfg
(
&
project
.program
,
extern_sub_tids
);
let
analysis_results
=
AnalysisResults
::
new
(
&
binary
,
&
runtime_memory_image
,
&
control_flow_graph
,
&
project
,
);
let
analysis_results
=
AnalysisResults
::
new
(
&
binary
,
&
control_flow_graph
,
&
project
);
let
modules_depending_on_string_abstraction
=
BTreeSet
::
from_iter
([
"CWE78"
]);
let
modules_depending_on_pointer_inference
=
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/context/id_manipulation.rs
View file @
170f44b1
...
...
@@ -22,7 +22,7 @@ impl<'a> Context<'a> {
for
param
in
callee_fn_sig
.parameters
.keys
()
{
let
param_id
=
AbstractIdentifier
::
from_arg
(
callee_tid
,
param
);
if
let
Ok
(
param_value
)
=
state_before_call
.eval_parameter_arg
(
param
,
self
.runtime_memory_image
)
state_before_call
.eval_parameter_arg
(
param
,
&
self
.project
.runtime_memory_image
)
{
id_map
.insert
(
param_id
,
param_value
);
}
else
{
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/context/mod.rs
View file @
170f44b1
use
crate
::
abstract_domain
::
*
;
use
crate
::
analysis
::
function_signature
::
FunctionSignature
;
use
crate
::
analysis
::
graph
::
Graph
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
log
::
*
;
use
crate
::{
abstract_domain
::
*
,
utils
::
binary
::
RuntimeMemoryImage
};
use
std
::
collections
::{
BTreeMap
,
BTreeSet
};
use
super
::
state
::
State
;
...
...
@@ -24,9 +24,6 @@ pub struct Context<'a> {
pub
graph
:
&
'a
Graph
<
'a
>
,
/// A reference to the `Project` object representing the binary
pub
project
:
&
'a
Project
,
/// The runtime memory image for reading global read-only variables.
/// Note that values of writeable global memory segments are not tracked.
pub
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
/// Maps the TIDs of functions that shall be treated as extern symbols to the `ExternSymbol` object representing it.
pub
extern_symbol_map
:
&
'a
BTreeMap
<
Tid
,
ExternSymbol
>
,
/// Maps the TIDs of internal functions to the function signatures computed for it.
...
...
@@ -54,7 +51,6 @@ impl<'a> Context<'a> {
Context
{
graph
:
analysis_results
.control_flow_graph
,
project
:
analysis_results
.project
,
runtime_memory_image
:
analysis_results
.runtime_memory_image
,
extern_symbol_map
:
&
analysis_results
.project.program.term.extern_symbols
,
fn_signatures
:
analysis_results
.function_signatures
.unwrap
(),
log_collector
,
...
...
@@ -73,7 +69,9 @@ impl<'a> Context<'a> {
address
:
&
Expression
,
)
->
bool
{
if
self
.project.cpu_architecture
.contains
(
"MIPS"
)
&&
var
.name
==
"gp"
{
if
let
Ok
(
gp_val
)
=
state
.load_value
(
address
,
var
.size
,
self
.runtime_memory_image
)
{
if
let
Ok
(
gp_val
)
=
state
.load_value
(
address
,
var
.size
,
&
self
.project.runtime_memory_image
)
{
gp_val
.is_top
()
}
else
{
true
...
...
@@ -137,23 +135,23 @@ impl<'a> Context<'a> {
"malloc"
=>
{
let
size_parameter
=
extern_symbol
.parameters
.get
(
0
)
.unwrap
();
state
.eval_parameter_arg
(
size_parameter
,
self
.runtime_memory_image
)
.eval_parameter_arg
(
size_parameter
,
&
self
.project
.runtime_memory_image
)
.unwrap_or_else
(|
_
|
Data
::
new_top
(
address_bytesize
))
}
"realloc"
=>
{
let
size_parameter
=
extern_symbol
.parameters
.get
(
1
)
.unwrap
();
state
.eval_parameter_arg
(
size_parameter
,
self
.runtime_memory_image
)
.eval_parameter_arg
(
size_parameter
,
&
self
.project
.runtime_memory_image
)
.unwrap_or_else
(|
_
|
Data
::
new_top
(
address_bytesize
))
}
"calloc"
=>
{
let
size_param1
=
extern_symbol
.parameters
.get
(
0
)
.unwrap
();
let
size_param2
=
extern_symbol
.parameters
.get
(
1
)
.unwrap
();
let
param1_value
=
state
.eval_parameter_arg
(
size_param1
,
self
.runtime_memory_image
)
.eval_parameter_arg
(
size_param1
,
&
self
.project
.runtime_memory_image
)
.unwrap_or_else
(|
_
|
Data
::
new_top
(
address_bytesize
));
let
param2_value
=
state
.eval_parameter_arg
(
size_param2
,
self
.runtime_memory_image
)
.eval_parameter_arg
(
size_param2
,
&
self
.project
.runtime_memory_image
)
.unwrap_or_else
(|
_
|
Data
::
new_top
(
address_bytesize
));
param1_value
.bin_op
(
BinOpType
::
IntMult
,
&
param2_value
)
}
...
...
@@ -225,7 +223,7 @@ impl<'a> Context<'a> {
match
extern_symbol
.get_unique_parameter
()
{
Ok
(
parameter
)
=>
{
let
parameter_value
=
state
.eval_parameter_arg
(
parameter
,
self
.runtime_memory_image
);
state
.eval_parameter_arg
(
parameter
,
&
self
.project
.runtime_memory_image
);
match
parameter_value
{
Ok
(
memory_object_pointer
)
=>
{
if
let
Err
(
possible_double_frees
)
=
...
...
@@ -275,7 +273,7 @@ impl<'a> Context<'a> {
I
:
Iterator
<
Item
=
&
'iter
Arg
>
,
{
for
parameter
in
parameters
{
match
state
.eval_parameter_arg
(
parameter
,
self
.runtime_memory_image
)
{
match
state
.eval_parameter_arg
(
parameter
,
&
self
.project
.runtime_memory_image
)
{
Ok
(
value
)
=>
{
if
state
.memory
.is_dangling_pointer
(
&
value
,
true
)
{
state
...
...
@@ -317,10 +315,12 @@ impl<'a> Context<'a> {
extern_symbol
:
&
ExternSymbol
,
)
{
for
parameter
in
extern_symbol
.parameters
.iter
()
{
match
state
.eval_parameter_arg
(
parameter
,
self
.runtime_memory_image
)
{
match
state
.eval_parameter_arg
(
parameter
,
&
self
.project
.runtime_memory_image
)
{
Ok
(
data
)
=>
{
if
state
.pointer_contains_out_of_bounds_target
(
&
data
,
self
.runtime_memory_image
)
{
if
state
.pointer_contains_out_of_bounds_target
(
&
data
,
&
self
.project.runtime_memory_image
,
)
{
let
warning
=
CweWarning
{
name
:
"CWE119"
.to_string
(),
version
:
VERSION
.to_string
(),
...
...
@@ -394,7 +394,7 @@ impl<'a> Context<'a> {
extern_symbol
:
&
ExternSymbol
,
)
->
State
{
self
.log_debug
(
new_state
.clear_stack_parameter
(
extern_symbol
,
self
.runtime_memory_image
),
new_state
.clear_stack_parameter
(
extern_symbol
,
&
self
.project
.runtime_memory_image
),
Some
(
&
call
.tid
),
);
let
calling_conv
=
self
.project
.get_calling_convention
(
extern_symbol
);
...
...
@@ -413,7 +413,9 @@ impl<'a> Context<'a> {
}
}
else
{
for
parameter
in
extern_symbol
.parameters
.iter
()
{
if
let
Ok
(
data
)
=
state
.eval_parameter_arg
(
parameter
,
self
.runtime_memory_image
)
{
if
let
Ok
(
data
)
=
state
.eval_parameter_arg
(
parameter
,
&
self
.project.runtime_memory_image
)
{
possible_referenced_ids
.extend
(
data
.referenced_ids
()
.cloned
());
}
}
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/context/trait_impls.rs
View file @
170f44b1
...
...
@@ -42,7 +42,9 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
Ok
(
false
)
=>
(),
// no null dereference detected
}
// check for out-of-bounds memory access
if
new_state
.contains_out_of_bounds_mem_access
(
&
def
.term
,
self
.runtime_memory_image
)
{
if
new_state
.contains_out_of_bounds_mem_access
(
&
def
.term
,
&
self
.project.runtime_memory_image
)
{
let
(
warning_name
,
warning_description
)
=
match
&
def
.term
{
Def
::
Load
{
..
}
=>
(
"CWE125"
,
...
...
@@ -75,7 +77,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
match
&
def
.term
{
Def
::
Store
{
address
,
value
}
=>
{
self
.log_debug
(
new_state
.handle_store
(
address
,
value
,
self
.runtime_memory_image
),
new_state
.handle_store
(
address
,
value
,
&
self
.project
.runtime_memory_image
),
Some
(
&
def
.tid
),
);
Some
(
new_state
)
...
...
@@ -87,7 +89,7 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
Def
::
Load
{
var
,
address
}
=>
{
if
!
self
.is_mips_gp_load_to_top_value
(
state
,
var
,
address
)
{
self
.log_debug
(
new_state
.handle_load
(
var
,
address
,
self
.runtime_memory_image
),
new_state
.handle_load
(
var
,
address
,
&
self
.project
.runtime_memory_image
),
Some
(
&
def
.tid
),
);
}
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/mod.rs
View file @
170f44b1
...
...
@@ -275,7 +275,11 @@ impl<'a> PointerInference<'a> {
}
Def
::
Load
{
var
,
address
}
=>
{
let
loaded_value
=
state
.load_value
(
address
,
var
.size
,
context
.runtime_memory_image
)
.load_value
(
address
,
var
.size
,
&
context
.project.runtime_memory_image
,
)
.unwrap_or_else
(|
_
|
Data
::
new_top
(
var
.size
));
self
.values_at_defs
.insert
(
def
.tid
.clone
(),
loaded_value
);
self
.addresses_at_defs
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/object_list/cwe_helpers.rs
View file @
170f44b1
...
...
@@ -2,6 +2,8 @@
//! or check whether they are violated.
//! E.g. checks for use-after-free or buffer overflow checks.
use
crate
::
intermediate_representation
::
RuntimeMemoryImage
;
use
super
::
*
;
impl
AbstractObjectList
{
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/object_list/mod.rs
View file @
170f44b1
use
super
::
object
::
*
;
use
super
::{
Data
,
ValueDomain
};
use
crate
::
abstract_domain
::
*
;
use
crate
::
prelude
::
*
;
use
crate
::{
abstract_domain
::
*
,
utils
::
binary
::
RuntimeMemoryImage
};
use
serde
::{
Deserialize
,
Serialize
};
use
std
::
collections
::{
BTreeMap
,
BTreeSet
};
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/state/access_handling.rs
View file @
170f44b1
//! Methods of [`State`] for handling memory and register access operations.
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
super
::
*
;
impl
State
{
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/state/mod.rs
View file @
170f44b1
...
...
@@ -4,7 +4,6 @@ use crate::abstract_domain::*;
use
crate
::
analysis
::
function_signature
::
FunctionSignature
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
std
::
collections
::{
BTreeMap
,
BTreeSet
};
mod
access_handling
;
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/state/tests.rs
View file @
170f44b1
use
super
::
super
::
ValueDomain
;
use
super
::
*
;
use
crate
::
analysis
::
pointer_inference
::
object
::
*
;
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
Expression
::
*
;
fn
bv
(
value
:
i64
)
->
ValueDomain
{
...
...
src/cwe_checker_lib/src/analysis/pointer_inference/vsa_result_impl.rs
View file @
170f44b1
...
...
@@ -27,7 +27,7 @@ impl<'a> VsaResult for PointerInference<'a> {
let
state
=
self
.states_at_tids
.get
(
jmp_tid
)
?
;
let
context
=
self
.computation
.get_context
()
.get_context
();
state
.eval_parameter_arg
(
parameter
,
contex
t
.runtime_memory_image
)
.eval_parameter_arg
(
parameter
,
&
context
.projec
t.runtime_memory_image
)
.ok
()
}
}
src/cwe_checker_lib/src/analysis/string_abstraction/context/mod.rs
View file @
170f44b1
...
...
@@ -17,7 +17,6 @@ use crate::{
pointer_inference
::
State
as
PointerInferenceState
,
},
intermediate_representation
::{
Def
,
ExternSymbol
,
Project
,
Term
,
Tid
},
utils
::
binary
::
RuntimeMemoryImage
,
};
use
super
::{
state
::
State
,
Config
};
...
...
@@ -31,9 +30,6 @@ mod trait_impls;
pub
struct
Context
<
'a
,
T
:
AbstractDomain
+
DomainInsertion
+
HasTop
+
Eq
+
From
<
String
>>
{
/// A reference to the `Project` object representing the binary
pub
project
:
&
'a
Project
,
/// The runtime memory image for reading global read-only variables.
/// Note that values of writeable global memory segments are not tracked.
pub
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
/// 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.
...
...
@@ -62,7 +58,6 @@ impl<'a, T: AbstractDomain + HasTop + Eq + From<String> + DomainInsertion> Conte
/// Create a new context object for a given project.
pub
fn
new
(
project
:
&
'a
Project
,
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
pointer_inference_results
:
&
'a
PointerInferenceComputation
<
'a
>
,
config
:
Config
,
)
->
Context
<
'a
,
T
>
{
...
...
@@ -95,7 +90,6 @@ impl<'a, T: AbstractDomain + HasTop + Eq + From<String> + DomainInsertion> Conte
Context
{
project
,
runtime_memory_image
,
pointer_inference_results
,
format_string_index_map
:
config
.format_string_index
.into_iter
()
.collect
(),
string_symbol_map
,
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/symbol_calls.rs
View file @
170f44b1
...
...
@@ -169,7 +169,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
/// Inserts a char constant into the format string.
pub
fn
get_constant_char_domain
(
&
self
,
constant
:
Bitvector
)
->
Option
<
T
>
{
if
let
Ok
(
Some
(
char_code
))
=
self
.runtime_memory_image
.read
(
if
let
Ok
(
Some
(
char_code
))
=
self
.
project.
runtime_memory_image
.read
(
&
constant
,
self
.project
.datatype_properties
...
...
@@ -199,6 +199,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
/// Inserts a string constant into the format string.
pub
fn
get_constant_string_domain
(
&
self
,
constant
:
Bitvector
)
->
Option
<
T
>
{
if
let
Ok
(
string
)
=
self
.project
.runtime_memory_image
.read_string_until_null_terminator
(
&
constant
)
{
...
...
@@ -218,7 +219,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
if
let
Some
(
dest_arg
)
=
extern_symbol
.parameters
.first
()
{
if
let
Some
(
pi_state
)
=
state
.get_pointer_inference_state
()
{
if
let
Ok
(
pointer
)
=
pi_state
.eval_parameter_arg
(
dest_arg
,
self
.runtime_memory_image
)
pi_state
.eval_parameter_arg
(
dest_arg
,
&
self
.project
.runtime_memory_image
)
{
let
heap_to_string_map
=
state
.get_heap_to_string_map
();
for
(
target
,
_
)
in
pointer
.get_relative_values
()
.iter
()
{
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/symbol_calls/memcpy.rs
View file @
170f44b1
...
...
@@ -44,7 +44,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
)
->
Result
<
DataDomain
<
IntervalDomain
>
,
Error
>
{
if
let
Some
(
return_arg
)
=
extern_symbol
.parameters
.first
()
{
if
let
Ok
(
return_data
)
=
pi_state
.eval_parameter_arg
(
return_arg
,
self
.runtime_memory_image
)
pi_state
.eval_parameter_arg
(
return_arg
,
&
self
.project
.runtime_memory_image
)
{
if
!
return_data
.get_relative_values
()
.is_empty
()
{
return
Ok
(
return_data
);
...
...
@@ -62,7 +62,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state
:
&
PointerInferenceState
,
)
->
Result
<
DataDomain
<
IntervalDomain
>
,
Error
>
{
if
let
Some
(
input_arg
)
=
extern_symbol
.parameters
.get
(
1
)
{
return
pi_state
.eval_parameter_arg
(
input_arg
,
self
.runtime_memory_image
);
return
pi_state
.eval_parameter_arg
(
input_arg
,
&
self
.project
.runtime_memory_image
);
}
Err
(
anyhow!
(
"No input values"
))
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/symbol_calls/scanf.rs
View file @
170f44b1
...
...
@@ -25,7 +25,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state
,
extern_symbol
,
&
self
.format_string_index_map
,
self
.runtime_memory_image
,
)
{
self
.create_abstract_domain_entries_for_function_return_values
(
pi_state
,
...
...
@@ -47,7 +46,8 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
)
{
for
(
argument
,
value
)
in
arg_to_value_map
.into_iter
()
{
if
argument
.get_data_type
()
.unwrap
()
==
Datatype
::
Pointer
{
if
let
Ok
(
data
)
=
pi_state
.eval_parameter_arg
(
&
argument
,
self
.runtime_memory_image
)
if
let
Ok
(
data
)
=
pi_state
.eval_parameter_arg
(
&
argument
,
&
self
.project.runtime_memory_image
)
{
if
!
data
.get_relative_values
()
.is_empty
()
{
Context
::
add_constant_or_top_value_to_return_locations
(
...
...
@@ -91,8 +91,8 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
let
mut
new_state
=
state
.clone
();
if
let
Some
(
pi_state
)
=
state
.get_pointer_inference_state
()
{
if
let
Some
(
source_string_arg
)
=
extern_symbol
.parameters
.first
()
{
if
let
Ok
(
source_string
)
=
pi_state
.eval_parameter_arg
(
source_string_arg
,
self
.runtime_memory_image
)
if
let
Ok
(
source_string
)
=
pi_state
.eval_parameter_arg
(
source_string_arg
,
&
self
.project
.runtime_memory_image
)
{
if
self
.source_string_mapped_to_return_locations
(
pi_state
,
...
...
@@ -120,11 +120,15 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
extern_symbol
:
&
ExternSymbol
,
)
->
bool
{
if
let
Some
(
global_address
)
=
source_string
.get_absolute_value
()
{
if
let
Ok
(
source_string
)
=
self
.runtime_memory_image
.read_string_until_null_terminator
(
&
global_address
.try_to_bitvec
()
.expect
(
"Could not translate interval address to bitvector."
),
)
{
if
let
Ok
(
source_string
)
=
self
.project
.runtime_memory_image
.read_string_until_null_terminator
(
&
global_address
.try_to_bitvec
()
.expect
(
"Could not translate interval address to bitvector."
),
)
{
if
let
Ok
(
source_return_string_map
)
=
self
.map_source_string_parameters_to_return_arguments
(
pi_state
,
...
...
@@ -158,7 +162,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state
,
extern_symbol
,
&
self
.format_string_index_map
,
self
.runtime_memory_image
,
)
{
let
return_values
:
Vec
<
String
>
=
source_string
.split
(
' '
)
.map
(|
s
|
s
.to_string
())
.collect
();
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/symbol_calls/sprintf.rs
View file @
170f44b1
...
...
@@ -25,7 +25,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
if
let
Some
(
return_arg
)
=
extern_symbol
.parameters
.first
()
{
if
let
Some
(
pi_state
)
=
state
.get_pointer_inference_state
()
{
if
let
Ok
(
return_pointer
)
=
pi_state
.eval_parameter_arg
(
return_arg
,
self
.runtime_memory_image
)
pi_state
.eval_parameter_arg
(
return_arg
,
&
self
.project
.runtime_memory_image
)
{
if
!
return_pointer
.get_relative_values
()
.is_empty
()
{
let
format_string_index
=
self
...
...
@@ -63,7 +63,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state
,
extern_symbol
,
format_string_index
,
self
.runtime_memory_image
,
&
self
.project
.runtime_memory_image
,
)
{
let
returned_abstract_domain
=
self
.create_string_domain_for_sprintf_snprintf
(
pi_state
,
...
...
@@ -105,7 +105,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state
,
extern_symbol
,
&
self
.format_string_index_map
,
self
.runtime_memory_image
,
)
{
Ok
(
var_args
)
=>
{
if
var_args
.is_empty
()
{
...
...
@@ -258,7 +257,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
pi_state
:
&
PointerInferenceState
,
state
:
&
State
<
T
>
,
)
->
T
{
if
let
Ok
(
data
)
=
pi_state
.eval_parameter_arg
(
arg
,
self
.runtime_memory_image
)
{
if
let
Ok
(
data
)
=
pi_state
.eval_parameter_arg
(
arg
,
&
self
.project
.runtime_memory_image
)
{
let
constant_domain
:
Option
<
T
>
=
self
.fetch_constant_domain_if_available
(
&
data
,
arg
);
if
let
Some
(
generated_domain
)
=
Context
::
<
T
>
::
fetch_subdomains_if_available
(
&
data
,
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/symbol_calls/strcat.rs
View file @
170f44b1
...
...
@@ -17,7 +17,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
if
let
Some
(
pi_state
)
=
state
.get_pointer_inference_state
()
{
if
let
Some
(
return_arg
)
=
extern_symbol
.parameters
.first
()
{
if
let
Ok
(
return_pointer
)
=
pi_state
.eval_parameter_arg
(
return_arg
,
self
.runtime_memory_image
)
pi_state
.eval_parameter_arg
(
return_arg
,
&
self
.project
.runtime_memory_image
)
{
if
!
return_pointer
.get_relative_values
()
.is_empty
()
{
let
target_domain
=
...
...
@@ -64,7 +64,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
let
mut
input_domain
=
T
::
create_top_value_domain
();
if
let
Some
(
input_arg
)
=
extern_symbol
.parameters
.get
(
1
)
{
if
let
Ok
(
input_value
)
=
pi_state
.eval_parameter_arg
(
input_arg
,
self
.runtime_memory_image
)
pi_state
.eval_parameter_arg
(
input_arg
,
&
self
.project
.runtime_memory_image
)
{
// Check whether the second input string is in read only memory or on stack/heap.
if
!
input_value
.get_relative_values
()
.is_empty
()
{
...
...
@@ -78,6 +78,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
if
let
Some
(
value
)
=
input_value
.get_absolute_value
()
{
if
let
Ok
(
global_address
)
=
value
.try_to_bitvec
()
{
if
let
Ok
(
input_string
)
=
self
.project
.runtime_memory_image
.read_string_until_null_terminator
(
&
global_address
)
{
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/symbol_calls/tests.rs
View file @
170f44b1
...
...
@@ -61,7 +61,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String> + Debu
mock_string_symbol_map
(
&
pi_context
.project
),
mock_format_index_map
(),
&
pi_results
,
&
pi_context
.runtime_memory_image
,
);
let
state_before_call
:
State
<
T
>
=
State
::
mock_with_given_pi_state
(
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/tests.rs
View file @
170f44b1
...
...
@@ -6,7 +6,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
string_symbols
:
HashMap
<
Tid
,
&
'a
ExternSymbol
>
,
format_string_index
:
HashMap
<
String
,
usize
>
,
pointer_inference_results
:
&
'a
PointerInferenceComputation
<
'a
>
,
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
)
->
Self
{
let
mut
extern_symbol_map
=
HashMap
::
new
();
for
(
tid
,
symbol
)
in
project
.program.term.extern_symbols
.iter
()
{
...
...
@@ -35,7 +34,6 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>> Conte
Context
{
project
,
runtime_memory_image
,
pointer_inference_results
,
string_symbol_map
:
string_symbols
,
extern_symbol_map
,
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/trait_impls.rs
View file @
170f44b1
...
...
@@ -46,7 +46,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
new_state
.handle_assign_and_load
(
output
,
input
,
self
.runtime_memory_image
,
&
self
.project
.runtime_memory_image
,
&
self
.block_first_def_set
,
true
,
);
...
...
@@ -58,7 +58,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
new_state
.handle_assign_and_load
(
output
,
input
,
self
.runtime_memory_image
,
&
self
.project
.runtime_memory_image
,
&
self
.block_first_def_set
,
false
,
);
...
...
@@ -66,7 +66,7 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
Def
::
Store
{
address
,
value
}
=>
new_state
.handle_store
(
address
,
value
,
self
.runtime_memory_image
,
&
self
.project
.runtime_memory_image
,
&
self
.block_first_def_set
,
),
}
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/context/trait_impls/tests.rs
View file @
170f44b1
...
...
@@ -13,7 +13,6 @@ use crate::{
},
},
intermediate_representation
::{
Bitvector
,
Blk
,
ByteSize
,
ExternSymbol
,
Jmp
,
Tid
,
Variable
},
utils
::
binary
::
RuntimeMemoryImage
,
};
#[test]
...
...
@@ -23,7 +22,6 @@ fn test_update_def() {
vec!
[(
memcpy_symbol
.clone
(),
vec!
[
true
])],
"func"
,
);
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
project
);
pi_results
.compute
(
false
);
...
...
@@ -62,7 +60,7 @@ fn test_update_def() {
let
_
=
setup
.pi_state_before_symbol_call
.store_value
(
&
pointer_to_pointer
,
&
loaded_pointer
,
&
mem
_image
,
&
project
.runtime_memory
_image
,
);
let
r2_reg
=
Variable
{
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/mod.rs
View file @
170f44b1
...
...
@@ -10,7 +10,6 @@ use crate::{
abstract_domain
::{
AbstractDomain
,
DomainInsertion
,
HasTop
},
intermediate_representation
::
Project
,
prelude
::
*
,
utils
::
binary
::
RuntimeMemoryImage
,
};
use
self
::
state
::
State
;
...
...
@@ -49,17 +48,11 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
/// Generate a new string abstraction computation for a project.
pub
fn
new
(
project
:
&
'a
Project
,
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
control_flow_graph
:
&
'a
Graph
<
'a
>
,
pointer_inference_results
:
&
'a
PointerInferenceComputation
<
'a
>
,
config
:
Config
,
)
->
StringAbstraction
<
'a
,
T
>
{
let
context
=
Context
::
new
(
project
,
runtime_memory_image
,
pointer_inference_results
,
config
,
);
let
context
=
Context
::
new
(
project
,
pointer_inference_results
,
config
);
let
mut
sub_to_entry_blocks_map
=
HashMap
::
new
();
for
sub
in
project
.program.term.subs
.values
()
{
...
...
@@ -132,18 +125,12 @@ impl<'a, T: AbstractDomain + DomainInsertion + HasTop + Eq + From<String>>
/// Compute the string abstraction and return its results.
pub
fn
run
<
'a
,
T
:
AbstractDomain
+
HasTop
+
Eq
+
From
<
String
>
+
DomainInsertion
>
(
project
:
&
'a
Project
,
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
control_flow_graph
:
&
'a
Graph
<
'a
>
,
pointer_inference
:
&
'a
PointerInferenceComputation
<
'a
>
,
config
:
Config
,
)
->
StringAbstraction
<
'a
,
T
>
{
let
mut
string_abstraction
=
StringAbstraction
::
new
(
project
,
runtime_memory_image
,
control_flow_graph
,
pointer_inference
,
config
,
);
let
mut
string_abstraction
=
StringAbstraction
::
new
(
project
,
control_flow_graph
,
pointer_inference
,
config
);
string_abstraction
.compute
();
...
...
src/cwe_checker_lib/src/analysis/string_abstraction/state/mod.rs
View file @
170f44b1
...
...
@@ -9,14 +9,13 @@ use itertools::Itertools;
use
petgraph
::
graph
::
NodeIndex
;
use
crate
::
abstract_domain
::{
DataDomain
,
DomainInsertion
,
HasTop
,
TryToBitvec
};
use
crate
::
intermediate_representation
::{
ExternSymbol
,
Project
};
use
crate
::
intermediate_representation
::{
ExternSymbol
,
Project
,
RuntimeMemoryImage
};
use
crate
::{
abstract_domain
::
IntervalDomain
,
prelude
::
*
};
use
crate
::{
abstract_domain
::{
AbstractDomain
,
AbstractIdentifier
},
analysis
::
pointer_inference
::
PointerInference
as
PointerInferenceComputation
,
analysis
::
pointer_inference
::
State
as
PointerInferenceState
,
intermediate_representation
::{
Expression
,
Sub
,
Variable
},
utils
::
binary
::
RuntimeMemoryImage
,
};
/// Contains all information known about the state of a program at a specific point of time.
...
...
src/cwe_checker_lib/src/checkers/cwe_134.rs
View file @
170f44b1
...
...
@@ -31,8 +31,8 @@ use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use
crate
::
analysis
::
pointer_inference
::
PointerInference
;
use
crate
::
intermediate_representation
::
ExternSymbol
;
use
crate
::
intermediate_representation
::
Jmp
;
use
crate
::
intermediate_representation
::
RuntimeMemoryImage
;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
crate
::
utils
::
log
::
CweWarning
;
use
crate
::
utils
::
log
::
LogMessage
;
use
crate
::
CweModule
;
...
...
@@ -91,7 +91,7 @@ pub fn check_cwe(
symbol
,
&
format_string_index
,
pointer_inference_results
,
analysis_results
.runtime_memory_image
,
&
analysis_results
.project
.runtime_memory_image
,
);
if
matches!
(
...
...
@@ -219,7 +219,6 @@ pub mod tests {
#[test]
fn
test_locate_format_string
()
{
let
sprintf_symbol
=
ExternSymbol
::
mock_string
();
let
runtime_memory_image
=
RuntimeMemoryImage
::
mock
();
let
project
=
mock_project
();
let
graph
=
crate
::
analysis
::
graph
::
get_program_cfg
(
&
project
.program
,
HashSet
::
new
());
let
mut
pi_results
=
PointerInferenceComputation
::
mock
(
&
project
);
...
...
@@ -241,7 +240,7 @@ pub mod tests {
&
sprintf_symbol
,
&
format_string_index
,
&
pi_results
,
&
runtime_memory_image
,
&
project
.
runtime_memory_image
,
),
StringLocation
::
GlobalReadable
);
...
...
src/cwe_checker_lib/src/checkers/cwe_467.rs
View file @
170f44b1
...
...
@@ -24,7 +24,6 @@ use crate::abstract_domain::TryToBitvec;
use
crate
::
analysis
::
pointer_inference
::
State
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
crate
::
utils
::
log
::{
CweWarning
,
LogMessage
};
use
crate
::
utils
::
symbol_utils
::{
get_callsites
,
get_symbol_map
};
use
crate
::
CweModule
;
...
...
@@ -45,24 +44,20 @@ pub struct Config {
/// Compute the program state at the end of the given basic block
/// assuming nothing is known about the state at the start of the block.
fn
compute_block_end_state
(
project
:
&
Project
,
global_memory
:
&
RuntimeMemoryImage
,
block
:
&
Term
<
Blk
>
,
)
->
State
{
fn
compute_block_end_state
(
project
:
&
Project
,
block
:
&
Term
<
Blk
>
)
->
State
{
let
stack_register
=
&
project
.stack_pointer_register
;
let
mut
state
=
State
::
new
(
stack_register
,
block
.tid
.clone
());
for
def
in
block
.term.defs
.iter
()
{
match
&
def
.term
{
Def
::
Store
{
address
,
value
}
=>
{
let
_
=
state
.handle_store
(
address
,
value
,
global_memory
);
let
_
=
state
.handle_store
(
address
,
value
,
&
project
.runtime_memory_image
);
}
Def
::
Assign
{
var
,
value
}
=>
{
let
_
=
state
.handle_register_assign
(
var
,
value
);
}
Def
::
Load
{
var
,
address
}
=>
{
let
_
=
state
.handle_load
(
var
,
address
,
global_memory
);
let
_
=
state
.handle_load
(
var
,
address
,
&
project
.runtime_memory_image
);
}
}
}
...
...
@@ -72,14 +67,13 @@ fn compute_block_end_state(
/// Check whether a parameter value of the call to `symbol` has value `sizeof(void*)`.
fn
check_for_pointer_sized_arg
(
project
:
&
Project
,
global_memory
:
&
RuntimeMemoryImage
,
block
:
&
Term
<
Blk
>
,
symbol
:
&
ExternSymbol
,
)
->
bool
{
let
pointer_size
=
project
.stack_pointer_register.size
;
let
state
=
compute_block_end_state
(
project
,
global_memory
,
block
);
let
state
=
compute_block_end_state
(
project
,
block
);
for
parameter
in
symbol
.parameters
.iter
()
{
if
let
Ok
(
param
)
=
state
.eval_parameter_arg
(
parameter
,
global_memory
)
{
if
let
Ok
(
param
)
=
state
.eval_parameter_arg
(
parameter
,
&
project
.runtime_memory_image
)
{
if
let
Ok
(
param_value
)
=
param
.try_to_bitvec
()
{
if
Ok
(
u64
::
from
(
pointer_size
))
==
param_value
.try_to_u64
()
{
return
true
;
...
...
@@ -120,12 +114,7 @@ pub fn check_cwe(
let
symbol_map
=
get_symbol_map
(
project
,
&
config
.symbols
);
for
sub
in
project
.program.term.subs
.values
()
{
for
(
block
,
jmp
,
symbol
)
in
get_callsites
(
sub
,
&
symbol_map
)
{
if
check_for_pointer_sized_arg
(
project
,
analysis_results
.runtime_memory_image
,
block
,
symbol
,
)
{
if
check_for_pointer_sized_arg
(
project
,
block
,
symbol
)
{
cwe_warnings
.push
(
generate_cwe_warning
(
jmp
,
symbol
))
}
}
...
...
src/cwe_checker_lib/src/checkers/cwe_476.rs
View file @
170f44b1
...
...
@@ -85,12 +85,7 @@ pub fn check_cwe(
let
config
:
Config
=
serde_json
::
from_value
(
cwe_params
.clone
())
.unwrap
();
let
symbol_map
=
crate
::
utils
::
symbol_utils
::
get_symbol_map
(
project
,
&
config
.symbols
[
..
]);
let
general_context
=
Context
::
new
(
project
,
analysis_results
.runtime_memory_image
,
pointer_inference_results
,
cwe_sender
,
);
let
general_context
=
Context
::
new
(
project
,
pointer_inference_results
,
cwe_sender
);
for
edge
in
general_context
.get_graph
()
.edge_references
()
{
if
let
Edge
::
ExternCallStub
(
jmp
)
=
edge
.weight
()
{
...
...
src/cwe_checker_lib/src/checkers/cwe_476/context.rs
View file @
170f44b1
...
...
@@ -8,7 +8,6 @@ use crate::analysis::interprocedural_fixpoint_generic::NodeValue;
use
crate
::
analysis
::
pointer_inference
::
PointerInference
as
PointerInferenceComputation
;
use
crate
::
analysis
::
pointer_inference
::
State
as
PointerInferenceState
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
crate
::
utils
::
log
::
CweWarning
;
use
petgraph
::
graph
::
NodeIndex
;
use
petgraph
::
visit
::
IntoNodeReferences
;
...
...
@@ -26,8 +25,6 @@ use std::sync::Arc;
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
,
/// 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.
...
...
@@ -64,7 +61,6 @@ impl<'a> Context<'a> {
/// since this function can be expensive!
pub
fn
new
(
project
:
&
'a
Project
,
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
pointer_inference_results
:
&
'a
PointerInferenceComputation
<
'a
>
,
cwe_collector
:
crossbeam_channel
::
Sender
<
CweWarning
>
,
)
->
Self
{
...
...
@@ -92,7 +88,6 @@ impl<'a> Context<'a> {
}
Context
{
project
,
runtime_memory_image
,
pointer_inference_results
,
block_start_node_map
:
Arc
::
new
(
block_start_node_map
),
extern_symbol_map
:
Arc
::
new
(
extern_symbol_map
),
...
...
@@ -203,8 +198,8 @@ impl<'a> Context<'a> {
{
return
true
;
}
if
let
Ok
(
stack_param
)
=
pi_state
.eval_parameter_arg
(
parameter
,
self
.runtime_memory_image
)
if
let
Ok
(
stack_param
)
=
pi_state
.eval_parameter_arg
(
parameter
,
&
self
.project
.runtime_memory_image
)
{
if
state
.check_if_address_points_to_taint
(
stack_param
,
pi_state
)
{
return
true
;
...
...
@@ -417,16 +412,14 @@ impl<'a> crate::analysis::forward_interprocedural_fixpoint::Context<'a> for Cont
#[cfg(test)]
mod
tests
{
use
super
::
*
;
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
impl
<
'a
>
Context
<
'a
>
{
pub
fn
mock
(
project
:
&
'a
Project
,
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
pi_results
:
&
'a
PointerInferenceComputation
<
'a
>
,
)
->
Context
<
'a
>
{
let
(
cwe_sender
,
_
)
=
crossbeam_channel
::
unbounded
();
let
mut
context
=
Context
::
new
(
project
,
runtime_memory_image
,
pi_results
,
cwe_sender
);
let
mut
context
=
Context
::
new
(
project
,
pi_results
,
cwe_sender
);
let
taint_source
=
Box
::
new
(
Term
{
tid
:
Tid
::
new
(
"taint_source"
),
term
:
Jmp
::
Call
{
...
...
@@ -445,9 +438,8 @@ mod tests {
#[test]
fn
check_parameter_arg_for_taint
()
{
let
project
=
Project
::
mock_x64
();
let
runtime_memory_image
=
RuntimeMemoryImage
::
mock
();
let
pi_results
=
PointerInferenceComputation
::
mock
(
&
project
);
let
context
=
Context
::
mock
(
&
project
,
&
runtime_memory_image
,
&
pi_results
);
let
context
=
Context
::
mock
(
&
project
,
&
pi_results
);
let
(
mut
state
,
_pi_state
)
=
State
::
mock_with_pi_state
();
assert_eq!
(
...
...
@@ -476,9 +468,8 @@ mod tests {
#[test]
fn
handle_generic_call
()
{
let
project
=
Project
::
mock_x64
();
let
runtime_memory_image
=
RuntimeMemoryImage
::
mock
();
let
pi_results
=
PointerInferenceComputation
::
mock
(
&
project
);
let
context
=
Context
::
mock
(
&
project
,
&
runtime_memory_image
,
&
pi_results
);
let
context
=
Context
::
mock
(
&
project
,
&
pi_results
);
let
mut
state
=
State
::
mock
();
assert
!
(
context
...
...
@@ -497,9 +488,8 @@ mod tests {
#[test]
fn
update_def
()
{
let
project
=
Project
::
mock_x64
();
let
runtime_memory_image
=
RuntimeMemoryImage
::
mock
();
let
pi_results
=
PointerInferenceComputation
::
mock
(
&
project
);
let
context
=
Context
::
mock
(
&
project
,
&
runtime_memory_image
,
&
pi_results
);
let
context
=
Context
::
mock
(
&
project
,
&
pi_results
);
let
(
mut
state
,
pi_state
)
=
State
::
mock_with_pi_state
();
state
.set_pointer_inference_state
(
Some
(
pi_state
));
...
...
@@ -550,9 +540,8 @@ mod tests {
#[test]
fn
update_jump
()
{
let
project
=
Project
::
mock_x64
();
let
runtime_memory_image
=
RuntimeMemoryImage
::
mock
();
let
pi_results
=
PointerInferenceComputation
::
mock
(
&
project
);
let
context
=
Context
::
mock
(
&
project
,
&
runtime_memory_image
,
&
pi_results
);
let
context
=
Context
::
mock
(
&
project
,
&
pi_results
);
let
(
state
,
_pi_state
)
=
State
::
mock_with_pi_state
();
let
jump
=
Term
{
...
...
src/cwe_checker_lib/src/checkers/cwe_560.rs
View file @
170f44b1
...
...
@@ -26,7 +26,6 @@ use crate::abstract_domain::TryToBitvec;
use
crate
::
analysis
::
pointer_inference
::
State
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
crate
::
utils
::
log
::{
CweWarning
,
LogMessage
};
use
crate
::
utils
::
symbol_utils
::{
get_callsites
,
get_symbol_map
};
use
crate
::
CweModule
;
...
...
@@ -50,7 +49,6 @@ fn get_umask_permission_arg(
block
:
&
Term
<
Blk
>
,
umask_symbol
:
&
ExternSymbol
,
project
:
&
Project
,
global_memory
:
&
RuntimeMemoryImage
,
)
->
Result
<
u64
,
Error
>
{
let
stack_register
=
&
project
.stack_pointer_register
;
let
mut
state
=
State
::
new
(
stack_register
,
block
.tid
.clone
());
...
...
@@ -58,19 +56,19 @@ fn get_umask_permission_arg(
for
def
in
block
.term.defs
.iter
()
{
match
&
def
.term
{
Def
::
Store
{
address
,
value
}
=>
{
let
_
=
state
.handle_store
(
address
,
value
,
global_memory
);
let
_
=
state
.handle_store
(
address
,
value
,
&
project
.runtime_memory_image
);
}
Def
::
Assign
{
var
,
value
}
=>
{
let
_
=
state
.handle_register_assign
(
var
,
value
);
}
Def
::
Load
{
var
,
address
}
=>
{
let
_
=
state
.handle_load
(
var
,
address
,
global_memory
);
let
_
=
state
.handle_load
(
var
,
address
,
&
project
.runtime_memory_image
);
}
}
}
let
parameter
=
umask_symbol
.get_unique_parameter
()
?
;
let
param_value
=
state
.eval_parameter_arg
(
parameter
,
global_memory
)
?
;
let
param_value
=
state
.eval_parameter_arg
(
parameter
,
&
project
.runtime_memory_image
)
?
;
if
let
Ok
(
umask_arg
)
=
param_value
.try_to_bitvec
()
{
Ok
(
umask_arg
.try_to_u64
()
?
)
}
else
{
...
...
@@ -115,12 +113,7 @@ pub fn check_cwe(
if
!
umask_symbol_map
.is_empty
()
{
for
sub
in
project
.program.term.subs
.values
()
{
for
(
block
,
jmp
,
umask_symbol
)
in
get_callsites
(
sub
,
&
umask_symbol_map
)
{
match
get_umask_permission_arg
(
block
,
umask_symbol
,
project
,
analysis_results
.runtime_memory_image
,
)
{
match
get_umask_permission_arg
(
block
,
umask_symbol
,
project
)
{
Ok
(
permission_const
)
=>
{
if
is_chmod_style_arg
(
permission_const
)
{
cwes
.push
(
generate_cwe_warning
(
sub
,
jmp
,
permission_const
));
...
...
src/cwe_checker_lib/src/checkers/cwe_78.rs
View file @
170f44b1
...
...
@@ -45,9 +45,9 @@ use crate::intermediate_representation::Arg;
use
crate
::
intermediate_representation
::
Expression
;
use
crate
::
intermediate_representation
::
ExternSymbol
;
use
crate
::
intermediate_representation
::
Jmp
;
use
crate
::
intermediate_representation
::
RuntimeMemoryImage
;
use
crate
::
intermediate_representation
::
Sub
;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
binary
::
RuntimeMemoryImage
;
use
crate
::
utils
::
log
::
CweWarning
;
use
crate
::
utils
::
log
::
LogMessage
;
...
...
@@ -117,7 +117,10 @@ pub fn check_cwe(
&
jmp
.tid
,
&
cwe_sender
,
&
log_sender
,
string_abstraction
.get_context
()
.runtime_memory_image
,
&
string_abstraction
.get_context
()
.project
.runtime_memory_image
,
)
}
}
...
...
src/cwe_checker_lib/src/intermediate_representation/mod.rs
View file @
170f44b1
...
...
@@ -30,6 +30,8 @@ mod program;
pub
use
program
::
*
;
mod
project
;
pub
use
project
::
*
;
mod
runtime_memory_image
;
pub
use
runtime_memory_image
::
*
;
/// An unsigned number of bytes.
///
...
...
src/cwe_checker_lib/src/intermediate_representation/project.rs
View file @
170f44b1
use
super
::
*
;
use
crate
::
utils
::
log
::
LogMessage
;
use
std
::
collections
::{
BTreeMap
,
BTreeSet
,
HashMap
,
HashSet
};
mod
block_duplication_normalization
;
use
crate
::
utils
::
log
::
LogMessage
;
use
block_duplication_normalization
::
*
;
/// The `Project` struct is the main data structure representing a binary.
...
...
@@ -24,6 +23,8 @@ pub struct Project {
pub
register_set
:
BTreeSet
<
Variable
>
,
/// Contains the properties of C data types. (e.g. size)
pub
datatype_properties
:
DatatypeProperties
,
/// Represents the memory after loading the binary.
pub
runtime_memory_image
:
RuntimeMemoryImage
,
}
impl
Project
{
...
...
@@ -319,6 +320,7 @@ mod tests {
calling_conventions
,
register_set
:
integer_register
.iter
()
.cloned
()
.collect
(),
datatype_properties
:
DatatypeProperties
::
mock_x64
(),
runtime_memory_image
:
RuntimeMemoryImage
::
mock
(),
}
}
...
...
@@ -357,6 +359,7 @@ mod tests {
)]),
register_set
:
integer_register
.collect
(),
datatype_properties
:
DatatypeProperties
::
mock_arm32
(),
runtime_memory_image
:
RuntimeMemoryImage
::
mock
(),
}
}
}
...
...
src/cwe_checker_lib/src/intermediate_representation/runtime_memory_image.rs
0 → 100644
View file @
170f44b1
use
super
::
*
;
use
crate
::
utils
::
binary
::{
parse_hex_string_to_u64
,
BareMetalConfig
,
MemorySegment
};
use
goblin
::{
elf
,
Object
};
/// A representation of the runtime image of a binary after being loaded into memory by the loader.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone)]
pub
struct
RuntimeMemoryImage
{
memory_segments
:
Vec
<
MemorySegment
>
,
is_little_endian
:
bool
,
}
impl
RuntimeMemoryImage
{
/// Generate a runtime memory image containing no memory segments.
/// Primarily useful in situations where any access to global memory would be an error.
pub
fn
empty
(
is_little_endian
:
bool
)
->
RuntimeMemoryImage
{
RuntimeMemoryImage
{
memory_segments
:
Vec
::
new
(),
is_little_endian
,
}
}
/// Generate a runtime memory image for a given binary.
///
/// The function can parse ELF and PE files as input.
pub
fn
new
(
binary
:
&
[
u8
])
->
Result
<
Self
,
Error
>
{
let
parsed_object
=
Object
::
parse
(
binary
)
?
;
match
parsed_object
{
Object
::
Elf
(
elf_file
)
=>
{
let
mut
memory_segments
=
Vec
::
new
();
for
header
in
elf_file
.program_headers
.iter
()
{
if
header
.p_type
==
elf
::
program_header
::
PT_LOAD
{
memory_segments
.push
(
MemorySegment
::
from_elf_segment
(
binary
,
header
));
}
}
if
memory_segments
.is_empty
()
{
return
Err
(
anyhow!
(
"No loadable segments found"
));
}
Ok
(
RuntimeMemoryImage
{
memory_segments
,
is_little_endian
:
elf_file
.header
.endianness
()
.unwrap
()
.is_little
(),
})
}
Object
::
PE
(
pe_file
)
=>
{
let
mut
memory_segments
=
Vec
::
new
();
for
header
in
pe_file
.sections
.iter
()
{
if
(
header
.characteristics
&
0x02000000
)
==
0
{
// Only load segments which are not discardable
memory_segments
.push
(
MemorySegment
::
from_pe_section
(
binary
,
header
));
}
}
if
memory_segments
.is_empty
()
{
return
Err
(
anyhow!
(
"No loadable segments found"
));
}
let
mut
memory_image
=
RuntimeMemoryImage
{
memory_segments
,
is_little_endian
:
true
,
};
memory_image
.add_global_memory_offset
(
pe_file
.image_base
as
u64
);
Ok
(
memory_image
)
}
_
=>
Err
(
anyhow!
(
"Object type not supported."
)),
}
}
/// Generate a runtime memory image for a bare metal binary.
///
/// The generated runtime memory image contains:
/// * one memory region corresponding to non-volatile memory
/// * one memory region corresponding to volatile memory (RAM)
///
/// See [`BareMetalConfig`] for more information about the assumed memory layout for bare metal binaries.
pub
fn
new_from_bare_metal
(
binary
:
&
[
u8
],
bare_metal_config
:
&
BareMetalConfig
,
)
->
Result
<
Self
,
Error
>
{
let
processor_id_parts
:
Vec
<&
str
>
=
bare_metal_config
.processor_id
.split
(
':'
)
.collect
();
if
processor_id_parts
.len
()
<
3
{
return
Err
(
anyhow!
(
"Could not parse processor ID."
));
}
let
is_little_endian
=
match
processor_id_parts
[
1
]
{
"LE"
=>
true
,
"BE"
=>
false
,
_
=>
return
Err
(
anyhow!
(
"Could not parse endianness of the processor ID."
)),
};
let
flash_base_address
=
parse_hex_string_to_u64
(
&
bare_metal_config
.flash_base_address
)
?
;
let
ram_base_address
=
parse_hex_string_to_u64
(
&
bare_metal_config
.ram_base_address
)
?
;
let
ram_size
=
parse_hex_string_to_u64
(
&
bare_metal_config
.ram_size
)
?
;
// Check that the whole binary is contained in addressable space.
let
address_bit_length
=
processor_id_parts
[
2
]
.parse
::
<
u64
>
()
?
;
match
flash_base_address
.checked_add
(
binary
.len
()
as
u64
)
{
Some
(
max_address
)
=>
{
if
(
max_address
>>
address_bit_length
)
!=
0
{
return
Err
(
anyhow!
(
"Binary too large for given base address"
));
}
}
None
=>
return
Err
(
anyhow!
(
"Binary too large for given base address"
)),
}
Ok
(
RuntimeMemoryImage
{
memory_segments
:
vec!
[
MemorySegment
::
from_bare_metal_file
(
binary
,
flash_base_address
),
MemorySegment
::
new_bare_metal_ram_segment
(
ram_base_address
,
ram_size
),
],
is_little_endian
,
})
}
/// Return whether values in the memory image should be interpreted in little-endian
/// or big-endian byte order.
pub
fn
is_little_endian_byte_order
(
&
self
)
->
bool
{
self
.is_little_endian
}
/// Add a global offset to the base addresses of all memory segments.
/// Useful to align the addresses with those reported by Ghidra
/// if the Ghidra backend added such an offset to all addresses.
pub
fn
add_global_memory_offset
(
&
mut
self
,
offset
:
u64
)
{
for
segment
in
self
.memory_segments
.iter_mut
()
{
segment
.base_address
+=
offset
;
}
}
/// Read the contents of the memory image at the given address
/// to emulate a read instruction to global data at runtime.
///
/// The read method is endian-aware,
/// i.e. values are interpreted with the endianness of the CPU architecture.
/// If the address points to a writeable segment, the returned value is a `Ok(None)` value,
/// since the data may change during program execution.
///
/// Returns an error if the address is not contained in the global data address range.
pub
fn
read
(
&
self
,
address
:
&
Bitvector
,
size
:
ByteSize
)
->
Result
<
Option
<
Bitvector
>
,
Error
>
{
let
address
=
address
.try_to_u64
()
.unwrap
();
for
segment
in
self
.memory_segments
.iter
()
{
if
address
>=
segment
.base_address
&&
u64
::
from
(
size
)
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
&&
address
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
-
u64
::
from
(
size
)
{
if
segment
.write_flag
{
// The segment is writeable, thus we do not know the content at runtime.
return
Ok
(
None
);
}
let
index
=
(
address
-
segment
.base_address
)
as
usize
;
let
mut
bytes
=
segment
.bytes
[
index
..
index
+
u64
::
from
(
size
)
as
usize
]
.to_vec
();
if
self
.is_little_endian
{
bytes
=
bytes
.into_iter
()
.rev
()
.collect
();
}
let
mut
bytes
=
bytes
.into_iter
();
let
mut
bitvector
=
Bitvector
::
from_u8
(
bytes
.next
()
.unwrap
());
for
byte
in
bytes
{
let
new_byte
=
Bitvector
::
from_u8
(
byte
);
bitvector
=
bitvector
.bin_op
(
BinOpType
::
Piece
,
&
new_byte
)
?
;
}
return
Ok
(
Some
(
bitvector
));
}
}
// No segment fully contains the read.
Err
(
anyhow!
(
"Address is not a valid global memory address."
))
}
/// Read the contents of memory from a given address onwards until a null byte is reached and checks whether the
/// content is a valid UTF8 string.
pub
fn
read_string_until_null_terminator
(
&
self
,
address
:
&
Bitvector
)
->
Result
<&
str
,
Error
>
{
let
address
=
address
.try_to_u64
()
.unwrap
();
for
segment
in
self
.memory_segments
.iter
()
{
if
address
>=
segment
.base_address
&&
address
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
let
start_index
=
(
address
-
segment
.base_address
)
as
usize
;
if
let
Some
(
end_index
)
=
segment
.bytes
[
start_index
..
]
.iter
()
.position
(|
&
b
|
b
==
0
)
{
let
c_str
=
std
::
ffi
::
CStr
::
from_bytes_with_nul
(
&
segment
.bytes
[
start_index
..
start_index
+
end_index
+
1
],
)
?
;
return
Ok
(
c_str
.to_str
()
?
);
}
else
{
return
Err
(
anyhow!
(
"Not a valid string in memory."
));
}
}
}
Err
(
anyhow!
(
"Address is not a valid global memory address."
))
}
/// Checks whether the constant is a global memory address.
pub
fn
is_global_memory_address
(
&
self
,
constant
:
&
Bitvector
)
->
bool
{
if
self
.read
(
constant
,
constant
.bytesize
())
.is_ok
()
{
return
true
;
}
false
}
/// Check whether all addresses in the given interval point to a readable segment in the runtime memory image.
///
/// Returns an error if the address interval intersects more than one memory segment
/// or if it does not point to global memory at all.
pub
fn
is_interval_readable
(
&
self
,
start_address
:
u64
,
end_address
:
u64
,
)
->
Result
<
bool
,
Error
>
{
for
segment
in
self
.memory_segments
.iter
()
{
if
start_address
>=
segment
.base_address
&&
start_address
<
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
if
end_address
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
return
Ok
(
segment
.read_flag
);
}
else
{
return
Err
(
anyhow!
(
"Interval spans more than one segment"
));
}
}
}
Err
(
anyhow!
(
"Address not contained in runtime memory image"
))
}
/// For an address to global read-only memory, return the memory segment it points to
/// and the index inside the segment, where the address points to.
///
/// Returns an error if the target memory segment is marked as writeable
/// or if the pointer does not point to global memory.
pub
fn
get_ro_data_pointer_at_address
(
&
self
,
address
:
&
Bitvector
,
)
->
Result
<
(
&
[
u8
],
usize
),
Error
>
{
let
address
=
address
.try_to_u64
()
.unwrap
();
for
segment
in
self
.memory_segments
.iter
()
{
if
address
>=
segment
.base_address
&&
address
<
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
if
segment
.write_flag
{
return
Err
(
anyhow!
(
"Target segment is writeable"
));
}
else
{
return
Ok
((
&
segment
.bytes
,
(
address
-
segment
.base_address
)
as
usize
));
}
}
}
Err
(
anyhow!
(
"Pointer target not in global memory."
))
}
/// Check whether the given address points to a writeable segment in the runtime memory image.
///
/// Returns an error if the address does not point to global memory.
pub
fn
is_address_writeable
(
&
self
,
address
:
&
Bitvector
)
->
Result
<
bool
,
Error
>
{
let
address
=
address
.try_to_u64
()
.unwrap
();
for
segment
in
self
.memory_segments
.iter
()
{
if
address
>=
segment
.base_address
&&
address
<
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
return
Ok
(
segment
.write_flag
);
}
}
Err
(
anyhow!
(
"Address not contained in runtime memory image"
))
}
/// Check whether all addresses in the given interval point to a writeable segment in the runtime memory image.
///
/// Returns an error if the address interval intersects more than one memory segment
/// or if it does not point to global memory at all.
pub
fn
is_interval_writeable
(
&
self
,
start_address
:
u64
,
end_address
:
u64
,
)
->
Result
<
bool
,
Error
>
{
for
segment
in
self
.memory_segments
.iter
()
{
if
start_address
>=
segment
.base_address
&&
start_address
<
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
if
end_address
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
return
Ok
(
segment
.write_flag
);
}
else
{
return
Err
(
anyhow!
(
"Interval spans more than one segment"
));
}
}
}
Err
(
anyhow!
(
"Address not contained in runtime memory image"
))
}
}
impl
RuntimeMemoryImage
{
/// Creates a mock runtime memory image with: byte series, strings and format strings.
pub
fn
mock
()
->
RuntimeMemoryImage
{
RuntimeMemoryImage
{
memory_segments
:
vec!
[
MemorySegment
{
bytes
:
[
0xb0u8
,
0xb1
,
0xb2
,
0xb3
,
0xb4
]
.to_vec
(),
base_address
:
0x1000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
MemorySegment
{
bytes
:
[
0u8
;
8
]
.to_vec
(),
base_address
:
0x2000
,
read_flag
:
true
,
write_flag
:
true
,
execute_flag
:
false
,
},
// Contains the Hello World string at byte 3002.
MemorySegment
{
bytes
:
[
0x01
,
0x02
,
0x48
,
0x65
,
0x6c
,
0x6c
,
0x6f
,
0x20
,
0x57
,
0x6f
,
0x72
,
0x6c
,
0x64
,
0x00
,
]
.to_vec
(),
base_address
:
0x3000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
MemorySegment
{
bytes
:
[
0x02
,
0x30
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
]
.to_vec
(),
base_address
:
0x4000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
// Contains strings: '/dev/sd%c%d' and 'cat %s'
MemorySegment
{
bytes
:
[
0x2f
,
0x64
,
0x65
,
0x76
,
0x2f
,
0x73
,
0x64
,
0x25
,
0x63
,
0x25
,
0x64
,
0x00
,
0x63
,
0x61
,
0x74
,
0x20
,
0x25
,
0x73
,
0x00
,
]
.to_vec
(),
base_address
:
0x5000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
// Contains string: 'cat %s %s %s %s' starting at the first byte.
MemorySegment
{
bytes
:
[
0x63
,
0x61
,
0x74
,
0x20
,
0x25
,
0x73
,
0x20
,
0x25
,
0x73
,
0x20
,
0x25
,
0x73
,
0x20
,
0x25
,
0x73
,
0x00
,
]
.to_vec
(),
base_address
:
0x6000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
// Contains string: 'str1 str2 str3 str4'
MemorySegment
{
bytes
:
[
0x73
,
0x74
,
0x72
,
0x31
,
0x20
,
0x73
,
0x74
,
0x72
,
0x32
,
0x20
,
0x73
,
0x74
,
0x72
,
0x33
,
0x20
,
0x73
,
0x74
,
0x72
,
0x34
,
0x00
,
]
.to_vec
(),
base_address
:
0x7000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
],
is_little_endian
:
true
,
}
}
}
#[cfg(test)]
mod
tests
{
use
crate
::
intermediate_representation
::{
Bitvector
,
ByteSize
,
RuntimeMemoryImage
};
#[test]
fn
read_endianness
()
{
let
mut
mem_image
=
RuntimeMemoryImage
::
mock
();
let
address
=
Bitvector
::
from_u32
(
0x1001
);
assert_eq!
(
mem_image
.read
(
&
address
,
ByteSize
::
new
(
4
))
.unwrap
(),
Bitvector
::
from_u32
(
0xb4b3b2b1
)
.into
()
);
mem_image
.is_little_endian
=
false
;
assert_eq!
(
mem_image
.read
(
&
address
,
ByteSize
::
new
(
4
))
.unwrap
(),
Bitvector
::
from_u32
(
0xb1b2b3b4
)
.into
()
);
}
#[test]
fn
ro_data_pointer
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
address
=
Bitvector
::
from_u32
(
0x1002
);
let
(
slice
,
index
)
=
mem_image
.get_ro_data_pointer_at_address
(
&
address
)
.unwrap
();
assert_eq!
(
index
,
2
);
assert_eq!
(
&
slice
[
index
..
],
&
[
0xb2u8
,
0xb3
,
0xb4
]);
}
#[test]
fn
test_read_string_until_null_terminator
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
// the byte array contains "Hello World".
let
expected_string
:
&
str
=
std
::
str
::
from_utf8
(
b
"
\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64
"
)
.unwrap
();
let
address
=
Bitvector
::
from_u32
(
0x3002
);
assert_eq!
(
expected_string
,
mem_image
.read_string_until_null_terminator
(
&
address
)
.unwrap
(),
);
}
}
src/cwe_checker_lib/src/lib.rs
View file @
170f44b1
...
...
@@ -74,8 +74,8 @@ use analysis::function_signature::FunctionSignature;
use
analysis
::
graph
::
Graph
;
use
analysis
::
pointer_inference
::
PointerInference
;
use
analysis
::
string_abstraction
::
StringAbstraction
;
use
intermediate_representation
::
Project
;
use
utils
::
binary
::
RuntimeMemoryImage
;
use
utils
::
log
::{
CweWarning
,
LogMessage
};
mod
prelude
{
...
...
@@ -140,8 +140,6 @@ pub fn get_modules() -> Vec<&'static CweModule> {
pub
struct
AnalysisResults
<
'a
>
{
/// The content of the binary file
pub
binary
:
&
'a
[
u8
],
/// A representation of the runtime memory image of the binary.
pub
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
/// The computed control flow graph of the program.
pub
control_flow_graph
:
&
'a
Graph
<
'a
>
,
/// A pointer to the project struct
...
...
@@ -158,13 +156,11 @@ impl<'a> AnalysisResults<'a> {
/// Create a new `AnalysisResults` struct with only the project itself known.
pub
fn
new
(
binary
:
&
'a
[
u8
],
runtime_memory_image
:
&
'a
RuntimeMemoryImage
,
control_flow_graph
:
&
'a
Graph
<
'a
>
,
project
:
&
'a
Project
,
)
->
AnalysisResults
<
'a
>
{
AnalysisResults
{
binary
,
runtime_memory_image
,
control_flow_graph
,
project
,
function_signatures
:
None
,
...
...
@@ -231,7 +227,6 @@ impl<'a> AnalysisResults<'a> {
)
->
StringAbstraction
<
BricksDomain
>
{
crate
::
analysis
::
string_abstraction
::
run
(
self
.project
,
self
.runtime_memory_image
,
self
.control_flow_graph
,
pi_results
.unwrap
(),
serde_json
::
from_value
(
config
.clone
())
.unwrap
(),
...
...
@@ -264,11 +259,8 @@ mod tests {
HashSet
::
from_iter
(
project
.program.term.extern_symbols
.keys
()
.cloned
());
let
graph
=
Box
::
new
(
get_program_cfg
(
&
project
.program
,
extern_subs
));
let
graph
:
&
'a
Graph
=
Box
::
leak
(
graph
);
let
runtime_mem_image
=
Box
::
new
(
RuntimeMemoryImage
::
mock
());
let
runtime_mem_image
:
&
'a
RuntimeMemoryImage
=
Box
::
leak
(
runtime_mem_image
);
let
binary
:
&
'a
Vec
<
u8
>
=
Box
::
leak
(
Box
::
new
(
Vec
::
new
()));
let
analysis_results
=
AnalysisResults
::
new
(
binary
,
runtime_mem_image
,
graph
,
project
);
let
analysis_results
=
AnalysisResults
::
new
(
binary
,
graph
,
project
);
let
(
fn_sigs
,
_
)
=
analysis_results
.compute_function_signatures
();
let
fn_sigs
:
&
'a
BTreeMap
<
_
,
_
>
=
Box
::
leak
(
Box
::
new
(
fn_sigs
));
let
analysis_results
=
analysis_results
.with_function_signatures
(
Some
(
fn_sigs
));
...
...
src/cwe_checker_lib/src/pcode/term.rs
View file @
170f44b1
...
...
@@ -13,6 +13,7 @@ use crate::intermediate_representation::ExternSymbol as IrExternSymbol;
use
crate
::
intermediate_representation
::
Jmp
as
IrJmp
;
use
crate
::
intermediate_representation
::
Program
as
IrProgram
;
use
crate
::
intermediate_representation
::
Project
as
IrProject
;
use
crate
::
intermediate_representation
::
RuntimeMemoryImage
;
use
crate
::
intermediate_representation
::
Sub
as
IrSub
;
use
crate
::
intermediate_representation
::
Variable
as
IrVariable
;
use
crate
::
prelude
::
*
;
...
...
@@ -870,6 +871,7 @@ impl Project {
calling_conventions
,
register_set
,
datatype_properties
:
self
.datatype_properties
.clone
(),
runtime_memory_image
:
RuntimeMemoryImage
::
empty
(
true
),
}
}
}
...
...
src/cwe_checker_lib/src/utils/arguments.rs
View file @
170f44b1
//! Handles argument detection by parsing format string arguments during a function call. (e.g. sprintf)
use
super
::
binary
::
RuntimeMemoryImage
;
use
crate
::
prelude
::
*
;
use
crate
::{
abstract_domain
::{
IntervalDomain
,
TryToBitvec
},
...
...
@@ -105,7 +104,6 @@ pub fn get_variable_parameters(
pi_state
:
&
PointerInferenceState
,
extern_symbol
:
&
ExternSymbol
,
format_string_index_map
:
&
HashMap
<
String
,
usize
>
,
runtime_memory_image
:
&
RuntimeMemoryImage
,
)
->
Result
<
Vec
<
Arg
>
,
Error
>
{
let
format_string_index
=
match
format_string_index_map
.get
(
&
extern_symbol
.name
)
{
Some
(
index
)
=>
*
index
,
...
...
@@ -116,7 +114,7 @@ pub fn get_variable_parameters(
pi_state
,
extern_symbol
,
format_string_index
,
runtime_memory_image
,
&
project
.
runtime_memory_image
,
);
if
let
Ok
(
format_string
)
=
format_string_results
.as_ref
()
{
...
...
src/cwe_checker_lib/src/utils/arguments/tests.rs
View file @
170f44b1
...
...
@@ -9,7 +9,6 @@ fn mock_pi_state() -> PointerInferenceState {
#[test]
/// Tests extraction of format string parameters '/dev/sd%c%d' and 'cat %s'.
fn
test_get_variable_parameters
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
mut
pi_state
=
mock_pi_state
();
let
sprintf_symbol
=
ExternSymbol
::
mock_string
();
let
mut
format_string_index_map
:
HashMap
<
String
,
usize
>
=
HashMap
::
new
();
...
...
@@ -38,7 +37,6 @@ fn test_get_variable_parameters() {
&
pi_state
,
&
sprintf_symbol
,
&
format_string_index_map
,
&
mem_image
,
)
.unwrap
()
);
...
...
@@ -61,7 +59,6 @@ fn test_get_variable_parameters() {
&
pi_state
,
&
sprintf_symbol
,
&
format_string_index_map
,
&
mem_image
,
)
.unwrap
()
);
...
...
src/cwe_checker_lib/src/utils/binary.rs
View file @
170f44b1
//! Utility structs and functions which directly parse the binary file.
use
crate
::
intermediate_representation
::
BinOpType
;
use
crate
::
intermediate_representation
::
BitvectorExtended
;
use
crate
::
prelude
::
*
;
use
goblin
::
elf
;
use
goblin
::
pe
;
use
goblin
::
Object
;
/// Contains all information parsed out of the bare metal configuration JSON file.
///
...
...
@@ -50,23 +47,16 @@ impl BareMetalConfig {
}
/// A helper function to parse a hex string to an integer.
fn
parse_hex_string_to_u64
(
mut
string
:
&
str
)
->
Result
<
u64
,
Error
>
{
pub
fn
parse_hex_string_to_u64
(
mut
string
:
&
str
)
->
Result
<
u64
,
Error
>
{
if
string
.starts_with
(
"0x"
)
{
string
=
&
string
[
2
..
]
}
Ok
(
u64
::
from_str_radix
(
string
,
16
)
?
)
}
/// A representation of the runtime image of a binary after being loaded into memory by the loader.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone)]
pub
struct
RuntimeMemoryImage
{
memory_segments
:
Vec
<
MemorySegment
>
,
is_little_endian
:
bool
,
}
/// A continuous segment in the memory image.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone)]
struct
MemorySegment
{
pub
struct
MemorySegment
{
/// The contents of the segment
pub
bytes
:
Vec
<
u8
>
,
/// The base address, i.e. the address of the first byte of the segment
...
...
@@ -143,394 +133,3 @@ impl MemorySegment {
}
}
}
impl
RuntimeMemoryImage
{
/// Generate a runtime memory image containing no memory segments.
/// Primarily useful in situations where any access to global memory would be an error.
pub
fn
empty
(
is_little_endian
:
bool
)
->
RuntimeMemoryImage
{
RuntimeMemoryImage
{
memory_segments
:
Vec
::
new
(),
is_little_endian
,
}
}
/// Generate a runtime memory image for a given binary.
///
/// The function can parse ELF and PE files as input.
pub
fn
new
(
binary
:
&
[
u8
])
->
Result
<
Self
,
Error
>
{
let
parsed_object
=
Object
::
parse
(
binary
)
?
;
match
parsed_object
{
Object
::
Elf
(
elf_file
)
=>
{
let
mut
memory_segments
=
Vec
::
new
();
for
header
in
elf_file
.program_headers
.iter
()
{
if
header
.p_type
==
elf
::
program_header
::
PT_LOAD
{
memory_segments
.push
(
MemorySegment
::
from_elf_segment
(
binary
,
header
));
}
}
if
memory_segments
.is_empty
()
{
return
Err
(
anyhow!
(
"No loadable segments found"
));
}
Ok
(
RuntimeMemoryImage
{
memory_segments
,
is_little_endian
:
elf_file
.header
.endianness
()
.unwrap
()
.is_little
(),
})
}
Object
::
PE
(
pe_file
)
=>
{
let
mut
memory_segments
=
Vec
::
new
();
for
header
in
pe_file
.sections
.iter
()
{
if
(
header
.characteristics
&
0x02000000
)
==
0
{
// Only load segments which are not discardable
memory_segments
.push
(
MemorySegment
::
from_pe_section
(
binary
,
header
));
}
}
if
memory_segments
.is_empty
()
{
return
Err
(
anyhow!
(
"No loadable segments found"
));
}
let
mut
memory_image
=
RuntimeMemoryImage
{
memory_segments
,
is_little_endian
:
true
,
};
memory_image
.add_global_memory_offset
(
pe_file
.image_base
as
u64
);
Ok
(
memory_image
)
}
_
=>
Err
(
anyhow!
(
"Object type not supported."
)),
}
}
/// Generate a runtime memory image for a bare metal binary.
///
/// The generated runtime memory image contains:
/// * one memory region corresponding to non-volatile memory
/// * one memory region corresponding to volatile memory (RAM)
///
/// See [`BareMetalConfig`] for more information about the assumed memory layout for bare metal binaries.
pub
fn
new_from_bare_metal
(
binary
:
&
[
u8
],
bare_metal_config
:
&
BareMetalConfig
,
)
->
Result
<
Self
,
Error
>
{
let
processor_id_parts
:
Vec
<&
str
>
=
bare_metal_config
.processor_id
.split
(
':'
)
.collect
();
if
processor_id_parts
.len
()
<
3
{
return
Err
(
anyhow!
(
"Could not parse processor ID."
));
}
let
is_little_endian
=
match
processor_id_parts
[
1
]
{
"LE"
=>
true
,
"BE"
=>
false
,
_
=>
return
Err
(
anyhow!
(
"Could not parse endianness of the processor ID."
)),
};
let
flash_base_address
=
parse_hex_string_to_u64
(
&
bare_metal_config
.flash_base_address
)
?
;
let
ram_base_address
=
parse_hex_string_to_u64
(
&
bare_metal_config
.ram_base_address
)
?
;
let
ram_size
=
parse_hex_string_to_u64
(
&
bare_metal_config
.ram_size
)
?
;
// Check that the whole binary is contained in addressable space.
let
address_bit_length
=
processor_id_parts
[
2
]
.parse
::
<
u64
>
()
?
;
match
flash_base_address
.checked_add
(
binary
.len
()
as
u64
)
{
Some
(
max_address
)
=>
{
if
(
max_address
>>
address_bit_length
)
!=
0
{
return
Err
(
anyhow!
(
"Binary too large for given base address"
));
}
}
None
=>
return
Err
(
anyhow!
(
"Binary too large for given base address"
)),
}
Ok
(
RuntimeMemoryImage
{
memory_segments
:
vec!
[
MemorySegment
::
from_bare_metal_file
(
binary
,
flash_base_address
),
MemorySegment
::
new_bare_metal_ram_segment
(
ram_base_address
,
ram_size
),
],
is_little_endian
,
})
}
/// Return whether values in the memory image should be interpreted in little-endian
/// or big-endian byte order.
pub
fn
is_little_endian_byte_order
(
&
self
)
->
bool
{
self
.is_little_endian
}
/// Add a global offset to the base addresses of all memory segments.
/// Useful to align the addresses with those reported by Ghidra
/// if the Ghidra backend added such an offset to all addresses.
pub
fn
add_global_memory_offset
(
&
mut
self
,
offset
:
u64
)
{
for
segment
in
self
.memory_segments
.iter_mut
()
{
segment
.base_address
+=
offset
;
}
}
/// Read the contents of the memory image at the given address
/// to emulate a read instruction to global data at runtime.
///
/// The read method is endian-aware,
/// i.e. values are interpreted with the endianness of the CPU architecture.
/// If the address points to a writeable segment, the returned value is a `Ok(None)` value,
/// since the data may change during program execution.
///
/// Returns an error if the address is not contained in the global data address range.
pub
fn
read
(
&
self
,
address
:
&
Bitvector
,
size
:
ByteSize
)
->
Result
<
Option
<
Bitvector
>
,
Error
>
{
let
address
=
address
.try_to_u64
()
.unwrap
();
for
segment
in
self
.memory_segments
.iter
()
{
if
address
>=
segment
.base_address
&&
u64
::
from
(
size
)
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
&&
address
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
-
u64
::
from
(
size
)
{
if
segment
.write_flag
{
// The segment is writeable, thus we do not know the content at runtime.
return
Ok
(
None
);
}
let
index
=
(
address
-
segment
.base_address
)
as
usize
;
let
mut
bytes
=
segment
.bytes
[
index
..
index
+
u64
::
from
(
size
)
as
usize
]
.to_vec
();
if
self
.is_little_endian
{
bytes
=
bytes
.into_iter
()
.rev
()
.collect
();
}
let
mut
bytes
=
bytes
.into_iter
();
let
mut
bitvector
=
Bitvector
::
from_u8
(
bytes
.next
()
.unwrap
());
for
byte
in
bytes
{
let
new_byte
=
Bitvector
::
from_u8
(
byte
);
bitvector
=
bitvector
.bin_op
(
BinOpType
::
Piece
,
&
new_byte
)
?
;
}
return
Ok
(
Some
(
bitvector
));
}
}
// No segment fully contains the read.
Err
(
anyhow!
(
"Address is not a valid global memory address."
))
}
/// Read the contents of memory from a given address onwards until a null byte is reached and checks whether the
/// content is a valid UTF8 string.
pub
fn
read_string_until_null_terminator
(
&
self
,
address
:
&
Bitvector
)
->
Result
<&
str
,
Error
>
{
let
address
=
address
.try_to_u64
()
.unwrap
();
for
segment
in
self
.memory_segments
.iter
()
{
if
address
>=
segment
.base_address
&&
address
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
let
start_index
=
(
address
-
segment
.base_address
)
as
usize
;
if
let
Some
(
end_index
)
=
segment
.bytes
[
start_index
..
]
.iter
()
.position
(|
&
b
|
b
==
0
)
{
let
c_str
=
std
::
ffi
::
CStr
::
from_bytes_with_nul
(
&
segment
.bytes
[
start_index
..
start_index
+
end_index
+
1
],
)
?
;
return
Ok
(
c_str
.to_str
()
?
);
}
else
{
return
Err
(
anyhow!
(
"Not a valid string in memory."
));
}
}
}
Err
(
anyhow!
(
"Address is not a valid global memory address."
))
}
/// Checks whether the constant is a global memory address.
pub
fn
is_global_memory_address
(
&
self
,
constant
:
&
Bitvector
)
->
bool
{
if
self
.read
(
constant
,
constant
.bytesize
())
.is_ok
()
{
return
true
;
}
false
}
/// Check whether all addresses in the given interval point to a readable segment in the runtime memory image.
///
/// Returns an error if the address interval intersects more than one memory segment
/// or if it does not point to global memory at all.
pub
fn
is_interval_readable
(
&
self
,
start_address
:
u64
,
end_address
:
u64
,
)
->
Result
<
bool
,
Error
>
{
for
segment
in
self
.memory_segments
.iter
()
{
if
start_address
>=
segment
.base_address
&&
start_address
<
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
if
end_address
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
return
Ok
(
segment
.read_flag
);
}
else
{
return
Err
(
anyhow!
(
"Interval spans more than one segment"
));
}
}
}
Err
(
anyhow!
(
"Address not contained in runtime memory image"
))
}
/// For an address to global read-only memory, return the memory segment it points to
/// and the index inside the segment, where the address points to.
///
/// Returns an error if the target memory segment is marked as writeable
/// or if the pointer does not point to global memory.
pub
fn
get_ro_data_pointer_at_address
(
&
self
,
address
:
&
Bitvector
,
)
->
Result
<
(
&
[
u8
],
usize
),
Error
>
{
let
address
=
address
.try_to_u64
()
.unwrap
();
for
segment
in
self
.memory_segments
.iter
()
{
if
address
>=
segment
.base_address
&&
address
<
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
if
segment
.write_flag
{
return
Err
(
anyhow!
(
"Target segment is writeable"
));
}
else
{
return
Ok
((
&
segment
.bytes
,
(
address
-
segment
.base_address
)
as
usize
));
}
}
}
Err
(
anyhow!
(
"Pointer target not in global memory."
))
}
/// Check whether the given address points to a writeable segment in the runtime memory image.
///
/// Returns an error if the address does not point to global memory.
pub
fn
is_address_writeable
(
&
self
,
address
:
&
Bitvector
)
->
Result
<
bool
,
Error
>
{
let
address
=
address
.try_to_u64
()
.unwrap
();
for
segment
in
self
.memory_segments
.iter
()
{
if
address
>=
segment
.base_address
&&
address
<
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
return
Ok
(
segment
.write_flag
);
}
}
Err
(
anyhow!
(
"Address not contained in runtime memory image"
))
}
/// Check whether all addresses in the given interval point to a writeable segment in the runtime memory image.
///
/// Returns an error if the address interval intersects more than one memory segment
/// or if it does not point to global memory at all.
pub
fn
is_interval_writeable
(
&
self
,
start_address
:
u64
,
end_address
:
u64
,
)
->
Result
<
bool
,
Error
>
{
for
segment
in
self
.memory_segments
.iter
()
{
if
start_address
>=
segment
.base_address
&&
start_address
<
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
if
end_address
<=
segment
.base_address
+
segment
.bytes
.len
()
as
u64
{
return
Ok
(
segment
.write_flag
);
}
else
{
return
Err
(
anyhow!
(
"Interval spans more than one segment"
));
}
}
}
Err
(
anyhow!
(
"Address not contained in runtime memory image"
))
}
}
#[cfg(test)]
pub
mod
tests
{
use
super
::
*
;
impl
RuntimeMemoryImage
{
/// Creates a mock runtime memory image with: byte series, strings and format strings.
pub
fn
mock
()
->
RuntimeMemoryImage
{
RuntimeMemoryImage
{
memory_segments
:
vec!
[
MemorySegment
{
bytes
:
[
0xb0u8
,
0xb1
,
0xb2
,
0xb3
,
0xb4
]
.to_vec
(),
base_address
:
0x1000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
MemorySegment
{
bytes
:
[
0u8
;
8
]
.to_vec
(),
base_address
:
0x2000
,
read_flag
:
true
,
write_flag
:
true
,
execute_flag
:
false
,
},
// Contains the Hello World string at byte 3002.
MemorySegment
{
bytes
:
[
0x01
,
0x02
,
0x48
,
0x65
,
0x6c
,
0x6c
,
0x6f
,
0x20
,
0x57
,
0x6f
,
0x72
,
0x6c
,
0x64
,
0x00
,
]
.to_vec
(),
base_address
:
0x3000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
MemorySegment
{
bytes
:
[
0x02
,
0x30
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
,
0x00
]
.to_vec
(),
base_address
:
0x4000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
// Contains strings: '/dev/sd%c%d' and 'cat %s'
MemorySegment
{
bytes
:
[
0x2f
,
0x64
,
0x65
,
0x76
,
0x2f
,
0x73
,
0x64
,
0x25
,
0x63
,
0x25
,
0x64
,
0x00
,
0x63
,
0x61
,
0x74
,
0x20
,
0x25
,
0x73
,
0x00
,
]
.to_vec
(),
base_address
:
0x5000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
// Contains string: 'cat %s %s %s %s' starting at the first byte.
MemorySegment
{
bytes
:
[
0x63
,
0x61
,
0x74
,
0x20
,
0x25
,
0x73
,
0x20
,
0x25
,
0x73
,
0x20
,
0x25
,
0x73
,
0x20
,
0x25
,
0x73
,
0x00
,
]
.to_vec
(),
base_address
:
0x6000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
// Contains string: 'str1 str2 str3 str4'
MemorySegment
{
bytes
:
[
0x73
,
0x74
,
0x72
,
0x31
,
0x20
,
0x73
,
0x74
,
0x72
,
0x32
,
0x20
,
0x73
,
0x74
,
0x72
,
0x33
,
0x20
,
0x73
,
0x74
,
0x72
,
0x34
,
0x00
,
]
.to_vec
(),
base_address
:
0x7000
,
read_flag
:
true
,
write_flag
:
false
,
execute_flag
:
false
,
},
],
is_little_endian
:
true
,
}
}
}
#[test]
fn
read_endianness
()
{
let
mut
mem_image
=
RuntimeMemoryImage
::
mock
();
let
address
=
Bitvector
::
from_u32
(
0x1001
);
assert_eq!
(
mem_image
.read
(
&
address
,
ByteSize
::
new
(
4
))
.unwrap
(),
Bitvector
::
from_u32
(
0xb4b3b2b1
)
.into
()
);
mem_image
.is_little_endian
=
false
;
assert_eq!
(
mem_image
.read
(
&
address
,
ByteSize
::
new
(
4
))
.unwrap
(),
Bitvector
::
from_u32
(
0xb1b2b3b4
)
.into
()
);
}
#[test]
fn
ro_data_pointer
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
let
address
=
Bitvector
::
from_u32
(
0x1002
);
let
(
slice
,
index
)
=
mem_image
.get_ro_data_pointer_at_address
(
&
address
)
.unwrap
();
assert_eq!
(
index
,
2
);
assert_eq!
(
&
slice
[
index
..
],
&
[
0xb2u8
,
0xb3
,
0xb4
]);
}
#[test]
fn
test_read_string_until_null_terminator
()
{
let
mem_image
=
RuntimeMemoryImage
::
mock
();
// the byte array contains "Hello World".
let
expected_string
:
&
str
=
std
::
str
::
from_utf8
(
b
"
\x48\x65\x6c\x6c\x6f\x20\x57\x6f\x72\x6c\x64
"
)
.unwrap
();
let
address
=
Bitvector
::
from_u32
(
0x3002
);
assert_eq!
(
expected_string
,
mem_image
.read_string_until_null_terminator
(
&
address
)
.unwrap
(),
);
}
}
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