Skip to content

Commit

Permalink
Detector: Strict Equality Check on Contracts' balances (#625)
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 30, 2024
1 parent 2aea4d0 commit ee2f464
Show file tree
Hide file tree
Showing 9 changed files with 373 additions and 18 deletions.
5 changes: 5 additions & 0 deletions aderyn_core/src/detect/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub fn get_all_issue_detectors() -> Vec<Box<dyn IssueDetector>> {
Box::<RTLODetector>::default(),
Box::<UncheckedReturnDetector>::default(),
Box::<DangerousUnaryOperatorDetector>::default(),
Box::<DangerousStrictEqualityOnBalanceDetector>::default(),
Box::<StorageSignedIntegerArrayDetector>::default(),
Box::<RedundantStatementsDetector>::default(),
Box::<PublicVariableReadInExternalContextDetector>::default(),
Expand Down Expand Up @@ -134,6 +135,7 @@ pub(crate) enum IssueDetectorNamePool {
RTLO,
UncheckedReturn,
DangerousUnaryOperator,
DangerousStrictEquailtyOnContractBalance,
SignedStorageArray,
RedundantStatements,
PublicVariableReadInExternalContext,
Expand Down Expand Up @@ -276,6 +278,9 @@ pub fn request_issue_detector_by_name(detector_name: &str) -> Option<Box<dyn Iss
IssueDetectorNamePool::DangerousUnaryOperator => {
Some(Box::<DangerousUnaryOperatorDetector>::default())
}
IssueDetectorNamePool::DangerousStrictEquailtyOnContractBalance => {
Some(Box::<DangerousStrictEqualityOnBalanceDetector>::default())
}
IssueDetectorNamePool::SignedStorageArray => {
Some(Box::<StorageSignedIntegerArrayDetector>::default())
}
Expand Down
127 changes: 127 additions & 0 deletions aderyn_core/src/detect/high/dangerous_strict_equality_balance.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
use std::collections::BTreeMap;
use std::error::Error;

use crate::ast::{Expression, NodeID};

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

#[derive(Default)]
pub struct DangerousStrictEqualityOnBalanceDetector {
// 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 DangerousStrictEqualityOnBalanceDetector {
fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
// When you have found an instance of the issue,
// use the following macro to add it to `found_instances`:
//
// capture!(self, context, item);

for binary_operation in context
.binary_operations()
.into_iter()
.filter(|&op| op.operator == "==" || op.operator == "!=")
{
for expr in [
binary_operation.left_expression.as_ref(),
binary_operation.right_expression.as_ref(),
] {
if let Expression::MemberAccess(member_access) = expr {
if member_access.member_name == "balance"
&& member_access
.expression
.as_ref()
.type_descriptions()
.is_some_and(|type_desc| {
type_desc.type_string.as_ref().is_some_and(|type_string| {
// For older solc versions when you say this.balance, "this" is of type contract XXX
type_string.starts_with("contract ")
// In newers solidity versions, you say adddress(this).balance or payable(address(this)).balance
|| type_string == "address"
|| type_string == "address payable"
})
})
{
capture!(self, context, binary_operation);
}
}
}
}

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

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

fn title(&self) -> String {
String::from("Dangerous strict equality checks on contract balances.")
}

fn description(&self) -> String {
String::from("A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality.")
}

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

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

#[cfg(test)]
mod dangerous_strict_equality_balance_tests {
use crate::detect::{
detector::IssueDetector,
high::dangerous_strict_equality_balance::DangerousStrictEqualityOnBalanceDetector,
};

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

let mut detector = DangerousStrictEqualityOnBalanceDetector::default();
let found = detector.detect(&context).unwrap();
// 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
);
}

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

let mut detector = DangerousStrictEqualityOnBalanceDetector::default();
let found = detector.detect(&context).unwrap();
// assert that the detector found an issue
assert!(found);
// assert that the detector found the correct number of instances
assert_eq!(detector.instances().len(), 2);
// assert the severity is high
assert_eq!(
detector.severity(),
crate::detect::detector::IssueSeverity::High
);
}
}
2 changes: 2 additions & 0 deletions aderyn_core/src/detect/high/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub(crate) mod arbitrary_transfer_from;
pub(crate) mod avoid_abi_encode_packed;
pub(crate) mod block_timestamp_deadline;
pub(crate) mod dangerous_strict_equality_balance;
pub(crate) mod dangerous_unary_operator;
pub(crate) mod delegate_call_in_loop;
pub(crate) mod delegate_call_no_address_check;
Expand Down Expand Up @@ -33,6 +34,7 @@ pub(crate) mod yul_return;
pub use arbitrary_transfer_from::ArbitraryTransferFromDetector;
pub use avoid_abi_encode_packed::AvoidAbiEncodePackedDetector;
pub use block_timestamp_deadline::BlockTimestampDeadlineDetector;
pub use dangerous_strict_equality_balance::DangerousStrictEqualityOnBalanceDetector;
pub use dangerous_unary_operator::DangerousUnaryOperatorDetector;
pub use delegate_call_in_loop::DelegateCallInLoopDetector;
pub use delegate_call_no_address_check::DelegateCallOnUncheckedAddressDetector;
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 @@ -189,6 +189,7 @@
"rtlo",
"unchecked-return",
"dangerous-unary-operator",
"dangerous-strict-equailty-on-contract-balance",
"signed-storage-array",
"weak-randomness",
"pre-declared-local-variable-usage",
Expand Down
64 changes: 61 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": 71,
"total_sloc": 1981
"total_source_units": 73,
"total_sloc": 1996
},
"files_details": {
"files_details": [
Expand Down Expand Up @@ -49,6 +49,14 @@
"file_path": "src/CrazyPragma.sol",
"n_sloc": 4
},
{
"file_path": "src/DangerousStrictEquality1.sol",
"n_sloc": 6
},
{
"file_path": "src/DangerousStrictEquality2.sol",
"n_sloc": 9
},
{
"file_path": "src/DangerousUnaryOperator.sol",
"n_sloc": 13
Expand Down Expand Up @@ -292,7 +300,7 @@
]
},
"issue_count": {
"high": 31,
"high": 32,
"low": 25
},
"high_issues": {
Expand Down Expand Up @@ -1650,6 +1658,31 @@
}
]
},
{
"title": "Dangerous strict equality checks on contract balances.",
"description": "A contract's balance can be forcibly manipulated by another selfdestructing contract. Therefore, it's recommended to use >, <, >= or <= instead of strict equality.",
"detector_name": "dangerous-strict-equailty-on-contract-balance",
"instances": [
{
"contract_path": "src/DangerousStrictEquality1.sol",
"line_no": 6,
"src": "177:25",
"src_char": "177:25"
},
{
"contract_path": "src/DangerousStrictEquality2.sol",
"line_no": 6,
"src": "177:34",
"src_char": "177:34"
},
{
"contract_path": "src/DangerousStrictEquality2.sol",
"line_no": 10,
"src": "305:43",
"src_char": "305:43"
}
]
},
{
"title": "Compiler Bug: Signed array in storage detected for compiler version `<0.5.10`",
"description": "If you want to leverage signed arrays in storage by assigning a literal array with at least one negative number, then you mus use solidity version 0.5.10 or above. This is because of a bug in older compilers.",
Expand Down Expand Up @@ -1986,6 +2019,12 @@
"src": "32:32",
"src_char": "32:32"
},
{
"contract_path": "src/DangerousStrictEquality1.sol",
"line_no": 2,
"src": "32:23",
"src_char": "32:23"
},
{
"contract_path": "src/DangerousUnaryOperator.sol",
"line_no": 2,
Expand Down Expand Up @@ -2343,6 +2382,18 @@
"src": "1275:66",
"src_char": "1275:66"
},
{
"contract_path": "src/DangerousStrictEquality2.sol",
"line_no": 6,
"src": "202:9",
"src_char": "202:9"
},
{
"contract_path": "src/DangerousStrictEquality2.sol",
"line_no": 10,
"src": "339:9",
"src_char": "339:9"
},
{
"contract_path": "src/DelegateCallWithoutAddressCheck.sol",
"line_no": 24,
Expand Down Expand Up @@ -2714,6 +2765,12 @@
"src": "32:32",
"src_char": "32:32"
},
{
"contract_path": "src/DangerousStrictEquality2.sol",
"line_no": 2,
"src": "32:23",
"src_char": "32:23"
},
{
"contract_path": "src/DelegateCallWithoutAddressCheck.sol",
"line_no": 2,
Expand Down Expand Up @@ -3662,6 +3719,7 @@
"rtlo",
"unchecked-return",
"dangerous-unary-operator",
"dangerous-strict-equailty-on-contract-balance",
"signed-storage-array",
"redundant-statements",
"public-variable-read-in-external-context",
Expand Down
Loading

0 comments on commit ee2f464

Please sign in to comment.