Skip to content

Commit 2cda3d3

Browse files
authored
Fix: New Strategy for ArbitraryTransferFrom Detector (#776)
1 parent f855512 commit 2cda3d3

File tree

6 files changed

+120
-178
lines changed

6 files changed

+120
-178
lines changed

aderyn_core/src/detect/high/arbitrary_transfer_from.rs

+66-46
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
use std::{collections::BTreeMap, error::Error};
22

3-
use crate::ast::{Expression, FunctionCall, NodeID, TypeName};
4-
53
use crate::{
4+
ast::{Expression, Identifier, NodeID},
65
capture,
7-
context::workspace_context::WorkspaceContext,
8-
detect::detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
6+
context::{browser::ExtractFunctionCalls, workspace_context::WorkspaceContext},
7+
detect::{
8+
detector::{IssueDetector, IssueDetectorNamePool, IssueSeverity},
9+
helpers::{get_implemented_external_and_public_functions, has_msg_sender_binary_operation},
10+
},
911
};
1012
use eyre::Result;
1113

@@ -16,51 +18,69 @@ pub struct ArbitraryTransferFromDetector {
1618
found_instances: BTreeMap<(String, usize, String), NodeID>,
1719
}
1820

19-
// Check if the first argument of the function call is valid
20-
// In function calls with 3 args, the first arg [0] is the `from` address
21-
// In function calls with 4 args, the second arg [1] is the `from` address
22-
fn check_argument_validity(function_call: &FunctionCall) -> bool {
23-
let arg_index = if function_call.arguments.len() == 3 {
24-
0
25-
} else if function_call.arguments.len() == 4 {
26-
1
27-
} else {
28-
return false;
29-
};
30-
31-
match &function_call.arguments[arg_index] {
32-
Expression::MemberAccess(arg_member_access) => {
33-
!(arg_member_access.member_name == "sender"
34-
&& matches!(&*arg_member_access.expression, Expression::Identifier(identifier) if identifier.name == "msg"))
35-
}
36-
Expression::FunctionCall(arg_function_call) => {
37-
!(matches!(&*arg_function_call.expression, Expression::ElementaryTypeNameExpression(arg_el_type_name_exp) if matches!(&arg_el_type_name_exp.type_name, TypeName::ElementaryTypeName(type_name) if type_name.name == "address"))
38-
&& matches!(arg_function_call.arguments.first(), Some(Expression::Identifier(arg_identifier)) if arg_identifier.name == "this"))
39-
}
40-
_ => true,
41-
}
42-
}
43-
4421
impl IssueDetector for ArbitraryTransferFromDetector {
4522
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
46-
let transfer_from_function_calls =
47-
context.function_calls().into_iter().filter(|&function_call| {
48-
// For each function call, check if the function call is a member access
49-
// and if the member name is "transferFrom" or "safeTransferFrom", then check if the
50-
// first argument is valid If the first argument is valid, add the
51-
// function call to found_instances
52-
if let Expression::MemberAccess(member_access) = &*function_call.expression {
53-
if member_access.member_name == "transferFrom"
54-
|| member_access.member_name == "safeTransferFrom"
55-
{
56-
return check_argument_validity(function_call);
57-
}
58-
}
59-
false
23+
// Applying devtooligan's suggestion
24+
// * Operate on public and external functions only
25+
// * See that msg.sender is not checked
26+
// * Check that the argument passed in is from the parameter list of the said function
27+
28+
let suspected_functions =
29+
get_implemented_external_and_public_functions(context).filter(|function_definition| {
30+
!has_msg_sender_binary_operation(&((*function_definition).into()))
31+
&& function_definition.modifiers.is_empty() // If there are modifiers, assume
32+
// the function is safe because
33+
// sometime modifiers' definition
34+
// may not be in scope
6035
});
6136

62-
for item in transfer_from_function_calls {
63-
capture!(self, context, item);
37+
for func in suspected_functions {
38+
let func_parameters_ids =
39+
&func.parameters.parameters.iter().map(|f| f.id).collect::<Vec<_>>();
40+
41+
let transfer_func_calls = ExtractFunctionCalls::from(func)
42+
.extracted
43+
.into_iter()
44+
.filter(|function_call| {
45+
// For each function call, check if the function call is a member access
46+
// and if the member name is "transferFrom" or "safeTransferFrom", then check if
47+
// the first argument is valid If the first argument is
48+
// valid, add the function call to found_instances
49+
if let Expression::MemberAccess(member_access) = &*function_call.expression {
50+
if member_access.member_name == "transferFrom"
51+
|| member_access.member_name == "safeTransferFrom"
52+
{
53+
return true;
54+
}
55+
}
56+
false
57+
})
58+
.collect::<Vec<_>>();
59+
60+
for func in transfer_func_calls {
61+
// Check if the first argument of the function call is valid
62+
// In function calls with 3 args, the first arg [0] is the `from` address
63+
// In function calls with 4 args, the second arg [1] is the `from` address
64+
let arg_index = if func.arguments.len() == 3 {
65+
0
66+
} else if func.arguments.len() == 4 {
67+
1
68+
} else {
69+
continue;
70+
};
71+
72+
let arg = &func.arguments[arg_index];
73+
74+
if let Expression::Identifier(Identifier {
75+
referenced_declaration: Some(referenced_id),
76+
..
77+
}) = arg
78+
{
79+
if func_parameters_ids.iter().any(|r| r == referenced_id) {
80+
capture!(self, context, func);
81+
}
82+
}
83+
}
6484
}
6585

6686
Ok(!self.found_instances.is_empty())
@@ -107,7 +127,7 @@ mod arbitrary_transfer_from_tests {
107127
// assert that the detector found an issue
108128
assert!(found);
109129
// assert that the detector found the correct number of instances
110-
assert_eq!(detector.instances().len(), 4);
130+
assert_eq!(detector.instances().len(), 2);
111131
// assert the severity is high
112132
assert_eq!(detector.severity(), crate::detect::detector::IssueSeverity::High);
113133
// assert the title is correct

reports/report.json

+20-32
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

reports/report.md

+8-20
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

reports/report.sarif

+7-29
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)