Implementazione di un modulo del fornitore pKVM

Questa pagina spiega come implementare un modulo del fornitore di una macchina virtuale protetta basata su kernel (pKVM). Al termine di questi passaggi, dovresti visualizzare una struttura di directory simile alla seguente:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Aggiungi il codice hypervisor EL2 (el2.c). Come minimo, questo codice deve dichiarare una funzione init che accetta un riferimento allo struct 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 fornitore pKVM è uno struct che incapsula i callback hypervisor pKVM. Questo struct segue le stesse regole ABI delle interfacce GKI.

  2. Crea 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 kernel EL1 (el1.c). La sezione init di questo codice deve contenere una chiamata a pkvm_load_el2 module per caricare il codice dell'hypervisor EL2 del 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 principale per collegare 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 per i moduli del fornitore GKI, i moduli del fornitore pKVM possono essere caricati utilizzando modprobe. Tuttavia, per motivi di sicurezza, il caricamento deve avvenire prima del deprivilegio. Per caricare un modulo pKVM, devi assicurarti che i moduli siano inclusi in il file system radice (initramfs) e devi aggiungere quanto segue al tuo riga di comando 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 viene caricato, il sistema viene considerato non sicuro e non sarà possibile avviare una macchina virtuale protetta.

Richiama 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 richiamare una funzione da eseguire su EL2 (nel modulo hypervisor) da EL1 (il modulo del kernel):

  1. Nel codice EL2 (el2.c), dichiara il gestore 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. Nel codice EL1 (el1.c), registra il gestore EL2 nel tuo fornitore pKVM modulo:

    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. Nel codice EL1 (el1.c), chiama l'HVC:

    pkvm_el2_mod_call(hvc_number);