{"content":{"title":"Substrate Pallet 的审计概述与建议","body":"## Substrate Pallets 的审计 - 概述与建议\r\n\r\n作者：Alexey Naberezhniy - MixBytes 的安全研究员\r\n\r\n![](https://img.learnblockchain.cn/2025/03/09/34.jpg)\r\n\r\n## 简介\r\n\r\nPolkadot 已成为一个蓬勃发展的生态系统，吸引了越来越多的项目。凭借其长期活跃的开发记录，它在创新方面继续引领潮流。尽管取得了成功，但能够审计基于 Substrate 平台构建的项目的公司仍然稀缺。此外，pallets 的审计过程仍然缺乏标准化，且信息有限。\r\n\r\n为了阐明这一重要主题，我们对基于 Substrate 平台构建的项目中的主要漏洞以及可用于自动化审计过程的工具进行了独立研究。作为我们发现的补充，我们对 Acala parachain 的流动质押模块进行了全面分析，未发现关键问题，但提供了几项有价值的建议。\r\n\r\nSubstrate pallets 中的典型漏洞\r\n\r\n1\\. Method on\\_initialized\r\n\r\n描述\r\n\r\n此方法在 pallet 中调用每个新区块。这个方法不得加载（或依赖可能引起加载的变量），并且不得导致 panic。否则，将会导致 DoS（拒绝服务攻击）。\r\n\r\n2\\. 返回结果未处理 / Runtime panic 条件\r\n\r\n描述\r\n\r\n方法的结果可能未被处理或可能导致 panic。\r\n\r\n示例\r\n\r\nOption 类使用 unwrap 方法安全地处理 None。若对象中存在 None，unwrap 会导致 panic。这对于区块链基础设施至关重要。\r\n\r\n相反，应该使用 unwrap\\_or\\_default。\r\n\r\n另外，try\\_mutate\\_exists 的结果被忽略也是常见情况。这不是错误，但如果方法返回 None，可能会导致副作用。\r\n\r\n3\\. 糟糕的 extrinsics 权重\r\n\r\n描述\r\n\r\n区块链资源的使用必须受到限制，以确保网络的连续运行。Substrate 使用权重机制来控制 extrinsic 的操作时间（类似于以太坊的 gas）。如果权重设置不正确，则垃圾邮件交易可能导致节点的 DoS。\r\n\r\n示例\r\n\r\n最显著的例子是一个循环。\r\n\r\n```auto hljs bash\r\n##[weight = 1_000_000]\r\n##[transactional]\r\npub fn work_with_users(origin: OriginFor<T>, users: Vec<T::AccountId>) -> DispatchResult {\r\n    let _ = ensure_signed(origin)?;\r\n\r\n    for user in users {\r\n        Self::do_something(&user)?;\r\n    }\r\n\r\n    Ok(())\r\n}\r\n```\r\n\r\n如上所示，work\\_with\\_users 方法的权重不依赖于方法执行的实际复杂性（循环运行的次数未被计算在内）。\r\n\r\n4\\. 溢出\r\n\r\n描述\r\n\r\n在 Rust 中有几种方式来处理数字。\r\n\r\n- 其中最不安全的是原始数学运算（例如 +），它可能会溢出。\r\n- saturating\\_add（在数值边界上而不是溢出，返回结果不准确）。它不会导致溢出，但在使用时有一些细微之处需要考虑（例如，0 - 1 = 0，当使用无符号时）。\r\n- 你可以使用 checked\\_add/ safe\\_add 来检查计算中的溢出。\r\n\r\n与其他编程语言一样，当涉及用户输入的操作时，checked\\_add 是更可取的选择。\r\n\r\n5\\. 需使用 transactional\r\n\r\n描述\r\n\r\n\\[transactional\\] 必须用于每个在 pallet 中的 extrinsic，否则状态在回滚时不会被取消。\r\n\r\n顺便提一下，在新版的 Substrate 中，“transactional”是默认行为。\r\n\r\n示例\r\n\r\n```auto hljs php\r\n...\r\nSelf::set_balance(currency_id, who, amount); # 存储的设置器\r\nErr(\"some revert\")?;\r\nOk(())\r\n...\r\n```\r\n\r\n没有 transactional 的话，回退后余额记录将不会被删除。\r\n\r\n6\\. EVM 中的经典漏洞（逻辑错误）：\r\n\r\n描述\r\n\r\n- 交易滑点\r\n- 进行前置审核 - 你可以进行多个批准并获得 Approve1 + Approve2\r\n- 紧急关闭机制拥有过多权限，可能导致不良影响\r\n- 地址未验证（如 require(owner != address(0x0)）\r\n- 以及其他许多错误\r\n\r\n示例\r\n\r\nSubstrate 是一种完全不同的技术，但错误对于任何智能合约来说都是相当标准的 [https://learnblockchain.cn/article/12435](https://learnblockchain.cn/article/12435)。\r\n\r\n然而，Substrate 和 EVM 审计之间存在非常重要的差异：\r\n\r\n1. 用户无法将任意逻辑的合约上传到网络并在单个交易中执行复杂操作（但是，可以在批处理中依次调用多个方法）；\r\n2. 黑客只能调用公用的 pallets 方法；\r\n3. 由于无法以通常的方式传递回调到函数，无法从黑客攻击中增加获利。\r\n\r\n这些特点缩短了黑客可以进行的操作名单，因此减少了审计人员在检查 Substrate 项目时必须研究的攻击向量数量。\r\n\r\n7\\. XCM 消息\r\n\r\nXCM 消息可能存在以下问题：\r\n\r\n- 发送 XCM 时权限不正确（此问题可能导致攻击者从 relay chain 中的 parachain 地址发送任何交易）\r\n- 不正确的“命令”内容\r\n\r\n示例\r\n\r\n[https://learnblockchain.cn/article/12434](https://learnblockchain.cn/article/12434)\r\n\r\n8\\. 与 pallets 的交互\r\n\r\n通常，一个单一的 parachain 使用多个相互交互的 pallets，用户与这些 pallets 互动。开发者必须非常小心，并提供所有测试，以确保能防止某个 pallet 错误地访问另一个 pallet（未预测的状态重写）。由于 pallets 类似于 EVM 中的智能合约，因此从一个 pallet 安全地访问另一个 pallet 的存储是至关重要的。\r\n\r\n示例\r\n\r\n例如，开发者使用 pallet\\_sudo 进行热修复，这在与区块链合作时并不是安全的方法。\r\n\r\n9\\. 不正确迁移 Parachain 至新版本\r\n\r\n迁移时应考虑在 pallet 中新增字段的所有添加，并检查在迁移过程中到新版本的转换是否正确。\r\n\r\n## 工具\r\n\r\n用于简化与 Rust 交互的工具不是很多（在分析的背景下）。\r\n\r\n- cargo-audit ( [https://rustsec.org](https://rustsec.org/))\r\n\r\nRustSec Advisory Database 是一个针对通过 [crates.io](http://crates.io/) 发布的 Rust crates 提出的安全建议的存储库，由 Rust Secure Code Working Group 维护。\r\n\r\n- 静态分析工具 ( [https://github.com/rust-lang/rust-analyzer](https://github.com/rust-lang/rust-analyzer))\r\n\r\n静态分析工具的一个示例是 rust-analyzer。它是 Rust 语言的模块化编译器前端。它是更大的 rls-2.0 工作的一部分，旨在为 Rust 提供出色的 IDE 支持。\r\n\r\n- 调试（通过 LLDB）\r\n\r\n使用 LLDB 调试来检测代码错误的众所周知的方法。\r\n\r\n- 测试（cargo test）\r\n\r\n编写测试并检查它们将帮助你确保系统中所有可用的脚本能够正常工作。在 pallets 之间的集成测试尤为重要。\r\n\r\n- 快速分叉 ( [https://github.com/maxsam4/fork-off-substrate](https://github.com/maxsam4/fork-off-substrate))\r\n\r\n该脚本可以利用当前实时链的状态来启动新的 Substrate 链。使用此工具，可以创建 Polkadot、Kusama 和其他 Substrate 链的分叉以用于开发目的。\r\n\r\n- [https://docs.substrate.io/test/check-runtime/](https://docs.substrate.io/test/check-runtime/)\r\n\r\ntry-runtime 命令行工具使你能够使用内存外部数据结构查询运行时存储的快照。通过使用内存存储，你可以针对指定的运行时状态编写测试，从而在进入生产之前对真实链状态进行测试。\r\n\r\n对 Acala 流动质押模块的研究攻击向量（附加内容）\r\n\r\ncommit: 37560835d477ee934fbca094af723d7d98b5f1d8\r\n\r\n1\\. 管理功能中输入参数的验证\r\n\r\n以下方法对输入参数的有效性检查不足，这可能导致系统暂时的 DoS：\r\n\r\n- update\\_homa\\_params\r\n- update\\_bump\\_era\\_params\r\n- force\\_bump\\_current\\_era\r\n\r\n例如：\r\n\r\n- get\\_staking\\_currency\\_soft\\_cap == 0 将会停止调用 do\\_mint 方法的能力\r\n- 大的 estimated\\_reward\\_rate\\_per\\_era 可能会导致利润计算不准确。\r\n- bump\\_era\\_frequency 可能会阻塞服务（不会调用 on\\_initialize）\r\n- force\\_bump\\_current\\_era 允许你跳过到任何时代。\r\n\r\n所有这些方法都是admin方法，因此对协议没有直接威胁，因为通常所有来自管理员的调用都经过多重参数检查，但我们仍然建议添加检查以提高系统安全性。\r\n\r\n2\\. 关于 LDOT 余额的数学运算\r\n\r\n用户可以燃烧比 pallet 中的代币数量更多的 LDOT 吗？\r\n\r\n- [https://github.com/AcalaNetwork/Acala/blob/37560835d477ee934fbca094af723d7d98b5f1d8/modules/homa/src/lib.rs#L885](https://github.com/AcalaNetwork/Acala/blob/37560835d477ee934fbca094af723d7d98b5f1d8/modules/homa/src/lib.rs#L885)（模块的 actual\\_balance 是否总是 > actual\\_liquid\\_to\\_redeem?）\r\n\r\n这里不可能，因为 actual\\_liquid\\_to\\_redeem 总是小于 Self::to\\_bond\\_pool()。\r\n\r\n- [https://github.com/AcalaNetwork/Acala/blob/37560835d477ee934fbca094af723d7d98b5f1d8/modules/homa/src/lib.rs#L1097](https://github.com/AcalaNetwork/Acala/blob/37560835d477ee934fbca094af723d7d98b5f1d8/modules/homa/src/lib.rs#L1097)（模块的 actual\\_balance 是否总是 > total\\_redeem\\_amount?）\r\n\r\ntotal\\_redeem\\_amount = sum(RedeemRequests.redeem\\_amount) => 在此之前，LDOT 已在合约上 100% 加载，因为没有其他方式调用 do\\_request\\_redeem。\r\n\r\n3\\. DOT 与 LDOT 的兑换率\r\n\r\n在 convert\\_liquid\\_to\\_staking 或 convert\\_staking\\_to\\_liquid 方法中，可以人为改变兑换比例吗？\r\n\r\n不能，因为此比例不会剧烈变化。\r\n\r\n```auto hljs php\r\nlet total_staking = Self::get_total_staking_currency();\r\nlet total_liquid = Self::get_total_liquid_currency();\r\n```\r\n\r\n方法中的变化：\r\n\r\n- process\\_staking\\_rewards（每个时代奖励）。影响不大\r\n- fast\\_match\\_redeems（收取赎回费）。影响不大\r\n- reset\\_ledgers（可以显著改变 total\\_staking）。但仅适用于 GovernanceOrigin，且也是协议限制。\r\n\r\n其余的价格变化是成比例的：\r\n\r\n- process\\_redeem\\_requests（燃烧质押和流动性）\r\n- mint（增加质押和流动性）\r\n\r\n- 谁是 MixBytes？\r\n\r\n\r\n\r\n[MixBytes](https://mixbytes.io/) 是一支专业的区块链审计与安全研究团队，专门为 EVM 兼容和基于 Substrate 的项目提供全面的智能合约审计和技术咨询服务。请关注我们的 [X](https://twitter.com/MixBytes)，以便随时了解最新的行业动态和见解。\r\n\r\n>- 原文链接： [mixbytes.io/blog/audit-o...](https://mixbytes.io/blog/audit-of-substrate-pallets-overview-tips)\r\n>- 登链社区 AI 助手，为大家转译优秀英文文章，如有翻译不通的地方，还请包涵～"},"author":{"user":"https://learnblockchain.cn/people/24680","address":null},"history":null,"timestamp":1741499038,"version":1}