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

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

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. מוסיפים את הקוד של hypervisor של EL2‏ (el2.c). הקוד הזה צריך להצהיר לפחות על פונקציית init שמקבלת הפניה ל-struct‏ 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) למכונה הווירטואלית של pKVM. המבנה הזה פועל לפי אותם כללי ABI כמו ממשקי GKI.

  2. יוצרים את hyp/Makefile כדי ליצור את קוד hypervisor:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. מוסיפים את קוד הליבה של EL1 (el1.c). הקטע ההתחלתי של הקוד חייב להכיל קריאה ל-pkvm_load_el2 module כדי לטעון את קוד ה-hypervisor של 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. לבסוף, יוצרים את קובץ ה-cookie ברמה הבסיסית (root) כדי לקשר בין הקוד 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 (מעבד וירטואלי) מ-EL2 (מודול ליבה)

קריאת hypervisor (HVC) היא הוראה שמאפשרת לליבה (kernel) לקרוא ל-hypervisor. עם ההשקה של מודולים של ספקי pKVM, אפשר להשתמש ב-HVC כדי לקרוא לפונקציה שתפעל ב-EL2 (במודול hypervisor) מ-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);