最初のレベルのタイトル
この論文は、Solidity コンパイラ (0.5.8<= バージョン <0.8.16) の ABIReencoding 処理における固定長 uint およびバイト 32 型配列のエラー処理によって引き起こされる脆弱性問題をソース コード レベルから詳細に分析し、関連する解決策と回避策を提案します。
最初のレベルのタイトル
脆弱性の詳細ABI エンコード形式は、ユーザーまたはコントラクトがコントラクトへの関数呼び出しを行ってパラメーターを渡すときに使用される標準のエンコード方法です。詳細についてはSolidity公式を参照してください。ABI エンコーディング
の詳しい説明。
受託開発プロセスでは、ユーザーまたは他の契約によって送信された通話データデータから必要なデータが取得され、取得されたデータが転送または送信される場合があります。 evm 仮想マシンに限定されるすべてのオペコード操作はメモリ、スタック、ストレージに基づいているため、Solidity では、データの ABI エンコードが必要な操作に関しては、calldata 内のデータは新しい規則に従って ABI 形式でエンコードされます。順序付けされ、メモリに保存されます。プロセス自体には大きなロジックの問題はありませんが、Solidity のプロセスと組み合わせると、クリーンアップメカニズム
組み合わせると、Solidity コンパイラー コード自体が省略されているため、脆弱性が存在します。
ABI エンコード規則に従って、関数セレクターを削除した後、ABI エンコードされたデータは先頭と末尾の 2 つの部分に分割されます。データ形式が固定長の uint またはバイト 32 配列の場合、ABI はこのタイプのデータをヘッド セクションに格納します。 Solidity のメモリ内のクリーンアップ メカニズムの実装では、次のインデックスのメモリがダーティ データの影響を受けるのを防ぐために、現在のインデックスのメモリが使用された後に次のインデックスのメモリを空にします。また、Solidity が一連のパラメーター データを ABI エンコードする場合、左から右の順序でエンコードされます。 !
contract Eocene {
event VerifyABI( bytes[], uint[ 2 ]);
function verifyABI(bytes[] calldata a, uint[ 2 ] calldata b) public {
emit VerifyABI(a,後で脆弱性原則の調査を容易にするために、次の形式のコントラクト コードを検討してください。
}
}
b); //イベント データは ABI 形式でエンコードされ、チェーンに保存されます
Eocene コントラクトの verifyABI 関数の機能は、関数パラメーター内の可変長 bytes[] a と固定長 uint[2] b を出力することだけです。
ここで、イベント イベントは ABI エンコードもトリガーすることに注意してください。ここで、パラメータ a、b は ABI 形式にエンコードされ、チェーンに保存されます。verifyABI(['0x aaaaaa','0x bbbbbb'],[0x 11111, 0x 22222 ])。
Solidity の v 0.8.14 バージョンを使用してコントラクト コードをコンパイルし、リミックスを通じてデプロイして、verifyABI(['0x aaaaaa','0x bbbbbb'],[0x 11111, 0x 22222 ])まず、見てみましょう。
0x 5 2c d 1 a 9 c // bytes 4(sha 3("verify(btyes[], uint[ 2 ])"))
0000000000000000000000000000000000000000000000000000000000000060 // index of a
0000000000000000000000000000000000000000000000000000000000011111 // b[0 ]
0000000000000000000000000000000000000000000000000000000000022222 // b[1 ]
0000000000000000000000000000000000000000000000000000000000000002 // length of a
0000000000000000000000000000000000000000000000000000000000000040 // index of a[0 ]
0000000000000000000000000000000000000000000000000000000000000080 // index of a[1 ]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[0 ]
aaaaaa 0000000000000000000000000000000000000000000000000000000000 // a[0 ]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[1 ]
bbbbbb 0000000000000000000000000000000000000000000000000000000000 // a[1 ]
の正しいエンコード形式:a, bSolidity コンパイラが正常であれば、パラメータがTX。
イベントイベントをチェーン上に記録する場合、データ形式は送信したものと同じである必要があります。実際にコントラクトを呼び出してチェーン上のログを確認してみましょう。
呼び出しが成功すると、コントラクト イベント イベントが次のように記録されます。
0000000000000000000000000000000000000000000000000000000000000060 // index of a
0000000000000000000000000000000000000000000000000000000000011111 // b[0 ]
0000000000000000000000000000000000000000000000000000000000022222 // b[1 ]
0000000000000000000000000000000000000000000000000000000000000000 // length of a?? why become 0??
0000000000000000000000000000000000000000000000000000000000000040 // index of a[0 ]
0000000000000000000000000000000000000000000000000000000000000080 // index of a[1 ]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[0 ]
aaaaaa 0000000000000000000000000000000000000000000000000000000000 // a[0 ]
0000000000000000000000000000000000000000000000000000000000000003 // length of a[1 ]
bbbbbb 0000000000000000000000000000000000000000000000000000000000 // a[1 ]
! !衝撃的なことに、b[1 ] の直後に、a パラメータの長さを格納する値が誤って削除されてしまいました。 !
なぜ?
前に述べたように、Solidity が ABI エンコードが必要な一連のパラメーターに遭遇した場合、パラメーターの生成順序は左から右です。a と b の具体的なエンコード ロジックは次のとおりです。
Solidity はまず a に対して ABI エンコードを実行します。エンコード規則に従って、先頭に a のインデックスが配置され、末尾に a の要素長と特定の値が格納されます。
bデータを処理します。bデータ型はuint[2]形式なので、データの具体的な値が先頭部分に格納されます。ただし、Solidity 独自のクリーンアップ メカニズムにより、b[1] がメモリに保存された後、b[1] データが配置されている次のメモリ アドレスの値 (a 要素の長さを保存するために使用されるメモリ アドレス)は0に設定されます。
ABI エンコード操作が終了し、誤ってエンコードされたデータがオンチェーンに保存され、SOL-2022-6 脆弱性が発生します。
ソース コード レベルでは、特定のエラー ロジックも明らかです。固定長のバイト 32 または uint 配列データを calldata からメモリに取得する必要がある場合、Solidity はデータのコピー後に後者のメモリ インデックス データを常に 0 に設定します。 . .また、ABI エンコードには先頭と末尾の 2 つの部分があり、エンコード順序も左から右であるため、脆弱性が存在します。
特定の脆弱性に対して Solidity でコンパイルされたコードは次のとおりです。ABIFunctions::abiEncodingFunctionCalldataArrayWithoutCleanup()
ソース データの格納場所が Calldata で、ソース データ型が ByteArray、String、またはソース配列の基本型が uint または bytes 32 の場合に入力します。fromArrayType.isDynamicallySized()ソース データが固定長配列かどうかを確認します。固定長配列のみが脆弱性のトリガー条件を満たします。
意思isByteArrayOrString()意思YulUtilFunctions::copyToMemoryFunction(),判定結果が届くのは、
判定結果に応じて、calldatacopy 操作の完了後に次のインデックス位置でクリーンアップを実行するかどうかを決定します。
上記のいくつかの制約を組み合わせると、calldata 形式のソース データが固定長の uint またはメモリにコピーされたバイト 32 の配列である場合にのみ、この脆弱性が引き起こされます。つまり、脆弱性によって引き起こされる制約の理由です。
理由は明白です。固定長データがエンコードされる最後のパラメータ位置にない場合、次のメモリ位置に 0 を設定しても、次のエンコード パラメータがこの位置を上書きするため、何の効果もありません。末尾部分に格納する必要がある固定長データの前にデータがない場合、後者のメモリ位置が 0 に設定されていても問題ありません。この位置は ABI コードによって使用されないためです。
加えて、加えて、。
最初のレベルのタイトル
event
error
abi.encode*
returns //the return of function
struct //the user defined struct
all external call
解決
解決
コントラクト コードにアピールの影響を受ける操作がある場合、最後のパラメータが固定長の uint またはバイト 32 配列でないことを確認してください。
最初のレベルのタイトル
私たちについて
At Eocene Research, we provide the insights of intentions and security behind everything you know or don't know of blockchain, and empower every individual and organization to answer complex questions we hadn't even dreamed of back then.
