- Ladybird có thể xử lý ở mức nhất định các nội dung web bình thường, nhưng khi chạy Domato — trình fuzzer DOM của Google Project Zero — thì các edge case ẩn trong engine trình duyệt nhanh chóng lộ ra
- Trong các đầu vào bất thường nhưng có thể xảy ra ngoài thực tế như DOM được tạo bằng JavaScript để lách quy tắc parser, tài liệu không có window, hay tham chiếu SVG tuần hoàn, đã phát hiện và sửa 5 lỗi thực tế
- Những giả định ngầm trong nội bộ triển khai như việc `` luôn có tổ tiên là table, tài liệu
DOMParser luôn có window, hay lỗi trong việc duyệt sibling của Element.before() đã dẫn đến crash hoặc vòng lặp vô hạn
- Vấn đề truy cập
contentWindow của iframe đã bị xóa không chỉ là lỗi riêng của Ladybird mà còn liên quan đến giả định về browsing context trong đặc tả HTML, dẫn tới một issue trên WHATWG HTML
- Những fuzzer như Domato phơi bày các vấn đề về bảo mật và độ ổn định mà việc chỉ kiểm thử trang web bình thường khó phát hiện, và nhiệm vụ tiếp theo của Ladybird là ổn định đủ để chịu được fuzzing liên tục rồi tự động hóa việc chạy nó
Stress test Ladybird với Domato
- Ladybird có thể xử lý ở mức nào đó các nội dung web được viết chuẩn, nhưng ở đây người ta dùng công cụ nghiên cứu bảo mật để ném vào các đầu vào bất thường và xem chuyện gì xảy ra
- Công cụ được dùng là trình fuzzer DOM Domato của Google Project Zero
- Domato tạo ra các trang web ngẫu nhiên gồm HTML, CSS, JavaScript phần lớn là hợp lệ nhưng pha trộn theo cách kỳ lạ
- Các trang được tạo ra sẽ được tải vào bản dựng debug của Ladybird để quan sát hành vi
- README của Domato nêu ra rất nhiều lỗi từng tìm thấy trên các trình duyệt lớn, nên có cơ sở để tin rằng nó cũng có thể tìm ra lỗi đáng kể trên Ladybird
Null pointer dereference khi nằm trong
- Vấn đề đầu tiên được tìm ra chưa đầy 1 giây, và đầu ra Domato 562KiB có thể rút gọn thành dạng dưới đây
let mfrac = document.createElement("mfrac");
mfrac.appendChild(document.createElement("th"));
document.body.appendChild(mfrac);
- Trên bản dựng Ladybird bật UBSAN, lời gọi
table_containing_cell trong HTMLTableCellElement.cpp gây ra null pointer dereference
- Nguyên nhân là phần triển khai
và của Ladybird giả định rằng trong cây DOM phía trên luôn có ``
- HTML parser không cho phép markup như ``
- Trình duyệt tuân thủ đặc tả khi tải markup trên sẽ tạo một `` rỗng ở bên trong
- Nhưng nếu tạo node trực tiếp bằng JavaScript DOM API thì có thể lách một phần quy tắc của parser để đặt
vào trong
- Đoạn mã gặp lỗi được dùng để triển khai hành vi cũ, trong đó
và không chỉ áp dụng CSS border và padding cho box của bảng mà còn cho từng ô
- Bản sửa loại bỏ giả định rằng
và luôn có tổ tiên là ``
- Dùng
first_ancestor_of_type() thay cho table_containing_cell(*this)
- Nếu không có tổ tiên là table thì trả về ngay
- Commit sửa lỗi ở đây
Gán trình xử lý sự kiện `` trong tài liệu không có window
- Vấn đề thứ hai cũng được tìm thấy trong chưa đầy 1 giây, và đầu ra Domato 472KiB được rút gọn thành đoạn mã sau
var parser = new DOMParser();
var doc = parser.parseFromString("", "text/html");
var body = doc.createElement("body");
body.onblur = null;
- Ladybird dừng lại do lỗi xác thực
GCPtr
- Điểm mấu chốt là thuộc tính trình xử lý sự kiện
onfoo của `` có hành vi đặc biệt
- Để tương thích với nội dung web cũ, việc gán
document.body.onfoo phải được chuyển tiếp sang window.onfoo
- Nhưng tài liệu được tạo bằng
DOMParser thì không có đối tượng window
- Mô hình đối tượng nội bộ của Ladybird đã được cấu trúc sai khi cho rằng mọi document đều luôn có window
- Sau khi sửa,
Document::window() trả về giá trị nullable và nhiều vị trí đã được cập nhật để xử lý null
- Khi gán
document.body.onblur trong tài liệu không có window, sẽ không có gì xảy ra, giống như ở các trình duyệt khác
Tham chiếu tuần hoàn trong SVG ``
- Vấn đề thứ ba là đệ quy vô hạn khi gradient SVG tự tham chiếu chính nó
- SVG phải hỗ trợ cả SVG inline trong HTML lẫn định dạng ảnh bên ngoài, và gradient có thể tham chiếu gradient khác để kế thừa màu sắc
- Phần triển khai của Ladybird không tính đến trường hợp gradient tự tham chiếu, nên khi lần theo chuỗi tham chiếu nó cứ lặp mãi
- Nếu chỉ chặn trường hợp tự tham chiếu trực tiếp thì vẫn không xử lý được tham chiếu tuần hoàn qua nhiều bước
- Cách xử lý đúng là theo dõi toàn bộ gradient đã đi qua, và khi gặp lại gradient đã thăm thì dừng việc lần theo chuỗi
- Firefox sẽ hiển thị cảnh báo về loại gradient này trong console dành cho nhà phát triển
Truy cập thuộc tính window của iframe đã bị xóa và lỗi trong đặc tả HTML
- Vấn đề thứ tư xảy ra khi gọi
getSelection() trên contentWindow đã giữ lại từ trước sau khi iframe bị xóa
window.onload = function() {
let iframe = document.querySelector("iframe")
let iframeWindow = iframe.contentWindow;
iframe.remove();
iframeWindow.getSelection();
}
- Ladybird báo lỗi runtime trong
WindowProxy.cpp do bind một tham chiếu null pointer tới BrowsingContext
- Khi iframe bị xóa khỏi DOM, content document của nó sẽ bị tách khỏi browsing context của chính nó
- Khi lấy hoặc gán thuộc tính của đối tượng window, thuật toán trong đặc tả HTML
"check if an access between two browsing contexts should be reported" sẽ được chạy
- Thuật toán này kiểm tra browsing context của window đang truy cập và window bị truy cập
- Đặc tả đã giả định sai rằng tại thời điểm truy cập thuộc tính, cả hai window đều có browsing context được gắn kết
- Một issue đã được mở cho đặc tả HTML, và phía Ladybird trước mắt đã thêm kiểm tra null
- Khi phát hiện lỗi trong đặc tả trong lúc làm Ladybird, có thể cải thiện đặc tả cho mọi người bằng cách gửi bug report hoặc đề xuất bản sửa
Vòng lặp vô hạn trong Element.before()
- Vấn đề thứ năm biểu hiện ở chỗ trang không bao giờ tải xong và CPU bị dùng 100%
two.before(one);
- Nguyên nhân là lỗi trong logic của phần triển khai
before(), vốn tìm sibling đứng trước `` đầu tiên mà không nằm trong các đối số truyền vào
- Vòng lặp cũ mỗi lần đều lấy lại
node->previous_sibling()
while (auto previous_sibling = node->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
- Thực tế nó phải tiếp tục đi dọc chuỗi sibling bằng
previous_sibling->previous_sibling()
for (auto sibling = node->previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
Kết quả fuzzing và bước tiếp theo
- Trong phiên này đã tìm ra 5 lỗi thực tế, trong đó một lỗi là lỗi của đặc tả HTML, và tất cả đều đã được sửa
- Việc gặp các đầu vào kỳ quặc và khó lường cho thấy Ladybird sụp đổ rất nhanh
- Những fuzzer như Domato là tài nguyên hữu ích cho bất kỳ ai muốn làm phần mềm vững chắc hơn
- Bước tiếp theo là ổn định Ladybird đến mức có thể chịu được đầu vào fuzzing liên tục
- Khi đủ ổn định, kế hoạch là chạy tự động ở đâu đó trên cloud để tìm thêm nhiều vấn đề hơn
1 bình luận
Các ý kiến trên Hacker News
Minh họa rất rõ vì sao nhiều implementation độc lập của một đặc tả lại có giá trị
Chỉ riêng bài này đã phát hiện một lỗ hổng trong đặc tả, và có vẻ đã có thêm hoặc sau này sẽ còn có thêm nữa
Nhiều implementation độc lập rất quan trọng đối với sức khỏe dài hạn của nền tảng web, nên chúng tôi cũng đang cố đảm nhận vai trò đó
Ví dụ, giống như tôi tweet “cà tím là loại rau tôi thích nhất”, rồi có người sửa ngay rằng “thật ra là trái cây”, và từ đó nói rằng “giá trị của Twitter đã được chứng minh”
Không có ý nói bản thân công việc này hay việc có nhiều implementation của đặc tả là không có giá trị, nhưng tôi nghĩ chỉ riêng ví dụ cụ thể này thì hàm ý đó vẫn chưa đứng vững
Tôi thích việc dự án này liên tục cho thấy ngay cả một nhóm nhỏ cũng có thể tạo ra những thứ đáng kinh ngạc
Trong một công ty có nhiều bên liên quan, có lẽ làm được chuyện như vậy sẽ khó hơn nhiều
Nếu là dự án sở thích thì lúc nào cũng có thể quay lại làm lại, nhưng khó bỏ cảm giác rằng một số thứ như thế lẽ ra phải được đưa vào kiến trúc ngay từ đầu
Họ đã triển khai SVG rồi sao? Dự án đang tiến triển nhanh hơn tôi nghĩ rất nhiều, nên tôi đang theo dõi khá hứng thú
Đặc biệt animation là một mảng lớn còn thiếu
Với issue #3, có vẻ cũng nên đặt giới hạn độ sâu tối đa cho gradient trỏ tới gradient khác
Đây có thể là một lớp phòng thủ chiều sâu trước lỗi hoặc giới hạn của logic “ta đã thấy tham chiếu này trước đây chưa”
Tôi không rành SVG gradient, và có thể có lý do chính đáng nào đó khiến chuỗi tham chiếu cần kéo dài tới 1000 mục, nhưng trong môi trường thực tế, nếu thấy thứ như vậy thì nhiều khả năng đó là tấn công hoặc input của fuzzer
Bình luận này đang được viết trong Ladybird
Giờ Hacker News đã chạy được trong Ladybird
Mỗi ngày tôi dùng Ladybird vài phút để lướt các trang như Hacker News hay OSnews
Nó chậm và dễ vỡ, nhưng vẫn chạy. Chỉ riêng điều đó đã rất ấn tượng nếu xét dự án còn trẻ như vậy và đúng nghĩa là mọi thứ đều được viết từ đầu
Tôi thật sự mong chờ Ladybird trưởng thành hơn
Thú vị đấy, nhưng tôi khó chịu vì gần như mọi developer đều kết thúc theo kiểu “tìm ra rồi! commit bản sửa, xong!” như thấy ở issue #1
Không nên như vậy; cần hiểu chính xác điều gì đã sai. Ví dụ, nếu vấn đề là giả định “parent chắc chắn tồn tại”, thì phải tìm trong toàn bộ codebase những lỗi cùng loại
Cần dùng sự sáng tạo để tìm xem chuyện tương tự còn có thể xảy ra ở đâu nữa. Nó tuyệt đối không chỉ nằm ở một chỗ
Việc phần mềm hiện đại là một cơn ác mộng đầy bug và khó tin cậy phần lớn là do các ràng buộc mang tính tư bản, nhưng dù vậy ta vẫn có thể làm tốt hơn
Tôi tò mò không biết Ladybird có xuất hiện tại Web Engines Hackfest năm nay không
Hơi lạc đề, nhưng tôi tò mò không biết các video hacking trên YouTube đã ra sao
Trước đây tôi từng chờ video mới, nhưng hình như đã lâu rồi chưa thấy
Tôi vẫn đăng video cập nhật hằng tháng, nhưng đã vài tháng trôi qua kể từ video hacking cuối cùng
Dù vậy, tôi vẫn làm việc với Ladybird mỗi ngày, và nhờ sự tài trợ hào phóng từ Shopify cùng những nơi khác vào năm ngoái, giờ tôi còn đang quản lý hai kỹ sư toàn thời gian