Implementare un modulo fornitore pKVM

Questa pagina spiega come implementare un modulo fornitore di macchina virtuale protetta basata su kernel (pKVM). Una volta terminati questi passaggi, dovresti avere un albero di directory simile a:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Aggiungere il codice dell'hypervisor EL2 ( el2.c ). Come minimo, questo codice deve dichiarare una funzione init che accetta un riferimento alla struttura 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;
    }
    

    L'API del modulo del fornitore pKVM è una struttura che incapsula i callback all'hypervisor pKVM. Questa struttura segue le stesse regole ABI delle interfacce GKI.

  2. Crea l' hyp/Makefile per creare il codice dell'hypervisor:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Aggiungi il codice del kernel EL1 ( el1.c ). La sezione init di questo codice deve contenere una chiamata al pkvm_load_el2 module per caricare il codice dell'hypervisor EL2 dal passaggio 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. Infine, crea il makefile root per unire insieme il codice EL1 ed 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
    

Carica un modulo pKVM

Come con i moduli del fornitore GKI, i moduli del fornitore pKVM possono essere caricati utilizzando modprobe. Tuttavia, per ragioni di sicurezza, il caricamento deve avvenire prima del privamento. Per caricare un modulo pKVM, devi assicurarti che i tuoi moduli siano inclusi nel filesystem root ( initramfs ) e devi aggiungere quanto segue alla riga di comando del kernel:

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

I moduli del fornitore pKVM archiviati in initramfs ereditano la firma e la protezione di initramfs .

Se uno dei moduli del fornitore pKVM non riesce a caricarsi, il sistema è considerato non sicuro e non sarà possibile avviare una macchina virtuale protetta.

Chiama una funzione EL2 (hypervisor) da EL2 (modulo kernel)

Una chiamata hypervisor (HVC) è un'istruzione che consente al kernel di chiamare l'hypervisor. Con l'introduzione dei moduli del fornitore pKVM, un HVC può essere utilizzato per richiedere l'esecuzione di una funzione su EL2 (nel modulo hypervisor) da EL1 (il modulo kernel):

  1. Nel codice EL2 ( el2.c ), dichiarare il gestore EL2:

    void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
    {
      /* Handle the call */
    
      cpu_reg(ctx, 1) = 0;
    }
    
  2. Nel tuo codice EL1 ( el1.c ), registra il gestore EL2 nel modulo del fornitore 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. Nel tuo codice EL1 ( el1.c ), chiama l'HVC:

    pkvm_el2_mod_call(hvc_number);