本文介绍了如何使用 Aderyn(一个基于 Rust 的 Solidity 静态分析器)创建一个自定义检测器,用于检测 Solidity 代码中常见的“先除后乘”的错误。文章详细解释了该错误可能导致的精度损失问题,并提供了逐步指南,包括编写包含漏洞的合约、分析抽象语法树(AST)、以及在 Rust 中实现和测试检测器。
创建 division_before_multiplication 检测器:我们的流程解释
在 Solidity 中,一个常见但代价高昂的错误是在乘法之前进行除法。 因为 Solidity 默认使用整数除法,任何小数结果都会被默默截断,通常会降到零。这可能会引入严重的精度误差,尤其是在金融计算、质押逻辑和其他需要大量数学运算的合约中。最糟糕的是什么?编译器不会抛出任何警告。
这正是 Aderyn 等静态分析工具旨在捕捉的问题。
在本指南中,你将学习如何在 Aderyn(一个基于 Rust 的 Solidity 静态分析器)中构建自定义检测器。我们将逐步讲解所有内容,从编写易受攻击的合约并检查其 AST,到在 Rust 中实现和测试 division_before_multiplication 检测器,以便你可以跟随或将其用作编写自己的检测器的基础。
检测器的任务很简单:标记在单个表达式中除法发生在乘法之前的任何情况,使其永远不会在代码审查中漏掉。
让我们开始吧。
Aderyn 是一个用于 Solidity 智能合约的开源 Rust 静态分析器。它可以帮助协议工程师和安全研究人员在错误到达生产环境之前捕获它们。Aderyn 通过检查合约的抽象语法树 (AST) 来突出潜在问题,并使构建自己的自定义检测器变得容易。它可以从命令行快速运行,并且易于集成到你的开发或审计工作流程中。
现在你已经了解了 Aderyn 是什么,让我们开始工作并构建你的第一个自定义检测器。
division_before_multiplication 检测器的分步指南在本节中,我们需要识别要检测的漏洞,并了解如何在我们的 AST(抽象语法树)中识别它。
AST 是代码的结构化表示,编译器主要使用它来读取代码并生成目标二进制文件。它通常存储为 JSON。
在这种情况下,我们将自动化 Solidity 中数学漏洞的检测,该漏洞涉及在乘法之前执行除法。由于编译器处理结果中的小数会导致数学运算中精度损失,这是一个重大错误。
在此步骤中,我们将创建一个包含我们要检测的特定漏洞的智能合约。这将使我们能够测试和完善我们的检测器。以下是一个包含除法先于乘法问题的 Solidity 合约示例:
1pragma solidity 0.8.20;
2
3contract DivisionBeforeMultiplication {
4 uint public result;
5
6 function calculateWrong(uint a, uint b, uint c, uint d) external {
7 result = a * d + b / c * b / d;
8 }
9
10 function calculateAlsoWrong(uint a, uint b, uint c) external {
11 result = (a + b / c * b) * c;
12 }
13
14 function calculateAl(uint a, uint b, uint c) external {
15 result = (a / b * c);
16 }
17
18 function calculateStillWrong(uint a, uint b, uint c) external {
19 result = a + b / c * b * c;
20 }
21
22 function calculateCorrect(uint a, uint b, uint c) external {
23 result = a + b * b / c + b * c;
24 }
25
26 function calculateAlsoCorrect(uint a, uint b, uint c, uint d) external {
27 result = (a + ((b * d) / (c * b))) * d;
28 }
29}
为了开始理解如何提取有关问题的信息并编写我们的检测器来识别它,我们需要分析 .json AST 文件。
AST(抽象语法树)提供了 Solidity 代码的结构化表示,可用于查明漏洞发生的位置。通过检查 AST,我们可以确定除法和乘法的表示方式,并识别指示除法先于乘法问题的模式。
生成 .json AST 文件:使用 Solidity 编译器为你的合约生成 AST。
检查 AST 结构:打开生成的 DivisionBeforeMultiplication.json 文件,并查找表示除法和乘法运算的节点。这些节点将包含有关运算类型和执行顺序的信息。
识别相关节点:在 AST 中查找 BinaryOperation 节点。每个 BinaryOperation 节点都将包含有关运算(/ 表示除法,* 表示乘法)和所涉及的操作数的详细信息。
确定模式:识别出除法运算之后紧跟着乘法运算,但没有进行适当处理以确保精度的模式。此模式将是你检测器的基础。
让我们逐步分解 DivisionBeforeMultiplication 检测器的实现。
1pub struct DivisionBeforeMultiplicationDetector {
2 found_instances: BTreeMap<(String, usize, String), NodeID>,
3}
此结构使用 BTreeMap 来存储漏洞的实例。键是由源文件名、行号和问题描述组成的元组。NodeID 表示 AST 中发现问题的节点。
theIssueDetector Trait
我们为检测器实现 theIssueDetector trait。此 trait 需要一个执行分析的 detect 方法。1 fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
2 // Iterate over all binary operations in the context and filter for multiplication operations (*)
3 for op in context.binary_operations().iter().filter(|op| op.operator == "*") {
4 // Check if the left operand of the multiplication is a binary operation (i.e., another operation)
5 if let Expression::BinaryOperation(left_op) = op.left_expression.as_ref() {
6 // Check if this left operation is a division
7 if left_op.operator == "/" {
8 // Capture the instance of the vulnerability
9 capture!(self, context, left_op)
10 }
11 }
12 }
13 // Return true if any instances were found, otherwise false
14 Ok(!self.found_instances.is_empty())
15 }
16}
context.binary_operations() 获取所有二元运算,并使用 filter 仅选择乘法运算。捕获实例:如果同时满足这两个条件,我们将捕获该实例作为潜在漏洞。


1use std::error::Error;
2
3use crate::ast::NodeID;
4
5use crate::capture;
6use crate::detect::detector::IssueDetectorNamePool;
7use crate::{
8 ast::Expression,
9 context::workspace_context::WorkspaceContext,
10 detect::detector::{IssueDetector, IssueSeverity},
11};
12use eyre::Result;
13
14#[derive(Default)]
15pub struct DivisionBeforeMultiplicationDetector {
16 // Keys are source file name, line number, and description
17 found_instances: BTreeMap<(String, usize, String), NodeID>,
18}
19
20impl IssueDetector for DivisionBeforeMultiplicationDetector {
21 fn detect(&mut self, context: &WorkspaceContext) -> Result<bool, Box<dyn Error>> {
22 for op in context
23 .binary_operations()
24 .iter()
25 .filter(|op| op.operator == "*")
26 {
27 if let Expression::BinaryOperation(left_op) = op.left_expression.as_ref() {
28 if left_op.operator == "/" {
29 capture!(self, context, left_op)
30 }
31 }
32 }
33
34 Ok(!self.found_instances.is_empty())
35 }
36
37 fn severity(&self) -> IssueSeverity {
38 IssueSeverity::Low
39 }
40
41 fn title(&self) -> String {
42 String::from("Incorrect Order of Division and Multiplication")
43 }
44
45 fn description(&self) -> String {
46 String::from("Division operations followed directly by multiplication operations can lead to precision loss due to the way integer arithmetic is handled in Solidity.")
47 }
48
49 fn instances(&self) -> BTreeMap<(String, usize, String), NodeID> {
50 self.found_instances.clone()
51 }
52
53 fn name(&self) -> String {
54 format!("{}", IssueDetectorNamePool::DivisionBeforeMultiplication)
55 }
56}
57
58#[cfg(test)]
59mod division_before_multiplication_detector_tests {
60 use super::DivisionBeforeMultiplicationDetector;
61 use crate::detect::detector::{detector_test_helpers::load_contract, IssueDetector};
62
63 #[test]
64 fn test_template_detector() {
65 let context = load_contract(
66 "../tests/contract-playground/out/DivisionBeforeMultiplication.sol/DivisionBeforeMultiplication.json",
67 );
68
69 let mut detector = DivisionBeforeMultiplicationDetector::default();
70 let found = detector.detect(&context).unwrap();
71 assert!(found);
72 assert_eq!(detector.instances().len(), 4);
73 assert_eq!(
74 detector.severity(),
75 crate::detect::detector::IssueSeverity::Low
76 );
77 assert_eq!(
78 detector.title(),
79 String::from("Incorrect Order of Division and Multiplication")
80 );
81 assert_eq!(
82 detector.description(),
83 String::from("Division operations followed directly by multiplication operations can lead to precision loss due to the way integer arithmetic is handled in Solidity.")
84 );
85 }
86}
- 原文链接: zealynx.io/blogs/How-To-...
- 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!