การเลือกความรุนแรงของเซิร์ฟเวอร์
(cliffle.com)- Hubris เป็น OS ที่มีงาน (task) แยกขาดจากกันและสื่อสารกันผ่าน IPC โดย system call ตัวที่ 13 คือ
REPLY_FAULTทำให้เซิร์ฟเวอร์สามารถจบคำขอที่ผิดพลาดจากไคลเอนต์ด้วย fault แทนค่าข้อผิดพลาดได้ - จากมุมมองไคลเอนต์ IPC ดูเหมือนการเรียกฟังก์ชัน แต่เพราะ task ถูก คอมไพล์แยกกัน คอมไพเลอร์จึงไม่สามารถกัน opcode ที่ผิดพลาด, ไบต์ที่ตีความไม่ได้, หรือ loaned memory ที่ไม่เหมาะสมได้ทั้งหมด
- โปรแกรม Hubris ปกติแทบไม่พบข้อผิดพลาดเหล่านี้ ด้วยการตั้งค่าบิลด์และโค้ด Rust ที่สร้างขึ้นมา ดังนั้นหากบังคับให้ทุกการเรียกใช้
Result<T, IpcError>และunwrap()จะทำให้ ขนาดโค้ด และค่าใช้จ่ายตอนรันไทม์เพิ่มขึ้น - เคอร์เนลจะฆ่า task ที่ละเมิดเงื่อนไขก่อนเรียก system call ทันทีโดยไม่มีรหัสข้อผิดพลาด และ
REPLY_FAULTขยาย นโยบาย fail-fast แบบเดียวกันไปถึงการตอบกลับของเซิร์ฟเวอร์ - การออกแบบนี้ทำให้การใช้ API ผิดถูกเปิดเผยอย่างรวดเร็ว แต่ fuzz test หรือ task แบบ chaos ที่ส่ง IPC และ system call แบบสุ่มจะถูกรีสตาร์ตแทบจะทันที ทำให้ทดสอบได้ยาก
ตำแหน่งของ Hubris IPC และ REPLY_FAULT
- Hubris มีเคอร์เนลขนาดเล็กที่เป็นอิสระจากแอปพลิเคชัน และวางโค้ดส่วนใหญ่ เช่น ไดรเวอร์, ลอจิกของแอปพลิเคชัน, และ network stack ไว้ใน task ที่แยกกัน ถูกคอมไพล์แยก และถูกกักกัน
- การสื่อสารระหว่าง task ทำผ่าน system call สำหรับ IPC ที่เคอร์เนลเป็นผู้ทำงาน
RECV: ดึงข้อความรับที่มีลำดับความสำคัญสูงสุด หรือบล็อกจนกว่าจะมีข้อความเข้ามาSEND: หยุดผู้เรียก แล้วส่งข้อความและสิทธิ์ควบคุมไปยัง task ผู้รับ จากนั้นรอจนกว่าจะได้รับคำตอบREPLY: ส่งคำตอบกลับไปยัง task ที่เคยSENDมา เพื่อให้รันต่อได้
- ไคลเอนต์และเซิร์ฟเวอร์ใน Hubris ไม่ใช่ตัวตนตายตัว แต่เป็น บทบาทที่ task ทำอยู่
- task ที่ใช้
SENDทำหน้าที่เป็นไคลเอนต์ - task ที่ใช้
RECVและREPLYทำหน้าที่เป็นเซิร์ฟเวอร์ - task หนึ่งอาจเป็นเซิร์ฟเวอร์สำหรับ task หนึ่ง และเป็นไคลเอนต์สำหรับอีก task หนึ่งได้
- task ที่ใช้
ข้อผิดพลาดที่คอมไพเลอร์มองข้ามที่ขอบเขตของ task
- ในการเรียกฟังก์ชันทั่วไป คอมไพเลอร์และลิงเกอร์รับประกัน type และเป้าหมายการเรียกได้ค่อนข้างมาก
- หากฟังก์ชัน Rust รับอาร์กิวเมนต์
Stringคอมไพเลอร์จะกันไม่ให้ผู้เรียกส่งboolเข้าไป - ความสับสนเรื่องเป้าหมาย เช่น ตั้งใจเรียก
pet_catแต่ไปเรียกfire_missilesโดยปกติก็ไม่เกิดขึ้น
- หากฟังก์ชัน Rust รับอาร์กิวเมนต์
- Hubris IPC ข้ามขอบเขตของ task และแต่ละ task ถูกคอมไพล์เป็น โปรแกรมแยกต่างหาก ทำให้คอมไพเลอร์ตรวจสอบความสัมพันธ์ IPC ทั้งหมดโดยตรงไม่ได้
- ข้อผิดพลาดที่เซิร์ฟเวอร์ IPC อาจเจอแบ่งได้เป็นสามประเภทใหญ่ ๆ
- opcode ที่ไม่ตรงกับอินเทอร์เฟซ เช่น มี “operation number 48” เข้ามาในอินเทอร์เฟซที่มีแค่สอง operation
- ได้ก้อนไบต์ที่ไม่ใช่ชนิดข้อความที่คาดไว้และตีความไม่ได้ หรือข้อความสั้นหรือยาวเกินไป
- ไม่มี loaned memory ที่ต้องใช้ หรือเมื่อจำเป็นต้องใช้หน่วยความจำที่เขียนได้แต่ได้รับมาเป็นแบบอ่านอย่างเดียว
เหตุผลที่ไม่บังคับให้โปรแกรมปกติต้องจัดการข้อผิดพลาด
- ในโปรแกรม Hubris ปกติ ระบบถูกจัดวางให้ข้อผิดพลาด IPC เหล่านี้ไม่เกิดขึ้น
- การเชื่อมต่อระหว่าง task ถูกกำหนดผ่านการตั้งค่าของระบบบิลด์ ทำให้สับสนกันได้ยาก
- ไคลเอนต์ประกอบและส่ง IPC ด้วยโค้ด Rust ที่สร้างขึ้นมา
- เซิร์ฟเวอร์ก็ประมวลผลผลลัพธ์ด้วยโค้ด Rust ที่สร้างขึ้นมาแยกต่างหาก
- หากทำให้ทุก operation ของ IPC คืนค่าเป็น
Result<T, IpcError>โปรแกรมปกติจะต้องใส่unwrap()สำหรับข้อผิดพลาดที่ในทางปฏิบัติไม่มีวันเจอunwrap()เป็นภาระด้าน ขนาดโค้ด อย่างมาก- ตอนรันไทม์ก็มีต้นทุนในการตรวจสอบข้อผิดพลาดที่จะไม่เกิดขึ้น
- หากใส่
unwrap()หรือpanic!ไว้ในโค้ดที่สร้างขึ้นมา จะรวมตำแหน่ง panic ไว้ที่ศูนย์กลางและลดผลกระทบต่อขนาดโค้ดได้ แต่ต้นทุนตอนรันไทม์ยังคงอยู่ - การรองรับรหัสข้อผิดพลาดแบบทั่วไปต้องให้ทุก operation ทำตามกฎการเข้ารหัสข้อผิดพลาดเดียวกัน
- ทุก operation ต้องสามารถคืนข้อผิดพลาดได้
- ทุก operation ต้องเข้ารหัสข้อผิดพลาดนั้นด้วยวิธีเดียวกัน
- แม้ operation ที่ไม่มีทางล้มเหลวก็ต้องถูกแสดงในรูปแบบที่ล้มเหลวได้
- ในเฟิร์มแวร์ที่ใช้ Hubris พบ operation ที่ล้มเหลวไม่ได้จริง ๆ อยู่เรื่อย ๆ และ การตั้งค่าขา GPIO ก็เป็นตัวอย่างหนึ่ง
นโยบาย fault เชิงรุกของเคอร์เนล Hubris
- ระบบปฏิบัติการจำนวนมากแม้จะละเมิดเงื่อนไขก่อนเรียก system call ก็ยังคืนรหัสข้อผิดพลาดหรือให้โอกาสจัดการ exception/signal
- ใน Unix หาก
closefile descriptor ที่ไม่ได้เปิด จะคืนรหัสข้อผิดพลาด - หากส่ง null pointer ให้
openแทน path name ก็จะคืนรหัสข้อผิดพลาด
- ใน Unix หาก
- Hubris จะทำลาย task นั้นทันทีเมื่อทำผิดเงื่อนไขก่อนเรียก system call
- task จะไม่สามารถรันคำสั่งต่อไปได้อีก
- ตัว task เองไม่มีโอกาสกู้คืนหรือทำงานต่อ
- task supervisor ของแอปพลิเคชันจะได้รับแจ้ง fault และโดยปกติจะลบ task แล้วรีสตาร์ต
- fault ที่เคอร์เนลสร้างขึ้นคือ synthetic fault
- คล้ายกับ hardware fault ที่ CPU สร้างขึ้น เช่น dereference null pointer หรือหารด้วย 0
- hardware fault เกิดจากการละเมิดกฎของสถาปัตยกรรมโปรเซสเซอร์ ส่วน synthetic fault เกิดจากการละเมิดกฎของเคอร์เนล
- ตัวอย่างเช่น ในการเรียก
SENDหากดัชนี task ผู้รับอยู่นอกขอบเขตของแอปพลิเคชัน หรือ pointer ของข้อความชี้ไปยังหน่วยความจำที่ไม่มีสิทธิ์เข้าถึง จะเกิด synthetic fault - Hubris ไม่อนุญาตให้มี fault ที่กู้คืนได้หรือทำต่อได้
- ไม่ว่าจะเป็น hardware fault หรือ synthetic fault task ที่ได้รับ fault จะกลายเป็นสถานะตาย
- การเลือกนี้มีไว้เพื่อหลีกเลี่ยงโหมดความล้มเหลวที่ละเอียดอ่อน และทำให้การให้เหตุผลเกี่ยวกับระบบง่ายขึ้น
วิธีที่เซิร์ฟเวอร์ตอบกลับไคลเอนต์ด้วย fault
REPLY_FAULTคือ system call ที่ให้เซิร์ฟเวอร์ ส่ง fault ไปยังไคลเอนต์แทนการตอบกลับปกติ- ลำดับการทำงานของ
REPLYทั่วไปเป็นดังนี้- เมื่อไคลเอนต์ใช้
SENDเคอร์เนลจะทำเครื่องหมาย task ไคลเอนต์เป็นสถานะ “waiting to send” ไปยัง task ผู้รับ - เมื่อ task ผู้รับใช้
RECVไคลเอนต์นั้นจะกลายเป็นสถานะ “waiting for reply” - เมื่อเซิร์ฟเวอร์เรียก
REPLYไคลเอนต์จะกลับมาอยู่ในสถานะ runnable
- เมื่อไคลเอนต์ใช้
REPLY_FAULTคล้ายกับREPLYแต่แทนที่จะส่งข้อความและทำให้กลับมารันได้ จะ ส่ง fault และทำให้ task อยู่ในสถานะตาย- เซิร์ฟเวอร์ไม่สามารถฆ่า task ใดก็ได้ตามใจ
REPLY_FAULTใช้ได้เฉพาะกับ task ที่เซิร์ฟเวอร์นั้นRECVมาแล้วและยังไม่ได้REPLY- ใช้ได้เฉพาะกับไคลเอนต์ที่กำลังรอการตอบกลับจากเซิร์ฟเวอร์นั้น
- Hubris ใช้
REPLY_FAULTสำหรับการจัดการข้อผิดพลาดต่อไปนี้- opcode ที่ผิดพลาด
- ข้อความที่เสียหาย ถูกตัด หรือไร้ความหมาย
- กรณีที่ไคลเอนต์ไม่ได้ส่ง loaned memory ชนิดที่ถูกต้องมาให้
ข้อผิดพลาดของแอปพลิเคชันและประสบการณ์แบบ fail-fast
REPLY_FAULTใช้ได้ไม่เพียงกับข้อผิดพลาดของรูปแบบ IPC แต่ยังใช้กับ ข้อผิดพลาดเฉพาะแอปพลิเคชัน ได้ด้วย- Hubris IP stack จัดสรร IP port ให้ task แบบคงที่
- หาก task หนึ่งพยายามยุ่งกับ IP port ของ task อื่น IP stack จะให้ fault กับ task นั้น
- วิธีนี้ลดการจัดการข้อผิดพลาด “เชิงทฤษฎี” ที่ไม่ควรเกิดขึ้นจริง และเปิดเผยการใช้งานผิดอย่างรวดเร็วระหว่างการพัฒนา
REPLY_FAULTกลายเป็นวิธีให้เซิร์ฟเวอร์ทำให้เกิดpanic!ข้ามโปรเซส ต่อโปรเซสไคลเอนต์ คล้ายกับโมเดลที่โดยทั่วไปจะเกิดpanic!เมื่อมีการละเมิดเงื่อนไขก่อนเรียกฟังก์ชัน Rust- ไคลเอนต์ไม่จำเป็นต้องมีโค้ดสำหรับสิ่งนี้หรือให้ความร่วมมือใด ๆ
แนวโน้มด้านความปลอดภัยและข้อจำกัดในการทดสอบ
- Eliza Weissman อธิบาย Hubris ว่า “เป็นศัตรูอย่างก้าวร้าวต่อโปรแกรมประสงค์ร้าย”
- ความพยายามโจมตีมักปรากฏก่อนในรูปแบบข้อผิดพลาดของ API หรือการใช้งานผิด ดังนั้นระบบที่ล้างสถานะของคอมโพเนนต์ที่ทำตัวผิดอาจถูกโจมตีได้ยากขึ้น
- สมมติฐานนี้ยังไม่ได้รับการทดสอบ
- มีคำขอให้ติดต่อหากสนใจลอง exploit Hubris
- ข้อเสียที่สังเกตได้คือระบบนี้ fuzz test ได้ยากมาก
- มีการทำ task แบบ chaos ขนาดเล็กที่สร้าง IPC และ system call แบบสุ่ม แต่แทบไม่ว่าจะทำอะไร ก็ถูกรีเซ็ตทันที
- หากต้องการให้ทำงานอย่างมีประโยชน์ มันต้องตัดสินใจโดยอิงกับ system uptime counter ที่เปลี่ยนแปลงให้สังเกตได้ทุกครั้งที่เริ่มต้น
REPLY_FAULTยังให้วิธีที่เซิร์ฟเวอร์สุ่มฆ่าไคลเอนต์เพื่อบังคับให้เกิด chaos ได้ด้วย แต่ตัวเลือกนี้ยังไม่ได้รับการประเมินอย่างเต็มที่- โดยทั่วไป task ของ Hubris ไม่ได้ตั้งใจสร้างข้อความ IPC ที่ผิดแบบไดนามิก จึงมักรันได้โดยไม่ต้องรับรู้ถึงการมีอยู่ของ
REPLY_FAULT
1 ความคิดเห็น
ความคิดเห็นบน Hacker News
REPLY_FAULT ดูดีเมื่อระบบมีขนาดเล็กและแน่นกระชับ และแอปพลิเคชันก็เขียนโดยคนกลุ่มเดียวกับที่ออกแบบระบบทั้งหมดเป็นหลัก
แต่จากมุมมองของนักพัฒนาแอปพลิเคชัน การเชื่อมต่อกับโค้ดของบุคคลที่สามผ่านโมเดล IPC ที่บริการอื่นสามารถส่งยาพิษฆ่าทันทีคืนมายังกระบวนการของฉันได้ทุกเมื่อนั้นน่าจะค่อนข้างน่ากลัว
ผมไม่ได้เชื่อใจนักพัฒนาแอปพลิเคชันคนอื่นถึงขนาดนั้น โลกนี้เต็มไปด้วยคนขับรถแย่ ๆ และโปรเซสเบื้องหลังที่สร้างโดยนักพัฒนาซึ่งถูกผู้จัดการกดดัน และถ้าแค่ได้เลิกงานก่อนสองทุ่ม พวกเขาก็อาจใส่ REPLY_FAULT ค่าเริ่มต้นที่อาจไม่เหมาะสมไว้เต็มไปหมด
เงื่อนไขล่วงหน้าทั้งหมดก็ไม่ใช่ว่าจะเข้าใจได้ง่าย และยังอาจแตกต่างกันไปตามอุปกรณ์หรือเวอร์ชันของ OS ด้วย
ขอบเขตย่อมขยายออกไปเสมอ แต่คงไม่มีใครอยากยัดงานที่ควรจัดการบนโฮสต์มากกว่าเข้าไปเป็นทาสก์ Hubris ในคอนโทรลเลอร์ฝังตัวโดยไม่จำเป็น
ถ้าเซิร์ฟเวอร์บอกว่า “ไคลเอนต์ตัวนั้นผิด” เคอร์เนลก็ฆ่าไคลเอนต์นั้น ประเด็นสำคัญคือทั้งสองฝ่ายไม่เข้าใจกัน
อีกอย่าง คำว่า “โปรเซส” ที่พูดถึงอาจต่างจากสิ่งที่คุณนึกถึง ใน Hubris เธรดทั้งหมดแชร์ address space เดียวกัน
REPLY_FAULT จะลามเป็นลูกโซ่ไหม? เช่น A
SENDไปหา B แล้วรอ, BSENDไปหา C แล้วรอ ถ้า C ทำREPLY_FAULTผมสงสัยว่า A จะตายพร้อมกับ B ด้วยหรือเปล่าถ้าไม่เป็นเช่นนั้น ทาสก์ประสงค์ร้ายก็แค่มอบหมายการทดลองให้ทาสก์ผู้ช่วยทำแทนก็จบ ในทางกลับกัน ถ้าเป็นเช่นนั้น โดยรวมก็ดูเปราะบางพอสมควร แม้ผมจะไม่ได้รู้จัก Hubris ดีไปกว่านี้ก็ตาม
แถมถ้า
SENDสามารถเป็นแบบวนรอบหรือส่งกลับกันไปมาได้ ทาสก์ก็อาจเผลอฆ่าตัวเองได้ กรณีอย่าง B → A → B อาจกลายเป็นแรงจูงใจให้ไม่ใช้ REPLY_FAULTเหตุผลที่เซิร์ฟเวอร์สามารถยิงกลับใส่ไคลเอนต์ได้ไม่ใช่เรื่องความปลอดภัย แต่เป็นเรื่องความน่าเชื่อถือ กล่าวคือมองว่าข้อผิดพลาดมาจากบั๊ก ไม่ใช่การโจมตีโดยเจตนา และปฏิกิริยาสุดโต่งของเคอร์เนลช่วยให้นักพัฒนาหาปัญหาได้เร็วที่สุด
แน่นอนว่ามีส่วนที่ทับซ้อนกับความปลอดภัย และอาจเป็นแนวป้องกันสำรองที่มีประโยชน์เมื่อโปรเซสพยายามทำสิ่งที่ไม่ควรทำ
Hubris กับดีบักเกอร์ Humility เป็นเทคโนโลยีที่ถ้ามีเวลาหรือมีภารกิจต้องทำ ก็อยากขุดลึกลงไปจริง ๆ น่าเสียดายที่ตอนนี้ทำไม่ได้
ในระบบที่ทีมเดียวเขียนโค้ดทั้งหมด น่าสนใจที่วิธีระเบิดไคลเอนต์ออกจากวงโคจรเพียงเพราะมันมองมาแปลก ๆ อาจช่วยเพิ่มความเร็วในการพัฒนาแบบวนซ้ำได้
หลังจากอ่านเรื่อง algebraic effects แล้วเผลอหลับไป พอตอนเช้าได้มาอ่านบทความนี้ก็สนุกดี ถ้ามองแบบบิดมุมนิดหนึ่ง นี่คือเคอร์เนลที่ทำให้เซิร์ฟเวอร์สามารถทำ effect ที่ไคลเอนต์รับมือไม่ได้
การใช้โค้ดซ้ำและการประกอบเข้าด้วยกันน่าจะยากขึ้นมาก แต่โมเดลการรันกลับเรียบง่ายขึ้นมาก ในระบบฝังตัวแบบสแตติก นี่ชัดเจนว่าเป็นการแลกเปลี่ยนที่เหมาะสม ถ้าต้องการใช้ซ้ำ ก็ vendor ทาสก์มาปรับแก้ได้ทุกเมื่อ
กลับกัน Unix มีข้อผิดพลาดที่เพิกเฉยได้มากเกินไป และโดยส่วนตัวผมคิดว่าหลายอย่างในนั้นควรทำให้เกิด สัญญาณร้ายแรง ด้วยซ้ำ ถ้าเป็นแบบนั้น คุณภาพซอฟต์แวร์โดยรวมคงดีขึ้นไม่น้อย
ตัวอย่างเช่น การเรียก
close()กับ file descriptor ที่ไม่ถูกต้องเป็นข้อผิดพลาดที่ไม่ร้ายแรง จึงมักถูกเพิกเฉย แต่ในความเป็นจริงมันอันตรายมาก โดยเฉพาะในแอปหลายเธรด ส่วนใหญ่การปิด file descriptor ที่ผิดจะล้มเหลวแบบ harmless แต่ 1% อาจปิด logging socket หรือไฟล์ล็อกฐานข้อมูล หรือการเชื่อมต่อ IPC ที่ไม่เกี่ยวข้องไปเลย แล้วก็กลายเป็นซอฟต์แวร์ไม่นิ่งที่ทุกคนเกลียดนึกถึงประโยคจาก Errand of Mercy ที่ว่า “คุณจะได้รู้ว่ามีกฎและข้อบังคับต่าง ๆ อยู่ กฎเหล่านั้นจะถูกประกาศไว้ การละเมิดแม้แต่ข้อที่เล็กที่สุดมีโทษถึงตาย”
ควรเอาเรื่องนี้ไปทำเป็น RFC วันเมษาหน้าโง่สำหรับ HTTP
ขอเสนอ HTTP 499 “Shame on you.” ไคลเอนต์ที่ได้รับ 499 ควรยุติทาสก์ที่ออกคำขอนั้นด้วยวิธีเฉพาะของแต่ละภาษา อาจจำกัดเฉพาะคำขอที่เริ่มด้วยเฮดเดอร์บางอย่างอย่าง
Strict: trueมันจับสมดุลแบบ “อะไรกันเนี่ย… แต่เอาจริง ๆ ก็โอเคนะ?” ที่เห็นในบริบทนี้ได้อย่างสมบูรณ์แบบ
อ่านแล้วสนุกมาก และวิธีใช้ supervisor เดี่ยวแบบนี้คล้ายกับวิธีที่สตาร์ทอัปเก่าของผมจัดแอปพลิเคชันให้ unwrap ทุกอย่าง
ยังทำให้นึกถึงบทความหนึ่งที่ผมชอบคือ https://medium.com/@mattklein123/crash-early-and-crash-often...
สงสัยจริง ๆ ว่านี่ก้าวร้าวเกินไปหรือเปล่า
บน Linux เป็นไปไม่ได้ที่จะทำให้โปรแกรมอื่นที่กำลังสื่อสารกันผ่านแค่ซ็อกเก็ตล่มโดยตรง ยกเว้นกรณีส่งข้อมูลผิดรูปแบบไปยังซ็อกเก็ต
แต่การฆ่ามันนั้นทำได้แน่นอน อะไรก็ตามที่รันด้วย root สามารถฆ่าอย่างอื่นได้ และยังรีบูตเพื่อทำให้ทั้งระบบลงได้ด้วย
ยากขึ้นและไม่พบบ่อยเท่าไร แต่ในคอนเทนเนอร์อย่างน้อยสิทธิ์ root ก็เป็นเรื่องปกติ แน่นอนว่ามี cgroup อยู่เลยถูกจำกัดมากขึ้น แต่ประเด็นคือแบบนั้น
มันก็แตกต่างจากภูมิปัญญาทั่วไปที่ว่า “จงใจกว้างกับสิ่งที่รับเข้า และระมัดระวังกับสิ่งที่ส่งออก” อยู่เล็กน้อย เพียงแต่นั่นอาจเป็นเรื่องที่ผูกกับระบบเครือข่ายมากกว่า
ถึงอย่างนั้น บางทีระบบก็คงต้องใจกว้างกับสิ่งที่รับเข้าอย่างเลี่ยงไม่ได้ ไม่อย่างนั้นจะมีวิธีปรับ API เล็กน้อยโดยไม่ทำให้โปรแกรมเดิมพังได้อย่างไร?
เท่าที่รู้ มันไม่อนุญาตให้มีโปรเซสชนิดใหม่ใน runtime ด้วย ไฟล์รันได้ทั้งหมดที่เป็นไปได้ต้องถูกกำหนดไว้ตั้งแต่ตอนคอมไพล์
สำหรับส่วนที่ว่า “ไม่มีวิธีแก้ปัญหาแล้วกลับไปทำงานต่อ นี่เป็นการเลือกโดยตั้งใจเพื่อหลีกเลี่ยง failure mode ที่ละเอียดอ่อนและทำให้การให้เหตุผลเกี่ยวกับระบบง่ายขึ้น” ทำให้นึกถึงคำพูดดังของไอน์สไตน์ว่า “ให้เรียบง่ายที่สุดเท่าที่เป็นไปได้ แต่ไม่เรียบง่ายไปกว่านั้น”
ดีไซน์นี้ดูเหมือนจะละเมิดเงื่อนไขส่วนหลัง ผมไม่สนใจสภาพแวดล้อมการทำงานที่ทนต่อความยุ่งเหยิงของโลกจริงไม่ได้เลย และก็ไม่ค่อยรู้ว่ามีพื้นที่เชิงพาณิชย์ที่ใช้ได้จริงตรงไหนจะยอมรับเรื่องแบบนั้น
สุดท้ายคือให้ย้อนกลับไปที่ระบบ init แล้วลองใหม่ไปเรื่อย ๆ อย่างนั้นหรือ? แล้วจะเข้าใจ fault ที่เกิดขึ้นด้วยกลไกแบบไหนเพื่อจะลองใหม่ด้วยวิธีที่ดีกว่าได้?
อย่างไรก็ดี ขอปรบมือให้กับความบริสุทธิ์ของความเชื่อ
ตามที่ Cliff เขียนไว้อย่างละเอียดในบล็อก จริง ๆ แล้ว REPLY_FAULT เป็นฟีเจอร์ที่ตอนแรกคิดว่าอาจก้าวร้าวเกินไป แต่ประสบการณ์จากการสร้างและ deploy ระบบ รวมถึงพูดตรง ๆ คือการดีบัก ทำให้เรามั่นใจว่ามันจะไม่ได้ทำให้ระบบของเราพังแบบเอาแน่เอานอนไม่ได้ แต่จะทำให้แข็งแกร่งขึ้น
สามารถดูแนวคิดและสภาพจริงของเรื่องนี้เพิ่มเติมได้ที่ [0] และ [1]
[0] https://www.mattkeeter.com/blog/2024-03-25-packing/
[1] https://cliffle.com/blog/who-killed-the-network-switch/
แม้แต่ในโปรเจกต์งานอดิเรก ผมก็เคยเห็นบัส I2C ค้างบ่อยเมื่อบิตของโปรโตคอลสักบิตเพี้ยน แล้วทำให้ทั้งระบบลงไปเลย ดังนั้นจึงคิดว่าดีไซน์นี้ค่อนข้างให้แรงบันดาลใจ
ตามที่เข้าใจ นี่เป็นเรื่องของกรณีข้อผิดพลาดที่รู้อยู่แล้ว กล่าวคือไม่ใช่ข้อผิดพลาดที่ถูกจัดการ แต่เป็นความไม่ตรงกันของโปรโตคอลและสิ่งที่ไม่ควรเกิดขึ้นเด็ดขาด
ความเห็นอื่น ๆ ก็ชี้ไว้แล้วว่าเป็น OS ที่ทำมาเฉพาะวัตถุประสงค์ เหมือนกับที่เราคงไม่ทำ UI ด้วย Erlang, Hubris ก็ดูเหมาะกับพื้นที่ที่มันครอบครองอยู่ดี
สาเหตุอาจเป็นบั๊ก การโจมตี หรือฮาร์ดแวร์เสียหาย และไม่ว่ากรณีไหนก็ไม่ควรเดินหน้าต่อ ผู้เรียกมีปัญหาร้ายแรง และถ้ายังทำต่อก็มีแต่จะก่อความเสียหายมากขึ้น
ฟังดูคล้ายปรัชญา “let it crash” ของ Erlang/OTP อยู่บ้าง Erlang ถูกใช้ในฮาร์ดแวร์ mission-critical จำนวนไม่น้อยและขึ้นชื่อเรื่องความน่าเชื่อถือ ดังนั้นในทางปฏิบัติอาจไม่ใช่ข้อเสียร้ายแรงขนาดนั้น
มันถูกเขียนมาให้รันอยู่ลึก ๆ ภายในแร็กเซิร์ฟเวอร์ 0xide
ในส่วนที่ว่า “ความพยายาม exploit มักปรากฏก่อนในรูปของข้อผิดพลาด API หรือการใช้งานผิด ดังนั้นระบบที่ล้างสถานะของคอมโพเนนต์ที่ทำงานผิดเมื่อมีพฤติกรรมผิดปกติใด ๆ ควรถูก exploit ได้ยากขึ้น” ตรงนี้เหมือนเป็นการตรวจสิ่งที่แอปพลิเคชันยอมรับให้เข้มงวดขึ้นเล็กน้อย
ดังนั้นจึงมีข้อดีด้านความปลอดภัย แต่ไม่ใช่แบบที่คิดกัน ไม่ใช่การทำลายความคืบหน้าของผู้โจมตีเพื่อผลักให้ถอยกลับ แต่เป็นการทำให้สถานะผิดพลาดบางอย่างที่เมื่อก่อนเคยนำไปต่อยอดเป็นสถานะผิดพลาดที่ต้องการมากกว่า ไม่สามารถใช้ได้อีกต่อไป
เมื่อนั้นผู้โจมตีก็จะไปหาเป้าหมายอื่นแทนที่จะลองวิธีนั้น