กำลังดำเนินการ dm-verity

Android 4.4 และสูงกว่ารองรับ Verified Boot ผ่านฟีเจอร์เคอร์เนล device-mapper-verity (dm-verity) ซึ่งเป็นตัวเลือก ซึ่งให้การตรวจสอบความสมบูรณ์ที่โปร่งใสของอุปกรณ์บล็อก dm-verity ช่วยป้องกันรูทคิทแบบถาวรที่สามารถยึดสิทธิ์ของรูทและประนีประนอมอุปกรณ์ได้ คุณสมบัตินี้ช่วยให้ผู้ใช้ Android แน่ใจได้เมื่อทำการบูทอุปกรณ์ อุปกรณ์จะอยู่ในสถานะเดียวกับที่มีการใช้งานครั้งล่าสุด

แอปพลิเคชันที่อาจเป็นอันตราย (PHA) ที่มีสิทธิ์ใช้งานรูทสามารถซ่อนจากโปรแกรมตรวจจับหรือปิดบังตัวเองได้ ซอฟต์แวร์การรูทสามารถทำได้เนื่องจากมักมีสิทธิพิเศษมากกว่าตัวตรวจจับ ทำให้ซอฟต์แวร์ "โกหก" กับโปรแกรมตรวจจับได้

คุณสมบัติ dm-verity ช่วยให้คุณดูอุปกรณ์บล็อก ซึ่งเป็นเลเยอร์การจัดเก็บข้อมูลพื้นฐานของระบบไฟล์ และพิจารณาว่าตรงกับการกำหนดค่าที่คาดไว้หรือไม่ มันทำสิ่งนี้โดยใช้แผนผังแฮชที่เข้ารหัส สำหรับทุกบล็อก (โดยทั่วไปคือ 4k) จะมีแฮช SHA256

เนื่องจากค่าแฮชจะถูกจัดเก็บไว้ในแผนผังของเพจ ดังนั้นจึงต้องเชื่อถือเฉพาะแฮช "รูท" ระดับบนสุดเท่านั้นเพื่อตรวจสอบส่วนที่เหลือของแผนผัง ความสามารถในการแก้ไขบล็อกใด ๆ จะเทียบเท่ากับการทำลายแฮชการเข้ารหัส ดูแผนภาพต่อไปนี้เพื่ออธิบายโครงสร้างนี้

DM-Verity-แฮชตาราง

รูปที่ 1. ตารางแฮช dm-verity

คีย์สาธารณะจะรวมอยู่ในพาร์ติชั่นสำหรับบูต ซึ่งผู้ผลิตอุปกรณ์จะต้องตรวจสอบจากภายนอก คีย์นั้นใช้เพื่อตรวจสอบลายเซ็นสำหรับแฮชนั้น และยืนยันว่าพาร์ติชันระบบของอุปกรณ์ได้รับการป้องกันและไม่เปลี่ยนแปลง

การดำเนินการ

การป้องกัน dm-verity อยู่ในเคอร์เนล ดังนั้นหากซอฟต์แวร์การรูททำให้ระบบเสียหายก่อนที่เคอร์เนลจะปรากฏขึ้น ซอฟต์แวร์จะคงการเข้าถึงนั้นไว้ เพื่อลดความเสี่ยงนี้ ผู้ผลิตส่วนใหญ่จะตรวจสอบเคอร์เนลโดยใช้คีย์ที่ฝังอยู่ในอุปกรณ์ กุญแจนั้นไม่สามารถเปลี่ยนแปลงได้เมื่ออุปกรณ์ออกจากโรงงาน

ผู้ผลิตใช้คีย์นั้นในการตรวจสอบลายเซ็นบน bootloader ระดับแรก ซึ่งจะตรวจสอบลายเซ็นในระดับต่อๆ ไป รวมถึง bootloader ของแอปพลิเคชัน และในท้ายที่สุดคือเคอร์เนล ผู้ผลิตแต่ละรายที่ต้องการใช้ประโยชน์จาก การบูตที่ได้รับการตรวจสอบ ควรมีวิธีการในการตรวจสอบความสมบูรณ์ของเคอร์เนล สมมติว่าเคอร์เนลได้รับการตรวจสอบแล้ว เคอร์เนลสามารถดูอุปกรณ์บล็อกและตรวจสอบได้ในขณะที่ติดตั้ง

วิธีหนึ่งในการตรวจสอบอุปกรณ์บล็อกคือการแฮชเนื้อหาโดยตรงและเปรียบเทียบกับค่าที่เก็บไว้ อย่างไรก็ตาม การพยายามตรวจสอบอุปกรณ์บล็อกทั้งหมดอาจใช้เวลานานและสิ้นเปลืองพลังงานของอุปกรณ์มาก อุปกรณ์จะใช้เวลานานในการบู๊ต จากนั้นจึงสิ้นเปลืองพลังงานอย่างมากก่อนใช้งาน

แต่ dm-verity จะตรวจสอบบล็อกทีละรายการและเฉพาะเมื่อมีการเข้าถึงแต่ละบล็อกเท่านั้น เมื่ออ่านเข้าไปในหน่วยความจำ บล็อกจะถูกแฮชแบบขนาน จากนั้นแฮชจะถูกตรวจสอบบนต้นไม้ และเนื่องจากการอ่านบล็อกเป็นการดำเนินการที่มีราคาแพง เวลาแฝงที่เกิดจากการตรวจสอบระดับบล็อกนี้จึงค่อนข้างน้อย

หากการตรวจสอบล้มเหลว อุปกรณ์จะสร้างข้อผิดพลาด I/O ซึ่งระบุว่าไม่สามารถอ่านบล็อกได้ จะปรากฏราวกับว่าระบบไฟล์เสียหายตามที่คาดไว้

แอปพลิเคชันอาจเลือกที่จะดำเนินการต่อโดยไม่มีข้อมูลที่เป็นผล เช่น เมื่อผลลัพธ์เหล่านั้นไม่จำเป็นต่อฟังก์ชันหลักของแอปพลิเคชัน อย่างไรก็ตาม หากแอปพลิเคชันไม่สามารถดำเนินการต่อได้หากไม่มีข้อมูล ก็จะล้มเหลว

แก้ไขข้อผิดพลาดไปข้างหน้า

Android 7.0 และสูงกว่าปรับปรุงความทนทานของ dm-verity ด้วยการแก้ไขข้อผิดพลาดไปข้างหน้า (FEC) การใช้งาน AOSP เริ่มต้นด้วยโค้ดแก้ไขข้อผิดพลาดทั่วไป ของ Reed-Solomon และใช้เทคนิคที่เรียกว่า Interleaving เพื่อลดค่าใช้จ่ายด้านพื้นที่และเพิ่มจำนวนบล็อกที่เสียหายซึ่งสามารถกู้คืนได้ สำหรับรายละเอียดเพิ่มเติมเกี่ยวกับ FEC โปรดดู การบูตที่บังคับใช้อย่างเข้มงวดพร้อมการแก้ไขข้อผิดพลาด

การนำไปปฏิบัติ

สรุป

  1. สร้างอิมเมจระบบ ext4
  2. สร้างแผนผังแฮช สำหรับรูปภาพนั้น
  3. สร้างตาราง dm-verity สำหรับแผนผังแฮชนั้น
  4. ลงนามในตาราง dm-verity เพื่อสร้างลายเซ็นตาราง
  5. รวมลายเซ็นตาราง และตาราง dm-verity เข้ากับข้อมูลเมตาของ verity
  6. เชื่อมต่ออิมเมจระบบ ข้อมูลเมตาของความจริง และแผนผังแฮชเข้าด้วยกัน

ดู The Chromium Projects - Verified Boot สำหรับคำอธิบายโดยละเอียดของแฮชทรีและตาราง dm-verity

การสร้างต้นไม้แฮช

ตามที่อธิบายไว้ในบทนำ แผนผังแฮชเป็นส่วนสำคัญของ dm-verity เครื่องมือ cryptsetup จะสร้างแผนผังแฮชให้กับคุณ อีกวิธีหนึ่งคือกำหนดสิ่งที่เข้ากันได้ไว้ที่นี่:

<your block device name> <your block device name> <block size> <block size> <image size in blocks> <image size in blocks + 8> <root hash> <salt>

ในการสร้างแฮช อิมเมจระบบจะถูกแบ่งที่เลเยอร์ 0 ออกเป็นบล็อกขนาด 4k โดยแต่ละบล็อกจะกำหนดแฮช SHA256 เลเยอร์ 1 ถูกสร้างขึ้นโดยการรวมเฉพาะแฮช SHA256 เหล่านั้นเข้ากับบล็อกขนาด 4k ส่งผลให้ได้ภาพที่เล็กลงมาก เลเยอร์ 2 ถูกสร้างขึ้นเหมือนกัน โดยมีแฮช SHA256 ของเลเยอร์ 1

ทำเช่นนี้จนกว่าแฮช SHA256 ของเลเยอร์ก่อนหน้าจะรวมอยู่ในบล็อกเดียวได้ เมื่อได้รับ SHA256 ของบล็อกนั้น คุณจะมีรูทแฮชของแผนผัง

ขนาดของแฮชทรี (และการใช้พื้นที่ดิสก์ที่สอดคล้องกัน) จะแตกต่างกันไปตามขนาดของพาร์ติชันที่ตรวจสอบแล้ว ในทางปฏิบัติ ขนาดของแฮชทรีมักจะมีขนาดเล็ก ซึ่งมักจะน้อยกว่า 30 MB

หากคุณมีบล็อกในเลเยอร์ที่ไม่ได้ถูกเติมเต็มตามธรรมชาติด้วยแฮชของเลเยอร์ก่อนหน้า คุณควรเติมด้วยศูนย์เพื่อให้ได้ 4k ตามที่คาดหวัง วิธีนี้ช่วยให้คุณทราบว่าแฮชทรีไม่ได้ถูกลบออก แต่กลับเติมข้อมูลว่างลงไปแทน

หากต้องการสร้างแผนผังแฮช ให้ต่อแฮชของเลเยอร์ 2 เข้ากับแฮชสำหรับเลเยอร์ 1 ต่อเลเยอร์ 3 เข้ากับแฮชของเลเยอร์ 2 และอื่นๆ เขียนทั้งหมดนี้ลงดิสก์ โปรดทราบว่านี่ไม่ได้อ้างอิงเลเยอร์ 0 ของรูทแฮช

เพื่อสรุป อัลกอริธึมทั่วไปในการสร้างแผนผังแฮชมีดังนี้:

  1. เลือกเกลือแบบสุ่ม (การเข้ารหัสเลขฐานสิบหก)
  2. แยกอิมเมจระบบของคุณออกเป็นบล็อกขนาด 4,000 บล็อก
  3. สำหรับแต่ละบล็อก รับแฮช SHA256 (แบบเค็ม)
  4. เชื่อมต่อแฮชเหล่านี้เพื่อสร้างระดับ
  5. เลื่อนระดับด้วย 0 ไปยังขอบเขตบล็อก 4k
  6. เชื่อมต่อระดับกับแผนผังแฮชของคุณ
  7. ทำซ้ำขั้นตอนที่ 2-6 โดยใช้ระดับก่อนหน้าเป็นแหล่งที่มาของขั้นตอนถัดไปจนกว่าคุณจะมีแฮชเพียงอันเดียว

ผลลัพธ์ที่ได้คือแฮชเดียว ซึ่งก็คือรูทแฮชของคุณ สิ่งนี้และเกลือของคุณถูกใช้ระหว่างการสร้างตารางการแมป dm-verity

การสร้างตารางการแมป dm-verity

สร้างตารางการแมป dm-verity ซึ่งระบุอุปกรณ์บล็อก (หรือเป้าหมาย) สำหรับเคอร์เนลและตำแหน่งของแผนผังแฮช (ซึ่งเป็นค่าเดียวกัน) การแมปนี้ใช้สำหรับการสร้าง fstab และการบูต ตารางยังระบุขนาดของบล็อกและ hash_start ซึ่งเป็นตำแหน่งเริ่มต้นของแผนผังแฮช (โดยเฉพาะ หมายเลขบล็อกจากจุดเริ่มต้นของรูปภาพ)

ดู การตั้งค่าการเข้ารหัส สำหรับคำอธิบายโดยละเอียดของฟิลด์ตารางการแมปเป้าหมาย Verity

ลงนามในตาราง dm-verity

ลงนามในตาราง dm-verity เพื่อสร้างลายเซ็นตาราง เมื่อตรวจสอบพาร์ติชัน ลายเซ็นของตารางจะถูกตรวจสอบก่อน ซึ่งจะกระทำกับคีย์บนอิมเมจสำหรับบูตของคุณในตำแหน่งคงที่ โดยทั่วไปคีย์จะรวมอยู่ในระบบการสร้างของผู้ผลิตเพื่อรวมโดยอัตโนมัติบนอุปกรณ์ในตำแหน่งที่ตายตัว

วิธีตรวจสอบพาร์ติชันด้วยลายเซ็นและคีย์ผสมนี้:

  1. เพิ่มคีย์ RSA-2048 ในรูปแบบที่เข้ากันได้กับ libmincrypt ให้กับพาร์ติชัน /boot ที่ /verity_key ระบุตำแหน่งของคีย์ที่ใช้ในการตรวจสอบแผนผังแฮช
  2. ใน fstab สำหรับรายการที่เกี่ยวข้อง ให้เพิ่ม verify ในแฟล็ก fs_mgr

การรวมลายเซ็นของตารางเข้ากับข้อมูลเมตา

รวมลายเซ็นตารางและตาราง dm-verity เข้ากับข้อมูลเมตาของ verity บล็อกข้อมูลเมตาทั้งหมดได้รับการกำหนดเวอร์ชัน ดังนั้นจึงอาจขยายได้ เช่น เพื่อเพิ่มลายเซ็นประเภทที่สอง หรือเปลี่ยนแปลงการเรียงลำดับบางอย่าง

ในการตรวจสอบสุขภาพจิต หมายเลขมหัศจรรย์จะเชื่อมโยงกับชุดข้อมูลเมตาของตารางแต่ละชุดที่ช่วยระบุตาราง เนื่องจากความยาวรวมอยู่ในส่วนหัวของอิมเมจระบบ ext4 จึงมีวิธีการค้นหาข้อมูลเมตาโดยไม่ต้องทราบเนื้อหาของข้อมูลนั้นเอง

เพื่อให้แน่ใจว่าคุณไม่ได้เลือกตรวจสอบพาร์ติชันที่ไม่ผ่านการตรวจสอบ หากเป็นเช่นนั้น การไม่มีหมายเลขมหัศจรรย์นี้จะหยุดกระบวนการตรวจสอบ หมายเลขนี้มีลักษณะดังนี้:
0xb001b001

ค่าไบต์ในฐานสิบหกคือ:

  • ไบต์แรก = b0
  • ไบต์ที่สอง = 01
  • ไบต์ที่สาม = b0
  • ไบต์ที่สี่ = 01

แผนภาพต่อไปนี้แสดงรายละเอียดข้อมูลเมตาของความจริง:

<magic number>|<version>|<signature>|<table length>|<table>|<padding>
\-------------------------------------------------------------------/
\----------------------------------------------------------/   |
                            |                                  |
                            |                                 32K
                       block content

และตารางนี้จะอธิบายช่องข้อมูลเมตาเหล่านั้น

ตารางที่ 1. ฟิลด์ข้อมูลเมตาของ Verity

สนาม วัตถุประสงค์ ขนาด ค่า
หมายเลขมหัศจรรย์ ใช้โดย fs_mgr เพื่อตรวจสอบความมีสติ 4 ไบต์ 0xb001b001
รุ่น ใช้เพื่อกำหนดเวอร์ชันบล็อกข้อมูลเมตา 4 ไบต์ ปัจจุบัน 0
ลายเซ็น ลายเซ็นต์ของตารางในรูปแบบเบาะ PKCS1.5 256 ไบต์
ความยาวโต๊ะ ความยาวของตาราง dm-verity เป็นไบต์ 4 ไบต์
โต๊ะ ตาราง dm-verity ที่อธิบายไว้ก่อนหน้านี้ ไบต์ความยาวตาราง
การขยายความ โครงสร้างนี้มีความยาวเบาะ 0 ถึง 32k 0

การเพิ่มประสิทธิภาพ dm-verity

เพื่อให้ได้ประสิทธิภาพที่ดีที่สุดจาก dm-verity คุณควร:

  • ในเคอร์เนล ให้เปิด NEON SHA-2 สำหรับ ARMv7 และส่วนขยาย SHA-2 สำหรับ ARMv8
  • ทดลองใช้การตั้งค่า read-ahead และ prefetch_cluster ที่แตกต่างกันเพื่อค้นหาการกำหนดค่าที่ดีที่สุดสำหรับอุปกรณ์ของคุณ