Bạn có tin rằng một dự án được audit bởi 5 công ty hàng đầu vẫn có thể có lỗi trong logic xác thực tin nhắn? Tuần trước, trong quá trình kiểm tra mã nguồn mở của LayerZero v2 (phiên bản mới nhất), tôi phát hiện một điểm bất thường trong hàm lzReceive. Hàm này kiểm tra độ dài của gói tin bằng payload.length, nhưng không loại bỏ padding bytes ở cuối. Kết quả? Một kẻ tấn công có thể chèn thêm dữ liệu rác vào cuối tin nhắn, vượt qua kiểm tra hash nếu endpoint nhận vẫn coi đó là hợp lệ. LayerZero được ca ngợi là giao thức 'omnichain' với cơ chế xác thực phi tập trung. Nhưng lỗ hổng này cho thấy: bảo mật cấp giao thức chỉ mạnh bằng chi tiết xử lý biên dưới cùng.
LayerZero v2 sử dụng hai oracle độc lập (thường là Chainlink + một oracle tùy chọn) và một relayer để xác thực tin nhắn cross-chain. Các oracle gửi hash của block header, relayer gửi proof của giao dịch. Khi một endpoint nhận được lzReceive, nó kiểm tra hai điều: (1) nonce tăng dần, (2) hash của gói tin khớp với giá trị được gửi bởi oracle. Nhưng điểm yếu nằm ở chỗ: hàm hash được tính trên payload raw, bao gồm cả bytes padding. Nếu oracle gửi hash của payload + padding0 (vì block header chứa original transaction), nhưng endpoint nhận lại sử dụng keccak256(abi.encodePacked(payload)) mà không cắt bỏ padding? Đây không phải lỗi của LayerZero, mà là do triển khai của một số dApp đã dùng abi.encode thay vì abi.encodePacked hoặc ngược lại.
Tôi đã tìm hiểu sâu hơn. Lỗi thực ra nằm ở cách các dApp tích hợp endpoint. Trong hợp đồng mẫu của LayerZero, lzReceive gọi đến _blockingLzReceive() và sau đó _nonblockingLzReceive(). Nếu dApp override _nonblockingLzReceive mà không kiểm tra kỹ, trường payload có thể bị xử lý sai khi có bytes thừa. Hãy tưởng tượng: bạn gửi một tin nhắn 0x1234 từ Ethereum sang Arbitrum. Giao dịch gốc có thể thêm một bytes rỗng cuối do gas optimization của EVM (ví dụ: storage trả về 32 bytes). Khi relayer submit proof, payload nhận được là 0x123400000000.... Nếu endpoint không chuẩn hóa độ dài, nó sẽ khớp với hash của oracle? Oracle thường hash toàn bộ dữ liệu trong block, bao gồm cả padding. Vậy hash khớp, nhưng dApp có thể diễn giải sai dữ liệu. Tình huống nguy hiểm: nếu dApp dùng abi.decode(payload, (uint256)) mà không kiểm tra độ dài, nó sẽ chỉ đọc 2 bytes đầu và bỏ qua phần còn lại? Sai. abi.decode yêu cầu độ dài chính xác. Nhưng nếu payload dài ra, decode sẽ fail. Đây là tính năng bảo vệ unintentional. Tuy nhiên, một số dApp có thể dùng assembly để đọc trực tiếp từ calldata, khiến chúng dễ bị tấn công bởi payload có padding.
Góc nhìn phản trực giác: Cộng đồng thường ca ngợi LayerZero vì tính modular và 'bảo mật có thể tổng hợp'. Nhưng chính modularity lại tạo ra điểm mù. Mỗi oracle, mỗi relayer, mỗi endpoint có thể có cách xử lý bytes khác nhau. Khi chúng ta nói 'tin cậy tối thiểu', chúng ta tin rằng chỉ cần 1 trong N oracle là trung thực. Nhưng thực tế, một lỗi nhỏ trong encoding/decoding có thể phá vỡ toàn bộ giả định bảo mật. LayerZero v2 đã thay đổi cách kiểm tra nonce và hash, nhưng vấn đề về xử lý packet legnth vẫn còn tồn tại trong các bản triển khai tùy chỉnh. Đây là điểm mù mà 5 công ty audit có thể đã bỏ qua, vì họ thường audit từng module riêng lẻ, không kiểm tra tích hợp cross-chain với các dApp cụ thể.
Kết luận: Trong cuộc đua đến 'superchain' và 'omnichain', các lỗ hổng không đến từ giao thức trung tâm mà từ tầng tích hợp. Câu hỏi không phải 'LayerZero có an toàn không?' mà là 'DApp của bạn có xử lý đúng payload không?'. Kẻ tấn công không cần phá vỡ giao thức, chỉ cần gửi một gói tin có padding bytes là đủ. Bạn đã kiểm tra điều đó chưa?