Android มีการใช้งานอ้างอิงสำหรับคอมโพเนนต์ทั้งหมดที่จำเป็นในการใช้งานเฟรมเวิร์กการจำลองการทำงานแบบเสมือนของ Android ปัจจุบันการใช้งานนี้จำกัดไว้สำหรับ ARM64 หน้านี้จะอธิบายสถาปัตยกรรมของเฟรมเวิร์ก
ฉากหลัง
สถาปัตยกรรม Arm อนุญาตให้มีระดับข้อยกเว้นได้สูงสุด 4 ระดับ โดยระดับข้อยกเว้น 0 (EL0) มีสิทธิ์น้อยที่สุด และระดับข้อยกเว้น 3 (EL3) มีสิทธิ์มากที่สุด โค้ดฐาน Android ส่วนใหญ่ (คอมโพเนนต์สเปซผู้ใช้ทั้งหมด) จะทำงานที่ EL0 ส่วนที่เหลือซึ่งเรียกกันโดยทั่วไปว่า "Android" คือเคอร์เนล Linux ซึ่งทำงานที่ EL1
เลเยอร์ EL2 ช่วยให้สามารถนํา Hypervisor มาใช้แยกหน่วยความจําและอุปกรณ์ออกเป็น pVM แต่ละรายการที่ EL1/EL0 พร้อมการรับประกันความลับและความเป็นระเบียบเรียบร้อยที่เข้มงวด
ไฮเปอร์ไวเซอร์
เครื่องเสมือนที่ใช้เคอร์เนลที่ได้รับการปกป้อง (pKVM) สร้างขึ้นจากไฮเปอร์วิซอร์ KVM ของ Linux ซึ่งได้รับการขยายความสามารถให้จำกัดการเข้าถึงเพย์โหลดที่ทำงานอยู่ในเครื่องเสมือนที่ติดตั้งใช้งานซึ่งทําเครื่องหมายว่า "ได้รับการปกป้อง" ณ เวลาที่สร้างขึ้น
KVM/arm64 รองรับโหมดการดำเนินการที่แตกต่างกัน ทั้งนี้ขึ้นอยู่กับความพร้อมใช้งานของฟีเจอร์บางอย่างของ CPU เช่น Virtualization Host Extensions (VHE) (ARMv8.1 ขึ้นไป) ในโหมดใดโหมดหนึ่งซึ่งโดยทั่วไปเรียกว่าโหมดที่ไม่ใช่ VHE ระบบจะแยกโค้ดไฮเปอร์วิซอร์ออกจากอิมเมจเคอร์เนลระหว่างการบูตและติดตั้งที่ EL2 ส่วนเคอร์เนลจะทำงานที่ EL1 แม้ว่าจะเป็นส่วนหนึ่งของโค้ดเบส Linux แต่คอมโพเนนต์ EL2 ของ KVM เป็นคอมโพเนนต์ขนาดเล็กที่รับผิดชอบการสลับระหว่าง EL1 หลายรายการ คอมโพเนนต์ไฮเปอร์วิซอร์จะคอมไพล์ด้วย Linux แต่อยู่ในส่วนหน่วยความจำเฉพาะแยกต่างหากของvmlinux
ไฟล์ภาพ pKVM ใช้ประโยชน์จากการออกแบบนี้โดยการขยายโค้ดไฮเปอร์วิซอร์ด้วยฟีเจอร์ใหม่ๆ ซึ่งช่วยให้สามารถจำกัดเคอร์เนลโฮสต์ Android และพื้นที่ผู้ใช้ รวมถึงจำกัดการเข้าถึงหน่วยความจำของผู้ใช้และไฮเปอร์วิซอร์ของโฮสต์
โมดูลผู้ให้บริการ pKVM
โมดูลผู้ให้บริการ pKVM คือโมดูลเฉพาะฮาร์ดแวร์ที่มีฟังก์ชันเฉพาะอุปกรณ์ เช่น โปรแกรมควบคุมหน่วยจัดการหน่วยความจำอินพุต-เอาต์พุต (IOMMU) โมดูลเหล่านี้ช่วยให้คุณพอร์ตฟีเจอร์ด้านความปลอดภัยที่ต้องใช้สิทธิ์เข้าถึงระดับ 2 (EL2) ไปยัง pKVM ได้
ดูวิธีติดตั้งใช้งานและโหลดโมดูลผู้ให้บริการ pKVM ได้ที่หัวข้อติดตั้งใช้งานโมดูลผู้ให้บริการ pKVM
ขั้นตอนการบูต
รูปภาพต่อไปนี้แสดงขั้นตอนการบูต pKVM
- บูตโหลดเดอร์จะเข้าสู่เคอร์เนลทั่วไปที่ EL2
- เคอร์เนลทั่วไปจะตรวจพบว่ากำลังทำงานที่ EL2 และยกเลิกสิทธิ์ตัวเองเป็น EL1 ขณะที่ pKVM และโมดูลต่างๆ ยังคงทำงานที่ EL2 นอกจากนี้ ระบบจะโหลดโมดูลของผู้ให้บริการ pKVM ในขณะนี้ด้วย
- เคอร์เนลทั่วไปจะบูตตามปกติ โหลดไดรเวอร์อุปกรณ์ที่จำเป็นทั้งหมดจนกว่าจะถึงพื้นที่ผู้ใช้ เมื่อถึงจุดนี้ pKVM จะทำงานและจัดการตารางหน้าเว็บระยะที่ 2
กระบวนการบูตจะเชื่อถือ Bootloader เพื่อรักษาความสมบูรณ์ของภาพเคอร์เนลในช่วงบูตช่วงต้นเท่านั้น เมื่อยกเลิกสิทธิ์ของเคอร์เนลแล้ว ไฮเปอร์วิซอร์จะไม่ถือว่าเคอร์เนลดังกล่าวน่าเชื่อถืออีกต่อไป ซึ่งจะมีหน้าที่รับผิดชอบในการป้องกันตนเองแม้ว่าเคอร์เนลจะถูกบุกรุกก็ตาม
การมีเคอร์เนล Android และไฮเปอร์วิซอร์ในรูปภาพไบนารีเดียวกันช่วยให้อินเทอร์เฟซการสื่อสารระหว่างทั้งสองทำงานร่วมกันได้อย่างแนบแน่น การเชื่อมโยงที่แน่นหนานี้รับประกันการอัปเดตแบบอะตอมสำหรับคอมโพเนนต์ 2 รายการ ซึ่งทำให้ไม่ต้องทำให้อินเทอร์เฟซระหว่างคอมโพเนนต์ทั้งสองมีเสถียรภาพอยู่เสมอ และมีความยืดหยุ่นอย่างมากโดยไม่ทำให้ความสามารถในการบำรุงรักษาในระยะยาวลดลง การเชื่อมโยงที่แน่นแฟ้นยังช่วยให้เพิ่มประสิทธิภาพได้เมื่อคอมโพเนนต์ทั้ง 2 รายการทำงานร่วมกันได้โดยไม่ส่งผลกระทบต่อการรับประกันความปลอดภัยที่ไฮเปอร์วิซอร์มอบให้
นอกจากนี้ การใช้ GKI ในระบบนิเวศ Android ยังช่วยให้สามารถติดตั้งใช้งานไฮเปอร์วิซอร์ pKVM ในอุปกรณ์ Android ในรูปแบบไบนารีเดียวกับเคอร์เนลได้โดยอัตโนมัติ
การป้องกันการเข้าถึงหน่วยความจำของ CPU
สถาปัตยกรรม Arm ระบุหน่วยจัดการหน่วยความจำ (MMU) ที่แบ่งออกเป็น 2 ระยะอิสระ ซึ่งทั้ง 2 ระยะสามารถใช้เพื่อใช้การเปลี่ยนที่อยู่และการควบคุมการเข้าถึงส่วนต่างๆ ของหน่วยความจำ MMU ระยะที่ 1 ควบคุมโดย EL1 และอนุญาตให้มีการแปลที่อยู่ระดับแรก Linux ใช้ MMU ระยะที่ 1 เพื่อจัดการพื้นที่ที่อยู่เสมือนที่จัดสรรให้กับแต่ละกระบวนการใน Userspace และพื้นที่ที่อยู่เสมือนของตนเอง
MMU ระยะที่ 2 ควบคุมโดย EL2 และช่วยให้ใช้การเปลี่ยนที่อยู่ครั้งที่ 2 ในที่อยู่เอาต์พุตของ MMU ระยะที่ 1 ได้ ซึ่งจะทำให้เกิดที่อยู่จริง (PA) ไฮเปอร์วิซอร์สามารถใช้การแปลระดับ 2 เพื่อควบคุมและแปลการเข้าถึงหน่วยความจำจาก VM ทั้งหมดของผู้มาเยือน ดังที่แสดงในรูปที่ 2 เมื่อเปิดใช้การแปลทั้ง 2 ระยะ ที่อยู่เอาต์พุตของระยะที่ 1 จะเรียกว่าที่อยู่จริงระดับกลาง (IPA) หมายเหตุ: ระบบจะแปลที่อยู่เสมือน (VA) เป็น IPA แล้วจึงแปลเป็น PA
ที่ผ่านมา KVM จะทำงานโดยเปิดใช้การแปลระยะที่ 2 ขณะเรียกใช้ระบบปฏิบัติการแบบแขก และปิดใช้ระยะที่ 2 ขณะเรียกใช้เคอร์เนล Linux ของโฮสต์ สถาปัตยกรรมนี้ช่วยให้การเข้าถึงหน่วยความจำจาก MMU ระยะที่ 1 ของโฮสต์ผ่าน MMU ระยะที่ 2 ได้ จึงช่วยให้เข้าถึงหน้าหน่วยความจำของผู้มาเยือนจากโฮสต์ได้แบบไม่จำกัด ในทางกลับกัน pKVM จะเปิดใช้การป้องกันระยะที่ 2 แม้ในบริบทของโฮสต์ และกำหนดให้ไฮเปอร์วิซอร์เป็นผู้รับผิดชอบในการปกป้องหน้าหน่วยความจำของผู้ใช้แทนโฮสต์
KVM ใช้ประโยชน์จากการแปลที่อยู่ในระยะที่ 2 อย่างเต็มที่เพื่อใช้การแมป IPA/PA ที่ซับซ้อนสำหรับแขก ซึ่งสร้างภาพลวงว่าแขกมีหน่วยความจำต่อเนื่องกัน แม้ว่าจะมีการจัดสรรหน่วยความจำแบบกระจัดกระจายก็ตาม อย่างไรก็ตาม การใช้ MMU ระยะที่ 2 สำหรับโฮสต์จะจำกัดไว้ที่การควบคุมการเข้าถึงเท่านั้น ระยะที่ 2 ของโฮสต์จะได้รับการแมปข้อมูลประจำตัวเพื่อให้หน่วยความจำที่ต่อเนื่องกันในพื้นที่ IPA ของโฮสต์ต่อเนื่องกันในพื้นที่ PA สถาปัตยกรรมนี้ช่วยให้ใช้การแมปขนาดใหญ่ในตารางหน้าเว็บได้ จึงช่วยลดภาระให้กับบัฟเฟอร์การมองข้ามการแปล (TLB) เนื่องจาก PA สามารถจัดทำดัชนีการแมปข้อมูลประจำตัวได้ ระบบจึงใช้ระยะที่ 2 ของโฮสต์เพื่อติดตามการเป็นเจ้าของหน้าเว็บโดยตรงในตารางหน้าเว็บด้วย
การป้องกันการเข้าถึงหน่วยความจำโดยตรง (DMA)
ดังที่อธิบายไว้ก่อนหน้านี้ การยกเลิกการแมปหน้าเว็บของแขกจากโฮสต์ Linux ในตารางหน้าของ CPU เป็นขั้นตอนที่จำเป็นแต่ยังไม่เพียงพอต่อการปกป้องหน่วยความจำของแขก นอกจากนี้ pKVM ยังต้องป้องกันไม่ให้อุปกรณ์ที่รองรับ DMA ภายใต้การควบคุมของเคอร์เนลของโฮสต์เข้าถึงหน่วยความจำ และป้องกันไม่ให้โฮสต์ที่เป็นอันตรายเริ่มการโจมตี DMA pKVM ต้องใช้ฮาร์ดแวร์หน่วยจัดการหน่วยความจำอินพุต-เอาต์พุต (IOMMU) สำหรับอุปกรณ์ทุกเครื่องที่รองรับ DMA ในระบบเพื่อป้องกันไม่ให้อุปกรณ์ดังกล่าวเข้าถึงหน่วยความจำของผู้ใช้ตามที่แสดงในรูปภาพ 3
อย่างน้อยที่สุด ฮาร์ดแวร์ IOMMU จะมีวิธีให้สิทธิ์และเพิกถอนสิทธิ์การอ่าน/เขียนสำหรับอุปกรณ์ไปยังหน่วยความจำหลักในระดับรายละเอียดหน้า อย่างไรก็ตาม ฮาร์ดแวร์ IOMMU นี้จะจํากัดการใช้อุปกรณ์ใน pVM เนื่องจากถือว่ามีการกำหนดค่าระยะที่ 2 ที่มีการแมปข้อมูลประจำตัว
เพื่อให้มีการแยกระหว่างเครื่องเสมือน การทำธุรกรรมหน่วยความจำที่สร้างขึ้นในนามของเอนทิตีต่างๆ จะต้องแยกแยะได้โดย IOMMU เพื่อให้ใช้ชุดตารางหน้าเว็บที่เหมาะสมสำหรับการแปลได้
นอกจากนี้ การลดจำนวนโค้ดเฉพาะ SoC ที่ EL2 เป็นกลยุทธ์หลักในการลดฐานการคำนวณที่เชื่อถือได้ (TCB) โดยรวมของ pKVM และขัดแย้งกับการรวมไดรเวอร์ IOMMU ในไฮเปอร์วิซอร์ เพื่อลดปัญหานี้ โฮสต์ที่ EL1 จะรับผิดชอบงานการจัดการ IOMMU เสริม เช่น การจัดการพลังงาน การจัดเตรียม และการจัดการการขัดจังหวะตามความเหมาะสม
อย่างไรก็ตาม การกำหนดให้โฮสต์ควบคุมสถานะของอุปกรณ์จะเพิ่มข้อกำหนดเพิ่มเติมในอินเทอร์เฟซการเขียนโปรแกรมของฮาร์ดแวร์ IOMMU เพื่อให้มั่นใจว่าการตรวจสอบสิทธิ์จะไม่สามารถข้ามผ่านด้วยวิธีอื่นได้ เช่น หลังจากการรีเซ็ตอุปกรณ์
IOMMU มาตรฐานที่รองรับได้ดีสำหรับอุปกรณ์ Arm ซึ่งทำให้ทั้งการแยกและการกำหนดโดยตรงเป็นไปได้คือสถาปัตยกรรมหน่วยจัดการหน่วยความจำของระบบ (SMMU) ของ Arm สถาปัตยกรรมนี้เป็นโซลูชันอ้างอิงที่แนะนำ
ความเป็นเจ้าของหน่วยความจำ
ขณะบูต ระบบจะถือว่าโฮสต์เป็นเจ้าของหน่วยความจำทั้งหมดที่ไม่ใช่ไฮเปอร์วิซอร์ และไฮเปอร์วิซอร์จะติดตามหน่วยความจำดังกล่าว เมื่อสร้าง pVM โฮสต์จะบริจาคหน้าหน่วยความจำเพื่อให้ระบบบูตได้ และไฮเปอร์วิซอร์จะเปลี่ยนความเป็นเจ้าของหน้าเหล่านั้นจากโฮสต์ไปยัง pVM ดังนั้น ไฮเปอร์วิซอร์จึงกำหนดข้อจำกัดการควบคุมการเข้าถึงในตารางหน้าเว็บระยะที่ 2 ของโฮสต์เพื่อป้องกันไม่ให้ผู้ใช้เข้าถึงหน้าเว็บดังกล่าวอีกครั้ง ซึ่งจะรักษาความลับของผู้มาเยือน
การสื่อสารระหว่างโฮสต์กับแขกเกิดขึ้นได้ด้วยการแชร์หน่วยความจำที่ควบคุมระหว่างกัน ผู้มาเยือนได้รับอนุญาตให้แชร์หน้าเว็บบางส่วนกลับไปกับโฮสต์ได้โดยใช้ Hypercall ซึ่งจะสั่งให้ไฮเปอร์วิซอร์แมปหน้าเว็บเหล่านั้นใหม่ในตารางหน้าเว็บระยะที่ 2 ของโฮสต์ ในทำนองเดียวกัน การสื่อสารของโฮสต์กับ TrustZone เกิดขึ้นได้ด้วยการแชร์หน่วยความจำและ/หรือการดำเนินการให้ยืม ซึ่งทั้งหมดนี้ pKVM จะตรวจสอบและควบคุมอย่างใกล้ชิดโดยใช้ข้อกำหนดของเฟรมเวิร์กเฟิร์มแวร์สำหรับ Arm (FF-A)
เนื่องจากข้อกำหนดด้านหน่วยความจำของ pVM อาจเปลี่ยนแปลงได้เมื่อเวลาผ่านไป ระบบจึงมี Hypercall ไว้ให้ ซึ่งช่วยให้สามารถส่งคืนการเป็นเจ้าของหน้าเว็บที่ระบุซึ่งเป็นของ Caller กลับไปให้โฮสต์ได้ ในทางปฏิบัติ ระบบจะใช้ Hypercall นี้กับโปรโตคอลบอลลูน Virtio เพื่ออนุญาตให้ VMM ขอหน่วยความจำคืนจาก pVM และเพื่อให้ pVM แจ้ง VMM เกี่ยวกับหน้าที่ปล่อย
ไฮเปอร์วิซอร์มีหน้าที่ติดตามการเป็นเจ้าของหน้าหน่วยความจำทั้งหมดในระบบ และติดตามว่ามีการแชร์หรือให้ยืมหน้าหน่วยความจำแก่บุคคลอื่นหรือไม่ การติดตามสถานะส่วนใหญ่นี้ทำโดยใช้ข้อมูลเมตาที่แนบมากับตารางหน้าระยะที่ 2 ของโฮสต์และผู้เข้าร่วมโดยใช้บิตที่สงวนไว้ในรายการตารางหน้า (PTE) ซึ่งสงวนไว้สําหรับการใช้งานซอฟต์แวร์ตามที่ชื่อบอกไว้
โฮสต์ต้องตรวจสอบว่าไม่ได้พยายามเข้าถึงหน้าที่ไฮเปอร์วิซอร์ทำให้เข้าถึงไม่ได้ การเข้าถึงที่ผิดกฎหมายของโฮสต์จะทำให้ไฮเปอร์วิซอร์แทรกข้อยกเว้นแบบซิงค์ลงในโฮสต์ ซึ่งอาจส่งผลให้งานใน Userspace ที่เกี่ยวข้องได้รับสัญญาณ SEGV หรือเคอร์เนลของโฮสต์ขัดข้อง เพื่อป้องกันไม่ให้เข้าถึงโดยไม่ตั้งใจ เคอร์เนลของโฮสต์จะกำหนดให้หน้าเว็บที่บริจาคแก่ผู้มาเยือนไม่มีสิทธิ์สลับหรือผสาน
การจัดการการขัดจังหวะและตัวจับเวลา
การขัดจังหวะเป็นส่วนสําคัญของวิธีที่ผู้ใช้แบบไม่ระบุตัวตนโต้ตอบกับอุปกรณ์และการสื่อสารระหว่าง CPU โดยที่การขัดจังหวะระหว่างโปรเซสเซอร์ (IPI) เป็นกลไกการสื่อสารหลัก รูปแบบ KVM คือการมอบสิทธิ์การจัดการการขัดจังหวะเสมือนทั้งหมดให้กับโฮสต์ใน EL1 ซึ่งจะทํางานเป็นส่วนที่ไม่น่าเชื่อถือของไฮเปอร์วิซอร์
pKVM มีการจําลอง Generic Interrupt Controller เวอร์ชัน 3 (GICv3) แบบเต็มโดยอิงตามโค้ด KVM ที่มีอยู่ ระบบจะจัดการตัวจับเวลาและ IPI เป็นส่วนหนึ่งของโค้ดการจําลองที่ไม่เชื่อถือนี้
การรองรับ GICv3
อินเทอร์เฟซระหว่าง EL1 กับ EL2 ต้องทำให้โฮสต์ EL1 เห็นสถานะการขัดจังหวะทั้งหมด รวมถึงสำเนารีจิสเตอร์ไฮเปอร์วิซอร์ที่เกี่ยวข้องกับการขัดจังหวะ โดยปกติแล้ว การแสดงผลนี้จะทำได้โดยใช้ภูมิภาคหน่วยความจำที่ใช้ร่วมกัน 1 ภูมิภาคต่อ CPU เสมือน (vCPU) 1 ตัว
รหัสการสนับสนุนรันไทม์ของรีจิสทอร์ระบบสามารถลดความซับซ้อนให้รองรับเฉพาะการบันทึกรีจิสทอร์การขัดจังหวะที่เกิดจากซอฟต์แวร์ (SGIR) และการบันทึกรีจิสทอร์การขัดจังหวะที่ปิดใช้งาน (DIR) สถาปัตยกรรมกำหนดให้รีจิสเตอร์เหล่านี้ทริกเกอร์ไปยัง EL2 เสมอ ส่วนทริกเกอร์อื่นๆ จนถึงตอนนี้มีประโยชน์เพียงในการบรรเทาข้อผิดพลาด ส่วนที่เหลือจะจัดการในฮาร์ดแวร์
ในส่วนของ MMIO ระบบจะจําลองทุกอย่างที่ EL1 โดยใช้โครงสร้างพื้นฐานปัจจุบันทั้งหมดใน KVM สุดท้าย ระบบจะส่งต่อการรอการขัดจังหวะ (WFI) ไปยัง EL1 เสมอ เนื่องจากเป็นคำสั่งพื้นฐานสำหรับการกำหนดเวลาอย่างหนึ่งที่ KVM ใช้
การรองรับตัวจับเวลา
ค่าตัวเปรียบเทียบสำหรับตัวจับเวลาเสมือนต้องแสดงต่อ EL1 ในการดักจับ WFI แต่ละครั้งเพื่อให้ EL1 สามารถแทรกการขัดจังหวะตัวจับเวลาขณะที่ vCPU ถูกบล็อก ตัวจับเวลาจริงได้รับการจําลองทั้งหมด และส่งต่อกับดักลวงทั้งหมดไปยัง EL1
การจัดการ MMIO
หากต้องการสื่อสารกับเครื่องมือตรวจสอบเครื่องเสมือน (VMM) และทำการจําลอง GIC ต้องมีการส่งต่อกับดักของ MMIO กลับไปที่โฮสต์ใน EL1 เพื่อคัดแยกปัญหาเพิ่มเติม pKVMต้องใช้สิ่งต่อไปนี้
- IPA และขนาดของการเข้าถึง
- ข้อมูลในกรณีที่มีการเขียน
- Endianness ของ CPU ณ จุดที่มีการวางกับดักแฮ็ก
นอกจากนี้ กับดักที่มีรีจิสเตอร์วัตถุประสงค์ทั่วไป (GPR) เป็นแหล่งที่มา/ปลายทางจะได้รับการรีเลย์โดยใช้รีจิสเตอร์จำลองการโอนแบบนามธรรม
อินเทอร์เฟซสำหรับผู้เข้าร่วม
ผู้เข้าร่วมสามารถสื่อสารกับผู้เข้าร่วมที่ได้รับการปกป้องได้โดยใช้การผสมผสานระหว่าง Hypercall และการเข้าถึงหน่วยความจำไปยังภูมิภาคที่ถูกกักเก็บ Hypercall จะแสดงตามมาตรฐาน SMCCC โดยมีช่วงที่กำหนดไว้สำหรับการกำหนดค่าผู้ให้บริการโดย KVM Hypercall ต่อไปนี้มีความสำคัญอย่างยิ่งสำหรับผู้เข้าร่วม pKVM
Hypercall ทั่วไป
- PSCI มีกลไกมาตรฐานสำหรับผู้เข้าร่วมในการควบคุมวงจรชีวิตของ vCPU ซึ่งรวมถึงการออนไลน์ การออฟไลน์ และการปิดระบบ
- TRNG มีกลไกมาตรฐานสำหรับผู้เข้าร่วมในการขอข้อมูลสุ่มจาก PKVM ซึ่งจะส่งต่อคําเรียกไปยัง EL3 กลไกนี้มีประโยชน์อย่างยิ่งในกรณีที่ไม่สามารถเชื่อถือโฮสต์ให้ใช้การจำลองฮาร์ดแวร์แบบสุ่ม (RNG)
Hypercall ของ pKVM
- การแชร์ความทรงจำกับผู้จัด ในตอนแรก โฮสต์จะเข้าถึงหน่วยความจำทั้งหมดของผู้มาเยือนไม่ได้ แต่การเข้าถึงของโฮสต์จําเป็นสําหรับการสื่อสารที่ใช้หน่วยความจําร่วมกันและสำหรับอุปกรณ์แบบพาราเวอร์ชวลไลเซชันซึ่งอาศัยบัฟเฟอร์ที่ใช้ร่วมกัน Hypercall สำหรับการแชร์และการเลิกแชร์หน้าเว็บกับผู้โฮสต์ช่วยให้ผู้เข้าร่วมตัดสินใจได้ว่าจะอนุญาตให้ Android ส่วนใดเข้าถึงหน่วยความจำได้บ้างโดยไม่ต้องใช้การจับมือ
- การส่งคืนหน่วยความจำไปยังโฮสต์ โดยปกติแล้ว หน่วยความจำทั้งหมดของแขกจะเป็นของแขกจนกว่าหน่วยความจำดังกล่าวจะถูกทำลาย สถานะนี้อาจไม่เพียงพอสำหรับ VM ที่มีการใช้งานเป็นเวลานานซึ่งมีข้อกำหนดด้านหน่วยความจำที่เปลี่ยนแปลงไปเมื่อเวลาผ่านไป Hypercall
relinquish
ช่วยให้ผู้มาเยือนโอนการเป็นเจ้าของหน้าเว็บกลับไปยังโฮสต์ได้อย่างชัดเจนโดยไม่ต้องสิ้นสุดสถานะผู้มาเยือน - การหน่วงเวลาการเข้าถึงหน่วยความจำไปยังโฮสต์ โดยทั่วไปแล้ว หากผู้เข้าร่วม KVM เข้าถึงที่อยู่ที่ไม่สอดคล้องกับรีจินัลหน่วยความจำที่ถูกต้อง เทรดของ vCPU จะออกจากโฮสต์ และโดยทั่วไปแล้วการเข้าถึงจะใช้สำหรับ MMIO และ VMM จะจำลองในสเปซผู้ใช้ pKVM ต้องแสดงรายละเอียดเกี่ยวกับคำสั่งที่ทำให้เกิดข้อผิดพลาด เช่น ที่อยู่ พารามิเตอร์การลงทะเบียน และเนื้อหาที่อาจส่งกลับไปยังโฮสต์ เพื่อให้จัดการได้ง่ายขึ้น ซึ่งอาจเปิดเผยข้อมูลที่ละเอียดอ่อนจากแขกที่ได้รับการปกป้องโดยไม่ตั้งใจหากไม่คาดการณ์ไว้ pKVM จะแก้ปัญหานี้โดยถือว่าข้อผิดพลาดเหล่านี้ร้ายแรง เว้นแต่แขกจะเคยออก Hypercall เพื่อระบุว่าช่วง IPA ที่ทำให้เกิดข้อผิดพลาดเป็นช่วงที่อนุญาตให้มีการเข้าถึงเพื่อส่งกลับไปยังโฮสต์ โซลูชันนี้เรียกว่าMMIO Guard
อุปกรณ์ I/O เสมือน (virtio)
Virtio เป็นมาตรฐานที่ได้รับความนิยม พกพาได้ และมีประสิทธิภาพสำหรับการใช้งานและการโต้ตอบกับอุปกรณ์แบบพาราเวอร์ชวล อุปกรณ์ส่วนใหญ่ที่แสดงต่อผู้ใช้ที่ได้รับการปกป้องจะใช้ virtio นอกจากนี้ Virtio ยังรองรับการใช้งาน vsock ที่ใช้สำหรับการสื่อสารระหว่างผู้ใช้ชั่วคราวที่ได้รับการปกป้องกับส่วนที่เหลือของ Android
โดยปกติแล้ว VMM จะติดตั้งใช้งานอุปกรณ์ Virtio ในพื้นที่ผู้ใช้ของโฮสต์ ซึ่งจะขัดจังหวะการเข้าถึงหน่วยความจำที่ถูกกักเก็บไว้จากแขกไปยังอินเทอร์เฟซ MMIO ของอุปกรณ์ Virtio และจำลองลักษณะการทำงานที่คาดไว้ การเข้าถึง MMIO ค่อนข้างมีค่าใช้จ่ายสูงเนื่องจากการเข้าถึงอุปกรณ์แต่ละครั้งต้องส่งข้อมูลไป-กลับไปยัง VMM และกลับ ดังนั้นการโอนข้อมูลจริงส่วนใหญ่ระหว่างอุปกรณ์กับแขกจะเกิดขึ้นโดยใช้ชุด virtqueue ในหน่วยความจำ สมมติฐานหลักของ VMWare Virtio คือโฮสต์สามารถเข้าถึงหน่วยความจำของผู้มาเยือนได้ตามต้องการ สมมติฐานนี้เห็นได้ชัดในการออกแบบ virtqueue ซึ่งอาจมีตัวชี้ไปยังบัฟเฟอร์ในระบบปฏิบัติการที่จำลองอุปกรณ์มีไว้เพื่อเข้าถึงโดยตรง
แม้ว่าไฮเปอร์คอลการแชร์หน่วยความจำที่อธิบายไปก่อนหน้านี้อาจใช้เพื่อแชร์บัฟเฟอร์ข้อมูล virtio จากแขกไปยังโฮสต์ได้ แต่การแชร์นี้จำเป็นต้องดำเนินการในระดับรายละเอียดหน้าเว็บ และอาจทำให้เปิดเผยข้อมูลมากกว่าที่จำเป็นหากขนาดบัฟเฟอร์น้อยกว่าหน้าเว็บ แต่ระบบจะกำหนดค่าให้แขกจัดสรรทั้งคิวเสมือนและบัฟเฟอร์ข้อมูลที่เกี่ยวข้องจากกรอบเวลาคงที่ของหน่วยความจำที่ใช้ร่วมกัน โดยมีการคัดลอกข้อมูล (ส่งต่อ) ไปยังและจากกรอบเวลาตามที่จำเป็น
การโต้ตอบกับ TrustZone
แม้ว่าผู้เข้าร่วมจะโต้ตอบกับ TrustZone โดยตรงไม่ได้ แต่ผู้จัดต้องยังคงออกการเรียก SMC ไปยังโลกที่ปลอดภัยได้ การเรียกเหล่านี้สามารถระบุบัฟเฟอร์หน่วยความจำที่ระบุที่อยู่จริงซึ่งโฮสต์เข้าถึงไม่ได้ เนื่องจากโดยทั่วไปซอฟต์แวร์ที่ปลอดภัยจะไม่ทราบว่าบัฟเฟอร์เข้าถึงได้ โฮสต์ที่เป็นอันตรายจึงอาจใช้บัฟเฟอร์นี้เพื่อทำการโจมตีด้วยการเปลี่ยนเส้นทางข้อมูล (คล้ายกับการโจมตี DMA) pKVM จะป้องกันไม่ให้มีการโจมตีดังกล่าวด้วยการดักจับ SMC ทั้งหมดของโฮสต์ที่เรียกใช้ EL2 และทำหน้าที่เป็นพร็อกซีระหว่างโฮสต์กับจอภาพที่มีความปลอดภัยที่ EL3
การเรียก PSCI จากโฮสต์จะส่งต่อไปยังเฟิร์มแวร์ EL3 ด้วยการแก้ไขเพียงเล็กน้อย กล่าวโดยละเอียดคือ ระบบจะเขียนจุดแรกเข้าของ CPU ที่ออนไลน์หรือกลับมาทำงานต่อหลังจากหยุดชั่วคราวใหม่เพื่อให้ติดตั้งตารางหน้าระยะที่ 2 ที่ EL2 ก่อนที่จะกลับไปที่โฮสต์ที่ EL1 ในระหว่างการบูท pKVM จะบังคับใช้การป้องกันนี้
สถาปัตยกรรมนี้อาศัย SoC ที่รองรับ PSCI โดยควรใช้ TF-A เวอร์ชันล่าสุดเป็นเฟิร์มแวร์ EL3
เฟรมเวิร์กเฟิร์มแวร์สำหรับ Arm (FF-A) กำหนดมาตรฐานการโต้ตอบระหว่างโลกปกติและโลกที่ปลอดภัย โดยเฉพาะอย่างยิ่งเมื่อมีไฮเปอร์วิซอร์ที่ปลอดภัย ส่วนสําคัญของข้อกําหนดจะกําหนดกลไกการแชร์หน่วยความจํากับโลกที่ปลอดภัย โดยใช้ทั้งรูปแบบข้อความทั่วไปและรูปแบบสิทธิ์ที่กําหนดไว้อย่างชัดเจนสําหรับหน้าเว็บที่เกี่ยวข้อง pKVM จะทำหน้าที่เป็นพร็อกซีสำหรับข้อความ FF-A เพื่อให้แน่ใจว่าโฮสต์ไม่ได้พยายามแชร์หน่วยความจํากับฝั่งที่ปลอดภัยซึ่งไม่มีสิทธิ์เพียงพอ
สถาปัตยกรรมนี้อาศัยซอฟต์แวร์โลกที่ปลอดภัยซึ่งบังคับใช้รูปแบบการเข้าถึงหน่วยความจำ เพื่อให้มั่นใจว่าแอปที่เชื่อถือได้และซอฟต์แวร์อื่นๆ ที่ทำงานในโลกที่ปลอดภัยจะเข้าถึงหน่วยความจำได้ก็ต่อเมื่อโลกที่ปลอดภัยเป็นเจ้าของหน่วยความจำนั้นแต่เพียงผู้เดียว หรือมีการแชร์หน่วยความจำดังกล่าวกับโลกที่ปลอดภัยอย่างชัดเจนโดยใช้ FF-A ในระบบที่มี S-EL2 การบังคับใช้รูปแบบการเข้าถึงหน่วยความจำควรทำโดย Secure Partition Manager Core (SPMC) เช่น Hafnium ซึ่งดูแลตารางหน้าระยะที่ 2 สำหรับโลกที่ปลอดภัย ในระบบที่ไม่มี S-EL2 TEE สามารถบังคับใช้รูปแบบการเข้าถึงหน่วยความจำผ่านตารางหน้าระยะที่ 1 แทน
หากการเรียก SMC ไปยัง EL2 ไม่ใช่การเรียก PSCI หรือข้อความที่ FF-A กำหนด ระบบจะส่งต่อ SMC ที่ไม่ได้รับการจัดการไปยัง EL3 สมมติฐานคือเฟิร์มแวร์ที่ปลอดภัย (ต้องเชื่อถือได้) สามารถจัดการ SMC ที่ไม่ได้รับการจัดการได้อย่างปลอดภัย เนื่องจากเฟิร์มแวร์เข้าใจถึงข้อควรระวังที่จำเป็นในการรักษาการแยก pVM
เครื่องมือตรวจสอบเครื่องเสมือน
crosvm เป็นเครื่องมือตรวจสอบเครื่องเสมือน (VMM) ที่เรียกใช้เครื่องเสมือนผ่านอินเทอร์เฟซ KVM ของ Linux สิ่งที่ทำให้ crosvm โดดเด่นคือความมุ่งเน้นด้านความปลอดภัยด้วยการใช้ภาษาโปรแกรม Rust และ Sandbox รอบๆ อุปกรณ์เสมือนเพื่อปกป้องเคอร์เนลของโฮสต์ ดูข้อมูลเพิ่มเติมเกี่ยวกับ crosvm ได้ในเอกสารประกอบอย่างเป็นทางการที่นี่
ตัวระบุไฟล์และ ioctl
KVM จะแสดงอุปกรณ์อักขระ /dev/kvm
ไปยังพื้นที่ผู้ใช้ด้วย ioctl ต่างๆ ที่ประกอบกันเป็น KVM API ซึ่ง ioctl เหล่านี้จัดอยู่ในหมวดหมู่ต่อไปนี้
- ioctls ของระบบจะค้นหาและตั้งค่าแอตทริบิวต์ส่วนกลางที่ส่งผลต่อระบบย่อย KVM ทั้งหมด และสร้าง pVM
- ioctl ของ VM จะค้นหาและตั้งค่าแอตทริบิวต์ที่สร้าง CPU (vCPU) และอุปกรณ์เสมือน รวมถึงส่งผลต่อ pVM ทั้งหมด เช่น เลย์เอาต์หน่วยความจำ และจำนวน CPU (vCPU) และอุปกรณ์เสมือน
- ioctl ของ vCPU จะค้นหาและตั้งค่าแอตทริบิวต์ที่ควบคุมการทํางานของ CPU เสมือนตัวเดียว
- Device ioctls จะค้นหาและตั้งค่าแอตทริบิวต์ที่ควบคุมการทํางานของอุปกรณ์เสมือนเครื่องเดียว
กระบวนการ crosvm แต่ละรายการจะเรียกใช้อินสแตนซ์ของเครื่องเสมือนเพียง 1 อินสแตนซ์ กระบวนการนี้ใช้ KVM_CREATE_VM
system ioctl เพื่อสร้างตัวระบุไฟล์ VM ที่ใช้ออกคำสั่ง pVM ได้ ioctl KVM_CREATE_VCPU
หรือ KVM_CREATE_DEVICE
ใน FD ของ VM จะสร้าง vCPU/อุปกรณ์และแสดงผลตัวระบุไฟล์ที่ชี้ไปยังทรัพยากรใหม่ ioctl ใน vCPU หรือ FD ของอุปกรณ์สามารถใช้เพื่อควบคุมอุปกรณ์ที่สร้างขึ้นโดยใช้ ioctl ใน FD ของ VM สำหรับ vCPU การดำเนินการนี้รวมถึงงานสำคัญๆ ของการทำงานโค้ดผู้มาเยือน
ในทางภายใน crosvm จะลงทะเบียนตัวระบุไฟล์ของ VM กับเคอร์เนลโดยใช้อินเทอร์เฟซ epoll
ที่ทริกเกอร์เมื่อขอบ จากนั้นเคอร์เนลจะแจ้งให้ crosvm ทราบทุกครั้งที่มีเหตุการณ์ใหม่รอดำเนินการในตัวระบุไฟล์
pKVM เพิ่มความสามารถใหม่ KVM_CAP_ARM_PROTECTED_VM
ซึ่งสามารถใช้เพื่อรับข้อมูลเกี่ยวกับสภาพแวดล้อม pVM และตั้งค่าโหมดที่ได้รับการป้องกันสำหรับ VM ได้ crosvm จะใช้ความสามารถนี้ในระหว่างการสร้าง pVM หากมีการส่งผ่าน Flag --protected-vm
เพื่อค้นหาและจองหน่วยความจำในปริมาณที่เหมาะสมสำหรับเฟิร์มแวร์ pVM จากนั้นจึงเปิดใช้โหมดที่ได้รับการป้องกัน
การจัดสรรหน่วยความจำ
หน้าที่หลักอย่างหนึ่งของ VMM คือการจัดสรรหน่วยความจำของ VM และจัดการเลย์เอาต์หน่วยความจำ crosvm สร้างเลย์เอาต์หน่วยความจำแบบคงที่ตามที่อธิบายไว้คร่าวๆ ในตารางด้านล่าง
FDT ในโหมดปกติ | PHYS_MEMORY_END - 0x200000
|
พื้นที่ว่าง | ...
|
Ramdisk | ALIGN_UP(KERNEL_END, 0x1000000)
|
เคอร์เนล | 0x80080000
|
Bootloader | 0x80200000
|
FDT ในโหมด BIOS | 0x80000000
|
ฐานหน่วยความจําจริง | 0x80000000
|
เฟิร์มแวร์ pVM | 0x7FE00000
|
หน่วยความจำอุปกรณ์ | 0x10000 - 0x40000000
|
ระบบจะจัดสรรหน่วยความจําจริงด้วย mmap
และบริจาคหน่วยความจําให้กับ VM เพื่อป้อนข้อมูลในภูมิภาคหน่วยความจําที่เรียกว่า memslots ด้วย ioctl KVM_SET_USER_MEMORY_REGION
ดังนั้น หน่วยความจำ pVM ทั้งหมดของผู้ใช้จะเป็นของอินสแตนซ์ crosvm ที่จัดการหน่วยความจำดังกล่าว และอาจส่งผลให้กระบวนการถูกยกเลิก (สิ้นสุดการทำงานของ VM) หากโฮสต์เริ่มมีหน่วยความจำว่างไม่เพียงพอ เมื่อหยุด VM ฮไฮเปอร์วิซอร์จะล้างหน่วยความจําโดยอัตโนมัติและส่งคืนไปยังเคอร์เนลของโฮสต์
ใน KVM ปกติ VMM จะยังคงเข้าถึงหน่วยความจำทั้งหมดของผู้มาเยือนได้ เมื่อใช้ pKVM ระบบจะยกเลิกการแมปหน่วยความจำของแขกจากพื้นที่ที่อยู่จริงของโฮสต์เมื่อบริจาคหน่วยความจำให้กับแขก ข้อยกเว้นเพียงอย่างเดียวคือหน่วยความจำที่ผู้มาเยือนแชร์กลับอย่างชัดเจน เช่น สำหรับอุปกรณ์ virtio
ระบบจะไม่แมปภูมิภาค MMIO ในพื้นที่ที่อยู่ของผู้มาเยือน การเข้าถึงภูมิภาคเหล่านี้โดยผู้ใช้ชั่วคราวจะถูกบล็อกไว้และส่งผลให้เกิดเหตุการณ์ I/O ใน FD ของ VM กลไกนี้ใช้เพื่อติดตั้งใช้งานอุปกรณ์เสมือน ในโหมดที่ได้รับการป้องกัน ผู้มาเยือนต้องรับทราบว่ามีการใช้พื้นที่ที่อยู่ของโฮสต์สำหรับ MMIO โดยใช้ Hypercall เพื่อลดความเสี่ยงที่ข้อมูลจะรั่วไหลโดยไม่ตั้งใจ
การตั้งเวลา
CPU เสมือนแต่ละตัวจะแสดงด้วยเธรด POSIX และกำหนดเวลาโดยตัวจัดตารางเวลา Linux ของโฮสต์ เทรดเรียก KVM_RUN
ioctl ใน FD ของ vCPU ซึ่งส่งผลให้ไฮเปอร์วิซอร์เปลี่ยนไปใช้บริบท vCPU ของแขก ตัวจัดตารางเวลาของโฮสต์จะพิจารณาเวลาที่ใช้ในบริบทของผู้ใช้ชั่วคราวเป็นเวลาที่ใช้ในการ์ด vCPU ที่เกี่ยวข้อง KVM_RUN
จะแสดงผลเมื่อมีเหตุการณ์ที่ VMM ต้องจัดการ เช่น I/O, การสิ้นสุดการขัดจังหวะ หรือ vCPU หยุดทำงาน VMM จะจัดการเหตุการณ์และเรียก KVM_RUN
อีกครั้ง
ในระหว่าง KVM_RUN
เทรดยังคงถูกขัดจังหวะโดยตัวจัดตารางเวลาของโฮสต์ได้ ยกเว้นการเรียกใช้โค้ดไฮเปอร์วิซอร์ EL2 ซึ่งไม่สามารถขัดจังหวะได้ pVM ของผู้ใช้ไม่ได้มีกลไกในการควบคุมลักษณะการทำงานนี้
เนื่องจากเธรด vCPU ทั้งหมดได้รับการกำหนดเวลาไว้เหมือนกับงานอื่นๆ ใน Userspace จึงอยู่ภายใต้กลไก QoS มาตรฐานทั้งหมด กล่าวโดยละเอียดคือ คุณสามารถปรับแต่งแต่ละเธรด vCPU ให้เหมาะกับ CPU จริง วางไว้ใน cpuset เพิ่มประสิทธิภาพหรือจำกัดการใช้โดยใช้การจำกัดการใช้งาน เปลี่ยนนโยบายลําดับความสําคัญ/การจัดตารางเวลา และอื่นๆ
อุปกรณ์เสมือน
crosvm รองรับอุปกรณ์หลายรุ่น ซึ่งรวมถึงอุปกรณ์ต่อไปนี้
- virtio-blk สำหรับอิมเมจดิสก์แบบคอมโพสิต เป็นแบบอ่านอย่างเดียวหรืออ่านและเขียนได้
- vhost-vsock สําหรับการสื่อสารกับโฮสต์
- virtio-pci เป็นพาหนะ virtio
- นาฬิกาเรียลไทม์ pl030 (RTC)
- 16550a UART สําหรับการสื่อสารแบบอนุกรม
เฟิร์มแวร์ pVM
เฟิร์มแวร์ pVM (pvmfw) คือโค้ดแรกที่ pVM เรียกใช้ ซึ่งคล้ายกับ ROM บูตของอุปกรณ์จริง เป้าหมายหลักของ pvmfw คือการสร้างบูตระบบเพื่อบูตอย่างปลอดภัยและดึงข้อมูลลับที่ไม่ซ้ำกันของ pVM โดย pvmfw ไม่ได้จำกัดให้ใช้กับระบบปฏิบัติการใดระบบหนึ่งโดยเฉพาะ เช่น Microdroid ตราบใดที่ crosvm รองรับระบบปฏิบัติการนั้นและได้รับการลงนามอย่างถูกต้อง
ระบบจะจัดเก็บไบนารี pvmfw ไว้ในพาร์ติชันแฟลชที่มีชื่อเดียวกันและอัปเดตโดยใช้ OTA
การบูตอุปกรณ์
ระบบจะเพิ่มลำดับขั้นตอนต่อไปนี้ลงในกระบวนการบูตของอุปกรณ์ที่เปิดใช้ pKVM
- บูตโหลดเดอร์ Android (ABL) จะโหลด pvmfw จากพาร์ติชันลงในหน่วยความจำและตรวจสอบอิมเมจ
- ABL จะได้รับข้อมูลลับของ Device Identifier Composition Engine (DICE) (ตัวระบุอุปกรณ์แบบผสม (CDI) และเชนใบรับรอง DICE) จากรูทแห่งการเชื่อถือ
- ABL จะดึงข้อมูล CDI ที่จำเป็นสำหรับ pvmfw และเพิ่มลงใน pvmfw ไบนารี
- ABL จะเพิ่ม
linux,pkvm-guest-firmware-memory
โหนดพื้นที่หน่วยความจำที่จองไว้ ลงใน DT ซึ่งจะอธิบายตำแหน่งและขนาดของไบนารี pvmfw และข้อมูลลับที่ได้จากขั้นตอนก่อนหน้า - ABL จะส่งการควบคุมไปยัง Linux และ Linux จะเริ่มต้น pKVM
- pKVM จะยกเลิกการแมปพื้นที่หน่วยความจำ pvmfw จากตารางหน้าระยะที่ 2 ของโฮสต์ และปกป้องพื้นที่หน่วยความจำดังกล่าวจากโฮสต์ (และผู้เข้าร่วม) ตลอดช่วงเวลาที่อุปกรณ์ทำงาน
หลังจากบูตอุปกรณ์แล้ว ระบบจะบูต Microdroid ตามขั้นตอนในส่วนลําดับการบูตของเอกสาร Microdroid
การบูต pVM
เมื่อสร้าง pVM แล้ว crosvm (หรือ VMM อื่น) จะต้องสร้าง memslot ที่ใหญ่พอเพื่อให้ Hypervisor ใส่ข้อมูลไฟล์อิมเมจ pvmfw นอกจากนี้ VMM ยังถูกจํากัดในรายการรีจิสทร์ที่กําหนดค่าเริ่มต้นได้ (x0-x14 สําหรับ vCPU หลัก ไม่มีสําหรับ vCPU รอง) รีจิสเตอร์ที่เหลือจะสงวนไว้และเป็นส่วนหนึ่งของ ABI ของไฮเปอร์วิซอร์-pvmfw
เมื่อเรียกใช้ pVM ไฮเปอร์วิซอร์จะส่งการควบคุม vCPU หลักให้กับ pvmfw ก่อน เฟิร์มแวร์คาดว่า crosvm จะโหลดเคอร์เนลที่ลงนาม AVB ซึ่งอาจเป็นบูตโหลดเดอร์หรืออิมเมจอื่นๆ และ FDT ที่ไม่ได้ลงนามลงในหน่วยความจำที่ออฟเซตที่ทราบ pvmfw จะตรวจสอบลายเซ็น AVB และหากตรวจสอบสำเร็จ ก็จะสร้าง Device Tree ที่เชื่อถือได้จาก FDT ที่ได้รับ ลบความลับออกจากหน่วยความจำ และเปลี่ยนไปที่จุดเริ่มต้นของเพย์โหลด หากขั้นตอนการตรวจสอบใดขั้นตอนหนึ่งไม่สำเร็จ เฟิร์มแวร์จะส่ง Hypercall SYSTEM_RESET
ของ PSCI
ระหว่างการบูต ข้อมูลเกี่ยวกับอินสแตนซ์ pVM จะจัดเก็บไว้ในพาร์ติชัน (อุปกรณ์ virtio-blk) และเข้ารหัสด้วยข้อมูลลับของ pvmfw เพื่อให้แน่ใจว่าหลังจากการรีบูต ระบบจะจัดสรรข้อมูลลับให้กับอินสแตนซ์ที่ถูกต้อง