הטמעת מודול של ספק pKVM

בדף הזה מוסבר איך מטמיעים מודול ספק של מכונה וירטואלית מוגנת מבוססת-ליבה (pKVM).

ב-Android 16-6.12 ואילך, אחרי שמסיימים את השלבים האלה, אמורה להיות לכם היררכיית ספריות שדומה לזו:

BUILD.bazel
el1.c
hyp/
    BUILD.bazel
    el2.c

דוגמה מלאה זמינה במאמר איך בונים מודול pKVM באמצעות DDK.

בגרסה android15-6.6 ובגרסאות קודמות:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. מוסיפים את קוד ההיפר-ויזור EL2 ‏ (el2.c). לכל הפחות, הקוד הזה צריך להצהיר על פונקציית init שמקבלת הפניה למבנה pkvm_module_ops:

    #include <asm/kvm_pkvm_module.h>
    
    int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops)
    {
      /* Init the EL2 code */
    
      return 0;
    }
    

    ה-API של מודול הספק pKVM הוא מבנה שמכיל קריאות חוזרות (callbacks) ל-hypervisor של pKVM. המבנה הזה פועל לפי אותם כללי ABI כמו ממשקי GKI.

  2. יוצרים את hyp/Makefile כדי לבנות את קוד ההיפר-ויזור:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. מוסיפים את קוד הליבה EL1 ‏ (el1.c). קטע ה-init של הקוד הזה חייב להכיל קריאה ל-pkvm_load_el2 module כדי לטעון את קוד ההיפר-ויז'ר EL2 משלב 1.

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kernel.h>
    #include <asm/kvm_pkvm_module.h>
    
    int __kvm_nvhe_pkvm_driver_hyp_init(const struct pkvm_module_ops *ops);
    
    static int __init pkvm_driver_init(void)
    {
        unsigned long token;
    
        return pkvm_load_el2_module(__kvm_nvhe_pkvm_driver_hyp_init, &token);
    }
    module_init(pkvm_driver_init);
    
  4. לבסוף, יוצרים את כללי הבנייה.

    ב-android16-6.12 ואילך, אפשר לעיין במאמר Build a pKVM module with DDK (יצירת מודול pKVM באמצעות DDK) כדי ליצור ddk_library() עבור EL2 ו-ddk_module() עבור EL1.

    ב-android15-6.6 ובגרסאות קודמות, יוצרים את קובץ ה-Makefile הבסיסי כדי לקשר בין הקודים EL1 ו-EL2:

    ifneq ($(KERNELRELEASE),)
    clean-files := hyp/hyp.lds hyp/hyp-reloc.S
    
    obj-m := pkvm_module.o
    pkvm_module-y := el1.o hyp/kvm_nvhe.o
    
    $(PWD)/hyp/kvm_nvhe.o: FORCE
             $(Q)$(MAKE) $(build)=$(obj)/hyp $(obj)/hyp/kvm_nvhe.o
    else
    all:
            make -C $(KDIR) M=$(PWD) modules
    clean:
            make -C $(KDIR) M=$(PWD) clean
    endif
    

טעינת מודול pKVM

בדומה למודולים של ספקים ב-GKI, אפשר לטעון מודולים של ספקים ב-pKVM באמצעות modprobe. עם זאת, מטעמי אבטחה, הטעינה חייבת להתבצע לפני ביטול ההרשאות. כדי לטעון מודול pKVM, צריך לוודא שהמודולים כלולים במערכת הקבצים הבסיסית (initramfs) ולהוסיף את הפקודה הבאה לשורת הפקודה של ליבת המערכת:

kvm-arm.protected_modules=mod1,mod2,mod3,...

מודולים של ספק pKVM שמאוחסנים ב-initramfs מקבלים בירושה את החתימה וההגנה של initramfs.

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

קריאה לפונקציה EL2 (hypervisor) מ-EL1 (מודול ליבה)

קריאה ל-hypervisor‏ (HVC) היא הוראה שמאפשרת לליבת מערכת ההפעלה לקרוא ל-hypervisor. עם ההשקה של מודולי ספקים של pKVM, אפשר להשתמש ב-HVC כדי לקרוא לפונקציה שתפעל ב-EL2 (במודול ההיפר-ויזור) מ-EL1 (מודול הליבה):

  1. בקוד EL2 ‏ (el2.c), מכריזים על ה-handler של EL2:

Android-14

  void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
  {
    /* Handle the call */

    cpu_reg(ctx, 1) = 0;
  }

Android-15

  void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
  {
    /* Handle the call */

    regs->regs[0] = SMCCC_RET_SUCCESS;
    regs->regs[1] = 0;
  }
  1. בקוד EL1 (el1.c), רושמים את ה-handler של EL2 במודול הספק של pKVM:

    int __kvm_nvhe_pkvm_driver_hyp_init(const struct pkvm_module_ops *ops);
    void __kvm_nvhe_pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx); // Android14
    void __kvm_nvhe_pkvm_driver_hyp_hvc(struct user_pt_regs *regs);   // Android15
    
    static int hvc_number;
    
    static int __init pkvm_driver_init(void)
    {
      long token;
      int ret;
    
      ret = pkvm_load_el2_module(__kvm_nvhe_pkvm_driver_hyp_init,token);
      if (ret)
        return ret;
    
      ret = pkvm_register_el2_mod_call(__kvm_nvhe_pkvm_driver_hyp_hvc, token)
      if (ret < 0)
        return ret;
    
      hvc_number = ret;
    
      return 0;
    }
    module_init(pkvm_driver_init);
    
  2. בקוד EL1 (מספר el1.c), קוראים ל-HVC:

    pkvm_el2_mod_call(hvc_number);