Báo cáo sự cố Scroll tối nay còn thú vị hơn nhiều so với bốn chữ “chưa bị khai thác”.
Tôi đã tìm đọc bài đăng gốc trên diễn đàn (11/11 14:24 UTC). Vấn đề nằm ở hợp đồng xác minh bundle proof trên mainnet, ZkEvmVerifierPostFeynman (0x7966…B254). Hợp đồng này tự ghép dữ liệu đầu vào, dùng raw staticcall để gọi trình xác minh Halo2 của OpenVM 2.0, trong đó 4 byte đầu tiên của lệnh gọi được lấy trực tiếp từ proof do người gửi cung cấp. Phía OpenVM 2.0 tình cờ có một hàm verify có kiểu dữ liệu, với selector là 0x24270d54. Nếu proof bắt đầu bằng 4 byte này, lời gọi sẽ không đi vào fallback mà chuyển thẳng đến hàm đó; các đầu vào công khai được đọc từ vị trí do bên gọi kiểm soát. Kết quả: một proof cũ hợp lệ có thể được dùng để xác nhận state root và withdrawal root mới. Nói nôm na, về lý thuyết có thể xác nhận một withdrawal root bịa ra từ hư không, khiến tiền trong bridge trở thành mục tiêu.
Trớ trêu là ở chỗ này. Hợp đồng được triển khai trong đợt nâng cấp OpenVM v2.0 ngày 22/9; một trong những lý do nâng cấp được nêu trong thông báo khi ấy là khắc phục cảnh báo bảo mật của v1.7.0, trong đó có lỗi mang tên “OpenVmHalo2Verifier self-call can bypass proof verification”, cũng là một lỗ hổng vượt qua lớp wrapper của trình xác minh. Đợt đó còn được xếp là nâng cấp bảo trì, chỉ đăng thông báo chứ không đưa ra bỏ phiếu. Vá một lỗ hổng ở lớp wrapper, rồi hợp đồng mới thay thế lại mở ra một lỗ hổng ở chính lớp đó, tồn tại suốt 49 ngày (22/9 đến 10/11).
Vì sao sự cố không xảy ra? Báo cáo ghi rõ: để khai thác, cần có vai trò prover được cấp phép, hoặc phải chờ chế độ batch cưỡng bức được mở sau khi không có xác nhận cuối cùng liên tục trong 7 ngày. Trong thời gian hợp đồng có lỗ hổng hoạt động, cả 748 lần xác nhận cuối cùng đều dùng proof hợp lệ; chế độ cưỡng bức chưa bao giờ được bật.
Vậy nên, thứ thực sự làm phao cứu sinh trong 49 ngày đó không phải toán học ZK, mà là “chỉ prover của Scroll mới được gửi proof”. Bình thường mọi người chê prover của L2 tập trung hóa; lần này chính sự tập trung hóa đã giữ cửa. Đau hơn là nhìn theo chiều ngược lại: chiếc xuồng thoát hiểm 7 ngày được thiết kế để chống kiểm duyệt, một khi được kích hoạt, lại trở thành lối tấn công mà bất kỳ ai cũng có thể dùng.
Tôi đánh giá cao cách công bố sự cố. White hat ZHOUCHUNGONG báo cáo ngày 3/11, đăng ký hợp đồng vá lỗi ngày 10/11, đồng thời đính kèm địa chỉ mới, PR sửa lỗi và giao dịch đăng ký, còn kèm cả các bước để mọi người tự xác minh—tốt hơn nhiều dự án khác. Nhưng câu “có validity proof thì không cần tin bên vận hành” lần này đã bị chính báo cáo của họ tự tay bác bỏ. Ai đang nắm tài sản trên L2, đừng chỉ hỏi có phải ZK hay không; hãy hỏi ai đang vận hành prover, hợp đồng xác minh được thay bao lâu một lần, và khi thay có bỏ phiếu cùng kiểm toán hay không. Báo cáo không nói tiền thưởng bug bounty là bao nhiêu hay hợp đồng mới có được kiểm toán bên ngoài không, nên tôi cũng không đoán mò.
#Scroll #Ethereum L2
Tôi đã tìm đọc bài đăng gốc trên diễn đàn (11/11 14:24 UTC). Vấn đề nằm ở hợp đồng xác minh bundle proof trên mainnet, ZkEvmVerifierPostFeynman (0x7966…B254). Hợp đồng này tự ghép dữ liệu đầu vào, dùng raw staticcall để gọi trình xác minh Halo2 của OpenVM 2.0, trong đó 4 byte đầu tiên của lệnh gọi được lấy trực tiếp từ proof do người gửi cung cấp. Phía OpenVM 2.0 tình cờ có một hàm verify có kiểu dữ liệu, với selector là 0x24270d54. Nếu proof bắt đầu bằng 4 byte này, lời gọi sẽ không đi vào fallback mà chuyển thẳng đến hàm đó; các đầu vào công khai được đọc từ vị trí do bên gọi kiểm soát. Kết quả: một proof cũ hợp lệ có thể được dùng để xác nhận state root và withdrawal root mới. Nói nôm na, về lý thuyết có thể xác nhận một withdrawal root bịa ra từ hư không, khiến tiền trong bridge trở thành mục tiêu.
Trớ trêu là ở chỗ này. Hợp đồng được triển khai trong đợt nâng cấp OpenVM v2.0 ngày 22/9; một trong những lý do nâng cấp được nêu trong thông báo khi ấy là khắc phục cảnh báo bảo mật của v1.7.0, trong đó có lỗi mang tên “OpenVmHalo2Verifier self-call can bypass proof verification”, cũng là một lỗ hổng vượt qua lớp wrapper của trình xác minh. Đợt đó còn được xếp là nâng cấp bảo trì, chỉ đăng thông báo chứ không đưa ra bỏ phiếu. Vá một lỗ hổng ở lớp wrapper, rồi hợp đồng mới thay thế lại mở ra một lỗ hổng ở chính lớp đó, tồn tại suốt 49 ngày (22/9 đến 10/11).
Vì sao sự cố không xảy ra? Báo cáo ghi rõ: để khai thác, cần có vai trò prover được cấp phép, hoặc phải chờ chế độ batch cưỡng bức được mở sau khi không có xác nhận cuối cùng liên tục trong 7 ngày. Trong thời gian hợp đồng có lỗ hổng hoạt động, cả 748 lần xác nhận cuối cùng đều dùng proof hợp lệ; chế độ cưỡng bức chưa bao giờ được bật.
Vậy nên, thứ thực sự làm phao cứu sinh trong 49 ngày đó không phải toán học ZK, mà là “chỉ prover của Scroll mới được gửi proof”. Bình thường mọi người chê prover của L2 tập trung hóa; lần này chính sự tập trung hóa đã giữ cửa. Đau hơn là nhìn theo chiều ngược lại: chiếc xuồng thoát hiểm 7 ngày được thiết kế để chống kiểm duyệt, một khi được kích hoạt, lại trở thành lối tấn công mà bất kỳ ai cũng có thể dùng.
Tôi đánh giá cao cách công bố sự cố. White hat ZHOUCHUNGONG báo cáo ngày 3/11, đăng ký hợp đồng vá lỗi ngày 10/11, đồng thời đính kèm địa chỉ mới, PR sửa lỗi và giao dịch đăng ký, còn kèm cả các bước để mọi người tự xác minh—tốt hơn nhiều dự án khác. Nhưng câu “có validity proof thì không cần tin bên vận hành” lần này đã bị chính báo cáo của họ tự tay bác bỏ. Ai đang nắm tài sản trên L2, đừng chỉ hỏi có phải ZK hay không; hãy hỏi ai đang vận hành prover, hợp đồng xác minh được thay bao lâu một lần, và khi thay có bỏ phiếu cùng kiểm toán hay không. Báo cáo không nói tiền thưởng bug bounty là bao nhiêu hay hợp đồng mới có được kiểm toán bên ngoài không, nên tôi cũng không đoán mò.
#Scroll #Ethereum L2