Abstract
Bài báo này giới thiệu tổng quan về framework Slither, thiết kế của dạng biểu diễn trung gian (Intermediate Representation - IR) mà framework sử dụng và đánh giá khả năng của nó đối với các real-world smart contract.
Introduction
Điển hình, các công cụ phân tích tĩnh dành cho smart contract sẽ phân tích mã nguồn Solidity hoặc dạng disassembled của contract đã được biên dịch. Sau đó, các công cụ này chuyển code của contract thành một internal representation thích hợp cho việc phân tích và phát hiện các vấn đề bảo mật.
Không giống như những compiler hiện đại khác (chẳng hạn như clang), Solidity không cung cấp các API để các static analyzer của bên thứ ba có thể phát triển dựa trên chúng. Một cách lý tưởng, static analyzer dành cho smart contract của Ethereum cần phải có các tính chất sau:
- Có một mức độ trừu tượng chính xác: nếu quá thấp thì nó sẽ chỉ tập trung vào một số vấn đề nhất định, dẫn đến khó mở rộng. Nếu quá cao thì khó có thể capture được những khuôn mẫu được sử dụng phổ biến.
- Mạnh mẽ: nó có thể phân tích các real-world code mà không bị crash.
- Hiệu năng: nhanh kể cả sử dụng cho các contract lớn.
- Độ chính xác: có tỷ lệ dương tính giả1 thấp.
- Đi kèm với nhiều kỹ thuật phân tích và detector mà hữu ích với đa số các contract.
Slither sử dụng một internal representation của riêng nó, SlithIR. Ngoài khả năng sử dụng static analysis để phát hiện các vấn đề bảo mật, Slither còn có thể được sử dụng để nâng cao độ hiểu biết của người dùng về các smart contract, hỗ trợ trong quá trình code review và phát hiện được những vấn đề về tối ưu.
Related Work
Có một số framework đã được tạo ra để phân tích các smart contract của Ethereum. Chúng được chia thành hai loại: static analysis và dynamic analysis.
Static Analysis
- Securify: hoạt động với bytecode.
- Đầu tiên, nó phân tích và decompile EVM bytecode.
- Sau đó, nó chuyển code đã được decompile sang các semantic facts sử dụng static analysis.
- Cuối cùng, nó so khớp với các pattern có sẵn để phát hiện các vấn đề phổ biến.
- SmartCheck:
- Trực tiếp chuyển mã nguồn Solidity sang XML-based intermediate representation.
- Sau đó, thực hiện so sánh với các XPath pattern để nhận diện các vấn đề có khả năng xảy ra liên quan đến bảo mật, chức năng, vận hành và deploy.
- Solhint: là một công cụ để linting Solidity. Nó được phát triển nhằm cung cấp các validation về bảo mật và style guide (code convention).
- Gasper và GasReduce là các công cụ dùng để tối ưu code. Chẳng hạn như tối ưu vòng lặp, could-be-declared-constant, etc.
Dynamic Analysis
Các tool dưới đây sử dụng cho smart contract của Ethereum:
- Oyente: phân tích các vấn đề ở trong contrac.
- Manticore: là một công cụ sử dụng kỹ thuật symbolic execution.
- Echidna: là một công cụ kiểm thử property-based dùng để fuzzing contract.
- Mythril Classic: sử dụng concolic analysis, taint analysis và control flow checking để phát hiện các lỗ hổng bảo mật.
- TeEther là một công cụ sinh ra các exploitation cho một số loại lỗ hổng nhất định một cách tự động.
Slither
Là một framework giúp phân tích các contract sử dụng static analysis thông qua một quy trình gồm nhiều giai đoạn. Slither nhận vào các AST (Abstract Syntax Tree) của mã nguồn (được sinh ra từ Solidity compiler).
- Đầu tiên, Slither sẽ khôi phục lại các thông tin quan trọng chẳng hạn như đồ thị kế thừa của contract (Contract’s Inheritance Graph), đồ thị luồng điều khiển (Control Flow Graph - CFG) và một danh sách các expression.
- Sau đó, Slither chuyển toàn bộ mã nguồn sang SlithIR. SlithIR sử dụng SSA (Static Single Assignment) để hỗ trợ cho việc tính toán của các dạng analysis khác.
- Giai đoạn thứ ba là giai đoạn mà Slither thực hiện việc phân tích, bằng cách áp dụng nhiều kỹ thuật phân tích đã được định sẵn.
Minh họa cho quá trình phân tích của Slither:
Built-in Code Analyses
Read/Write
Nhận diện các thao tác đọc/ghi của các biến và lọc ra các loại biến (local hoặc state2). Ví dụ như là nhận diện các biến được ghi ở trong một hàm cho trước hoặc tìm các hàm mà có thực hiện việc ghi vào một biến cho trước.
Một số detector hoạt động dựa trên thông tin này, chẳng hạn như uninitialized variables và reentrancy.
Protected Function
Là các hàm được bảo vệ bởi một cơ chế kiểm soát truy cập (access control) nào đó, chẳng hạn như chỉ cho phép owner của contract thực thi hàm3.
Việc mô hình hóa các protection của một hàm giúp giảm thiểu tỷ lệ dương tính giả. Để phát hiện các unprotected function, Slither xét hai trường hợp:
- Hàm là constructor.
- Địa chỉ của caller (
msg.sender
) không được trực tiếp sử dụng trong các phép so sánh.
Mặc dù heuristic này cho ra các false positive và false negative, nhưng kinh nghiệm của các tác giả cho biết nó sẽ hiệu quả trong thực tế.
Data Dependency Analysis
- Slither tính toán data dependency của tất cả các biến bằng cách sử dụng SSA. Các dependency trước tiên sẽ được phân tích dựa trên ngữ cảnh của từng hàm.
- Sau đó, Slither tiến hành tìm và phân tích dependency dựa trên ngữ cảnh của một multi-transaction contract. Slither phân loại một số biến là taint, tức là nó phụ thuộc vào người dùng và do đó nó không tin cậy.
- Cuối cùng, việc phân tích data dependency sẽ được áp dụng dựa trên các protected function.
Automated Vulnerability Detection
Phiên bản mã nguồn mở của Slither có hơn 20 bug detectors, giúp phát hiện những vấn đề sau:
-
Shadowing: xảy ra khi có sự che phủ các thành phần của contract bằng các phần tử khác có cùng tên. Trong Solidity, shadowing có thể xảy ra đối với local variable, state variable, hàm hoặc các event. Ví dụ:
Với đoạn code trên, ta có thể thấy biến
value
trong hàmshadowValue
là biến cục bộ và nó trùng tên với state variable. Dẫn đến, câu lệnhvalue = value;
sẽ gán bản thân tham sốvalue
cho chính nó mà không làm thay đổi state variable. Trong khi mục đích mà ta muốn là thay đổi giá trị củastateVariable
. -
Các biến chưa khởi tạo (uninitialized variables).
-
Lỗ hổng reentrancy4.
-
Một loạt các vấn đề bảo mật khác, chẳng hạn như các contract tự sát, Ether bị khóa, hoặc gửi Ether tùy ý.
Phiên bản mã nguồn đóng của Slither còn có khả năng phát hiện những lỗi bảo mật khác nâng cao hơn, chẳng hạn như race condition, unprotected privileged functions hoặc các thao tác với token không đúng cách.
Automated Optimization Detection
Framework giúp phát hiện:
- Các biến mà có thể được khai báo là
constant
(could-be-declared-constant). Các biến constant được tối ưu bởi compiler, tiêu thụ ít gas hơn và không chiếm không gian lưu trữ5 - Các hàm mà nên được khai báo là
external
vì các hàm này cho phép compiler tối ưu code6
Code Understanding
Slither cung cấp các printer cho phép người dùng nhanh chóng hiểu chức năng của một contract nào đó và cách mà nó được tổ chức. Các printer của phiên bản mã nguồn mở cho phép:
- Tạo ra các loại graph: inheritance graph, control flow graph, và call graph của mỗi contract.
- Summary của các contracts, bao gồm số vấn đề được tìm thấy và thông tin về chất lượng code.
- Summary của các authorization accesses và các biến mà có thể bị thay đổi bởi chủ sở hữu contract.
Assisted Code Review
Người dùng có thể xây dựng các custom script sử dụng API của Slither (Python) để phục vụ cho các nhu cầu cụ thể.
Các công cụ của bên thứ ba có thể:
- Dùng SlithIR để xây dựng những kỹ thuật nâng cao hơn, chẳng hạn symbilic execution.
- Tạo ra một sự chuyển đổi từ SlithIR sang các dạng biểu diễn trung gian khác, chẳng hạn như LLVM.
SlithIR
Mỗi node trong control flow graph có thể có tối đa một Solidity expression, và sẽ được chuyển sang một tập các SlithIR instruction. Dạng biểu diễn này giúp cho việc phân tích dễ dàng hơn cũng như là đảm bảo các ngữ nghĩa quan trọng của code không bị mất.
Instruction Set
- Notation:
LV
vàRV
lần lượt biểu diễn cho một biến được gán (left-value) và một biến được đọc (right-value). Một biến ở đây có thể là một biến ở trong Solidity hoặc là một biến tạm thời được tạo bởi dạng biểu diễn trung gian. - Arithmetic Operation: được biểu diễn dưới dạng nhị phân hoặc đơn nguyên. Ví dụ:
LV = RV BINARY RV
LV = UNARY RV
- Mapping và mảng: Solidity cho phép truy xuất đến mapping và mảng thông qua dereference. SlithIR sử dụng một kiểu đặc biệt, gọi là
REF
, để lưu kết quả của việc deferencing:REF ← Variable[Index]
- Cấu trúc: truy cập đến cấu trúc được thực hiện thông qua member operator:
REF ← Variable·Member
- Calls: cung cấp 9 call instruction. Một số call có thể có đối số, chẳng hạn như
H_CALL
,L_CALL
,Send
(tương ứng với hàmsend
của Solidity) vàTransfer
(tương ứng với hàmtransfer
của Solidity) có thể có đối số làValue
cho biết lượng Ether cần chuyển. - Các instruction bổ sung: chẳng hạn như
PUSH
để thêm một phần tử vào array hayCONVERT
để chuyển kiểu dữ liệu.
Xét đoạn code sau:
Dạng SlithIR của nó là:
Static Single Assignment (SSA)
Là dạng biểu diễn phổ biến, thường được dùng trong quá trình biên dịch và static analysis. Nó yêu cầu rằng mỗi biến chỉ được gán một lần và cần phải được khai báo trước mới được sử dụng. Ví dụ:
Sẽ trở thành:
Mục đích của việc sử dụng SSA là để cho quá trình phân tích data dependency7 dễ dàng hơn cũng như là cho phép triển khai các loại phân tích khác mạnh mẽ hơn.
Slither lưu hai dạng SlithIR: có và không có SSA 🤔.
- State variable: chúng ta sử dụng hàm cho biết một biến nào đó có thể có nhiều định nghĩa (tương ứng với các phép gán làm thay đổi giá trị của biến). Một hàm sẽ được đặt trước mỗi hàm của Solidity và sau các external calls đối với mỗi state variable được đọc bởi hàm.
- Alias analysis: Solidity cho phép biến cục bộ tham chiếu đến biến trạng thái (xem đoạn code bên dưới). Sự thay đổi giá trị của biến cục bộ có thể làm thay đổi giá trị của biến toàn cục. Slither sử dụng alias analysis để tìm tất cả các target của một storage reference và đưa thông tin tìm được cho SSA. Khi đó, các hàm sẽ được đặt ở đằng sau câu lệnh ghi dữ liệu vào storage reference.
Với đoạn code sau:
Sẽ có hai hàm sẽ được đặt ở sau câu lệnh ref.val += 1;
, cho biết a
và b
đã được cập nhật giá trị.
Comparison to Other Smart Contract Intermediate Representations and Discussion
Có một số dạng biểu diễn trung gian khác đã được đề xuất:
- Scillia: sử dụng bởi Zilliqa blockchain.
- Michelson: sử dụng trong Tezos blockchain.
- IELE: dành cho Cardano blockchain.
- YUL: là ngôn ngữ assembly dành cho EVM và có thể được viết tích hợp với Solidity, thường sử dụng để tối ưu gas sử dụng.
SlithIR có một số hạn chế:
- Thiếu formal semantics.
- Dạng biểu diễn ở level quá cao nên không thể phản ánh được các thông tin low-level.
Evaluation and Comparison to State-of-the-art Tools
Vulnerability Detection Evaluation
Các tác giả tập trung vào lỗ hổng reentrancy4 và thực hiện so sánh với những công cụ khác chẳng hạn như: Securify, SmartCheck và Solhint.
Experiments
- Thí nghiệm 1: phân tích DAO và SpankChain (là hai ví dụ nổi tiếng của lỗ hổng reentrancy).
- Thí nghiệm 2: phân tích 1000 contract thường dùng lấy từ Etherscan8.
Metrics
Các công cụ này được đánh giá dựa trên 3 tiêu chí: perfomance (tốc độ xử lý), robustness (tỷ lệ thất bại) và accuracy (độ chính xác, có bao gồm số lượng contract được gắn cờ và tỷ lệ dương tính giả).
Kết quả thí nghiệm:
Discussion
- Tỷ lệ dương tính giả của công cụ Securify là thấp vì chỉ có rất ít contract được gắn cờ.
- Một vài contract trong thí nghiệm chia sẻ những đoạn code giống nhau, dẫn đến một sự thiên kiến trong kết quả. Ví dụ, tỷ lệ dương tính giả trong Slither là đến từ một số ít các hàm riêng biệt bị trùng ở nhiều contract.
- SmartCheck và Solhint có tỷ lệ dương tính giả cao vì chúng thiếu sự hiểu biết sâu về Solidity. Chẳng hạn như hàm
super
, là một hàm dùng để gọi lại những phương thức của contract cha, lại được các công cụ này xem như là một lời gọi hàm bên ngoài.
Code Understanding Comparison
Cũng có một tool dùng để cung cấp cho người dùng về thông tin của các contract có tên là Surya. Tuy nhiên, Slither và Surya lại khác nhau ở chỗ nội dung mà chúng cung cấp là khác nhau.
Threats to Validity
Trở ngại lớn nhất của các tác giả là các thí nghiệm chỉ tập trung vào lỗ hổng reentrancy và sử dụng một tập các contract rất giới hạn.
Future Work
Một số hướng cải thiện Slither:
- Tích hợp thêm nhiều detector.
- Sử dụng symbolic execution hoặc bounded model checking.
- Áp dụng cho các ngôn ngữ smart contract khác, chẳng hạn như Vyper.
- Có sự chuyển đổi từ SlithIR sang EVM hoặc Ewasm bytecode giúp cho phép Slither hoạt động như là một compiler.
Footnotes
-
Xem thêm Static Analysis for Security. ↩
-
Xem thêm Solidity - State Variables. ↩
-
Xem thêm Reentrancy Vulnerability ↩ ↩2
-
Tham khảo Contracts — Constant State Variables ↩
-
Tham khảo Contracts — Visibility and Getters ↩
-
Xem phần Data Dependency Analysis. ↩
-
Nguồn: thec00n/etherscan_verified_contracts: Verified contracts synced from Etherscan (github.com) ↩