Skip to content

Commit

Permalink
Detector: Unchecked send() on address (#611)
Browse files Browse the repository at this point in the history
Co-authored-by: Alex Roan <alex@cyfrin.io>
  • Loading branch information
TilakMaddy and alexroan authored Jul 29, 2024
1 parent c9c251d commit 5273e5c
Show file tree
Hide file tree
Showing 10 changed files with 409 additions and 31 deletions.
7 changes: 7 additions & 0 deletions aderyn_core/src/ast/impls/node/statements.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,16 @@ impl Node for ExpressionStatement {
fn accept(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
if visitor.visit_expression_statement(self)? {
self.expression.accept(visitor)?;
self.accept_metadata(visitor)?;
}
visitor.end_visit_expression_statement(self)
}
fn accept_metadata(&self, visitor: &mut impl ASTConstVisitor) -> Result<()> {
if let Some(child_id) = self.expression.get_node_id() {
visitor.visit_immediate_children(self.id, vec![child_id])?;
}
Ok(())
}
}

impl Node for VariableDeclarationStatement {
Expand Down
3 changes: 3 additions & 0 deletions aderyn_core/src/detect/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ pub fn get_all_issue_detectors() -> Vec<Box<dyn IssueDetector>> {
Box::<IncorrectUseOfCaretOperatorDetector>::default(),
Box::<YulReturnDetector>::default(),
Box::<StateVariableShadowingDetector>::default(),
Box::<UncheckedSendDetector>::default(),
Box::<MisusedBooleanDetector>::default(),
Box::<SendEtherNoChecksDetector>::default(),
Box::<DelegateCallOnUncheckedAddressDetector>::default(),
Expand Down Expand Up @@ -121,6 +122,7 @@ pub(crate) enum IssueDetectorNamePool {
IncorrectCaretOperator,
YulReturn,
StateVariableShadowing,
UncheckedSend,
MisusedBoolean,
SendEtherNoChecks,
DelegateCallUncheckedAddress,
Expand Down Expand Up @@ -252,6 +254,7 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option<Box<dyn Iss
IssueDetectorNamePool::StateVariableShadowing => {
Some(Box::<StateVariableShadowingDetector>::default())
}
IssueDetectorNamePool::UncheckedSend => Some(Box::<UncheckedSendDetector>::default()),
IssueDetectorNamePool::MisusedBoolean => Some(Box::<MisusedBooleanDetector>::default()),
IssueDetectorNamePool::SendEtherNoChecks => {
Some(Box::<SendEtherNoChecksDetector>::default())
Expand Down
2 changes: 2 additions & 0 deletions aderyn_core/src/detect/high/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ pub(crate) mod state_variable_shadowing;
pub(crate) mod storage_array_edit_with_memory;
pub(crate) mod tautological_compare;
pub(crate) mod unchecked_return;
pub(crate) mod unchecked_send;
pub(crate) mod uninitialized_state_variable;
pub(crate) mod unprotected_init_function;
pub(crate) mod unsafe_casting;
Expand Down Expand Up @@ -52,6 +53,7 @@ pub use state_variable_shadowing::StateVariableShadowingDetector;
pub use storage_array_edit_with_memory::StorageArrayEditWithMemoryDetector;
pub use tautological_compare::TautologicalCompareDetector;
pub use unchecked_return::UncheckedReturnDetector;
pub use unchecked_send::UncheckedSendDetector;
pub use uninitialized_state_variable::UninitializedStateVariableDetector;
pub use unprotected_init_function::UnprotectedInitializerDetector;
pub use unsafe_casting::UnsafeCastingDetector;
Expand Down
113 changes: 113 additions & 0 deletions aderyn_core/src/detect/high/unchecked_send.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
use std::collections::BTreeMap;
use std::error::Error;

use crate::ast::{ASTNode, NodeID, NodeType};

use crate::capture;
use crate::context::browser::GetImmediateParent;
use crate::detect::detector::IssueDetectorNamePool;
use crate::{
context::workspace_context::WorkspaceContext,
detect::detector::{IssueDetector, IssueSeverity},
};
use eyre::Result;

#[derive(Default)]
pub struct UncheckedSendDetector {
// Keys are: [0] source file name, [1] line number, [2] character location of node.
// Do not add items manually, use `capture!` to add nodes to this BTreeMap.
found_instances: BTreeMap<(String, usize, String), NodeID>,
}

impl IssueDetector for UncheckedSendDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
for member_access in context.member_accesses() {
if member_access.member_name == "send"
&& member_access
.expression
.type_descriptions()
.is_some_and(|type_desc| {
type_desc
.type_string
.as_ref()
.is_some_and(|type_string| type_string.starts_with("address"))
})
{
if let Some(ASTNode::FunctionCall(func_call)) = member_access.parent(context) {
if func_call
.parent(context)
.is_some_and(|node| node.node_type() == NodeType::Block)
{
capture!(self, context, func_call);
}
}
}
}

Ok(!self.found_instances.is_empty())
}

fn severity(&self) -> IssueSeverity {
IssueSeverity::High
}

fn title(&self) -> String {
String::from("Unchecked `bool success` value for send call.")
}

fn description(&self) -> String {
String::from("The transaction `address(payable?).send(address)` may fail because of reasons like out-of-gas, \
invalid receipient address or revert from the recipient. Therefore, the boolean returned by this function call must be checked \
to be `true` in order to verify that the transaction was successful")
}

fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
self.found_instances.clone()
}

fn name(&self) -> String {
IssueDetectorNamePool::UncheckedSend.to_string()
}
}

#[cfg(test)]
mod unchecked_send_tests {
use serial_test::serial;

use crate::detect::{detector::IssueDetector, high::unchecked_send::UncheckedSendDetector};

#[test]
#[serial]
fn test_unchecked_send() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/UncheckedSend.sol",
);

let mut detector = UncheckedSendDetector::default();
let found = detector.detect(&context).unwrap();

println!("Instances {:#?}", detector.instances());

// assert that the detector found an issue
assert!(found);
// assert that the detector found the correct number of instances
assert_eq!(detector.instances().len(), 1);
// assert the severity is high
assert_eq!(
detector.severity(),
crate::detect::detector::IssueSeverity::High
);
// assert the title is correct
assert_eq!(
detector.title(),
String::from("Unchecked `bool success` value for send call.")
);
// assert the description is correct
assert_eq!(
detector.description(),
String::from("The transaction `address(payable?).send(address)` may fail because of reasons like out-of-gas, \
invalid receipient address or revert from the recipient. Therefore, the boolean returned by this function call must be checked \
to be `true` in order to verify that the transaction was successful")
);
}
}
3 changes: 3 additions & 0 deletions aderyn_core/src/detect/high/weak_randomness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,12 @@ fn check_operand(operand: &Expression) -> bool {

#[cfg(test)]
mod weak_randomness_detector_tests {
use serial_test::serial;

use crate::detect::{detector::IssueDetector, high::weak_randomness::WeakRandomnessDetector};

#[test]
#[serial]
fn test_weak_randomness_detector() {
let context = crate::detect::test_utils::load_solidity_source_unit(
"../tests/contract-playground/src/WeakRandomness.sol",
Expand Down
1 change: 1 addition & 0 deletions reports/adhoc-sol-files-highs-only-report.json
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@
"incorrect-caret-operator",
"yul-return",
"state-variable-shadowing",
"unchecked-send",
"misused-boolean",
"send-ether-no-checks",
"delegate-call-unchecked-address",
Expand Down
66 changes: 63 additions & 3 deletions reports/report.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"files_summary": {
"total_source_units": 67,
"total_sloc": 1904
"total_source_units": 68,
"total_sloc": 1922
},
"files_details": {
"files_details": [
Expand Down Expand Up @@ -165,6 +165,10 @@
"file_path": "src/UncheckedReturn.sol",
"n_sloc": 33
},
{
"file_path": "src/UncheckedSend.sol",
"n_sloc": 18
},
{
"file_path": "src/UninitializedStateVariable.sol",
"n_sloc": 29
Expand Down Expand Up @@ -276,7 +280,7 @@
]
},
"issue_count": {
"high": 29,
"high": 30,
"low": 23
},
"high_issues": {
Expand Down Expand Up @@ -1380,6 +1384,19 @@
}
]
},
{
"title": "Unchecked `bool success` value for send call.",
"description": "The transaction `address(payable?).send(address)` may fail because of reasons like out-of-gas, invalid receipient address or revert from the recipient. Therefore, the boolean returned by this function call must be checked to be `true` in order to verify that the transaction was successful",
"detector_name": "unchecked-send",
"instances": [
{
"contract_path": "src/UncheckedSend.sol",
"line_no": 24,
"src": "815:22",
"src_char": "815:22"
}
]
},
{
"title": "Misused boolean with logical operators",
"description": "The patterns `if (… || true)` and `if (.. && false)` will always evaluate to true and false respectively.",
Expand Down Expand Up @@ -1476,6 +1493,30 @@
"src": "1795:5",
"src_char": "1795:5"
},
{
"contract_path": "src/UncheckedSend.sol",
"line_no": 6,
"src": "85:246",
"src_char": "85:246"
},
{
"contract_path": "src/UncheckedSend.sol",
"line_no": 12,
"src": "337:190",
"src_char": "337:190"
},
{
"contract_path": "src/UncheckedSend.sol",
"line_no": 17,
"src": "533:184",
"src_char": "533:184"
},
{
"contract_path": "src/UncheckedSend.sol",
"line_no": 22,
"src": "723:186",
"src_char": "723:186"
},
{
"contract_path": "src/UninitializedStateVariable.sol",
"line_no": 17,
Expand Down Expand Up @@ -1938,6 +1979,12 @@
"src": "32:23",
"src_char": "32:23"
},
{
"contract_path": "src/UncheckedSend.sol",
"line_no": 2,
"src": "32:23",
"src_char": "32:23"
},
{
"contract_path": "src/UsingSelfdestruct.sol",
"line_no": 2,
Expand Down Expand Up @@ -2932,6 +2979,12 @@
"src": "1795:5",
"src_char": "1795:5"
},
{
"contract_path": "src/UncheckedSend.sol",
"line_no": 27,
"src": "915:65",
"src_char": "915:65"
},
{
"contract_path": "src/auditor_mode/PublicFunctionsWithoutSenderCheck.sol",
"line_no": 11,
Expand Down Expand Up @@ -3197,6 +3250,12 @@
"line_no": 17,
"src": "388:11",
"src_char": "388:11"
},
{
"contract_path": "src/UncheckedSend.sol",
"line_no": 27,
"src": "915:65",
"src_char": "915:65"
}
]
},
Expand Down Expand Up @@ -3466,6 +3525,7 @@
"incorrect-caret-operator",
"yul-return",
"state-variable-shadowing",
"unchecked-send",
"misused-boolean",
"send-ether-no-checks",
"delegate-call-unchecked-address",
Expand Down
Loading

0 comments on commit 5273e5c

Please sign in to comment.