智能合约审计系列————3、变量覆盖&不一致性检查
字数 1066 2025-08-22 12:22:43

智能合约审计:变量覆盖与不一致性检查

1. 变量覆盖漏洞

1.1 漏洞简介

在Solidity中,存在Storage(存储器)和Memory(内存)两个不同的存储区域:

  • Storage变量:永久存储在区块链中的变量
  • Memory变量:临时变量,在外部调用结束后会被移除

当结构体(struct)和数组在局部变量中默认存放在storage中时,如果未正确定义存储指针,会导致变量被意外覆盖。

1.2 漏洞原理

结构体变量覆盖

struct NameRecord {
    bytes32 name;
    address mappedAddress;
}

NameRecord newRecord; // 未初始化的storage指针
newRecord.name = _name; // 可能覆盖其他storage变量

未初始化的结构体局部变量会被当作指针,默认指向slot[0]和slot[1],导致赋值操作实际上修改了其他变量的值。

数组变量覆盖

bytes[1] storage arr; // 未初始化的storage数组
arr[0] = elements[0]; // 可能覆盖其他storage变量

同样原理,未初始化的数组局部变量也会导致storage变量被意外覆盖。

1.3 解决方案

结构体的正确初始化

// 正确做法:创建memory结构体然后拷贝到storage
NameRecord memory newRecord = NameRecord({
    name: _name,
    mappedAddress: _mappedAddress
});
registeredNameRecord[msg.sender] = newRecord;

数组的正确初始化

// 正确做法:声明时初始化
bytes[1] storage arr = someExistingArray;
arr[0] = elements[0];

注意:Solidity 0.4.25及以上版本已修复此问题,编译器会阻止存在未初始化存储指针的代码通过编译。

1.4 案例分析

案例1:未初始化的结构体局部变量

pragma solidity ^0.4.22;
contract NameRegistrar {
    bool public unlocked = false;
    
    struct NameRecord {
        bytes32 name;
        address mappedAddress;
    }
    
    function register(bytes32 _name, address _mappedAddress) public {
        NameRecord newRecord; // 未初始化的storage指针
        newRecord.name = _name; // 覆盖slot0 (unlocked变量)
        newRecord.mappedAddress = _mappedAddress; // 覆盖slot1
        // ...
    }
}

攻击方式:通过设置_name为特定值(如63个0加1),可以覆盖unlocked变量使其变为true。

案例2:未初始化的数组局部变量

pragma solidity ^0.4.24;
contract UnfixedArr {
    bool public frozen = false;
    
    function wrongArr(bytes[] elements) public {
        bytes[1] storage arr; // 未初始化的storage数组
        arr[0] = elements[0]; // 可能覆盖frozen变量
    }
}

攻击方式:输入特定格式的elements参数可以覆盖frozen变量。

2. 不一致性检查漏洞

2.1 漏洞简介

在转账操作中,由于检查对象不一致导致的逻辑问题,可能造成合约资金损失。主要分为两类:

  1. allowed不一致性检查漏洞
  2. balances不一致性检查漏洞

2.2 漏洞原理

allowed不一致性检查

require(_value <= allowed[_from][msg.sender]); // 检查msg.sender的授权额度
allowed[_from][_to] -= _value; // 却修改_to的授权额度

检查对象(msg.sender)与修改对象(_to)不一致,导致授权机制失效。

balances不一致性检查

require(balances[msg.sender] >= _value); // 检查msg.sender余额
balances[_from] -= _value; // 却修改_from的余额

这种不一致性可能导致:

  1. 通过整型溢出使_from账户获得大量代币
  2. 即使使用safeMath避免了溢出,检查逻辑仍是冗余/错误的

2.3 解决方案

确保所有检查条件与实际修改的变量一致:

// allowed一致性检查
require(_value <= allowed[_from][msg.sender]);
allowed[_from][msg.sender] -= _value;

// balances一致性检查
require(balances[_from] >= _value);
balances[_from] -= _value;

3. 总结与最佳实践

  1. 变量初始化

    • 始终初始化storage指针
    • 使用Solidity 0.4.25及以上版本
    • 结构体优先使用memory初始化后拷贝到storage
  2. 一致性检查

    • 确保条件检查变量与实际修改变量一致
    • 转账相关操作要特别小心检查逻辑
    • 避免冗余的条件检查
  3. 审计要点

    • 检查所有struct和数组变量的初始化方式
    • 验证条件语句中的变量与操作语句中的变量是否一致
    • 特别注意转账、授权等敏感操作的逻辑一致性

通过遵循这些原则,可以避免变量覆盖和不一致性检查这两类常见但危险的智能合约漏洞。

智能合约审计:变量覆盖与不一致性检查 1. 变量覆盖漏洞 1.1 漏洞简介 在Solidity中,存在Storage(存储器)和Memory(内存)两个不同的存储区域: Storage变量 :永久存储在区块链中的变量 Memory变量 :临时变量,在外部调用结束后会被移除 当结构体(struct)和数组在局部变量中默认存放在storage中时,如果未正确定义存储指针,会导致变量被意外覆盖。 1.2 漏洞原理 结构体变量覆盖 未初始化的结构体局部变量会被当作指针,默认指向slot[ 0]和slot[ 1 ],导致赋值操作实际上修改了其他变量的值。 数组变量覆盖 同样原理,未初始化的数组局部变量也会导致storage变量被意外覆盖。 1.3 解决方案 结构体的正确初始化 数组的正确初始化 注意 :Solidity 0.4.25及以上版本已修复此问题,编译器会阻止存在未初始化存储指针的代码通过编译。 1.4 案例分析 案例1:未初始化的结构体局部变量 攻击方式 :通过设置 _name 为特定值(如63个0加1),可以覆盖 unlocked 变量使其变为true。 案例2:未初始化的数组局部变量 攻击方式 :输入特定格式的 elements 参数可以覆盖 frozen 变量。 2. 不一致性检查漏洞 2.1 漏洞简介 在转账操作中,由于检查对象不一致导致的逻辑问题,可能造成合约资金损失。主要分为两类: allowed不一致性检查漏洞 balances不一致性检查漏洞 2.2 漏洞原理 allowed不一致性检查 检查对象(msg.sender)与修改对象(_ to)不一致,导致授权机制失效。 balances不一致性检查 这种不一致性可能导致: 通过整型溢出使 _from 账户获得大量代币 即使使用safeMath避免了溢出,检查逻辑仍是冗余/错误的 2.3 解决方案 确保所有检查条件与实际修改的变量一致: 3. 总结与最佳实践 变量初始化 : 始终初始化storage指针 使用Solidity 0.4.25及以上版本 结构体优先使用memory初始化后拷贝到storage 一致性检查 : 确保条件检查变量与实际修改变量一致 转账相关操作要特别小心检查逻辑 避免冗余的条件检查 审计要点 : 检查所有struct和数组变量的初始化方式 验证条件语句中的变量与操作语句中的变量是否一致 特别注意转账、授权等敏感操作的逻辑一致性 通过遵循这些原则,可以避免变量覆盖和不一致性检查这两类常见但危险的智能合约漏洞。