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 abgeschlossen haben, sollte in etwa eine Verzeichnisstruktur wie diese aussehen:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Fügen Sie den EL2-Hypervisor-Code (el2.c) hinzu. Dieser Code muss mindestens eine Init-Funktion deklarieren, die einen Verweis auf die Struktur pkvm_module_ops 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 Rückrufe an die pKVM-Hypervisor. Diese Struktur folgt denselben ABI-Regeln wie GKI-Schnittstellen.

  2. Erstellen Sie den 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-Kernel-Code (el1.c) hinzu. Der Init-Abschnitt dieses Codes muss einen Aufruf von pkvm_load_el2 module enthalten, um den EL2-Hypervisor-Code 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
    

Ein pKVM-Modul laden

Wie die GKI-Anbietermodule können pKVM-Anbietermodule mit „modprobe“ geladen werden. Aus Sicherheitsgründen muss der Ladevorgang jedoch vor dem Deaktivieren der Berechtigungen erfolgen. Um ein pKVM-Modul zu laden, müssen Sie darauf achten, dass Ihre Module in das Root-Dateisystem (initramfs). Sie müssen Folgendes zu Ihrem Kernel-Befehlszeile:

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 wird, gilt das System als unsicher und es ist nicht möglich, eine geschützte virtuelle Maschine zu starten.

EL2-Funktion (Hypervisor) aus 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 zur Ausführung bei EL2 (im Hypervisor-Modul) von EL1 (dem Kernelmodul) aufzurufen:

  1. Deklariere 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 im EL1-Code (el1.c) den EL2-Handler bei Ihrem pKVM-Anbieter Modul:

    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) den HVC auf:

    pkvm_el2_mod_call(hvc_number);