Android מספק הטמעה לדוגמה של כל הרכיבים הדרושים להטמעת Android Virtualization Framework. כרגע ההטמעה מוגבלת ל-ARM64. בדף הזה נסביר על הארכיטקטורה של המסגרת.
רקע
ארכיטקטורת Arm מאפשרת עד ארבע רמות חריגות, כשרמת החריגה 0 (EL0) היא עם ההרשאות המינימליות ביותר, ורמת החריגה 3 (EL3). החלק הגדול ביותר של קוד Android (כל הרכיבים של מרחב המשתמש) פועל ב-EL0. שאר מה שמכונה Android הוא הליבה של Linux, שרצה ב-EL1.
שכבת EL2 מאפשרת להציג hypervisor שמאפשר לבודד זיכרון ומכשירים למכונות וירטואליות נפרדות ב-EL1/EL0, עם הבטחות חזקות של סודיות ושלמות.
hypervisor
המכונה הווירטואלית המבוססת-ליבה המוגנת (pKVM) מבוססת על ההיפר-ווירטואלי Linux KVM, שהורחבה עם היכולת להגביל את הגישה לעומסי העבודה שפועלים במכונות וירטואליות אורחות שמסומנות בתווית 'מוגן' בזמן היצירה.
KVM/arm64 תומך במצבי ביצוע שונים בהתאם לזמינות של תכונות מסוימות של מעבדים, כלומר, התוספים למארח של וירטואליזציה (VHE) (ARMv8.1 ואילך). באחד מהמצבים האלה, שנקרא בדרך כלל מצב ללא VHE, קוד ההיברומכונה מופרד מתמונת הליבה במהלך האתחול ומתקין ב-EL2, בעוד שהליבה עצמה פועלת ב-EL1. למרות שהרכיב EL2 ב-KVM הוא חלק מ-codebase של Linux, הוא רכיב קטן שאחראי למעבר בין כמה EL1s. רכיב hypervisor מתוזמן באמצעות Linux, אבל נמצא בקטע נפרד ומסור של זיכרון בתמונה vmlinux
. ב-pKVM נעשה שימוש בתכנון הזה על ידי הרחבת קוד hypervisor בתכונות חדשות שמאפשרות להטיל הגבלות על הליבה של המארח Android ועל מרחב המשתמש, ולהגביל את הגישה של המארח לזיכרון האורח ול-hypervisor.
מודולים של ספקי pKVM
מודול של ספק pKVM הוא מודול ספציפי לחומרה שמכיל פונקציונליות ספציפית למכשיר, כמו מנהלי התקנים של יחידות ניהול זיכרון (IOMMU) של פלט-קלט. בעזרת המודולים האלה אפשר להעביר ל-pKVM תכונות אבטחה שדורשות גישה ברמת החרגה 2 (EL2).
במאמר הטמעת מודול של ספק pKVM מוסבר איך מטמיעים ומטעינים מודול של ספק pKVM.
תהליך ההפעלה
האיור הבא מתאר את תהליך האתחול של pKVM:
- תוכנת האתחול נכנסת לליבה הגנרית ב-EL2.
- הליבה הגנרית מזהה שהיא פועלת ב-EL2 ומבטלת את ההרשאות שלה ל-EL1, בזמן ש-pKVM והמודולים שלו ממשיכים לפעול ב-EL2. בנוסף, המודולים של ספקי pKVM נטענים בשלב הזה.
- הליבה הגנרית ממשיכה את האתחול באופן רגיל, וטוענת את כל מנהלי ההתקנים הנדרשים עד שמגיעים למרחב המשתמש. בשלב הזה, ה-pKVM מותקן ומטפל בטבלאות הדפים של שלב 2.
תהליך האתחול סומך על תוכנת האתחול כדי לשמור על תקינות תמונת הליבה רק במהלך האתחול המוקדם. כשהרשאות הליבה מבוטלות, היא כבר לא נחשבת מהימנה על ידי hypervisor, שצריך להגן על עצמו גם אם הליבה נפרצה.
אם יש באותה תמונה בינארית את הליבה של Android ואת ה-hypervisor של Android, אפשר ליצור ממשק תקשורת צמוד מאוד ביניהם. הצימוד ההדוק הזה מבטיח עדכונים אטומיים של שני הרכיבים, שלא צריך לשמור על הממשק יציב ומספק מידה רבה של גמישות בלי לפגוע בתחזוקה לטווח הארוך. הצירוף ההדוק מאפשר גם לבצע אופטימיזציה של הביצועים כששני הרכיבים יכולים לשתף פעולה, בלי להשפיע על ההתחייבויות לאבטחה שמספק hypervisor.
בנוסף, השימוש ב-GKI בסביבת Android מאפשר לפרוס את hypervisor של pKVM במכשירי Android באופן אוטומטי באותו קובץ בינארי כמו הליבה.
הגנה על הגישה לזיכרון של מעבד (CPU)
בארכיטקטורת Arm מצוין שיחידה לניהול זיכרון (MMU) מחולקת לשני שלבים עצמאיים, ושאפשר להשתמש בשניהם כדי להטמיע תרגום כתובות ובקרת גישה לחלקים שונים בזיכרון. שלב 1 MMU נשלט על ידי EL1 ומאפשר רמה ראשונה של תרגום כתובת. מערכת Linux משתמשת ב-MMU של שלב 1 כדי לנהל את מרחב הכתובות הווירטואלי שסופק לכל תהליך במרחב המשתמש, וגם את מרחב הכתובות הווירטואלי שלה.
ה-MMU בשלב 2 נשלט על ידי EL2 ומאפשר להחיל תרגום כתובת שני על כתובת הפלט של ה-MMU בשלב 1, וכתוצאה מכך מתקבלת כתובת פיזית (PA). בזכות התרגום בשלב 2, hypervisors יכולים לשלוט בגישה לזיכרון מכל המכונות הווירטואליות של האורחים ולתרגם אותה. כפי שמוצג באיור 2, כששני שלבי התרגום מופעלים, כתובת הפלט של שלב 1 נקראת כתובת פיזית ביניים (IPA). הערה: הכתובת הווירטואלית (VA) מתורגמת ל-IPA ואז ל-PA.
בעבר, KVM פועל עם תרגום של שלב 2 מופעל בזמן הפעלת אורחים, ועם שלב 2 מושבת בזמן הפעלת ליבה של Linux המארח. הארכיטקטורה הזו מאפשרת לגישה לזיכרון מ-MMU של המארח בשלב 1 לעבור דרך MMU של המארח בשלב 2, וכך מאפשרת גישה בלתי מוגבלת מהמארח לדפי הזיכרון של האורח. לעומת זאת, ב-pKVM אפשר להפעיל הגנה של שלב 2 גם בהקשר של המארח, וה-hypervisor אחראי על הגנה על דפי הזיכרון של האורח במקום המארח.
KVM משתמש במלוא התרגום של הכתובות בשלב 2 כדי להטמיע למשתמשי האורח מיפויים מורכבים של IPA/PA, וכך יוצר את האשליה של זיכרון רציף למשתמשי האורח למרות הפיצול הפיזי. עם זאת, השימוש ב-MMU של שלב 2 למארח מוגבל לבקרת גישה בלבד. שלב 2 של המארח ממופה לזהות, כדי להבטיח שזיכרון רציף במרחב ה-IPA של המארח יהיה רציף במרחב ה-PA. הארכיטקטורה הזו מאפשרת להשתמש במיפויים גדולים בטבלת הדפים, וכתוצאה מכך מפחיתה את הלחץ על מאגר המשנה למעקב אחר תרגומים (TLB). מאחר שמערכת PA יכולה להוסיף לאינדקס מיפוי זהויות, שלב 2 של המארח משמש גם למעקב אחרי הבעלות על דפים ישירות בטבלת הדפים.
הגנה על גישה ישירה לזיכרון (DMA)
כפי שמתואר למעלה, ביטול המיפוי של דפי האורח מהמארח של Linux בטבלאות הדפים של המעבד הוא שלב הכרחי, אבל לא מספיק כדי להגן על זיכרון האורח. בנוסף, pKVM צריך להגן מפני גישה לזיכרון על ידי מכשירים עם יכולת DMA בשליטת הליבה של המארח, ומפני אפשרות של התקפת DMA שמתחילה על ידי מארח זדוני. כדי למנוע ממכשיר כזה לגשת לזיכרון של האורח, ב-pKVM נדרשת יחידת ניהול זיכרון של קלט-פלט (IOMMU) לכל מכשיר עם יכולת DMA במערכת, כפי שמוצג באיור 3.
לפחות, חומרת IOMMU מספקת את האמצעים להענקה ולביטול של גישה לקריאה/כתיבה של מכשיר לזיכרון פיזי ברמת פירוט של דף. עם זאת, החומרה של IOMMU מגבילה את השימוש במכשירים ב-pVMs כי היא מניחה שלב 2 עם מיפוי זהויות.
כדי להבטיח בידוד בין מכונות וירטואליות, עסקאות זיכרון שנוצרות בשם ישויות שונות צריכות להיות מבדילות על ידי IOMMU כדי שניתן יהיה להשתמש בקבוצה המתאימה של טבלאות דפים לתרגום.
בנוסף, צמצום כמות הקוד הספציפי ל-SoC ברמה EL2 היא אסטרטגיה מרכזית לצמצום בסיס המחשוב המהימן (TCB) הכולל של pKVM, והיא מנוגדת להכללת מנהלי IOMMU במכונה הווירטואלית. כדי לצמצם את הבעיה הזו, המארח ב-EL1 אחראי על משימות ניהול משניות של IOMMU, כמו ניהול צריכת החשמל, אתחול וטיפול בהפרעות (interrupts) במקרים הרלוונטיים.
עם זאת, הענקת השליטה במצב המכשיר למארח מחייבת דרישות נוספות לממשק התכנות של חומרת ה-IOMMU, כדי לוודא שלא ניתן לעקוף את בדיקות ההרשאות באמצעים אחרים, למשל לאחר איפוס המכשיר.
ארכיטקטורת SMMU (יחידה לניהול זיכרון המערכת) של Arm היא IOMMU סטנדרטי עם תמיכה רחבה במכשירי Arm, שמאפשרת גם בידוד וגם הקצאה ישירה. הארכיטקטורה הזו היא פתרון העזר המומלץ.
הבעלות על הזיכרון
בזמן האתחול, ההנחה היא שכל הזיכרון שאינו של hypervisor הוא בבעלות המארח, וה-hypervisor עוקב אחריו ככזה. כשיוצרים מכונה וירטואלית, המארח מעביר לה דפי זיכרון כדי לאפשר את האתחול שלה, וה-hypervisor מעביר את הבעלות על הדפים האלה מהמארח למכונה הווירטואלית. לכן, המכונה הווירטואלית מטילה הגבלות על בקרת הגישה בטבלת הדפים של השלב השני של המארח כדי למנוע ממנה לגשת שוב לדפים, וכך לשמור על הסודיות של האורח.
התקשורת בין המארח לבין האורחים מתאפשרת באמצעות שיתוף זיכרון מבוקר ביניהם. אורחים יכולים לשתף חלק מהדפים שלהם עם המארח באמצעות hypercall, שמורה להיפרוויזר למפות מחדש את הדפים האלה בטבלת הדפים של המארח בשלב 2. באופן דומה, התקשורת של המארח עם TrustZone מתאפשרת באמצעות פעולות שיתוף או השאלה של זיכרון, שכולן עוברות מעקב הדוק ובקרה על ידי pKVM באמצעות המפרט של Firmware Framework for Arm (FF-A).
דרישות הזיכרון של ה-pVM עשויות להשתנות עם הזמן, ולכן ניתנת היפר-קריאה שמאפשרת לוותר על הבעלות על דפים ספציפיים ששייכים למקבל הקריאה החוזרת. בפועל, הקריאה הזו ל-hypercall משמשת עם פרוטוקול הבלון של virtio כדי לאפשר ל-VMM לבקש חזרה זיכרון מה-pVM, ול-pVM להודיע ל-VMM על דפים שהועברו, באופן מבוקר.
המארח המאולתר אחראי למעקב אחרי הבעלות על כל דפי הזיכרון במערכת, ולמעקב אחרי האופן שבו הם משותפים או מושאלים לישויות אחרות. רוב המעקב אחרי המצב מתבצע באמצעות מטא-נתונים שמצורפים לטבלאות הדפים של השלב השני של המארח והאורחים, באמצעות ביטים שמורים ברשומות של טבלאות הדפים (PTE), ששמותיהם מרמזים על כך שהם שמורים לשימוש בתוכנה.
המארח צריך לוודא שהוא לא מנסה לגשת לדפים שההיפר-ווירטואליזציה מנעה את הגישה אליהם. גישה לא חוקית למארח גורמת להחדרת חריג סינכרוני למארח על ידי hypervisor, ויכולה לגרום לכך שהמשימה האחראית במרחב המשתמשים תקבל אות SEGV או לקריסה של הליבה של המארח. כדי למנוע גישה לא מכוונת, דפים שנתרמו לאורחים לא עומדים בדרישות להחלפה או למיזוג על ידי הליבה של המארח.
טיפול בהפרעות וטיימרים
הפרעות הן חלק חשוב באינטראקציה של אורחים עם מכשירים ובתקשורת בין מעבדים, שבהם הפרעות בין מעבדי מידע (IPI) הן מנגנון התקשורת העיקרי. המודל של KVM הוא להעניק גישה לכל ניהול ההפרעות הווירטואליות למארח ב-EL1, שמתנהג למטרה הזו כחלק לא מהימן של hypervisor.
ב-pKVM יש אמולציה מלאה של Generic Interrupt Controller version 3 (GICv3) שמבוססת על הקוד הקיים של KVM. הטיפול ב-Timer וב-IPIs מתבצע כחלק מקוד ההדמיה הלא מהימן הזה.
תמיכה ב-GICv3
הממשק בין EL1 ל-EL2 חייב להבטיח שמצב ההפרעה המלא גלוי למארח EL1, כולל עותקים של הרישומים של hypervisor שקשורים להפרעות. בדרך כלל, החשיפה הזו מתבצעת באמצעות אזורים של זיכרון משותף, אחד לכל מעבד וירטואלי (vCPU).
אפשר לפשט את קוד התמיכה בסביבת זמן הריצה של מרשם המערכת כך שיתמוך רק במלכודות של מרשם ההפעלה של ההפרעה שנוצרה על ידי תוכנה (SGIR) ומרשם ההפעלה של ההפרעה המושבתת (DIR). הארכיטקטורה קובעת שהרשמות האלה תמיד יפנו ל-EL2, בעוד שהמלכודות האחרות שימשו עד כה רק לצמצום שגיאות. כל השאר מטופל בחומרה.
בצד של MMIO, הכול אמולציה ב-EL1 תוך שימוש חוזר בכל התשתית הנוכחית ב-KVM. לבסוף, Wait for Interrupt (WFI) תמיד מועבר ל-EL1, כי זו אחת מהפעולות הבסיסיות של תזמון שבהן KVM משתמש.
תמיכה בטיימר
ערך המשווה של הטיימר הווירטואלי חייב להיות חשוף ל-EL1 בכל WFI שנלכד, כדי ש-EL1 יוכל להחדיר הפסקות טיימרים בזמן ש-vCPU חסום. הטיימר הפיזי הוא באמולציה מלאה, וכל המלכודות ממסרות ל-EL1.
טיפול ב-MMIO
כדי לתקשר עם צג המכונה הווירטואלית (VMM) ולבצע הדמיה של GIC, צריך להעביר את מלכודות ה-MMIO חזרה למארח ב-EL1 לצורך טיפול נוסף. ל-pKVM נדרשים:
- IPA וגודל הגישה
- נתונים במקרה של כתיבה
- הסופיות של המעבד (CPU) בנקודת הקליטה
בנוסף, מלכודות עם רישום של שימוש כללי (GPR) כמקור או ליעד מועברות באמצעות פסאודו-רישום של העברה מופשטת.
ממשקי אורחים
אורח יכול לתקשר עם אורח מוגן באמצעות שילוב של היפר-קריאות וגישה לזיכרון באזורים שנלכדו. הקריאות Hypercall נחשפות בהתאם לתקן SMCCC, עם טווח ששמור להקצאה של ספק על ידי KVM. הקריאות הבאות ל-hypercall חשובות במיוחד לאורחים ב-pKVM.
פעולות היפר-שיחות כלליות
- PSCI מספק מנגנון סטנדרטי שמאפשר לאורחים לשלוט במחזור החיים של מעבדי ה-vCPU שלהם, כולל הפעלה, השבתה וניתוק מהמערכת.
- TRNG מספק מנגנון סטנדרטי שבו האורח יכול לבקש אנטרופיה מ-pKVM, שמעביר את הקריאה ל-EL3. המנגנון הזה שימושי במיוחד במקרים שבהם לא ניתן לסמוך על המארח כדי ליצור וירטואליזציה של מחולל מספרים אקראיים (RNG) בחומרה.
קריאות Hypercall של pKVM
- שיתוף הזיכרון עם המארח. בהתחלה, המארח לא יכול לגשת לכל הזיכרון של האורח, אבל הגישה של המארח נחוצה לתקשורת בזיכרון משותף ולמכשירים בווירטואליזציה פרה (paravirtualization) שמסתמכים על מאגרי נתונים זמניים משותפים. שימוש בהיפרקאלים לשיתוף ולביטול שיתוף של דפים עם המארח מאפשר לאורחים להחליט בדיוק אילו חלקים מהזיכרון יהיו נגישים לשאר רכיבי Android, בלי צורך בהקשאה.
- העברת הזיכרון למארח. בדרך כלל, כל הזיכרון של האורח שייך לו עד שהוא נהרס. המצב הזה עשוי להיות לא מתאים למכונות וירטואליות לטווח ארוך עם דרישות זיכרון שמשתנות לאורך זמן. ההיפר-קריאה
relinquish
מאפשרת לאורח להעביר באופן מפורש את הבעלות על הדפים בחזרה למארח, בלי לדרוש סיום של האורח. - מלכודת גישה לזיכרון למארח. באופן מסורתי, אם אורח KVM ניגש לכתובת שלא תואמת לאזור זיכרון תקין, שרשור ה-vCPU יוצא למארח והגישה משמשת בדרך כלל ל-MMIO ומאומללת על ידי ה-VMM במרחב המשתמש. כדי לאפשר את הטיפול הזה, pKVM צריך לפרסם פרטים על ההוראה שגרמה לשגיאה, כמו הכתובת שלה, הפרמטרים של הרישום ואולי גם התוכן שלהם, חזרה למארח. אם לא תהיה ציפייה לטראפ, הדבר עלול לחשוף בטעות מידע אישי רגיש של אורח מוגן. pKVM פותר את הבעיה הזו על ידי התייחסות לשגיאות האלה כשגיאות קטלניות, אלא אם האורח הוציא בעבר קריאה ל-hypercall כדי לזהות את טווח ה-IPA הפגום כטווח שבו מותרים טרפים חזרה למארח. הפתרון הזה נקרא MMIO guard.
Virtual I/O device (virtio)
Virtio הוא תקן פופולרי, נייד ומותאם אישית להטמעה ולאינטראקציה עם מכשירים שעברו פרו-וירטואליות. רוב המכשירים שנחשפים לאורחים מוגנים מיושמים באמצעות virtio. Virtio גם מהווה את הבסיס של הטמעת Vsock שמשמשת לתקשורת בין אורח מוגן לבין שאר מכשירי Android.
בדרך כלל, מכשירי Virtio מיושמים במרחב המשתמש של המארח על ידי ה-VMM, שמיירט גישה לזיכרון שנלכדה מהאורח לממשק ה-MMIO של מכשיר ה-virtio ומחקה את ההתנהגות הצפויה. הגישה ל-MMIO היא יקרה יחסית כי כל גישה למכשיר מחייבת נסיעה הלוך ושוב ל-VMM ובחזרה, ולכן רוב העברת הנתונים בפועל בין המכשיר לבין האורח מתבצעת באמצעות קבוצה של virtqueues בזיכרון. הנחת המוצא העיקרית של virtio היא שהמארח יכול לגשת לזיכרון של האורח באופן שרירותי. ההנחה הזו ברורה בעיצוב של הניצחון, כי היא עשויה לכלול סימונים שאופייניים לאורחים לגשת לאמולציית המכשיר ישירות.
אפשר להשתמש בהיפרקלות של שיתוף הזיכרון שתיארנו למעלה כדי לשתף מאגרי נתונים של virtio מהאורח למארח, אבל שיתוף כזה מתבצע בהכרח ברמת פירוט של דף, ויכול להוביל לחשיפת יותר נתונים מהנדרש אם גודל המאגר קטן מגודל הדף. במקום זאת, המארח מוגדר להקצות את ה-virtqueues ואת מאגרי הנתונים התואמים שלהם מחלון קבוע של זיכרון משותף, כאשר הנתונים מועתקים (מוחזרים) אל החלון וממנו לפי הצורך.
אינטראקציה עם TrustZone
אורחים לא יכולים לתקשר ישירות עם TrustZone, אבל המארח עדיין צריך להיות מסוגל להנפיק קריאות SMC לעולם המאובטח. הקריאות האלה יכולות לציין מאגרי זיכרון עם כתובת פיזית שלא ניתנים לגישה למארח. בדרך כלל, התוכנה המאובטחת לא מודעת לנגישות של המאגר, ולכן מארח זדוני יכול להשתמש במאגר הזה כדי לבצע התקפת 'סגן מבולבל' (שדומה להתקפת DMA). כדי למנוע מתקפות כאלה, ה-pKVM תופס את כל הקריאות של SMC המארח ל-EL2 ופועל כשרת proxy בין המארח לבין המסך המאובטח ב-EL3.
קריאות PSCI מהמארח מועברות לקושחת EL3 עם שינויים מינימליים. באופן ספציפי, נקודת הכניסה למעבד (CPU) שמתחבר לאינטרנט או שחוזרת מההשעיה משוכתבת, כך שטבלת הדפים של שלב 2 תותקן ב-EL2 לפני חזרה למארח ב-EL1. במהלך ההפעלה, ההגנה הזו נאכפת על ידי pKVM.
הארכיטקטורה הזו מסתמכת על SoC שתומכת ב-PSCI, עדיף בשימוש בגרסה מעודכנת של TF-A כקושחת EL3.
Firmware Framework for Arm (FF-A) מספק תקן לאינטראקציות בין העולם הרגיל לעולם המאובטח, במיוחד בנוכחות של hypervisor מאובטח. חלק גדול מהמפרט מגדיר מנגנון לשיתוף זיכרון עם העולם המאובטח, באמצעות פורמט הודעה נפוץ ומודל הרשאות מוגדר היטב לדפים הבסיסיים. שרת ה-pKVM משמש כשרתי proxy להודעות FF-A כדי לוודא שהמארח לא מנסה לשתף זיכרון עם הצד המאובטח שאין לו הרשאות מספיקות לגביו.
הארכיטקטורה הזו מסתמכת על תוכנת העולם המאובטח שמאכפתת את מודל הגישה לזיכרון, כדי לוודא שאפליקציות מהימנות וכל תוכנה אחרת שפועלת בעולם המאובטח יכולות לגשת לזיכרון רק אם הוא בבעלות בלעדית של העולם המאובטח או אם הוא שותף איתו באופן מפורש באמצעות FF-A. במערכת עם S-EL2, אכיפת מודל הגישה לזיכרון צריכה להתבצע על ידי Secure Partition Manager Core (SPMC), כמו Hafnium, שמנהל טבלאות דפים של שלב 2 לעולם המאובטח. במערכת בלי S-EL2, שלב ה-TEE יכול לאכוף מודל גישה לזיכרון דרך טבלאות הדפים שלו בשלב 1.
אם קריאת ה-SMC ל-EL2 היא לא קריאת PSCI או הודעה מוגדרת של FF-A, קריאות SMC שלא מטופלות מועברות ל-EL3. ההנחה היא שהקושחה המאובטחת (המהימנת) יכולה לטפל ב-SMCs שלא טופלו בבטחה, כי הקושחה מבינה את אמצעי הזהירות הנדרשים כדי לשמור על בידוד ה-pVM.
Virtual machine monitor
crosvm הוא מוניטור למכונה וירטואלית (VMM) שמריץ מכונות וירטואליות דרך ממשק ה-KVM של Linux. מה שמייחד את crosvm הוא ההתמקדות בבטיחות באמצעות שפת התכנות Rust וחול טוב (sandbox) סביב מכשירים וירטואליים כדי להגן על הליבה המארחת. מידע נוסף על crosvm זמין כאן.
מתארי קבצים ויוקטלים
KVM חושף את מכשיר התווים /dev/kvm
למרחב המשתמש באמצעות ioctls שמרכיבים את KVM API. הפונקציות של ioctl שייכות לקטגוריות הבאות:
- System ioctls שולחים שאילתות ומגדירים מאפיינים גלובליים שמשפיעים על כל תת-המערכת של KVM, ויוצרים מכונות וירטואליות פרטיות (pVM).
- קריאות ioctl של מכונות וירטואליות שולחות שאילתות ומגדירות מאפיינים שיוצרים מעבדים וירטואליים (vCPU) ומכשירים, ומשפיעים על כל מכונה וירטואלית (pVM), למשל, כולל פריסת הזיכרון ומספר המעבדים והמכשירים הווירטואליים (vCPU).
- קריאות ioctl של vCPU שולחות שאילתות ומגדירות מאפיינים ששולטים בפעולה של מעבד וירטואלי יחיד.
- שליחת שאילתה על המכשיר ומגדירה מאפיינים ששולטים בפעולה של מכשיר וירטואלי יחיד.
כל תהליך crosvm מפעיל מופע אחד בלבד של מכונה וירטואלית. התהליך הזה משתמש ב-ioctl המערכת KVM_CREATE_VM
כדי ליצור מתאר קובץ של VM שאפשר להשתמש בו כדי להנפיק ioctls של pVM. קריאה ל-ioctl KVM_CREATE_VCPU
או KVM_CREATE_DEVICE
ב-FD של VM יוצרת vCPU או מכשיר ומחזירה מתאר קובץ שמצביע על המשאב החדש. אפשר להשתמש בקריאות ioctl ב-FD של vCPU או מכשיר כדי לשלוט במכשיר שנוצר באמצעות ה-ioctl ב-FD של VM. במעבדי vCPU, המשימה הזו כוללת את המשימה החשובה של הרצת קוד אורח.
באופן פנימי, crosvm רושם את מתארי הקבצים של ה-VM בליבה באמצעות הממשק epoll
שמופעל על ידי קצה. לאחר מכן הליבה תודיע ל-crosvm בכל פעם שיש אירוע חדש בהמתנה באחד מתיאורי הקבצים.
pKVM מוסיף יכולת חדשה, KVM_CAP_ARM_PROTECTED_VM
, שאפשר להשתמש בה כדי לקבל מידע על סביבת ה-pVM ולהגדיר מצב מוגן למכונה וירטואלית. crosvm משתמש ביכולת הזו במהלך יצירת ה-pVM, אם הדגל --protected-vm
מועבר, כדי לשלוח שאילתה ולשמור את כמות הזיכרון המתאימה לקושחה של ה-pVM, ולאחר מכן להפעיל את המצב המוגן.
הקצאת זיכרון
אחת מהאחריותות העיקריות של VMM היא הקצאת הזיכרון של המכונה הווירטואלית וניהול הפריסה של הזיכרון. crosvm יוצר פריסה קבועה של זיכרון, שמתוארת באופן כללי בטבלה שבהמשך.
FDT במצב רגיל | PHYS_MEMORY_END - 0x200000
|
פינוי מקום | ...
|
Ramdisk | ALIGN_UP(KERNEL_END, 0x1000000)
|
ליבה | 0x80080000
|
תוכנת אתחול | 0x80200000
|
FDT במצב BIOS | 0x80000000
|
בסיס הזיכרון הפיזי | 0x80000000
|
קושחה של pVM | 0x7FE00000
|
זיכרון המכשיר | 0x10000 - 0x40000000
|
הזיכרון הפיזי מוקצה באמצעות mmap
, והזיכרון הזה נתרם למכונה הווירטואלית כדי לאכלס את אזורי הזיכרון שלה, שנקראים memslots, באמצעות יוקטל KVM_SET_USER_MEMORY_REGION
. לכן, כל זיכרון ה-pVM של האורח משויך למכונה של ה-crosvm שמנהלת אותו, וכתוצאה מכך התהליך עשוי להתבטל (לסגור את המכונה הווירטואלית) אם המארח מתחיל להיגמר. כשמכונה וירטואלית מופסקת, ה-hypervisor מוחקת את הזיכרון באופן אוטומטי ומחזיר אותו לליבת המארח.
ב-KVM רגיל, ל-VMM יש גישה לכל זיכרון האורח. ב-pKVM, המערכת מבטלת את המיפוי של זיכרון האורח ממרחב הכתובות הפיזי של המארח כשהיא מעבירה אותו לאורחים. היוצא מן הכלל היחיד הוא זיכרון שמשתתף בחזרה על ידי האורח באופן מפורש, למשל במכשירי virtio.
אזורי MMIO במרחב הכתובות של האורח לא ממופים. הגישה של האורח לאזורים האלה נחסמת, וכתוצאה מכך מתרחש אירוע קלט/פלט ב-FD של המכונה הווירטואלית. המנגנון הזה משמש להטמעת מכשירים וירטואליים. במצב מוגן, האורח חייב לאשר באמצעות היפר-קריאה (hypercall) שאזור במרחב הכתובות שלו ישמש ל-MMIO, כדי לצמצם את הסיכון לדליפת מידע בטעות.
תזמון
כל מעבד (CPU) וירטואלי מיוצג על ידי שרשור POSIX ומתוזמן על ידי המתזמן של Linux. השרשור קורא ל-ioctl KVM_RUN
ב-FD של vCPU, וכתוצאה מכך hypervisor עובר להקשר של vCPU האורח. מתזמן המארח מתייחס לזמן שחלף בהקשר של האורח כזמן שבו חוט ה-vCPU התואם היה בשימוש. הפונקציה KVM_RUN
מחזירה אירוע ש-VMM צריך לטפל בו, כמו קלט/פלט, סיום ההפרעה או השבתת vCPU. ה-VMM מטפל באירוע וקורא שוב ל-KVM_RUN
.
במהלך KVM_RUN
, המנהל של המארח יכול להקדים את פעילות השרשור, מלבד בזמן ההפעלה של קוד hypervisor של EL2, שלא ניתן להקדים. ל-pVM של האורח אין מנגנון לשליטה בהתנהגות הזו.
מאחר שכל השרשור של vCPU מתוזמנים כמו כל משימות אחרות במרחב המשתמש, הם כפופים לכל המנגנונים הרגילים של QoS. באופן ספציפי, אפשר לקשר כל שרשור של vCPU למעבדי CPU פיזיים, להציב אותם ביחידות cpuset, להגדיל אותם או להגביל אותם באמצעות צמצום השימוש, לשנות את מדיניות העדיפות/התזמון שלהם ועוד.
מכשירים וירטואליים
ב-crosvm יש תמיכה בכמה מכשירים, כולל המכשירים הבאים:
- virtio-blk לתמונות דיסק מורכבות, לקריאה בלבד או לקריאה וכתיבה
- vhost-vsock לתקשורת עם המארח
- virtio-pci בתור העברת virtio
- שעון זמן אמת (RTC) מסוג pl030
- UART 16550a לתקשורת טורית
קושחה של pVM
קושחת ה-pVM (pvmfw) היא הקוד הראשון שמופעל על ידי pVM, בדומה ל-boot ROM של מכשיר פיזי. המטרה העיקרית של pvmfw היא להפעיל את האתחול של הפעלה מאובטחת ולהפיק את הסוד הייחודי של ה-pVM. השימוש ב-pvmfw לא מוגבל למערכת הפעלה ספציפית, כמו Microdroid, כל עוד מערכת ההפעלה נתמכת על ידי crosvm וחתומה כראוי.
הקובץ הבינארי של pvmfw מאוחסן במחיצת Flash באותו שם, ומתעדכן באמצעות OTA.
הפעלת המכשיר
רצף השלבים הבא מתווסף לתהליך האתחול של מכשיר עם תמיכה ב-pKVM:
- Android Bootloader (ABL) טוען את pvmfw מהמחיצה שלו לזיכרון ומאמת את התמונה.
- ה-ABL מקבל את הסודות של Device Identifier Composition Engine (DICE) (מזהי מכשירים מורכבים (CDI) ורשת האישורים של DICE) מ-Root of Trust.
- ה-ABL מסיק את ה-CDIs הנדרשים ל-pvmfw ומצרף אותם לקובץ הבינארי של pvmfw.
- ABL מוסיף לצומת DT צומת של אזור זיכרון
linux,pkvm-guest-firmware-memory
שמורה, שמתאר את המיקום והגודל של קובץ הבינארי pvmfw ואת הסודות שהתקבלו ממנו בשלב הקודם. - ה-ABL מעביר את השליטה ל-Linux, ו-Linux מאתחלת את pKVM.
- pKVM מבטל את המיפוי של אזור הזיכרון של pvmfw מטבלאות הדפים של השלב השני של המארח, ומגן עליו מפני המארח (והאורחים) במהלך זמן הפעולה של המכשיר.
אחרי אתחול המכשיר, ה-Microdroid מופעל לפי השלבים בקטע רצף אתחול במסמך Microdroid.
אתחול pVM
כשיוצרים pVM, crosvm (או VMM אחר) צריך ליצור משבצת memslot גדולה מספיק כדי שה-hypervisor יאכלס אותה בקובץ האימג' של pvmfw. ה-VMM מוגבל גם ברשימת הרשומות שאפשר להגדיר את הערך הראשוני שלהן (x0-x14 ל-vCPU הראשי, ולא למעבדי vCPU משניים). הרשומות הנותרות שמוגדרות כרשומות שמורות הן חלק מ-ABI של hypervisor-pvmfw.
כשמריצים את ה-pVM, ה-hypervisor שולטת ב-vCPU הראשית באופן ידני ל-pvmfw. הקושחה מצפה ש-crosvm יטען לזיכרון ליבה חתומה ב-AVB, שיכולה להיות מנהל אתחול או כל קובץ אימג' אחר, ו-FDT לא חתום במרווחים ידועים. הפונקציה pvmfw מאמתת את החתימה של AVB, ואם היא מצליחה, יוצרת עץ מכשיר מהימן מה-FDT שהתקבל, מוחקת את הסודות שלו מהזיכרון ומעבירה את השליטה לנקודת הכניסה של עומס העבודה. אם אחד משלב האימות נכשל, הקושחה נותנת קריאה היפר (hypercall) ל-PSCI SYSTEM_RESET
.
בין הפעלות, המידע על מכונה וירטואלית מאוחסן במחיצה (מכשיר virtio-blk) ומוצפן באמצעות הסוד של pvmfw כדי להבטיח שלאחר הפעלה מחדש, הסוד יוקצה למכונה הנכונה.