ארכיטקטורת AVF

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 הוא רכיב קטן שמופיע בקוד של Linux, והוא אחראי למעבר בין כמה EL1. רכיב hypervisor מתוזמן באמצעות Linux, אבל נמצא בקטע נפרד ומסור של זיכרון בתמונה vmlinux. ב-pKVM נעשה שימוש בתכנון הזה על ידי הרחבת קוד hypervisor בתכונות חדשות שמאפשרות להטיל הגבלות על הליבה של המארח Android ועל מרחב המשתמש, ולהגביל את הגישה של המארח לזיכרון האורח ול-hypervisor.

מודולים של ספקי pKVM

מודול של ספק pKVM הוא מודול ספציפי לחומרה שמכיל פונקציונליות ספציפית למכשיר, כמו מנהלי יחידת ניהול זיכרון של קלט-פלט (IOMMU). בעזרת המודולים האלה אפשר להעביר ל-pKVM תכונות אבטחה שדורשות גישה ברמת החרגה 2 (EL2).

במאמר הטמעת מודול של ספק pKVM מוסבר איך מטמיעים ומטעינים מודול של ספק pKVM.

תהליך האתחול

באיור הבא מוצג תהליך האתחול של pKVM:

תהליך האתחול של pKVM

איור 1. תהליך האתחול של pKVM

  1. תוכנת האתחול נכנסת לליבה הגנרית ב-EL2.
  2. הליבה הגנרית מזהה שהיא פועלת ב-EL2 ומבטלת את ההרשאות שלה ל-EL1, בזמן ש-pKVM והמודולים שלו ממשיכים לפעול ב-EL2. בנוסף, מודולים של ספקי pKVM נטענים בשלב הזה.
  3. הליבה הגנרית ממשיכה את האתחול באופן רגיל, וטוענת את כל מנהלי ההתקנים הנדרשים עד שמגיעים למרחב המשתמש. בשלב הזה, ה-pKVM מותקן ומטפל בטבלאות הדפים של שלב 2.

תהליך האתחול סומך על תוכנת האתחול כדי לשמור על תקינות תמונת הליבה רק במהלך האתחול המוקדם. כשהרשאות הליבה מבוטלות, היא כבר לא נחשבת מהימנה על ידי hypervisor, שצריך להגן על עצמו גם אם הליבה נפרצה.

העובדה שגרעין Android וה-hypervisor נמצאים באותה תמונה בינארית מאפשרת ליצור ממשק תקשורת הדוק מאוד ביניהם. הצירוף ההדוק הזה מבטיח עדכונים אטומיים של שני הרכיבים, וכך מאפשר לא לשמור על יציבות הממשק ביניהם, ומספק גמישות רבה בלי לפגוע ביכולת לתחזק את המערכת לטווח ארוך. הצירוף ההדוק מאפשר גם לבצע אופטימיזציה של הביצועים כששני הרכיבים יכולים לשתף פעולה, בלי להשפיע על ההתחייבויות לאבטחה שמספק hypervisor.

בנוסף, השימוש ב-GKI בסביבת Android מאפשר לפרוס את hypervisor של pKVM במכשירי Android באופן אוטומטי באותו קובץ בינארי כמו הליבה.

הגנה על הגישה לזיכרון של מעבד (CPU)

בארכיטקטורת Arm מצוין שיחידה לניהול זיכרון (MMU) מחולקת לשני שלבים עצמאיים, ושאפשר להשתמש בשניהם כדי להטמיע תרגום כתובות ובקרת גישה לחלקים שונים בזיכרון. ה-MMU של שלב 1 נשלט על ידי EL1 ומאפשר תרגום כתובות ברמה הראשונה. מערכת Linux משתמשת ב-MMU של שלב 1 כדי לנהל את מרחב הכתובות הווירטואלי שסופק לכל תהליך במרחב המשתמש, וגם את מרחב הכתובות הווירטואלי שלה.

ה-MMU בשלב 2 נשלט על ידי EL2 ומאפשר להחיל תרגום כתובת שני על כתובת הפלט של ה-MMU בשלב 1, וכתוצאה מכך מתקבלת כתובת פיזית (PA). בזכות התרגום בשלב 2, hypervisors יכולים לשלוט בגישה לזיכרון ולתרגם אותה מכל המכונות הווירטואליות של האורחים. כפי שמוצג באיור 2, כששני שלבי התרגום מופעלים, כתובת הפלט של שלב 1 נקראת כתובת פיזית ביניים (IPA). הערה: הכתובת הווירטואלית (VA) מתורגמת ל-IPA ואז ל-PA.

הגנה על הגישה לזיכרון של מעבד (CPU)

איור 2. הגנה על הגישה לזיכרון של המעבד (CPU)

בעבר, 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.

הגנה על גישה לזיכרון DMA

איור 3. הגנה על גישה לזיכרון DMA

לפחות, חומרת 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 כדי להחזיר למארח את הבעלות על דפים מסוימים ששייכים למבצע הקריאה. בפועל, הקריאה הזו ל-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). הארכיטקטורה קובעת שהרשמות האלה תמיד יגרמו ל-trap ל-EL2, בעוד שה-traps האחרים היו עד כה שימושיים רק כדי לצמצם שגיאות. כל השאר מתבצע בחומרה.

בצד ה-MMIO, הכול מומר ב-EL1, תוך שימוש חוזר בכל התשתית הנוכחית ב-KVM. לבסוף, Wait for Interrupt (WFI) תמיד מועבר ל-EL1, כי זו אחת מהפעולות הבסיסיות של תזמון שבהן KVM משתמש.

תמיכה בטיימר

ערך המשווה של הטיימר הווירטואלי חייב להיות חשוף ל-EL1 בכל מעידה של WFI, כדי ש-EL1 יוכל להחדיר הפסקות טיימרים בזמן ש-vCPU חסום. הטיימר הפיזי הוא בתוכנה בלבד, וכל הטריקים מועברים אל EL1.

טיפול ב-MMIO

כדי לתקשר עם צג המכונה הווירטואלית (VMM) ולבצע הדמיה של GIC, צריך להעביר את מלכודות ה-MMIO חזרה למארח ב-EL1 לצורך טיפול נוסף. ל-pKVM נדרשים:

  • IPA וגודל הגישה
  • נתונים במקרה של כתיבת נתונים
  • Endianness של המעבד בנקודת ה-trapping

בנוסף, מלכודות עם מרשם למטרות כלליות (GPR) כמקור/יעד מועברות באמצעות פסאודו-מרשם העברה מופשט.

ממשקי אורחים

אורח יכול לתקשר עם אורח מוגן באמצעות שילוב של היפר-קריאות וגישה לזיכרון באזורים שנלכדו. ה-Hypercalls נחשפים בהתאם לתקן SMCCC, עם טווח ששמור להקצאה של ספק על ידי KVM. הקריאות הבאות ל-hypercall חשובות במיוחד לאורחים ב-pKVM.

היפר-קריאות כלליות

  • ‏PSCI מספק מנגנון סטנדרטי שמאפשר לאורחים לשלוט במחזור החיים של מעבדי ה-vCPU שלהם, כולל הפעלה, השבתה וניתוק מהמערכת.
  • ‏TRNG מספק מנגנון רגיל שבו האורח יכול לבקש אנטרופיה מ-pKVM, שמעביר את הקריאה ל-EL3. המנגנון הזה שימושי במיוחד במקרים שבהם לא ניתן לסמוך על המארח כדי ליצור וירטואליזציה של גנרטור מספרים אקראיים (RNG) בחומרה.

קריאות Hypercall של pKVM

  • שיתוף הזיכרון עם המארח. בהתחלה, המארח לא יכול לגשת לכל הזיכרון של האורח, אבל הגישה של המארח נחוצה לתקשורת בזיכרון משותף ולמכשירים בווירטואליזציה פרה (paravirtualization) שמסתמכים על מאגרי נתונים משותפים. שימוש בהיפרקאלים לשיתוף ולביטול שיתוף של דפים עם המארח מאפשר לאורחים להחליט בדיוק אילו חלקים מהזיכרון יהיו נגישים לשאר חלקי Android, בלי צורך בהקשאה.
  • העברת הזיכרון למארח. בדרך כלל, כל הזיכרון של האורח שייך לו עד שהוא נהרס. המצב הזה עשוי להיות לא מתאים למכונות וירטואליות לטווח ארוך עם דרישות זיכרון שמשתנות לאורך זמן. היפר-קריאה (hypercall) של 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 היא שהמארח יכול לגשת לזיכרון של האורח באופן שרירותי. ההנחה הזו מופיעה בתכנון של virtqueue, שעשוי להכיל הפניות למאגרים (buffers) באורח, שאליהן אמורה לגשת ישירות הדמיית המכשיר.

אפשר להשתמש בהיפרקלות של שיתוף הזיכרון שתיארנו למעלה כדי לשתף מאגרי נתונים של virtio מהאורח למארח, אבל שיתוף כזה מתבצע בהכרח ברמת פירוט של דף, ויכול להוביל לחשיפת יותר נתונים מהנדרש אם גודל המאגר קטן מגודל הדף. במקום זאת, המארח מוגדר להקצות את ה-virtqueues ואת מאגרי הנתונים התואמים שלהם מחלון קבוע של זיכרון משותף, כאשר הנתונים מועתקים (מוחזרים) אל החלון וממנו לפי הצורך.

מכשיר וירטואלי

איור 4. מכשיר Virtio

אינטראקציה עם TrustZone

אורחים לא יכולים לתקשר ישירות עם TrustZone, אבל המארח עדיין צריך להיות מסוגל להנפיק קריאות SMC לעולם המאובטח. הקריאות האלה יכולות לציין מאגרי זיכרון עם כתובת פיזית שאין למארח גישה אליהם. בדרך כלל, התוכנה המאובטחת לא מודעת לנגישות של המאגר, ולכן מארח זדוני יכול להשתמש במאגר הזה כדי לבצע התקפת 'סגן מבולבל' (שדומה להתקפת DMA). כדי למנוע התקפות כאלה, ה-pKVM יוצר מלכודת לכל הקריאות של SMC למארח ב-EL2, ופועל כשרתי proxy בין המארח לבין המסך המאובטח ב-EL3.

קריאות PSCI מהמארח מועברות לקושחת EL3 עם שינויים מינימליים. באופן ספציפי, נקודת הכניסה של מעבד שמחובר לרשת או שממשיך לפעול לאחר השהיה נכתבת מחדש כך שטבלת הדפים של שלב 2 מותקנת ב-EL2 לפני החזרה למארח ב-EL1. במהלך האתחול, ההגנה הזו נאכפת על ידי pKVM.

הארכיטקטורה הזו מסתמכת על תמיכה ב-PSCI ב-SoC, רצוי באמצעות שימוש בגרסה עדכנית של 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 זמין כאן.

תיאור קובץ ו-ioctls

‏KVM חושף את מכשיר התווים /dev/kvm למרחב המשתמש באמצעות ioctls שמרכיבים את KVM API. הפונקציות של ioctl שייכות לקטגוריות הבאות:

  • System ioctls שולחים שאילתות ומגדירים מאפיינים גלובליים שמשפיעים על כל תת-המערכת של KVM, ויוצרים מכונות וירטואליות פרטיות (pVM).
  • קריאות ioctl של מכונות וירטואליות שולחות שאילתות ומגדירות מאפיינים שיוצרים מעבדים וירטואליים (vCPU) ומכשירים, ומשפיעים על כל מכונה וירטואלית (pVM), למשל, כולל פריסת הזיכרון ומספר המעבדים והמכשירים הווירטואליים (vCPU).
  • קריאות ioctl של vCPU שולחות שאילתות ומגדירות מאפיינים ששולטים בפעולה של מעבד וירטואלי יחיד.
  • קריאות ioctls של מכשיר שולחות שאילתות ומגדירות מאפיינים ששולטים בפעולה של מכשיר וירטואלי יחיד.

כל תהליך crosvm מפעיל מכונה וירטואלית אחת בלבד. התהליך הזה משתמש ב-ioctl המערכת KVM_CREATE_VM כדי ליצור מתאר קובץ של VM שאפשר להשתמש בו כדי להנפיק ioctls של pVM. קריאה ל-ioctl‏ KVM_CREATE_VCPU או KVM_CREATE_DEVICE ב-FD של VM יוצרת vCPU או מכשיר ומחזירה מתאר קובץ שמצביע על המשאב החדש. אפשר להשתמש בקריאות ioctl ב-FD של vCPU או מכשיר כדי לשלוט במכשיר שנוצר באמצעות ה-ioctl ב-FD של VM. ב-vCPUs, הפעולה הזו כוללת את המשימה החשובה של הפעלת קוד של אורח.

באופן פנימי, 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, והזיכרון מועבר ל-VM כדי לאכלס את אזורי הזיכרון שלו, שנקראים memslots, באמצעות ה-ioctl‏ KVM_SET_USER_MEMORY_REGION. לכן, כל הזיכרון של ה-pVM של האורח משויך למכונה הווירטואלית של crosvm שמנהלת אותו, ויכול לגרום להפסקת התהליך (סיום ה-VM) אם המארח מתחיל להתרוקן מזיכרון פנוי. כשמכונה וירטואלית מופסקת, ה-hypervisor מוחקת את הזיכרון באופן אוטומטי ומחזיר אותו לליבת המארח.

ב-KVM רגיל, ל-VMM יש גישה לכל זיכרון האורח. ב-pKVM, הזיכרון של האורח לא ממופה ממרחב הכתובות הפיזי של המארח כשהוא מוקצה לאורחים. היוצא מן הכלל היחיד הוא זיכרון שמשתתף בחזרה על ידי האורח באופן מפורש, למשל במכשירי virtio.

אזורי MMIO במרחב הכתובות של האורח לא ממופים. הגישה של האורח לאזורים האלה נחסמת, וכתוצאה מכך מתרחש אירוע קלט/פלט ב-FD של המכונה הווירטואלית. המנגנון הזה משמש להטמעת מכשירים וירטואליים. במצב מוגן, האורח חייב לאשר באמצעות היפר-קריאה (hypercall) שאזור במרחב הכתובות שלו ישמש ל-MMIO, כדי לצמצם את הסיכון לדליפת מידע בטעות.

תזמון

כל מעבד וירטואלי מיוצג על ידי חוט POSIX ומתוזמן על ידי מתזמן Linux של המארח. השרשור קורא ל-ioctl‏ KVM_RUN ב-FD של vCPU, וכתוצאה מכך המסוף עובר להקשר של vCPU האורח. מתזמן המארח מתייחס לזמן שחלף בהקשר של האורח כזמן שבו חוט ה-vCPU התואם היה בשימוש. הפונקציה KVM_RUN מחזירה אירוע ש-VMM צריך לטפל בו, כמו קלט/פלט, סיום ההפרעה או השבתת vCPU. ה-VMM מטפל באירוע וקורא שוב ל-KVM_RUN.

במהלך KVM_RUN, המתוזמן של המארח יכול להפסיק את פעילות השרשור, מלבד במהלך ההפעלה של קוד hypervisor של EL2, שלא ניתן להפסיק. ל-pVM האורח אין מנגנון לשלוט בהתנהגות הזו.

מאחר שכל השרשור של vCPU מתוזמנים כמו כל משימות אחרות במרחב המשתמש, הם כפופים לכל המנגנונים הרגילים של QoS. באופן ספציפי, אפשר להתאים כל חוט של vCPU למעבדים פיזיים, למקם אותו ב-cpusets, לשפר או להגביל את הביצועים שלו באמצעות clamping של ניצול, לשנות את העדיפות או את מדיניות התזמון שלו ועוד.

מכשירים וירטואליים

crosvm תומך במספר מכשירים, כולל:

  • virtio-blk לתמונות דיסק מורכבות, לקריאה בלבד או לקריאה וכתיבה
  • vhost-vsock לתקשורת עם המארח
  • virtio-pci כ-virtio transport
  • שעון זמן אמת (RTC) מסוג pl030
  • UART 16550a לתקשורת טורית

קושחה של pVM

הקושחה של pVM‏ (pvmfw) היא הקוד הראשון שמופעל על ידי pVM, בדומה ל-boot ROM של מכשיר פיזי. המטרה העיקרית של pvmfw היא להפעיל את האתחול של הפעלה מאובטחת ולהפיק את הסוד הייחודי של ה-pVM. השימוש ב-pvmfw לא מוגבל לשימוש עם מערכת הפעלה ספציפית, כמו Microdroid, כל עוד מערכת ההפעלה נתמכת על ידי crosvm וחתומה כראוי.

קובץ ה-binary של pvmfw מאוחסן במחיצה של פלאש באותו שם, והוא מתעדכן באמצעות OTA.

הפעלת המכשיר

רצף השלבים הבא מתווסף לתהליך האתחול של מכשיר עם תמיכה ב-pKVM:

  1. Android Bootloader‏ (ABL) טוען את pvmfw מהמחיצה שלו לזיכרון ומאמת את התמונה.
  2. ה-ABL מקבל את הסודות של Device Identifier Composition Engine‏ (DICE) (מזהי מכשירים מורכבים (CDI) ורשת האישורים של DICE) מ-Root of Trust.
  3. ה-ABL מסיק את ה-CDIs הנדרשים ל-pvmfw ומצרף אותם לקובץ הבינארי של pvmfw.
  4. ה-ABL מוסיף לצומת DT צומת של אזור זיכרון linux,pkvm-guest-firmware-memory שמורה, שמתאר את המיקום והגודל של קובץ הבינארי pvmfw ואת הסודות שהתקבלו ממנו בשלב הקודם.
  5. ה-ABL מעביר את השליטה ל-Linux, ו-Linux מאתחלת את pKVM.
  6. ‏pKVM מבטל את המיפוי של אזור הזיכרון של pvmfw מטבלאות הדפים של שלב 2 במארח, ומגן עליו מפני המארח (והאורחים) במהלך זמן פעולה תקינה של המכשיר.

אחרי הפעלת המכשיר, Microdroid מופעל לפי השלבים שמפורטים בקטע Boot sequence במסמך Microdroid.

אתחול של pVM

כשיוצרים pVM, ‏crosvm (או VMM אחר) צריך ליצור משבצת memslot גדולה מספיק כדי שה-hypervisor יאכלס אותה בקובץ האימג' של pvmfw. בנוסף, ל-VMM יש הגבלה ברשימת הרשומות שהוא יכול להגדיר את הערך הראשוני שלהן (x0-x14 ל-vCPU הראשי, אף אחת ל-vCPUs משניים). הרשומות הנותרות שמוגדרות כרשומות שמורות הן חלק מ-ABI של hypervisor-pvmfw.

כשמפעילים את ה-pVM, ה-hypervisor מעביר קודם את השליטה ב-vCPU הראשי ל-pvmfw. הקושחה מצפה ש-crosvm יטען לזיכרון ליבה חתומה ב-AVB, שיכולה להיות מנהל אתחול או כל קובץ אימג' אחר, ו-FDT לא חתום במרווחים ידועים. הפונקציה pvmfw מאמתת את החתימה של AVB, ואם היא מצליחה, יוצרת עץ מכשיר מהימן מה-FDT שהתקבל, מוחקת את הסודות שלו מהזיכרון ומעבירה את השליטה לנקודת הכניסה של עומס העבודה. אם אחד משלב האימות נכשל, הקושחה נותנת קריאה היפר (hypercall) ל-PSCI SYSTEM_RESET.

בין הפעלות, המידע על מכונה וירטואלית מאוחסן במחיצה (מכשיר virtio-blk) ומוצפן באמצעות הסוד של pvmfw, כדי להבטיח שהסוד יוקצה למכונה הנכונה אחרי הפעלה מחדש.