Wdróż moduł dostawcy pKVM

Na tej stronie wyjaśniamy, jak zaimplementować chroniony moduł dostawcy maszyny wirtualnej działającej na jądrze (pKVM). Po wykonaniu tych czynności powinna pojawić się struktura katalogu podobna do tej:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Dodaj kod hypervisora EL2 (el2.c). Ten kod musi co najmniej deklarować funkcję init, która przyjmuje 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 hypervisora pKVM. Ta struktura podlega tym samym regułom ABI co interfejsy GKI.

  2. Utwórz hyp/Makefile, aby skompilować 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, aby załadować kod hypervisora EL2 z etapu 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 plik make w katalogu głównym, aby połączyć kod 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
    

Wczytywanie modułu pKVM

Podobnie jak w przypadku modułów dostawców GKI, moduły dostawców pKVM można wczytywać za pomocą modprobe. Ze względów bezpieczeństwa wczytywanie musi jednak nastąpić przed odebraniem uprawnień. Aby załadować moduł pKVM, musisz się upewnić, że Twoje moduły są uwzględnione w systemie plików głównym (initramfs), a następnie dodać do wiersza poleceń jądra następujące polecenie:

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

Moduły dostawców pKVM przechowywane w initramfs dziedziczą podpis i ochronę od initramfs.

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

Wywołanie funkcji EL2 (hypervisor) z poziomu EL1 (moduł jądra)

Wywołanie hypervisora (HVC) to instrukcja, która pozwala jądro na wywołanie hypervisora. Dzięki wprowadzeniu modułów dostawcy pKVM można użyć modułu HVC do wywołania funkcji do wykonania na poziomie EL2 (w module hiperwizora) z poziomu EL1 (moduł jądra):

  1. W kodzie EL2 (el2.c) zadeklaruj moduł obsługi 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. W kodzie EL1 (el1.c) zarejestruj moduł 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); // 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. W przypadku kodu EL1 (el1.c) zadzwoń do HVC:

    pkvm_el2_mod_call(hvc_number);