Zaimplementuj moduł dostawcy pKVM

Na tej stronie wyjaśniono, jak wdrożyć moduł dostawcy chronionej maszyny wirtualnej opartej na jądrze (pKVM). Po wykonaniu tych kroków powinieneś mieć drzewo katalogów podobne do:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Dodaj kod hypervisora ​​EL2 ( el2.c ). Ten kod musi co najmniej zadeklarować funkcję init akceptującą odwołanie do struktury 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;
    }
    

    Interfejs API modułu dostawcy pKVM to struktura zawierająca wywołania zwrotne do hiperwizora pKVM. Ta struktura podlega tym samym regułom ABI, co interfejsy GKI.

  2. Utwórz plik hyp/Makefile aby zbudować kod hypervisora:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Dodaj kod jądra EL1 ( el1.c ). Sekcja init tego kodu musi zawierać wywołanie pkvm_load_el2 module w celu załadowania kodu hypervisora ​​EL2 z kroku 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. Na koniec utwórz główny plik makefile, aby powiązać ze sobą kody EL1 i 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
    

Załaduj moduł pKVM

Podobnie jak w przypadku modułów dostawców GKI, moduły dostawców pKVM można ładować za pomocą modprobe. Jednak ze względów bezpieczeństwa ładowanie musi nastąpić przed pozbawieniem uprawnień. Aby załadować moduł pKVM, musisz upewnić się, że moduły są zawarte w głównym systemie plików ( initramfs ) i musisz dodać następujące polecenie do wiersza poleceń jądra:

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

Moduły dostawcy pKVM przechowywane w initramfs dziedziczą sygnaturę i ochronę initramfs .

Jeśli nie uda się załadować jednego z modułów dostawcy pKVM, system zostanie uznany za niebezpieczny i nie będzie możliwe uruchomienie chronionej maszyny wirtualnej.

Wywołaj funkcję EL2 (hypervisor) z EL2 (moduł jądra)

Wywołanie hypervisora ​​(HVC) to instrukcja, która pozwala jądru wywołać hypervisor. Wraz z wprowadzeniem modułów dostawców pKVM, można użyć HVC do wywołania funkcji uruchamianej w EL2 (w module hypervisora) z EL1 (modułu jądra):

  1. W kodzie EL2 ( el2.c ) zadeklaruj procedurę obsługi EL2:

    void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
    {
      /* Handle the call */
    
      cpu_reg(ctx, 1) = 0;
    }
    
  2. W swoim kodzie EL1 ( el1.c ) zarejestruj procedurę obsługi EL2 w module dostawcy 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);
    
    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);
    
  3. W kodzie EL1 ( el1.c ) wywołaj HVC:

    pkvm_el2_mod_call(hvc_number);