- Ladybird จัดการเว็บคอนเทนต์ปกติได้ในระดับหนึ่ง แต่เมื่อรัน Domato ซึ่งเป็น DOM fuzzer ของ Google Project Zero ก็เผย edge case ที่ซ่อนอยู่ในเอนจินเบราว์เซอร์ได้อย่างรวดเร็ว
- พบและแก้ไขบั๊กจริง 5 รายการจาก อินพุตผิดปกติที่เกิดขึ้นได้จริง เช่น DOM ที่สร้างด้วย JavaScript เพื่อเลี่ยงกฎของ parser, document ที่ไม่มี window และการอ้างอิง SVG แบบวนลูป
- ข้อสมมติโดยนัยภายใน implementation เช่น การสมมติว่า `` มี table ancestor, การสมมติว่า document ของ
DOMParser มี window และความผิดพลาดในการไล่ sibling ของ Element.before() นำไปสู่ crash หรือ infinite loop
- ปัญหาการเข้าถึง
contentWindow ของ iframe ที่ถูกลบ ไม่ใช่ข้อบกพร่องเฉพาะของ Ladybird เท่านั้น แต่ยังเกี่ยวข้องกับสมมติฐานเรื่อง browsing context ในสเปก HTML จนนำไปสู่ issue ใน WHATWG HTML
- Fuzzer อย่าง Domato เปิดเผยปัญหาด้านความปลอดภัยและเสถียรภาพที่ยากจะจับได้ด้วยการทดสอบเว็บเพจปกติเท่านั้น และโจทย์ถัดไปของ Ladybird คือทำให้เสถียรพอจะทนต่อการ fuzzing ต่อเนื่อง แล้วจึงรันแบบอัตโนมัติ
ทดสอบความอึดของ Ladybird ด้วย Domato
- Ladybird จัดการเว็บคอนเทนต์ที่สร้างมาอย่างถูกต้องได้ในระดับหนึ่ง แต่ลองใช้เครื่องมือวิจัยความปลอดภัยส่งอินพุตแปลก ๆ เข้าไปเพื่อดูว่าจะเกิดปัญหาอะไรบ้าง
- เครื่องมือที่ใช้คือ DOM fuzzer Domato ของ Google Project Zero
- Domato สร้างเว็บเพจสุ่มที่ผสม HTML, CSS, JavaScript ซึ่งส่วนใหญ่ valid แต่มีความแปลก
- โหลดเพจที่สร้างขึ้นใน debug build ของ Ladybird แล้วสังเกตพฤติกรรม
- README ของ Domato ชูว่าพบ bugs จำนวนมากในเบราว์เซอร์หลัก ๆ จึงประเมินว่าน่าจะพบข้อบกพร่องที่มีนัยสำคัญใน Ladybird ได้เช่นกัน
Null pointer dereference เมื่อ อยู่ใน
- ปัญหาแรกถูกพบภายในไม่ถึง 1 วินาที และสามารถย่อ output ขนาด 562KiB จาก Domato ให้เหลือรูปแบบด้านล่างได้
let mfrac = document.createElement("mfrac");
mfrac.appendChild(document.createElement("th"));
document.body.appendChild(mfrac);
- ใน build ของ Ladybird ที่เปิด UBSAN การเรียก
table_containing_cell ใน HTMLTableCellElement.cpp ทำให้เกิด null pointer dereference
- สาเหตุคือ implementation ของ
และ ใน Ladybird สมมติว่า DOM tree ด้านบนมี `` อยู่เสมอ
- HTML parser ไม่อนุญาต markup อย่าง ``
- เบราว์เซอร์ที่ทำตามสเปก เมื่อโหลด markup ด้านบน จะสร้าง `` หนึ่งตัวที่ด้านในว่างเปล่า
- แต่ถ้าสร้าง node โดยตรงด้วย JavaScript DOM API จะเลี่ยงกฎบางส่วนของ parser และใส่
ไว้ใน ได้
- โค้ดที่มีปัญหาถูกใช้เพื่อ implement พฤติกรรมเก่า ๆ ของ
และ ที่นำ CSS border และ padding ไปใช้กับแต่ละ cell ไม่ใช่แค่ table box
- การแก้ไขทำโดยลบสมมติฐานที่ว่า
และ จะมี `` ancestor เสมอ
- ใช้
first_ancestor_of_type() แทน table_containing_cell(*this)
- ถ้าไม่มี table ancestor ให้ return ทันที
- commit ที่แก้ไขอยู่ ที่นี่
การกำหนด event handler `` ใน document ที่ไม่มี window
- ปัญหาที่สองก็ถูกพบภายในไม่ถึง 1 วินาทีเช่นกัน และ output ขนาด 472KiB จาก Domato ถูกย่อเหลือโค้ดต่อไปนี้
var parser = new DOMParser();
var doc = parser.parseFromString("", "text/html");
var body = doc.createElement("body");
body.onblur = null;
- Ladybird หยุดทำงานจากความล้มเหลวในการตรวจสอบ
GCPtr
- ประเด็นสำคัญคือ พฤติกรรมพิเศษ ของ property event handler
onfoo บน ``
- เพื่อความเข้ากันได้กับเว็บคอนเทนต์เก่า การกำหนด
document.body.onfoo ต้องถูกส่งต่อไปยัง window.onfoo
- แต่ document ที่สร้างด้วย
DOMParser ไม่มี object window
- โมเดล object ภายในของ Ladybird ถูกวางโครงสร้างผิด โดยถือว่า document ทุกตัวมี window เสมอ
- หลังแก้ไข
Document::window() จะ return ค่า nullable และหลายจุดจัดการกรณี null
- เมื่อกำหนด
document.body.onblur ใน document ที่ไม่มี window จะไม่เกิดอะไรขึ้นเหมือนเบราว์เซอร์อื่น
การอ้างอิงแบบวนลูปของ SVG ``
- ปัญหาที่สามคือ infinite recursion ที่เกิดเมื่อ SVG gradient อ้างอิงตัวเอง
- SVG ต้องรองรับทั้ง inline SVG ใน HTML และรูปแบบภาพภายนอก และ gradient สามารถอ้างอิง gradient อื่นเพื่อสืบทอดสีได้
- implementation ของ Ladybird ไม่ได้พิจารณากรณีที่ gradient อ้างอิงตัวเอง จึงไล่ตาม chain การอ้างอิงแล้ววนลูปต่อเนื่อง
- หากป้องกันเฉพาะกรณีอ้างอิงตัวเองแบบตรง ๆ จะยังจัดการ circular reference หลายขั้นไม่ได้
- วิธีจัดการที่ถูกต้องคือ track gradient ทั้งหมดที่เคยเยี่ยมชม และเมื่อพบ gradient ที่เคยเยี่ยมชมแล้วอีกครั้ง ให้หยุดการไล่ chain
- Firefox แสดงข้อความบ่นใน developer console สำหรับ gradient ประเภทนี้
การเข้าถึง property window ของ iframe ที่ถูกลบ และ bug ในสเปก HTML
- ปัญหาที่สี่เกิดขึ้นเมื่อเรียก
getSelection() จาก contentWindow ที่เก็บไว้ก่อนหน้า หลังจากลบ iframe แล้ว
window.onload = function() {
let iframe = document.querySelector("iframe")
let iframeWindow = iframe.contentWindow;
iframe.remove();
iframeWindow.getSelection();
}
- Ladybird แสดง binding runtime error ใน
WindowProxy.cpp จาก null pointer reference ไปยัง BrowsingContext
- เมื่อ iframe ถูกลบออกจาก DOM แล้ว content document ของมันจะถูกแยกออกจาก browsing context ของตัวเอง
- เมื่อดึงหรือตั้งค่า property ของ object window จะมีการรันอัลกอริทึมในสเปก HTML ชื่อ
"check if an access between two browsing contexts should be reported"
- อัลกอริทึมนี้ตรวจ browsing context ของ window ที่เข้าถึงและ window เป้าหมายที่ถูกเข้าถึง
- สเปกสมมติผิดว่า ณ เวลาที่มีการเข้าถึง property ทั้งสอง window จะมี browsing context ที่เชื่อมต่ออยู่เสมอ
- มีการเปิด issue ต่อสเปก HTML และใน Ladybird มีการเพิ่ม null check ไปก่อน
- หากพบ bug ในสเปกระหว่างทำงานกับ Ladybird ก็สามารถช่วยปรับปรุงสเปกให้ทุกคนได้ผ่าน bug report หรือข้อเสนอแก้ไข
Infinite loop ใน Element.before()
- ปัญหาที่ห้าคือหน้าโหลดไม่จบและใช้ CPU 100%
two.before(one);
- สาเหตุคือความผิดพลาดใน logic ของ implementation
before() ที่ต้องหา sibling ก่อนหน้าตัวแรกของ `` ที่ไม่อยู่ใน arguments
- loop เดิมดึง
node->previous_sibling() ใหม่ทุกครั้ง
while (auto previous_sibling = node->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
- ที่ถูกต้องคือควรไล่ไปตาม chain ของ sibling โดยเลื่อนต่อเนื่องด้วย
previous_sibling->previous_sibling()
for (auto sibling = node->previous_sibling(); sibling; sibling = sibling->previous_sibling()) {
// check if previous_sibling is one of the arguments
}
ผลการ fuzzing และขั้นตอนถัดไป
- ใน session นี้พบ bug จริง 5 รายการ หนึ่งในนั้นคือ bug ในสเปก HTML และทั้งหมดได้รับการแก้ไขแล้ว
- เผยให้เห็นว่าเมื่อเจออินพุตที่แปลกและคาดไม่ถึง Ladybird ล้มได้เร็วมาก
- Fuzzer อย่าง Domato เป็นทรัพยากรที่มีประโยชน์สำหรับผู้ที่ต้องการทำให้ซอฟต์แวร์แข็งแกร่งขึ้น
- ขั้นตอนถัดไปคือทำให้ Ladybird เสถียรถึงระดับที่ทนต่ออินพุต fuzzing แบบต่อเนื่องได้
- เมื่อเสถียรเพียงพอแล้ว มีแผนจะรันแบบอัตโนมัติบนคลาวด์ที่ไหนสักแห่งเพื่อค้นหาปัญหาเพิ่มเติม
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
แสดงให้เห็นได้ดีว่าทำไม การมี implementation อิสระหลายตัว ของสเปกจึงมีคุณค่า
แค่บทความนี้บทความเดียวก็พบช่องโหว่หนึ่งจุดในสเปกแล้ว และดูเหมือนอาจมีมากกว่านี้ หรือจะมีเพิ่มอีกในอนาคต
การมี implementation อิสระหลายตัวสำคัญต่อสุขภาพระยะยาวของเว็บแพลตฟอร์ม ดังนั้นเราก็กำลังพยายามทำบทบาทนั้นอยู่
เช่น ถ้าผมทวีตว่า “มะเขือม่วงเป็นผักที่ผมชอบที่สุด” แล้วมีคนมาแก้ให้ทันทีว่า “จริง ๆ เป็นผลไม้” จากนั้นเอาไปบอกว่า “นี่พิสูจน์คุณค่าของ Twitter แล้ว” ความรู้สึกก็คล้าย ๆ กัน
ไม่ได้หมายความว่างานนี้เองหรือการมี implementation หลายตัวของสเปกไม่มีคุณค่า แต่ผมคิดว่าตัวอย่างเฉพาะนี้ยังไม่เพียงพอให้สรุปนัยนั้นได้
ชอบที่โปรเจกต์นี้แสดงให้เห็นอยู่เรื่อย ๆ ว่า ทีมเล็ก ๆ ก็สร้างสิ่งน่าทึ่งได้
คิดว่าการทำเรื่องแบบนี้ในบริษัทที่มีผู้มีส่วนได้ส่วนเสียจำนวนมากคงยากกว่านี้มาก
ถ้าเป็นโปรเจกต์งานอดิเรก ก็ย้อนกลับไปสร้างใหม่ได้เสมอ แต่ก็อดรู้สึกไม่ได้ว่าบางอย่างในนี้ควรถูกใส่ไว้ในสถาปัตยกรรมตั้งแต่แรกหรือเปล่า
implement SVG แล้วเหรอ? กำลังติดตามอย่างสนใจ เพราะคืบหน้าเร็วกว่าที่คิดไว้มาก
โดยเฉพาะ animation เป็นส่วนใหญ่ที่ยังขาดไป
สำหรับ issue #3 การใส่ ขีดจำกัดความลึกสูงสุด ให้ gradient ที่ชี้ไปยัง gradient อื่นก็ดูเป็นความคิดที่ดี
อาจเป็น defense-in-depth เผื่อ logic “เคยเห็น reference นี้มาก่อนหรือยัง” มีข้อผิดพลาดหรือข้อจำกัด
ผมไม่ค่อยรู้เรื่อง SVG gradient แต่ถึงอาจมีเหตุผลชอบธรรมที่ reference chain ต้องยาวต่อกันเป็น 1000 ชั้น ในสภาพแวดล้อมจริงถ้าเห็นแบบนั้นก็น่าจะเป็นการโจมตีหรือ input จาก fuzzer มากกว่า
ผมกำลังเขียนคอมเมนต์นี้บน Ladybird
ตอนนี้ Hacker News ทำงานบน Ladybird แล้ว
ผมใช้ Ladybird ตอนเข้าเว็บอย่าง Hacker News หรือ OSnews วันละไม่กี่นาที
มันช้าและพังง่าย แต่ก็ใช้งานได้ เมื่อคิดว่าโปรเจกต์นี้ยังใหม่มากและเขียนทุกอย่างขึ้นมาตั้งแต่ศูนย์จริง ๆ แค่นั้นก็น่าทึ่งแล้ว
รอคอยอย่างมากที่จะได้เห็น Ladybird เติบโตขึ้น
น่าสนใจ แต่รำคาญที่ developer แทบทุกคนจบแบบ “เจอแล้ว! commit แก้แล้ว จบ!” อย่างที่เห็นใน issue #1
ไม่ควรทำแบบนั้น ต้องเข้าใจให้ชัดว่าอะไรผิดพลาดกันแน่ เช่น ถ้าปัญหาคือสมมติฐานว่า “ต้องมี parent อยู่เสมอ” ก็ต้องค้นหาความผิดพลาดประเภทเดียวกันทั่วทั้ง codebase
ต้องใช้ความคิดสร้างสรรค์หาว่าเรื่องเดียวกันนี้อาจเกิดขึ้นที่ไหนอีก ไม่มีทางมีอยู่แค่จุดเดียว
ที่ software สมัยใหม่เป็นฝันร้ายเต็มไปด้วย bug และเชื่อถือได้ยาก ส่วนใหญ่เป็นเพราะข้อจำกัดแบบทุนนิยม แต่ถึงอย่างนั้นเราก็ยังทำได้ดีกว่านี้
สงสัยว่าปีนี้ Web Engines Hackfest จะมี Ladybird ไปปรากฏตัวหรือไม่
ขอพูดต่างเรื่องนิดหน่อย แต่สงสัยว่า วิดีโอแฮ็กกิ้ง บน YouTube เป็นอย่างไรบ้าง
เมื่อก่อนผมเคยรอวิดีโอใหม่ ๆ แต่เหมือนไม่ได้เห็นมาพักหนึ่งแล้ว
วิดีโออัปเดตรายเดือนยังลงอยู่ แต่หลังจากวิดีโอแฮ็กกิ้งล่าสุดก็ผ่านมาหลายเดือนแล้ว
ถึงอย่างนั้นก็ยังทำงานกับ Ladybird ทุกวัน และด้วยการสนับสนุนอย่างใจกว้างจาก Shopify และที่อื่น ๆ เมื่อปีที่แล้ว ตอนนี้ยังดูแล วิศวกร full-time สองคน อยู่ด้วย