安全评估 - ink! & cargo-contract - OpenZeppelin 博客

OpenZeppelin 对 ink! 和 cargo-contract 进行了安全评估,未发现严重问题,但发现了两个高危问题,Parity 团队正在解决。

该安全评估由 OpenZeppelin 编写。

目录

简介

审查总结

对 ink! 和 cargo-contract 的安全审查被认为是成功的,没有检测到严重问题。 然而,发现了两个高危问题,Parity 团队已经在着手解决。 此外,OpenZeppelin 提出了一些修改和改进建议,以遵循最佳实践,最大限度地减少潜在的攻击面,并增强语言和工具。

描述

Parity 团队要求我们对他们用于编写智能合约的 Rust eDSL, ink!的第 4 版进行安全审查。 这次合作是由Polkadot treasury资助的。 为了确保全面和结构化的评估,我们将合作分为三个不同的阶段。 第一阶段是了解 ink! 及其生态系统,第二阶段是进行安全审查,第三阶段是与 Parity 团队合作解决在前一阶段发现的问题。 我们利用从 ink! 和 cargo contract 中获得的知识进行安全分析。 我们的目标是验证 ink! 在缓解已知安全风险方面的有效性。 本安全概述报告的主要目标是分享我们发现的安全漏洞,提供修复建议,并分享我们在维护 OpenZeppelin 合约和构建开发者工具方面获得的经验和见解。

OpenZeppelin 认为此次合作是一个机会,可以帮助 Parity 团队改善开发者和审计人员未来使用 ink! 的整体体验。 即将交付的成果将包括审查下面“范围”部分中提到的所有项目。

范围

OpenZeppelin 对 ink! 仓库、cargo-contract CLI 进行了安全分析,并对 Solidity 进行了功能对比。 主要的重点是识别潜在的漏洞并提供改进建议。 具体而言,分析包括以下领域:

  • 审查 ink! 仓库,特别关注 env、storage 和 ink crate。 此操作将涉及分析所有宏以确保它们是安全的,并评估代码库的整体设计。
  • cargo-contract 进行安全分析。 我们将专注于识别功能的不安全实现、错误配置以及可能影响工具安全性的其他问题。
  • Solidity 和 ink! 之间的功能比较,添加改进后者的建议。 这将包括识别 ink! 相对于 Solidity 缺乏的任何关键特性或功能,并提出增强 ink! 生态系统的方法。
  • 审查文档、教程和工具,添加改进使用 ink! 及其外围设备时的整体用户体验的建议。

审查范围内的提交和仓库是:

免责声明:鉴于本次服务的持续时间有限以及代码库的规模庞大,必须注意这次审查并非详尽无遗。 虽然 OpenZeppelin 尽了最大努力分享可能构成问题或改进领域的任何情况,但我们不能保证所有此类问题都已被检测到。

系统概述

ink! 是一种基于 Rust 的特定领域语言(DSL),用于编写在基于 Substrate 框架构建的区块链上执行的智能合约。 设计这种语言的主要目的是使其尽可能类似于编写常规 Rust 代码,同时仍然安全高效。 选择 Rust 作为 ink! 的基础语言提供了多种好处,例如类型安全、内存安全和小二进制大小,使其成为开发人员的理想选择。

要部署使用 ink! 编写的智能合约,目标区块链必须具有 pallet-contracts,这是一个支持合约执行的模块。 ink! 由 9 个 crate 组成:allocatore2eengineenvinkmetadatapreludeprimitivesstorage

架构

  • ink:包含程序宏,用于生成编译器将转换为 Wasm 的最终代码。 该代码是在区块链上运行并执行合约特定操作的代码。
  • env:提供与 pallet-contracts 的连接,允许与智能合约的底层执行引擎进行交互。 这些交互包括读取和写入智能合约的存储,以及访问环境变量函数,例如有关合约调用的调用者的信息和自我终止合约。
  • allocator:用于在执行期间智能合约中的动态内存分配。
  • engine:一种链下测试引擎,可模拟区块链环境并允许模拟指定的条件。
  • e2e:用于合约的端到端测试的包。
  • metadata:以平台无关的方式描述合约、其接口、类型、存储布局等。
  • prelude:提供标准库类型和功能的接口,因为合约在 no_std 环境中运行。
  • primitives:由多个 ink! 模块在内部使用的实用程序集合。
  • storage:为开发人员提供在合约存储中使用的集合。

Cargo Contract

用于创建、编译、测试、上传、实例化和解码 ink! 合约的命令行界面 (CLI),从而使开发过程更加高效。 该应用程序由 4 个 crate 组成:

  • Build:用于构建 ink! 合约
  • Cargo-contract:使用 Clap 开发的 CLI 实现。
  • Metadata:定义了 Substrate 的智能合约的扩展元数据的类型。
  • Transcode:包含将合约调用编码为 SCALE 的实用程序。

威胁模型

参与者、资产、外部依赖和入口点

本节定义了此威胁模型的参与者、资产、外部依赖和入口点。

参与者

使用 ink! 和 cargo-contract 的用户或智能合约。 这使我们能够定义每个入口点所需的访问权限或特权,以及与每个资产交互所需的访问权限或特权。 考虑到哪些参与者与 ink! 交互有助于确定语言和使用它制作的智能合约如何受到损害。

ID 名称 描述
1 普通用户 通过非限制性消息和访问事件与智能合约交互的用户。
2 智能合约开发者 创建和维护 ink! 智能合约并使用 cargo-contract CLI 的开发者。
3 智能合约 一个 ink! 智能合约。
4 特权用户 可以通过其受限制的消息与智能合约交互的经过身份验证的用户。
5 核心开发者 维护 ink! 和 cargo-contract 的代码库仓库的开发者。
6 贡献者 对 ink! 或 cargo-contract 的开发做出贡献的非官方团队成员。

入口点

指定恶意用户如何访问 ink!、其命令行界面工具以及使用它构建的智能合约并与之交互。

ID 名称 描述 参与者
1 构造函数 这些入口点在实例化智能合约时调用。 它初始化合约的状态变量并在需要时执行一些逻辑。 (2) 智能合约开发者, (3) 智能合约, (4) 特权用户
2 非限制性消息 具有 message 属性的公共函数是特权用户与智能合约交互的唯一方式。 此方法定义了合约响应特定参数的行为。 (1) 普通用户, (2) 智能合约开发者, (3) 智能合约, (4) 特权用户
3 限制性消息 修改合约存储的敏感部分的函数,只能由合约存储中设置的特权 AccountID 调用。 (4) 特权用户
4 合约元数据 它提供有关合约名称、版本、方法、存储和通用数据的信息。 (2) 智能合约开发者
5 Cargo-contract CLI 用于管理 ink! 智能合约的命令行界面 (CLI),包括编译、测试、部署和与已部署的智能合约交互。 (2) 智能合约开发者, (3) 智能合约
6 Dapp 的 UI 用于与 ink! Dapps 交互的用户界面 (UI)。 (1) 普通用户, (2) 智能合约开发者, (4) 特权用户
7 GitHub 仓库 用于存储和管理 ink! 智能合约源代码的代码仓库。 此入口点是恶意开发者的常见入口点。 (2) 智能合约开发者, (5) 核心开发者

资产

指 ink! 及其使用它开发的合约中可能对攻击者有价值的元素,例如代币、余额、凭据和抽象资产。 这些资产需要防止潜在的攻击者。

ID 名称 描述 参与者
1 ink 语言 语言实现及其规范,定义了 ink! 智能合约的行为。 ink! 语言的正确性和安全性对于防止潜在的攻击至关重要。 (5) 核心开发者, (2) 智能合约开发者, (6) 贡献者
2 ink 依赖 定义 ink! 语言及其依赖的 crate。 这些 crate 的所有权和控制对于 ink! 的稳定性和安全性至关重要。 (5) 核心开发者, (6) 贡献者
3 开放合约存储 智能合约的存储包括各种用户数据,例如余额和津贴。 用户可以授予其他用户或智能合约对其资产的特权。 (4) 特权用户, (3) 智能合约, (1) 普通用户
4 限制性合约存储 智能合约的存储可能包括用户权限、所有者和其他合约数据。 (2) 智能合约开发者, (3) 智能合约, (4) 特权用户
5 用户密钥 用于签署交易并与合约交互的用户的私钥。 (1) 普通用户, (4) 特权用户
6 修改代码库仓库 托管 ink! 和 cargo-contract 的仓库的完整性应仅由其核心开发者修改。 这包括版本控制和代码审查。 (5) 核心开发者
7 链原生代币 用于支付 gas 的底层代币。 (1) 普通用户, (3) 智能合约
8 项目声誉 声誉是 ink! 作为构建智能合约的首选语言取得成功的关键。 (1) 核心开发者

外部依赖

这些是可能对语言和使用它制作的合约构成威胁的主要外部元素。

ID 描述
1 Rust 语言及其包管理器 (Cargo),用于编写和管理 ink! 和 cargo-contract 的依赖。
2 Substrate 运行时模块,为使用 ink! 编写的智能合约提供特定于区块链的功能和接口。 在这种情况下,最相关的是 contract-pallet,用于部署和执行 WebAssembly 智能合约的运行时。
3 Substrate 客户端库,用于与 Substrate 区块链节点交互。
4 Blake2 哈希实现。
5 GitHub,ink! 和 cargo-contract 的官方代码库托管在此处。
6 Rust WebAssembly 目标,用于将 ink! 智能合约编译为 WebAssembly。
7 第三方 Rust crate,为 ink! 和 cargo-contract 提供额外的功能。
8 Substrate 区块链,ink! 智能合约将部署并在其上执行。

简化数据流

风险评估方法

作为标准化的一种手段,OpenZeppelin 实施了一个用于对问题进行分类的矩阵,该矩阵符合 OWASP 风险评级方法。 该方法广泛应用于网络安全行业,提供了一种系统的方法来评估和确定与给定系统或应用程序相关的潜在风险的优先级。 然而,必须承认的是,OWASP 方法并非专门为区块链技术的独特特征而设计。 我们认为有必要在我们的矩阵中更加强调影响轴。 生态系统中的金融资产的存在及其去中心化的性质放大了损害的可能性。 此外,由于这些技术的不可变性,减轻损害的可能性通常很低。

严重性分类

术语

  • 可能性:攻击者发现和利用漏洞的可能性有多大。
  • 影响:表示成功攻击的技术和业务损害。
  • 严重性:评估漏洞的总体严重程度。

潜在风险的可能性和影响分为三个等级:高、中和低。 风险的严重程度由其可能性和影响的组合决定,可以分为四个类别:严重、高、中和低,如表所示。 此外,如果我们发现其他不值得作为漏洞报告的建议,它们将在“注释 & 附加信息”部分下报告。

严重程度级别说明

严重

该问题将大量用户的敏感信息置于风险之中,并且/或者很可能对客户的声誉产生灾难性影响,或对客户和用户造成严重的财务影响。 它通常涉及但不限于某种形式的资金损失或锁定,或其他核心系统功能故障。

该问题将大量用户的敏感信息置于风险之中,并且/或者很可能对客户的声誉产生重大影响,或对客户和用户造成显着的财务影响。 它通常涉及但不限于某种形式的临时资金损失或锁定,或其他核心系统功能故障,并且可能存在合理的缓解措施。

该问题将一部分用户的敏感信息置于风险之中,如果被利用,将对客户的声誉产生不利影响,或者很可能对客户和用户造成适度的财务影响。

该问题相对较小,无法定期利用,或者客户根据其业务模型已将其归类为低影响的风险。

注释和附加信息

该问题与安全无关,但仍然值得注意以提高代码库的质量。

威胁类别

为了彻底评估 ink! 中的潜在漏洞,我们使用了 DASP 框架(一个广泛认可的开放项目,用于识别智能合约漏洞的前 10 个类别)和 SWC 注册表EIP-1470 中概述的分类系统)。 这种方法使我们能够根据常见的 Solidity 漏洞识别最容易受到攻击的 ink! 组件。 我们可以使用 Dasp 10 的适用部分和 OpenZeppelin 的内部知识来识别以下威胁和策略:

  • 重入:攻击者可能会利用使用 ink! 编写的合约中的漏洞来重复调用函数,耗尽资金或导致其他意外后果。
  • 访问控制:威胁参与者可能会通过调用受限制的函数来绕过访问控制规则。
  • 算术问题:ink! 必须能够处理算术运算中的溢出和下溢情况。
  • 不良随机性:如果 ink! 合约依赖于随机数,则底层随机数生成必须是安全的且没有可预测性,因为可预测或被操纵的随机性可能被攻击者利用。
  • 存储布局冲突:为了防止可升级合约中意外的数据修改或丢失,ink! 必须最大限度地减少攻击者可以利用的存储布局冲突的可能性。
  • 代理选择器冲突:如果系统中的不同合约使用与代理合约中已部署的函数相同的函数签名,则可能导致代理选择器冲突,导致交易失败并导致潜在的资金损失。 这也可能允许攻击者操纵合约行为并绕过安全控制
  • 拒绝服务:攻击者可以通过针对智能合约发起攻击来导致服务中断,方法是引发意外的回滚,消耗过多的内存或 gas,或填充区块链中的多个区块,这可以防止其他交易包含在任何区块中。
  • 外部数据源依赖:ink! 合约可能依赖于外部数据源,例如 API 或预言机,如果数据源不安全或可以被攻击者操纵,则可能会引入漏洞。 这可能导致恶意行为者利用这些漏洞来控制合约、窃取资金或操纵合约行为。
  • 依赖项劫持:恶意行为者可能会将恶意代码注入到 ink! 或 cargo-contract 仓库的依赖项之一中。 因此,攻击者可以控制整个系统并绕过安全控制。 因此,必须确保 ink! 和 cargo-contract 使用的所有依赖项都经过验证并且来自受信任的来源。
  • 网络钓鱼:攻击者可能会使用网络钓鱼策略来诱骗用户泄露敏感信息,例如私钥或密码,然后可以使用这些信息来破坏 ink! 合约的安全性并窃取资金。
  • 基础设施破坏:用于运行 ink! 合约的基础设施(例如区块链网络、节点或托管服务器)可能被攻击者破坏。 此问题可能导致各种攻击,例如分布式拒绝服务 (DDoS) 攻击、Sybil 攻击或操纵网络共识规则,这可能导致未经授权的交易、资金损失和合约执行中断。

由于 DASP 和 SWC 注册表可能不是最新的,因此我们还利用 OpenZeppelin 的内部知识来增强我们对可能在 ink! 上下文中应用的潜在漏洞的范围。

我们的重点是潜在的攻击者,他们可能会利用他们对 EVM 区块链中常见漏洞的了解,并试图在 ink! 的上下文中利用它们。

为了确保全面和结构化的审查,我们遵循了 SCSVS 清单(智能合约安全验证标准)。 这种方法可以帮助防止在评估 ink! 期间忽略潜在的漏洞。

对策和缓解措施

本节列出了可以防止威胁实现的保护措施。 如果某个威胁没有对策,则它是一个漏洞。

  • 重入

    • ink! 要求开发人员显式使用 set_allow_reentry 标志以允许被调用者重新进入当前合约。
    • 强制使用 Checks-Effects-Interactions 模式,以确保在进行任何外部调用之前进行所有状态更改。
    • 使用重入保护,如 OpenBrush 实现,以防止重入攻击。
  • 访问控制

    • 使用 最小特权原则 以确保只有授权用户才能访问受限制的函数。
    • 实施访问控制机制,例如基于角色的访问控制 (RBAC) 或基于属性的访问控制 (ABAC)。
    • ink! 开发人员可以利用第三方访问控制合约来定义和强制执行访问控制规则。
  • 算术问题

    • Rust 可以在编译时检测下溢和溢出,并恢复构建过程。
    • 开发人员可以实施输入验证以防止无效或恶意输入。
  • 不良随机性

    • ink! 删除了之前存在的初始随机函数。
  • 拒绝服务

    • 开发人员可以使用 gas 高效的编码技术来降低执行交易的成本。
    • 实施速率限制以防止过度使用并保护合约免受滥用。
  • 外部数据源依赖

    • 使用受信任的来源获取外部数据和预言机,避免依赖单个来源做出关键决策。
    • 实施输入验证以防止来自外部数据源的无效或恶意输入。
    • 使用回退机制,以防外部数据源发生故障或受到威胁。
  • 依赖项劫持

    • 验证所有依赖项,并确保它们来自受信任的来源。
    • 使用依赖项管理工具来跟踪和管理项目中使用的所有依赖项。
    • Cargo 具有 yank 功能,允许包维护者从索引中删除已推送的 crate。 如果发现某个版本存在安全漏洞或其他问题,这会很有用。
    • 定期审查项目中的依赖项,并检查是否存在任何已知漏洞或可疑活动。 RustSec 可以在此过程中提供帮助。 将所有依赖项更新到解决已知漏洞的最新版本。 Parity 使用 Dependabot 作为 CI 工作流程之一。

建议

以下是一些 Parity 团队可以实施以改善使用 ink! 和 cargo-contract 的整体用户和开发者体验的通用建议,同时促进 Solidity 开发者的顺利入门。

创建新的智能合约监控计划

随着智能合约变得越来越普遍,拥有更好的工具和流程来监控和响应事件变得越来越重要。 创建与事件响应更一致的智能合约监控新计划,例如 Forta 或 OpenZeppelin Defender,可以帮助开发者和组织更好地保护他们的合约并快速响应潜在问题。

更新: 已确认,未解决。 Parity 团队表示:

与 Sirato Substrate Explorer 正在讨论中。

检测 set_code_hash 的使用

实现合约可升级性的一种方法是使用 set_code_hash 函数,该函数允许在不修改合约地址的情况下更新合约代码。 为了检测合约是否具有此功能,区块浏览器或第三方应用程序可以检查合约的 Wat 并查找 set_code_hash 导入。 如果存在,则表明合约可以升级其代码。

更新: 已确认,未解决。 Parity 团队表示:

与 Sirato Substrate Explorer 正在讨论中。

实现类似 Solidity 的 immutable 变量

Solidity 中的 Immutable 变量允许开发者创建在运行时无法修改的只读变量,这对于安全性和效率目的非常有用。 在 ink! 中实现类似的功能将使开发者在设计和构建安全高效的智能合约方面具有更大的灵活性。

更新: 该建议已在 “ink” 仓库的 issue 1714 中得到确认。

允许 trait 方法中的默认实现。

添加在 trait 方法中具有默认实现的能力(类似于 Ethereum 生态系统中的抽象合约)将使开发者更容易编写可重用和可组合的合约。

更新: 该建议已在 “ink” 仓库的 issue 1689 中得到确认。

添加对类似 Solidity 库的支持。

在 ink! 中添加对类似 Solidity 库的支持将允许开发者跨多个合约创建和重用公共函数,从而提高代码的可重用性并减少重复。 此添加还将有助于简化开发过程,并使开发者更容易构建更复杂的智能合约。

更新: 该建议已在 “ink” 仓库的 issue 1684 中得到确认。

cargo-contract 功能建议

以下是 OpenZeppelin 团队在参与过程中详尽使用 cargo-contract 工具后提出的一些想法。 开发其中一些建议的可行性取决于用于构建 cargo-contract 的 rust 库的灵活性。

添加一个为 Solang 编译的合约生成接口的命令

cargo contract generate-interface $language

cargo-contract 上创建一个为目标编程语言生成接口的命令,将使开发者更容易将其智能合约与其他用不同编程语言编写的合约集成。 此命令将促进跨合约通信,并帮助创建更复杂和高级的合约应用程序。

最初,该功能应支持 ink! 和 Solidity,因为这些是 pallet-contracts 最常见的选择。 其设计应该足够灵活,可以添加可能变得流行的其他语言。

更新: 该建议已在 “cargo-contract” 仓库的 issue 807 中得到确认。

改进多合约项目的 build 命令

当前多合约项目的构建过程需要单独构建每个合约,这可能是一个耗时且乏味的过程。 为了改进此过程并使其对开发者来说更方便,建议包括直接在 cargo contract build 命令中构建单个项目中的多个合约的功能。

此功能将允许开发者使用单个命令构建项目中的所有合约,从而减少构建过程所需的时间和精力。 这也将使开发者更容易管理和维护他们的项目,因为他们不必担心为每个合约管理多个构建命令。

更新: 该建议已在 “cargo-contract” 仓库的 issue 961 中得到确认。

添加一个 encode 命令

cargo contract encode [options] --message <MESSAGE> --args <ARGS...>

当前 cargo contract 中没有编码命令。 encode 函数将允许开发者编码函数调用的参数,从而更容易测试不同的场景和用例并调试代码。

更新: 已在 “cargo-contract” 仓库的 pull request #998 中解决。

允许原始 RPC 调用

cargo contract rpc [options] METHOD [PARAMS...]

用户无法使用当前可用的命令对节点进行原始 RPC 调用。

添加此功能将通过让开发者直接访问节点的 RPC 接口来改进调试过程,从而更容易诊断、修复和试验不同的场景。

更新: 该建议已在 “cargo-contract” 仓库的 issue 987 中得到确认。

在 cargo-contract 中实现 fork 功能

为了促进测试和开发,在 cargo-contract 中实现 fork 功能会很方便。 此功能将允许开发者使用目标区块链的当前状态测试他们的智能合约,这可以帮助在将合约部署到实时网络之前识别和修复潜在问题。

更新: 该建议已在 “cargo-contract” 仓库的 issue 988 中得到确认。

添加 cargo-contract accounts 命令

cargo contract accounts

此命令应显示所有可用帐户的地址、别名和余额的列表。 它还可以指示哪个地址是默认帐户,并提供有关如何更改它的提示(请参阅下面的 set-default-account 命令)。

更新: 已确认,但尚未解决。

添加 cargo contract set-default-account $account 命令

cargo contract set-default-account $account

示例:

$ cargo contract set-default-account //Alice

此操作应将作为参数发送的 $account 设置为要在所有后续命令中使用的默认帐户。 在大多数情况下,当通过 CLI 与一个或多个合约交互时,通常总是使用相同的帐户,而不是多个帐户。 添加设置默认帐户的功能允许开发者避免每次实例化或与合约交互时都使用 --suri 标志。 如果在使用命令时定义了 --suri 标志,则该特定用途的默认帐户应被指定的帐户覆盖。

更新: 已确认,但尚未解决。

添加 cargo contract deployed-contracts 命令

$ cargo contract deployed-contracts

此命令应让开发者看到到目前为止已部署的所有合约的列表。 它应显示合约的名称(可以从元数据中获取)、部署的地址(或地址)以及所有可访问的方法及其参数(即它们的接口)的列表。 大多数这些功能已在合约 UI 工具中可用,但根据我们的经验,开发者在开发智能合约时会看到使用 CLI 工具的更多价值,并且能够看到已部署的所有合约及其接口。

更新: 已确认,但尚未解决。

添加 cargo contract deployed-contract $contract 命令

这与上面提到的命令类似,但用于特定合约。 此命令将接收合约 $contract 作为参数,并显示部署的地址(或地址)以及所有可访问的方法及其参数的列表。

更新: 该建议已在 “cargo-contract” 仓库的 issue 783 中解决。

将 cargo contract call 命令拆分为 cargo contract call 和 cargo contract send

$ cargo contract call --contract $contract --message $message --suri $account [metadata.json]

区块链生态系统中,可升级性是一个众所周知的特性:它允许修改、修复和改进合约实现,而无需在新地址中部署新合约。正如 Parity 团队所知,使用代理模式来实现可升级性既有优点也有缺点(下面提到了一些)。

此命令应允许开发人员将给定的代理 $proxy-address 升级到新的实现 $implementation-address。为了缓解问题,此合约应检查:

  • 实现的存储布局没有被破坏。 为此,有必要持久化先前实现的存储布局,并将它的存储布局与新实现的存储布局进行比较。 有关典型存储布局冲突的更多信息,请参见此处
  • 代理合约中定义的函数和实现合约中定义的函数不共享任何函数选择器,以避免函数选择器冲突。
  • 代理和实现之间没有共享的键:换句话说,代理合约中定义的所有变量都不应与实现合约中定义的变量冲突。

更新: 在“cargo-contract”存储库的 issue 981 中确认了该建议。

实现交互式命令

开发人员可能会忘记向命令发送一个或多个参数,并且命令的默认行为是执行失败。 相反,提示输入缺少的参数可能是改善使用命令的整体体验的一种方法。

例如,当用户在控制台中编写 $ cargo contract 而不指定参数时,该工具可以通过以下方式提供帮助:

$ cargo contract call

$ 你想调用哪个合约?
 [x] 合约 A
 [ ] 合约 B
 [ ] 合约 C
 ....

$ 你想调用哪个消息?
[ ] foo(u32)
[x] bar(u32, u32)
[ ] buz()

$ 指定参数,以逗号分隔:
$ 42,50

$ 使用默认元数据?
[x] 是
[ ] 否

在此示例中,如果用户手动(使用标志)指定他们想要调用的合约,则只会询问消息名称、参数和元数据,依此类推。 此示例可以应用于 cargo-contract 工具中接收参数的任何其他命令。

更新: 已确认,但未解决。 Parity 团队声明:

在你所有的建议中,我们为此建议分配了最低优先级。 我们普遍认为这可能有用,但它引发了关于 “cargo-contract” 更大愿景的讨论。 我们中的一些人认为 “cargo-contract” 应该是一个精简的工具,更多用户友好的功能应该构建在其之上,例如 swanky-cli 等工具,这些工具在底层使用 “cargo-contract”。

教育建议

Parity 团队可以通过瞄准两个不同的受众:审计员和开发人员,从而提高 ink! 作为 parachains 的主要智能合约语言的采用率。 两个受众可能都有学习新语言的不同原因。 以下是我们在其他区块链生态系统中看到的一些已被验证为有价值的建议。

对于审计员:

  • Immunefi 中启动一个漏洞赏金计划,针对 cargo-contracts 和 ink!。 这不仅有助于提高技术的整体安全性,而且还会引起可能从未听说过它的审计员的注意,从而学习它并尝试破解它以获得赏金。
  • 构建和/或推广 CTF(夺旗赛),类似于 EthernautDamnVulnerableDefi。 第一个侧重于语言和用于编写智能合约的工具中潜在的安全问题,而后者则更侧重于特定主题,例如 DeFi 或治理。
  • 启动一个有竞争力的审计计划,类似于 Code4rena,以便在发布之前对 ink! 和 cargo-contract 项目进行审计。 Parity 也可以为其他 ink! 项目(例如 OpenBrush)推广此计划。

对于开发人员:

  • 构建一个类似于 Cryptozombies 的交互式教程,以学习如何在 ink! 中编写代码。 该教程应强调 ink! 和 Solidity 之间的异同,从而使使用者更容易从 Ethereum 过渡。
  • 启动开发者资助计划,以资助 ink! 和 cargo-contract 的特定有用功能的开发。 与使用 ink! 作为其主要智能合约语言的其他公司合作,以开发新功能。 例如,为 OpenBrush 开发新功能以实现尚未创建的智能合约。

发现

Type智能合约语言和框架Timeline从 2023-01-16 到 2023-02-10Languagesink! – RustTotal Issues11 (3 已解决)Critical Severity Issues0 (0 已解决)High Severity Issues2 (0 已解决)Medium Severity Issues2 (0 已解决)Low Severity Issues5 (3 已解决)Notes & Additional Information2 (0 已解决)

高危

自定义选择器可能会促成代理选择器冲突攻击

ink! 具有一项功能,允许开发人员为给定的函数硬编码选择器。 此功能支持在保持相同选择器的同时更改函数名称,并且还有助于创建与语言无关的合约标准。

但是,在合约中允许自定义选择器可能会导致代理选择器冲突。 当使用者在实现上调用特定函数时,代理中匹配的选择器可能会导致代理中代码的意外执行。 此问题使诈骗项目更容易创建难以检测的恶意后门。 与 ink! 相比,Solidity 需要找到具有匹配选择器的函数签名,然后才能利用此漏洞,但这并非易事。 如果找到并添加了此类函数签名,则它们很可能会发出危险信号,因为该名称通常对代码库没有意义。

自定义选择器还会使使用函数选择器来识别特定函数的第三方监控或索引服务感到困惑。 这些服务可能依赖于标准选择器,这些选择器是标准的一部分或属于社区数据库,例如 4byte 目录。 如果合约使用自定义选择器,则这些服务可能无法识别和监控交易,从而导致错误。

鉴于概述的潜在危险,值得重新考虑此功能,并寻找替代方案来处理与语言无关的合约标准。或者,要求实现合约的元数据来构建代理,并在与实现发生选择器冲突时阻止代码被编译可能是一个可行的解决方案。如果使用自定义选择器的优势不大于潜在风险,请考虑删除它们。

更新: 已确认,将解决。可以在 “ink” 存储库的 issue 1643 上跟踪进度。

可升级合约中潜在的合约存储布局重叠

默认情况下,ink! 尝试将所有存储结构字段存储在单个存储单元下。此行为会导致可升级合约出现问题,因为代理和实现都会将其 Packed 字段写入相同的存储键(0x00000000),除非开发人员为实现合约中的变量显式设置手动键。因此,可能会发生存储覆盖。

如果修改了实现中的第一个变量,它将更改代理存储布局中的第一个变量或其中的某些字节,具体取决于变量大小。反过来也是如此。

如果没有充分的信息,开发人员可能无法正确修改实现合约的存储,这可能会导致意外行为和合约的潜在故障。此外,这可能会导致不可预测的存储布局,从而进一步使问题复杂化。因此,开发人员务必确保他们拥有所有必要的信息,并采取适当的步骤来修改存储,以防止出现此类问题。

此外,升级之间没有用于检查存储布局是否已更改的验证。文档指定开发人员不应更改合约状态变量的声明顺序,也不应更改其类型。

即使违反了该限制,编译过程仍然会成功,但可能会导致值混淆或无法正确读取存储。这些问题可能会导致使用合约的应用程序中出现严重错误。

解决这些问题的一些缓解措施可能是:

  • 为避免冲突,请定义一组标准槽来存储代理代码中存在的变量。EIP-1967在 Ethereum 中定义的可以作为灵感来源。
  • 考虑添加文档和示例来说明实现或代理变量之一需要使用 Lazy 集合。 Lazy 集合设置用于每个变量的存储键,确保它们不会与合约中的其他变量重叠。
  • 实现建议部分中提到的 cargo contract upgrade 命令。 这将保留实现的存储布局,并检查在升级之间是否未损坏,并检查在代理中定义的变量是否使用手动键而不是自动键定义,或者在代理中定义的每个变量的第一个位置是否与实现中定义的任何变量不冲突。

更新: 已确认,将解决。可以在 “ink” 存储库的 issue 16791680 上跟踪进度。

中危

Nonce 重置会增加成功重放攻击的风险

重放攻击对区块链技术构成了严重的安全威胁。 为了维护区块链网络中签名的完整性,必须使用 nonce,该值跟踪给定帐户发出的交易数量。

在基于 Substrate 的区块链上,如果帐户的余额低于存在性存款,则 nonce 将被重置。 此操作可能会损害重放保护机制,并增加成功攻击的风险。 此外,长时间的过期或截止日期也可能会增加重放攻击的可能性。

除了仅依赖截止日期之外,请考虑添加替代的保护机制,例如在哈希消息时强制执行强大的域分隔符,或者建议开发人员将用于给定地址的签名存储在相应的合约中。 另一个解决方案是即使帐户的余额低于所需的最低限额,也保留 nonce。

更新: 已确认,将添加更多文档以使使用者了解此行为。 可以在 ink-docs 存储库的 issue 178 中关注进度。 Parity 团队声明:

此行为在 Substrate 世界中是正常的,我们唯一能做的就是向新手更好地强调它。 我们将添加有关此行为的文档。

ink!智能合约中无法使用无界数组

默认情况下,ink! 尝试将所有向量元素存储在单个存储单元下。 因此,查询一个项目会返回向量中的所有元素,但缓冲区具有有限的容量(在默认配置中约为 16KB)。 因此,任何尝试解码超出此限制的合约都会引发错误,从而无法实现某些智能合约,例如 ERC20votes 扩展EnumerableSet。 如果未超过限制,则操作将在执行中消耗大量 gas,导致与这些合约的交互因其成本而不太有吸引力。

此缺陷的影响可能非常重大,因为它限制了合约开发人员的功能。 无界数组对于许多用例至关重要,并且无法使用 ink! 实现它们会大大降低 Dapp 开发的可能性范围。

如果可能,请考虑创建另一个存储集合以在不同的槽中存储数组元素。

更新: 已确认,将解决。可以在 “ink” 存储库的 issue 1682 上跟踪进度。

低危

令人困惑的示例

upgradeable-contracts 示例 展示了实现可升级性的两种方法:

  • 第一个,在 set-code-hash 目录中实现,展示了如何通过 ink::env::set_code_hash 函数更新实现逻辑来升级用 ink! 编写的智能合约。
  • 第二个,forward-calls,展示了如何使用代理执行升级。 与 Solidity 中众所周知的 Proxy 模式实现类似,此方法中的想法是使用前者的上下文但后者的逻辑将调用从 Proxy 合约转发到实现合约。

问题在于,在后者中,forward 函数 不使用 delegatecall 的实现,而是执行常规的调用操作。 因此,此示例中使用的上下文和存储将不是 Proxy 的上下文和存储,而是实现的上下文和存储,从而破坏了可升级性模式。

建议通过包含可升级和不可升级代理的示例来改进 ink! 中 delegatecall 的工作方式的文档。 此外,请考虑使用委托而不是常规调用来更新提到的示例。

更新:pull request #1697pull request #1704 中解决。

decode 命令中缺少输入验证

Cargo 合约有一个解码命令来解析编码的输入或输出数据并提取底层值。 该功能有两个标志。 一个用于指示要解码的数据类型,另一个用于数据本身,该数据必须是十六进制值。 但是,该函数的当前实现接受的字节数多于目标类型所需的字节数,这可能会导致数据的错误解释。

考虑更新实现以正确接受目标函数所需的预期字节数。 此措施将降低混淆和意外结果的风险。

更新: 在提交 769c112pull request #982 中解决。

代理和实现函数选择器之间潜在的冲突

ink! 允许开发人员为合约上定义的函数设置自定义选择器,如 自定义选择器可能会促成代理选择器冲突攻击 中所述。 如果未使用此功能,则改用来自 ink crate 的 compute 函数来计算函数选择器。 此函数通过哈希函数名称并获取其前 4 个字节来计算选择器(类似于 Solidity 中的做法)。

问题在于,函数选择器仅使用函数名称来计算,而不考虑任何其他值。 这可能会导致函数选择器冲突,因为很可能在代理和实现中使用相同的函数名称。

以下是解决此问题的一些潜在的缓解策略:

  • 开发一个 upgrade 命令,如建议部分所述,以检查系统要升级到的代理和实现之间是否没有重复的函数选择器。
  • 开发一个新的宏属性,名为 proxy,它可以覆盖 compute 函数 的实现,以便它不仅使用函数的名称,而且还将其附加到代理合约的名称,代理合约名称的哈希,或任何其他使选择器不同的内容,并正确记录它。 此外,proxy 宏将提高合约本身的可读性,因为开发人员和审计员会知道该合约的行为类似于代理。

更新: 已确认,将解决。可以在 “cargo-contract” 存储库的 issue 981 上跟踪进度。

ManualKey 功能的误导性行为

ink! 智能合约包括 ManualKey 功能,该功能允许开发人员指定 Mapping 或 Lazy 集合的键的值。 但是,此功能的潜在问题是,可以在代码中将键设置为零,而在元数据中显示为不同的值,从而导致混淆和可能的错误。

为避免此问题,可能值得禁止使用者在使用 ManualKey 时将变量的键设置为 0。 由于开发人员可能会依赖代码中指定的值,因此此更改可以防止混淆并提高代码的可靠性。

更新: 在提交 63c846dpull request #1670 中解决。

ink! 合约构建中的不确定性

使用 cargo contract CLI 构建 ink! 合约的过程会受到各种因素的影响,这些因素会改变最终产品。 这些因素包括 Rust 的版本、启用的功能、cargo-contract 版本、优化遍数和构建模式。 构建过程在不同的操作系统和架构中是不确定的。

构建过程的不确定性给合约验证带来了困难,这使得难以建立对合约及其可靠性的信任,这两者对于使用者来说都是至关重要的。

为解决此问题,请考虑标准化构建过程,并在最早阶段(而不是在合约部署之后)向开发人员提供明确的指导方针和通知。 这种方法将确保合约验证简单明了,并且使用者可以信任合约。

更新: 已确认,将解决。可以在 “ink-docs” 存储库的 issue 99 和 “cargo-contract” 存储库的 issue 525 上查看进度。

备注和其他信息

不完整的西班牙语翻译

该文档的西班牙语版本有许多页面是用英语编写的,这会导致混淆,并使说西班牙语的人难以理解信息。

考虑完成西班牙语翻译,并在它们准备好投入生产之前禁用它们。

更新: 已确认,尚未解决。

README.md 引用内部 crate

ink! 存储库的内部 crate 引用了存储库的主要 README.md,而不是它们自己的 README.md,导致缺少有关模块的信息以及损坏的图像链接。

缺少这些信息可能会妨碍内部 crate 的可用性,使开发人员难以理解每个 crate 的目的和用法。 此外,损坏的图像链接会给使用者造成负面印象。

为每个 crate 创建单独的 README.md 文件将在很大程度上解决此问题。

更新: 已确认,将解决。可以在 “ink” 存储库的 issue 1690 上跟踪进度。

结论

安全审查报告重点介绍了潜在的漏洞,并为改进 ink! 生态系统提供了建议。 我们很高兴地报告说,在此过程中与 Parity 团队的合作非常棒。 他们对我们的建议持开放态度,并且每周的会议都非常富有成效。

我们看到了 ink! 及其工具 cargo-contract 的巨大潜力,该工具展示了强大的安全措施,并坚定不移地致力于确保其使用者的安全。

总的来说,我们有信心,通过持续的合作和不断努力提高安全性,ink! 及其相关工具将在未来得到广泛采用。 我们期待看到 ink! 的持续发展和壮大。

有关 ink! 的更多信息,你可以访问 ink! 文档 或关注 ink! Twitter。 ink! 文档还对 智能合约如何在 Polkadot 中工作 进行了总体解释。

  • 原文链接: blog.openzeppelin.com/se...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
OpenZeppelin
OpenZeppelin
江湖只有他的大名,没有他的介绍。