คำเตือนความเสี่ยง: ระวังความเสี่ยงจากการระดมทุนที่ผิดกฎหมายในนาม 'สกุลเงินเสมือน' 'บล็อกเชน' — จากห้าหน่วยงานรวมถึงคณะกรรมการกำกับดูแลการธนาคารและการประกันภัย
ข่าวสาร
ค้นพบ
ค้นหา
เข้าสู่ระบบ
简中
繁中
English
日本語
한국어
ภาษาไทย
Tiếng Việt
BTC
ETH
HTX
SOL
BNB
ดูตลาด
ช่องโหว่ที่มีความเสี่ยงสูงในคอมไพเลอร์ Solidity: ลบการกำหนดตัวแปรสถานะโดยไม่ตั้งใจ
Eocene
特邀专栏作者
2023-05-06 12:49
บทความนี้มีประมาณ 2022 คำ การอ่านทั้งหมดใช้เวลาประมาณ 3 นาที
บทความนี้อธิบายรายละเอียดตั้งแต่ระดับซอร์สโค้ดของคอมไพเลอร์ Solidity (0.8.13<=Solidity<0.8.17) ในกระบวนการ

บทความนี้อธิบายรายละเอียดตั้งแต่ระดับซอร์สโค้ดของคอมไพเลอร์ Solidity (0.8.13<=Solidity<0.8.17) ในกระบวนการคอมไพล์ เนื่องจากข้อบกพร่องของกลไกการปรับให้เหมาะสม Yul การดำเนินการกำหนดตัวแปรสถานะจึงถูกลบโดยไม่ตั้งใจ สอดคล้องกัน ข้อควรระวัง.

ชื่อระดับแรก

1. รายละเอียดช่องโหว่

เอกสารราชการเอกสารราชการ

ในขั้นตอนการเพิ่มประสิทธิภาพ UnusedStoreEliminator ของกระบวนการคอมไพล์ คอมไพลเลอร์จะลบการดำเนินการเขียนสตอเรจที่ "ซ้ำซ้อน" แต่เนื่องจากข้อบกพร่องในการระบุ "ความซ้ำซ้อน" เมื่อบล็อกฟังก์ชัน Yul เรียกฟังก์ชันที่ผู้ใช้กำหนดโดยเฉพาะ (ฟังก์ชันมี สาขาภายในที่ไม่กระทบกระแสการดำเนินการของบล็อกการเรียกใช้) และมีการดำเนินการเขียนไปยังตัวแปรสถานะเดียวกันก่อนและหลังฟังก์ชันที่เรียกใช้ในบล็อกฟังก์ชัน Yul ซึ่งจะทำให้ฟังก์ชันที่ผู้ใช้กำหนดในบล็อกนั้น ถูกเรียกใช้โดยกลไกการเพิ่มประสิทธิภาพ Yul ก่อนหน้านี้การดำเนินการเขียนที่เก็บข้อมูลจะถูกลบอย่างถาวรจากระดับการคอมไพล์

พิจารณารหัสต่อไปนี้:

contract Eocene {

        uint public x;

        function attack() public {

                x = 1;

                x = 2;

        }

}

x=1 เห็นได้ชัดว่าซ้ำซ้อนสำหรับการดำเนินการทั้งหมดของ function attack() เมื่อ UnusedStoreEliminator ปรับให้เหมาะสม โดยธรรมชาติแล้ว รหัส Yul ที่ปรับให้เหมาะสมจะลบ x= 1 เพื่อลดการใช้ก๊าซของสัญญา

ถัดไป ให้พิจารณาแทรกการเรียกไปยังฟังก์ชันแบบกำหนดเองตรงกลาง:

contract Eocene {

uint public x;

function attack(uint i) public {

        x = 1;

        y(i);

        x = 2;

}

function y(uint i) internal{

        if (i > 0){

                return;

        }

        assembly { return( 0, 0) }

}

}

เห็นได้ชัดว่า เนื่องจากการเรียกใช้ฟังก์ชัน y() เราจำเป็นต้องตัดสินว่าฟังก์ชัน y() จะส่งผลต่อการดำเนินการของฟังก์ชัน attack() หรือไม่ หากฟังก์ชัน y() สามารถทำให้โฟลว์การดำเนินการของฟังก์ชันทั้งหมดยุติลง ( โปรดทราบว่าไม่ใช่การย้อนกลับ ฟังก์ชัน Yul code return()) ดังนั้น x= 1 จึงไม่สามารถลบออกได้ ดังนั้นสำหรับสัญญาข้างต้น การมีอยู่ของชุดประกอบ {return(0, 0)} ในฟังก์ชัน y() สามารถนำไปสู่การยุติการโทรข้อความทั้งหมด x= 1 ไม่สามารถลบได้ตามธรรมชาติ

แต่ในคอมไพเลอร์ Solidity เนื่องจากปัญหาโค้ดลอจิก x= 1 ถูกลบโดยไม่ได้ตั้งใจระหว่างการคอมไพล์ ซึ่งทำให้โค้ดลอจิกเปลี่ยนไปอย่างถาวร

ผลการทดสอบการรวบรวมจริงมีดังนี้:

ช็อก! โค้ด Yul ที่มี x=1 ที่ไม่ควรปรับจะหายไป! หากคุณต้องการทราบว่าเกิดอะไรขึ้นต่อไป โปรดอ่านด้านล่าง

ใน UnusedStoreEliminator ของโค้ดคอมไพเลอร์ solidiry การติดตามตัวแปร SSA และการติดตามโฟลว์การควบคุมจะถูกใช้เพื่อระบุว่าการดำเนินการเขียนที่เก็บข้อมูลซ้ำซ้อนหรือไม่ เมื่อป้อนฟังก์ชันแบบกำหนดเอง หาก UnusedStoreEliminator พบ:

  1. การดำเนินการเขียนหน่วยความจำหรือหน่วยเก็บข้อมูล: จัดเก็บหน่วยความจำและการดำเนินการเขียนหน่วยเก็บข้อมูลลงในตัวแปร m_store และตั้งค่าสถานะเริ่มต้นของการดำเนินการเป็น Undecided

  2. การเรียกใช้ฟังก์ชัน: รับตำแหน่งการดำเนินการอ่านและเขียนหน่วยความจำหรือหน่วยเก็บข้อมูลของฟังก์ชัน และเปรียบเทียบกับการดำเนินการทั้งหมดในสถานะ Undecided ที่จัดเก็บไว้ในตัวแปร m_store:

    1. หากเป็นการเขียนทับการดำเนินการจัดเก็บข้อมูลใน m_store ให้เปลี่ยนสถานะการดำเนินการที่เกี่ยวข้องใน m_store เป็น Unused

    2. หากเป็นการอ่านการดำเนินการจัดเก็บใน m_store ให้เปลี่ยนสถานะการดำเนินการที่เกี่ยวข้องใน m_store ที่เกี่ยวข้องเป็น ใช้แล้ว

    3. หากฟังก์ชันไม่มีสาขาใดๆ ที่สามารถเรียกใช้งานการเรียกข้อความต่อไปได้ ให้เปลี่ยนการดำเนินการเขียนหน่วยความจำทั้งหมดใน m_store เป็น Unused

    1. ภายใต้เงื่อนไขการอุทธรณ์ ถ้าฟังก์ชันสามารถยุติโฟลว์การดำเนินการได้ ให้เปลี่ยนการดำเนินการเขียนที่เก็บข้อมูลซึ่งมีสถานะเป็น Undecided ใน m_store เป็นใช้แล้ว มิฉะนั้น ให้ทำเครื่องหมายเป็นยังไม่ได้ใช้

  3. สิ้นสุดฟังก์ชัน: ลบการดำเนินการเขียนทั้งหมดที่ทำเครื่องหมายว่าไม่ได้ใช้

รหัสการเริ่มต้นสำหรับการเขียนไปยังหน่วยความจำหรือหน่วยเก็บข้อมูลมีดังนี้:

จะเห็นได้ว่าหน่วยความจำที่พบและการดำเนินการเขียนที่เก็บข้อมูลถูกจัดเก็บไว้ใน m_store

รหัสลอจิกการประมวลผลเมื่อพบการเรียกใช้ฟังก์ชันจะเป็นดังนี้:

ในหมู่พวกเขา operationFromFunctionCall() และ applyOperation() ใช้ตรรกะการประมวลผลของ 2.1 และ 2.2 ของการอุทธรณ์ คำสั่ง If ที่พิจารณาจากฟังก์ชัน canContinue และ canTerminate ด้านล่างใช้ตรรกะ 2.3

ควรสังเกตว่ามันเป็นข้อบกพร่องของการตัดสินของ If ด้านล่างที่นำไปสู่การมีอยู่ของช่องโหว่! ! !

operationFromFunctionCall() เพื่อรับการดำเนินการอ่านและเขียนหน่วยความจำหรือหน่วยเก็บข้อมูลทั้งหมดของฟังก์ชันนี้ ควรสังเกตว่า มีฟังก์ชันในตัวมากมายใน Yul เช่น sstore(), return() ที่นี่ คุณจะเห็นว่ามีตรรกะการประมวลผลที่แตกต่างกันสำหรับฟังก์ชันในตัวและฟังก์ชันที่ผู้ใช้กำหนด

ฟังก์ชัน applyOperation() เปรียบเทียบการดำเนินการอ่านและเขียนทั้งหมดที่ได้รับจาก operationFromFuncitonCall() เพื่อพิจารณาว่าข้อมูลที่จัดเก็บไว้ใน m_store ถูกอ่านหรือเขียนในการเรียกใช้ฟังก์ชันนี้ และแก้ไขสถานะการดำเนินการที่สอดคล้องกันใน m_store

พิจารณาการประมวลผลของตรรกะการเพิ่มประสิทธิภาพ UnusedStoreEliminator ที่กล่าวถึงข้างต้นบนฟังก์ชันการโจมตี () ของสัญญา Eocene:

เก็บ x= 1 ไว้ในตัวแปร m_store และตั้งค่าสถานะเป็น Undecided

1. พบการเรียกฟังก์ชัน y() รับการดำเนินการอ่านและเขียนทั้งหมดของการเรียกฟังก์ชัน y()

2. สำรวจตัวแปร m_store และพบว่าการดำเนินการอ่านและเขียนทั้งหมดที่เกิดจากการเรียก y() ไม่มีส่วนเกี่ยวข้องกับ x= 1 และสถานะของ x= 1 ยังไม่แน่ใจ

1. รับลอจิกโฟลว์ควบคุมของฟังก์ชัน y() เนื่องจากฟังก์ชัน y() มีแบรนช์ที่สามารถย้อนกลับได้ตามปกติ ดังนั้น canContinue จึงเป็น True และไม่เข้าสู่การตัดสิน If x= 1 สถานะยังไม่ตัดสินใจ! ! !

3. พบ x= 2 การดำเนินการจัดเก็บ:

1. สำรวจตัวแปร m_store และพบว่า x= 1 อยู่ในสถานะ Undecided การดำเนินการ x= 2 จะแทนที่ x= 1 และตั้งค่าสถานะของ x= 1 เป็น Unused

2. จัดเก็บการดำเนินการ x= 2 ไว้ใน m_store และสถานะเริ่มต้นยังไม่ได้กำหนด

4. สิ้นสุดฟังก์ชัน:

1. เปลี่ยนสถานะการดำเนินการของสถานะที่ยังไม่ได้ตัดสินใจใน m_stores ทั้งหมดเป็น ใช้แล้ว

2. ลบการดำเนินการทั้งหมดในสถานะที่ไม่ได้ใช้ใน m_store

เห็นได้ชัดว่าเมื่อมีการเรียกใช้ฟังก์ชัน หากฟังก์ชันที่เรียกใช้สามารถยุติการดำเนินการของข้อความได้ การดำเนินการเขียนทั้งหมดของสถานะ Undecided ก่อนฟังก์ชันที่เรียกใช้ควรเปลี่ยนเป็น Used แทนที่จะเหลือเป็น Undecided ส่งผลให้การดำเนินการเขียนก่อนหน้า ฟังก์ชันที่เรียกใช้ถูกลบโดยไม่ได้ตั้งใจ

มีอยู่

มีอยู่Solidityตัวอย่างของรหัสสัญญาที่จะไม่ได้รับผลกระทบภายใต้ตรรกะเดียวกัน อย่างไรก็ตาม โค้ดไม่ได้รับผลกระทบจากช่องโหว่นี้ ไม่ใช่เพราะมีความเป็นไปได้อื่น ๆ ในตรรกะการประมวลผลของ UnusedStoreEliminator แต่ในขั้นตอนการปรับให้เหมาะสม Yul ก่อน UnusedStoreEliminator มีกระบวนการเพิ่มประสิทธิภาพแบบ FullInliner ที่จะฝังฟังก์ชันที่เรียกว่ามีขนาดเล็กหรือเพียงอย่างเดียว เรียกใช้ฟังก์ชัน In the call ฟังก์ชันที่ผู้ใช้กำหนดในเงื่อนไขทริกเกอร์ช่องโหว่จะถูกหลีกเลี่ยง

contract Normal {

    uint public x;

    function f(bool a) public {

        x = 1;

        g(a);

        x = 2;

    }

    function g(bool a) internal {

        if (!a)

        assembly { return( 0, 0) }

    }

}

ผลการรวบรวมเป็นดังนี้:

ชื่อระดับแรก

2. โซลูชั่น

วิธีแก้ไขขั้นพื้นฐานที่สุดคือการคอมไพล์โดยไม่ใช้ solidity compiler ในช่วงที่ได้รับผลกระทบ หากคุณจำเป็นต้องใช้คอมไพเลอร์เวอร์ชันที่มีช่องโหว่ คุณสามารถพิจารณาลบขั้นตอนการเพิ่มประสิทธิภาพ UnusedStoreEliminator ระหว่างการคอมไพล์

หากคุณต้องการลดช่องโหว่จากระดับรหัสสัญญา โดยพิจารณาจากความซับซ้อนของขั้นตอนการเพิ่มประสิทธิภาพหลายขั้นตอนและความซับซ้อนของขั้นตอนการเรียกใช้ฟังก์ชันจริง โปรดหาเจ้าหน้าที่รักษาความปลอดภัยมืออาชีพเพื่อทำการตรวจสอบรหัสเพื่อช่วยค้นหาช่องโหว่ในสัญญาที่เกิดจากสิ่งนี้ คำถามเพื่อความปลอดภัยเกี่ยวกับช่องโหว่

ความปลอดภัย
สัญญาที่ชาญฉลาด
นักพัฒนา
เทคโนโลยี
ยินดีต้อนรับเข้าร่วมชุมชนทางการของ Odaily
กลุ่มสมาชิก
https://t.me/Odaily_News
กลุ่มสนทนา
https://t.me/Odaily_CryptoPunk
บัญชีทางการ
https://twitter.com/OdailyChina
กลุ่มสนทนา
https://t.me/Odaily_CryptoPunk
สรุปโดย AI
กลับไปด้านบน
บทความนี้อธิบายรายละเอียดตั้งแต่ระดับซอร์สโค้ดของคอมไพเลอร์ Solidity (0.8.13<=Solidity<0.8.17) ในกระบวนการ
อันดับบทความร้อน
Daily
Weekly
ดาวน์โหลดแอพ Odaily พลาเน็ตเดลี่
ให้คนบางกลุ่มเข้าใจ Web3.0 ก่อน
IOS
Android