pKVM-Anbietermodul implementieren

Auf dieser Seite wird erläutert, wie Sie ein pKVM-Anbietermodul (Protected Kernel-based Virtual Machine, pKVM) implementieren. Wenn Sie diese Schritte ausgeführt haben, sollte Ihr Verzeichnisbaum in etwa so aussehen:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Fügen Sie den EL2-Hypervisorcode (el2.c) hinzu. Dieser Code muss mindestens eine Init-Funktion deklarieren, die einen Verweis auf das pkvm_module_ops-Struktur akzeptiert:

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

    Die API des pKVM-Anbietermoduls ist eine Struktur, die Callbacks an den pKVM-Hypervisor einschließt. Diese Struktur folgt denselben ABI-Regeln wie GKI-Schnittstellen.

  2. Erstellen Sie die hyp/Makefile, um den Hypervisor-Code zu erstellen:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Fügen Sie den EL1-Kernelcode (el1.c) hinzu. Der Init-Abschnitt dieses Codes muss einen Aufruf an pkvm_load_el2 module enthalten, um den EL2-Hypervisorcode aus Schritt 1 zu laden.

    #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. Erstellen Sie abschließend das Root-Makefile, um den EL1- und den EL2-Code miteinander zu verknüpfen:

    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-Modul laden

Wie bei GKI-Anbietermodulen können auch pKVM-Anbietermodule mit modprobe geladen werden. Aus Sicherheitsgründen muss das Laden jedoch vor dem Entfernen der Berechtigungen erfolgen. Wenn Sie ein pKVM-Modul laden möchten, müssen Ihre Module im Stammdateisystem (initramfs) enthalten sein. Außerdem müssen Sie der Kernel-Befehlszeile Folgendes hinzufügen:

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

In initramfs gespeicherte pKVM-Anbietermodule übernehmen die Signatur und den Schutz von initramfs.

Wenn eines der pKVM-Anbietermodule nicht geladen werden kann, wird das System als unsicher eingestuft und es ist nicht möglich, eine geschützte virtuelle Maschine zu starten.

EL2-Funktion (Hypervisor) von EL2 (Kernelmodul) aufrufen

Ein Hypervisor-Aufruf (HVC) ist eine Anweisung, mit der der Kernel den Hypervisor aufrufen kann. Mit der Einführung von pKVM-Anbietermodulen kann ein HVC verwendet werden, um eine Funktion von EL1 (dem Kernelmodul) aus auf EL2 (im Hypervisormodul) auszuführen:

  1. Deklarieren Sie im EL2-Code (el2.c) den EL2-Handler:

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. Registrieren Sie in Ihrem EL1-Code (el1.c) den EL2-Handler in Ihrem pKVM-Anbietermodul:

    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. Rufe in deinem EL1-Code (el1.c) das HVC auf:

    pkvm_el2_mod_call(hvc_number);