Implémenter un module fournisseur pKVM

Cette page explique comment implémenter un module fournisseur de machine virtuelle basée sur le noyau (pKVM, protected kernel-based virtual machine). Une fois ces étapes terminées, vous devriez obtenir un arbre de répertoires semblable à celui-ci:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Ajoutez le code d'hyperviseur EL2 (el2.c). Ce code doit au moins déclarer une fonction init acceptant une référence à la structure 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 du module du fournisseur pKVM est une struct encapsulant les rappels de l'hyperviseur pKVM. Ce struct suit les mêmes règles d'ABI que les interfaces GKI.

  2. Créez le hyp/Makefile pour créer le code de l'hyperviseur:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Ajoutez le code du kernel EL1 (el1.c). La section d'initialisation de ce code doit contenir un appel à pkvm_load_el2 module pour charger le code de l'hyperviseur EL2 à partir de l'étape 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. Enfin, créez le fichier racine makefile pour lier le code EL1 et le code 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
    

Charger un module pKVM

Comme pour les modules de fournisseurs GKI, les modules de fournisseurs pKVM peuvent être chargés à l'aide de modprobe. Toutefois, pour des raisons de sécurité, le chargement doit avoir lieu avant l'octroi de droits. Pour charger un module pKVM, vous devez vous assurer que vos modules sont inclus dans le système de fichiers racine (initramfs) et que vous ajoutez les éléments suivants à votre ligne de commande du noyau:

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

Les modules du fournisseur de pKVM stockés dans le initramfs héritent de la signature et de la protection de initramfs.

Si le chargement de l'un des modules du fournisseur de pKVM échoue, le système est considéré comme non sécurisé et il est impossible de démarrer une machine virtuelle protégée.

Appeler une fonction EL2 (hyperviseur) à partir d'EL2 (module noyau)

Un appel d'hyperviseur (HVC) est une instruction qui permet au noyau d'appeler l'hyperviseur. Avec l'introduction des modules du fournisseur pKVM, un HVC peut être utilisé pour appeler une fonction à exécuter à EL2 (dans le module de l'hyperviseur) à partir d'EL1 (le module du noyau):

  1. Dans le code EL2 (el2.c), déclarez le gestionnaire 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. Dans votre code EL1 (el1.c), enregistrez le gestionnaire EL2 dans votre module fournisseur 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. Dans votre code EL1 (el1.c), appelez le HVC:

    pkvm_el2_mod_call(hvc_number);