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
a3093dcb
Unverified
Commit
a3093dcb
authored
4 years ago
by
Enkelmann
Committed by
GitHub
4 years ago
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Reimplement CWE 243 check (#119)
parent
6f2a5775
master
…
v0.7
v0.6
v0.5
v0.4
No related merge requests found
Hide whitespace changes
Inline
Side-by-side
Showing
11 changed files
with
317 additions
and
57 deletions
+317
-57
main.rs
caller/src/main.rs
+2
-1
checkers.rs
cwe_checker_rs/src/checkers.rs
+1
-0
cwe_243.rs
cwe_checker_rs/src/checkers/cwe_243.rs
+169
-0
cwe_367.rs
cwe_checker_rs/src/checkers/cwe_367.rs
+10
-56
cwe_782.rs
cwe_checker_rs/src/checkers/cwe_782.rs
+1
-0
lib.rs
cwe_checker_rs/src/lib.rs
+1
-0
graph_utils.rs
cwe_checker_rs/src/utils/graph_utils.rs
+59
-0
mod.rs
cwe_checker_rs/src/utils/mod.rs
+1
-0
config.json
src/config.json
+6
-0
cwe_243.c
test/artificial_samples/cwe_243.c
+46
-0
lib.rs
test/src/lib.rs
+21
-0
No files found.
caller/src/main.rs
View file @
a3093dcb
...
...
@@ -141,9 +141,10 @@ fn run_with_ghidra(args: CmdlineArgs) {
let
mut
all_logs
=
project
.normalize
();
let
mut
analysis_results
=
AnalysisResults
::
new
(
&
project
);
let
modules_depending_on_pointer_inference
=
vec!
[
"CWE243"
,
"CWE367"
,
"CWE476"
,
"Memory"
];
let
pointer_inference_results
=
if
modules
.iter
()
.any
(|
module
|
module
.name
==
"CWE476"
||
module
.name
==
"Memory"
||
module
.name
==
"CWE367"
)
.any
(|
module
|
module
s_depending_on_pointer_inference
.contains
(
&
module
.name
)
)
{
Some
(
analysis_results
.compute_pointer_inference
(
&
config
[
"Memory"
]))
}
else
{
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers.rs
View file @
a3093dcb
pub
mod
cwe_190
;
pub
mod
cwe_243
;
pub
mod
cwe_332
;
pub
mod
cwe_367
;
pub
mod
cwe_426
;
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_243.rs
0 → 100644
View file @
a3093dcb
//! This module implements a check for CWE-243: Creation of chroot Jail Without Changing Working Directory.
//!
//! Creating a chroot Jail without changing the working directory afterwards does
//! not prevent access to files outside of the jail.
//!
//! See <https://cwe.mitre.org/data/definitions/243.html> for detailed a description.
//!
//! ## How the check works
//!
//! According to <http://www.unixwiz.net/techtips/chroot-practices.html>, there are
//! several ways to achieve the safe creation of a chroot jail.
//! One can either call chdir after chroot
//! or, if chdir is called before chroot, drop priviledges after the chroot call.
//! The functions used to drop priviledges are configurable in config.json.
//! We check whether each function that calls
//! chroot is using one of these safe call sequences to create the chroot jail.
//! If not, a warning is emitted.
//!
//! ## False Positives
//!
//! None known.
//!
//! ## False Negatives
//!
//! We do not check whether the parameters to chdir, chroot and the priviledge dropping functions
//! are suitable to create a safe chroot jail.
use
crate
::
analysis
::
graph
::
Node
;
use
crate
::
intermediate_representation
::
*
;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
graph_utils
::
is_sink_call_reachable_from_source_call
;
use
crate
::
utils
::
log
::{
CweWarning
,
LogMessage
};
use
crate
::
utils
::
symbol_utils
::
find_symbol
;
use
crate
::
CweModule
;
pub
static
CWE_MODULE
:
CweModule
=
CweModule
{
name
:
"CWE243"
,
version
:
"0.2"
,
run
:
check_cwe
,
};
/// The configuration struct contains the list of functions
/// that are assumed to be used to correctly drop priviledges after a `chroot` call.
#[derive(Serialize,
Deserialize,
Debug,
PartialEq,
Eq,
Hash,
Clone)]
pub
struct
Config
{
priviledge_dropping_functions
:
Vec
<
String
>
,
}
/// Check whether the given block calls the given TID.
/// If yes, return the TID of the jump term that contains the call.
fn
blk_calls_tid
(
blk
:
&
Term
<
Blk
>
,
tid
:
&
Tid
)
->
Option
<
Tid
>
{
for
jmp
in
blk
.term.jmps
.iter
()
{
match
&
jmp
.term
{
Jmp
::
Call
{
target
,
..
}
if
target
==
tid
=>
{
return
Some
(
jmp
.tid
.clone
());
}
_
=>
(),
}
}
None
}
/// Check whether the given `sub` calls both the `chdir_tid`
/// and at least one of the `priviledge_dropping_tids`.
/// If yes, return true.
fn
sub_calls_chdir_and_priviledge_dropping_func
(
sub
:
&
Term
<
Sub
>
,
chdir_tid
:
&
Tid
,
priviledge_dropping_tids
:
&
[
Tid
],
)
->
bool
{
let
mut
is_chdir_called
=
false
;
for
blk
in
sub
.term.blocks
.iter
()
{
if
blk_calls_tid
(
blk
,
chdir_tid
)
.is_some
()
{
is_chdir_called
=
true
;
break
;
}
}
if
!
is_chdir_called
{
return
false
;
}
for
blk
in
sub
.term.blocks
.iter
()
{
if
priviledge_dropping_tids
.iter
()
.any
(|
tid
|
blk_calls_tid
(
blk
,
tid
)
.is_some
())
{
return
true
;
}
}
false
}
/// Generate a CWE warning for a CWE hit.
fn
generate_cwe_warning
(
sub
:
&
Term
<
Sub
>
,
callsite
:
&
Tid
)
->
CweWarning
{
CweWarning
::
new
(
CWE_MODULE
.name
,
CWE_MODULE
.version
,
format!
(
"(The program utilizes chroot without dropping privileges and/or changing the directory) at {} ({})"
,
callsite
.address
,
sub
.term.name
))
.tids
(
vec!
[
format!
(
"{}"
,
callsite
)])
.addresses
(
vec!
[
callsite
.address
.clone
()])
.symbols
(
vec!
[
sub
.term.name
.clone
()])
}
/// Run the check.
///
/// For each call to `chroot` we check
/// - that it is either followed by a call to `chdir` in the same function
/// - or that the same function contains calls to `chdir`
/// and a call to a function that can be used to drop priviledges.
///
/// If both are false, we assume that the chroot-jail is insecure and report a CWE hit.
pub
fn
check_cwe
(
analysis_results
:
&
AnalysisResults
,
cwe_params
:
&
serde_json
::
Value
,
)
->
(
Vec
<
LogMessage
>
,
Vec
<
CweWarning
>
)
{
let
project
=
analysis_results
.project
;
let
graph
=
analysis_results
.pointer_inference
.unwrap
()
.get_graph
();
let
config
:
Config
=
serde_json
::
from_value
(
cwe_params
.clone
())
.unwrap
();
let
priviledge_dropping_tids
:
Vec
<
Tid
>
=
config
.priviledge_dropping_functions
.into_iter
()
.filter_map
(|
func_name
|
{
if
let
Some
((
tid
,
_
))
=
find_symbol
(
&
project
.program
,
&
func_name
)
{
Some
(
tid
.clone
())
}
else
{
None
}
})
.collect
();
let
chroot_tid
=
match
find_symbol
(
&
project
.program
,
"chroot"
)
{
Some
((
tid
,
_
))
=>
tid
.clone
(),
None
=>
return
(
Vec
::
new
(),
Vec
::
new
()),
// chroot is never called by the program
};
let
mut
cwe_warnings
=
Vec
::
new
();
for
node
in
graph
.node_indices
()
{
if
let
Node
::
BlkEnd
(
blk
,
sub
)
=
graph
[
node
]
{
if
let
Some
(
callsite_tid
)
=
blk_calls_tid
(
blk
,
&
chroot_tid
)
{
if
let
Some
(
chdir_tid
)
=
find_symbol
(
&
project
.program
,
"chdir"
)
.map
(|(
tid
,
_
)|
tid
.clone
())
{
// If chdir is called after chroot, we assume a secure chroot jail.
if
is_sink_call_reachable_from_source_call
(
graph
,
node
,
&
chroot_tid
,
&
chdir_tid
)
.is_none
()
{
// If chdir is not called after chroot, it has to be called before it.
// Additionally priviledges must be dropped to secure the chroot jail in this case.
if
!
sub_calls_chdir_and_priviledge_dropping_func
(
sub
,
&
chdir_tid
,
&
priviledge_dropping_tids
[
..
],
)
{
cwe_warnings
.push
(
generate_cwe_warning
(
sub
,
&
callsite_tid
));
}
}
}
else
{
// There is no chdir symbol, so the chroot jail cannot be secured.
cwe_warnings
.push
(
generate_cwe_warning
(
sub
,
&
callsite_tid
));
}
}
}
}
(
Vec
::
new
(),
cwe_warnings
)
}
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_367.rs
View file @
a3093dcb
...
...
@@ -22,14 +22,14 @@
//! - If the check-call and the use-call happen in different functions it will not
//! be found by the check.
use
crate
::
analysis
::
graph
::{
Edge
,
Graph
,
Node
};
use
crate
::
analysis
::
graph
::{
Edge
,
Node
};
use
crate
::
intermediate_representation
::
Jmp
;
use
crate
::
prelude
::
*
;
use
crate
::
utils
::
graph_utils
::
is_sink_call_reachable_from_source_call
;
use
crate
::
utils
::
log
::{
CweWarning
,
LogMessage
};
use
crate
::
CweModule
;
use
petgraph
::
graph
::
NodeIndex
;
use
petgraph
::
visit
::
EdgeRef
;
use
std
::
collections
::
{
HashMap
,
HashSet
}
;
use
std
::
collections
::
HashMap
;
pub
static
CWE_MODULE
:
CweModule
=
CweModule
{
name
:
"CWE367"
,
...
...
@@ -45,56 +45,6 @@ struct Config {
pairs
:
Vec
<
(
String
,
String
)
>
,
}
/// Check whether a call to the `sink_symbol` is reachable from the `source_node`
/// through a path of intraprocedural edges in the control flow graph.
///
/// A simple depth-first-search on the graph is used to find such a path.
fn
is_reachable
(
graph
:
&
Graph
,
source_node
:
NodeIndex
,
source_symbol
:
&
Tid
,
sink_symbol
:
&
Tid
,
)
->
Option
<
(
Tid
,
Tid
)
>
{
let
mut
visited_nodes
=
HashSet
::
new
();
visited_nodes
.insert
(
source_node
);
let
mut
worklist
=
vec!
[
source_node
];
while
let
Some
(
node
)
=
worklist
.pop
()
{
for
edge
in
graph
.edges
(
node
)
{
if
let
Edge
::
ExternCallStub
(
jmp
)
=
edge
.weight
()
{
if
let
Jmp
::
Call
{
target
,
..
}
=
&
jmp
.term
{
if
target
==
sink_symbol
{
// We found a CWE hit
let
source_tid
=
graph
[
source_node
]
.get_block
()
.tid
.clone
();
return
Some
((
source_tid
,
jmp
.tid
.clone
()));
}
else
if
target
==
source_symbol
{
// Do not search past another source call,
// since subsequent sink calls probably belong to the new source.
continue
;
}
}
}
// Add the target node to the worklist if it was not already visited
// and as long as the edge does not leave the function.
match
edge
.weight
()
{
Edge
::
Block
|
Edge
::
CRCallStub
|
Edge
::
CallCombine
(
_
)
|
Edge
::
ReturnCombine
(
_
)
|
Edge
::
Jump
(
_
,
_
)
|
Edge
::
ExternCallStub
(
_
)
=>
{
if
visited_nodes
.get
(
&
edge
.target
())
.is_none
()
{
visited_nodes
.insert
(
edge
.target
());
worklist
.push
(
edge
.target
())
}
}
Edge
::
Call
(
_
)
|
Edge
::
CRReturnStub
=>
(),
// These edges would leave the function control flow graph.
}
}
}
None
}
/// Generate a CWE warning for a found CWE hit.
fn
generate_cwe_warning
(
source
:
&
str
,
...
...
@@ -142,9 +92,13 @@ pub fn check_cwe(
if
let
Edge
::
ExternCallStub
(
jmp
)
=
edge
.weight
()
{
if
let
Jmp
::
Call
{
target
,
..
}
=
&
jmp
.term
{
if
target
==
source_tid
{
if
let
Some
((
source_callsite
,
sink_callsite
))
=
is_reachable
(
graph
,
edge
.target
(),
target
,
sink_tid
)
{
if
let
Some
(
sink_callsite
)
=
is_sink_call_reachable_from_source_call
(
graph
,
edge
.target
(),
target
,
sink_tid
,
)
{
let
source_callsite
=
graph
[
edge
.target
()]
.get_block
()
.tid
.clone
();
let
sub_name
=
match
graph
[
edge
.target
()]
{
Node
::
BlkStart
(
_blk
,
sub
)
=>
sub
.term.name
.as_str
(),
_
=>
panic!
(
"Malformed control flow graph."
),
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/checkers/cwe_782.rs
View file @
a3093dcb
/*!
This module implements a check for CWE-782: Exposed IOCTL with Insufficient Access Control.
See <https://cwe.mitre.org/data/definitions/782.html> for a detailed description.
How the check works:
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/lib.rs
View file @
a3093dcb
...
...
@@ -55,6 +55,7 @@ impl std::fmt::Display for CweModule {
pub
fn
get_modules
()
->
Vec
<&
'static
CweModule
>
{
vec!
[
&
crate
::
checkers
::
cwe_190
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_243
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_332
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_367
::
CWE_MODULE
,
&
crate
::
checkers
::
cwe_426
::
CWE_MODULE
,
...
...
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/utils/graph_utils.rs
0 → 100644
View file @
a3093dcb
use
crate
::
analysis
::
graph
::
*
;
use
crate
::
intermediate_representation
::
Jmp
;
use
crate
::
prelude
::
*
;
use
petgraph
::
graph
::
NodeIndex
;
use
petgraph
::
visit
::
EdgeRef
;
use
std
::
collections
::
HashSet
;
/// Check whether a call to the `sink_symbol` is reachable from the given `source_node`
/// through a path of intraprocedural edges in the control flow graph.
///
/// A simple depth-first-search on the graph is used to find such a path.
/// We do not search past subsequent calls to the `source_symbol`
/// since we assume that sink calls after that belong to the new call to the source symbol and not the original one.
///
/// If a sink is found, the `Tid` of the jump term calling the sink is returned.
pub
fn
is_sink_call_reachable_from_source_call
(
graph
:
&
Graph
,
source_node
:
NodeIndex
,
source_symbol
:
&
Tid
,
sink_symbol
:
&
Tid
,
)
->
Option
<
Tid
>
{
let
mut
visited_nodes
=
HashSet
::
new
();
visited_nodes
.insert
(
source_node
);
let
mut
worklist
=
vec!
[
source_node
];
while
let
Some
(
node
)
=
worklist
.pop
()
{
for
edge
in
graph
.edges
(
node
)
{
if
let
Edge
::
ExternCallStub
(
jmp
)
=
edge
.weight
()
{
if
let
Jmp
::
Call
{
target
,
..
}
=
&
jmp
.term
{
if
target
==
sink_symbol
{
// We found a call to the sink
return
Some
(
jmp
.tid
.clone
());
}
else
if
target
==
source_symbol
{
// Do not search past another source call,
// since subsequent sink calls probably belong to the new source.
continue
;
}
}
}
// Add the target node to the worklist if it was not already visited
// and as long as the edge does not leave the function.
match
edge
.weight
()
{
Edge
::
Block
|
Edge
::
CRCallStub
|
Edge
::
CallCombine
(
_
)
|
Edge
::
ReturnCombine
(
_
)
|
Edge
::
Jump
(
_
,
_
)
|
Edge
::
ExternCallStub
(
_
)
=>
{
if
visited_nodes
.get
(
&
edge
.target
())
.is_none
()
{
visited_nodes
.insert
(
edge
.target
());
worklist
.push
(
edge
.target
())
}
}
Edge
::
Call
(
_
)
|
Edge
::
CRReturnStub
=>
(),
// These edges would leave the function control flow graph.
}
}
}
None
}
This diff is collapsed.
Click to expand it.
cwe_checker_rs/src/utils/mod.rs
View file @
a3093dcb
pub
mod
graph_utils
;
pub
mod
log
;
pub
mod
symbol_utils
;
...
...
This diff is collapsed.
Click to expand it.
src/config.json
View file @
a3093dcb
...
...
@@ -37,6 +37,12 @@
"chroot"
,
"setuid"
]
],
"priviledge_dropping_functions"
:
[
"setresuid"
,
"seteuid"
,
"setreuid"
,
"setuid"
]
},
"CWE248"
:
{
...
...
This diff is collapsed.
Click to expand it.
test/artificial_samples/cwe_243.c
View file @
a3093dcb
...
...
@@ -10,6 +10,52 @@ void chroot_fail(){
}
}
// these are safe according to http://www.unixwiz.net/techtips/chroot-practices.html
void
chroot_safe1
(){
chdir
(
"/tmp"
);
if
(
chroot
(
"/tmp"
)
!=
0
)
{
perror
(
"chroot /tmp"
);
}
setuid
(
1077
);
}
void
chroot_safe2
(){
chdir
(
"/tmp"
);
if
(
chroot
(
"/tmp"
)
!=
0
)
{
perror
(
"chroot /tmp"
);
}
setresuid
(
1077
,
1077
,
1077
);
}
void
chroot_safe3
(){
chdir
(
"/tmp"
);
if
(
chroot
(
"/tmp"
)
!=
0
)
{
perror
(
"chroot /tmp"
);
}
setreuid
(
1077
,
44
);
}
void
chroot_safe4
(){
chdir
(
"/tmp"
);
if
(
chroot
(
"/tmp"
)
!=
0
)
{
perror
(
"chroot /tmp"
);
}
seteuid
(
1077
);
}
void
chroot_safe5
(){
if
(
chroot
(
"/tmp"
)
!=
0
)
{
perror
(
"chroot /tmp"
);
}
chdir
(
"/"
);
}
int
main
(
void
)
{
chroot_fail
();
chroot_safe1
();
chroot_safe2
();
chroot_safe3
();
chroot_safe4
();
chroot_safe5
();
}
This diff is collapsed.
Click to expand it.
test/src/lib.rs
View file @
a3093dcb
...
...
@@ -200,6 +200,27 @@ mod tests {
#[test]
#[ignore]
fn
cwe_243
()
{
let
mut
error_log
=
Vec
::
new
();
let
mut
tests
=
linux_test_cases
(
"cwe_243"
,
"CWE243"
);
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.
for
test_case
in
tests
{
let
num_expected_occurences
=
1
;
if
let
Err
(
error
)
=
test_case
.run_test
(
"[CWE243]"
,
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_332
()
{
let
mut
error_log
=
Vec
::
new
();
let
mut
tests
=
all_test_cases
(
"cwe_332"
,
"CWE332"
);
...
...
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