ติดตั้งใช้งานโมดูลของผู้ให้บริการ pKVM

หน้านี้อธิบายวิธีติดตั้งใช้งานโมดูลของผู้ให้บริการเครื่องเสมือนที่ใช้เคอร์เนลที่ได้รับการปกป้อง (pKVM)

สำหรับ Android 6.12 ขึ้นไป เมื่อทำตามขั้นตอนเหล่านี้เสร็จแล้ว คุณควรมีโครงสร้างไดเรกทอรีที่คล้ายกับตัวอย่างต่อไปนี้

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

ดูตัวอย่างที่สมบูรณ์ได้ที่ สร้างโมดูล pKVM ด้วย DDK

สำหรับ Android 5.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 เป็นโครงสร้างที่ห่อหุ้มการเรียกกลับไปยัง ไฮเปอร์ไวเซอร์ 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. สุดท้าย ให้สร้างกฎการสร้าง

    สำหรับ Android 16-6.12 ขึ้นไป โปรดดู สร้างโมดูล 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 (ไฮเปอร์ไวเซอร์) จาก EL1 (โมดูลเคอร์เนล)

การเรียก Hypervisor (HVC) คือคำสั่งที่อนุญาตให้เคอร์เนลเรียก Hypervisor เมื่อเปิดตัวโมดูลของผู้ให้บริการ pKVM คุณจะใช้ HVC เพื่อเรียกฟังก์ชันให้ทำงานที่ EL2 (ในโมดูลไฮเปอร์ไวเซอร์) จาก EL1 (โมดูลเคอร์เนล) ได้

  1. ในโค้ด EL2 (el2.c) ให้ประกาศตัวแฮนเดิล 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) ให้ลงทะเบียนแฮนเดิล 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);