Cette page explique comment implémenter un module fournisseur de machine virtuelle protégée basée sur le noyau (pKVM).
Pour android16-6.12 et versions ultérieures, une fois ces étapes effectuées, vous devriez obtenir une arborescence de répertoires semblable à celle-ci :
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Pour obtenir un exemple complet, consultez Créer un module pKVM avec DDK.
Pour android15-6.6 et versions antérieures :
Makefile
el1.c
hyp/
Makefile
el2.c
Ajoutez le code de l'hyperviseur EL2 (
el2.c
). Au minimum, ce code doit déclarer une fonction d'initialisation acceptant une référence à la structurepkvm_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 fournisseur pKVM est une structure encapsulant les rappels de l'hyperviseur pKVM. Cette structure suit les mêmes règles ABI que les interfaces GKI.
Créez le
hyp/Makefile
pour compiler le code de l'hyperviseur :hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
Ajoutez le code du noyau 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 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);
Enfin, créez les règles de compilation.
Pour android16-6.12 et versions ultérieures, consultez Créer un module pKVM avec DDK pour créer
ddk_library()
pour EL2 etddk_module()
pour EL1.Pour android15-6.6 et les versions antérieures, créez le fichier makefile racine pour relier le code EL1 et 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 fournisseur GKI, les modules fournisseur pKVM peuvent être chargés à l'aide de modprobe.
Toutefois, pour des raisons de sécurité, le chargement doit avoir lieu avant la suppression des privilèges.
Pour charger un module pKVM, vous devez vous assurer que vos modules sont inclus dans le système de fichiers racine (initramfs
) et vous devez ajouter les éléments suivants à votre ligne de commande du noyau :
kvm-arm.protected_modules=mod1,mod2,mod3,...
Les modules du fournisseur pKVM stockés dans initramfs
héritent de la signature et de la protection de initramfs
.
Si l'un des modules du fournisseur pKVM ne parvient pas à se charger, le système est considéré comme non sécurisé et il ne sera pas possible de démarrer une machine virtuelle protégée.
Appeler une fonction EL2 (hyperviseur) depuis EL1 (module du noyau)
Un appel d'hyperviseur (HVC) est une instruction qui permet au noyau d'appeler l'hyperviseur. Avec l'introduction des modules de fournisseur pKVM, un HVC peut être utilisé pour demander l'exécution d'une fonction au niveau EL2 (dans le module d'hyperviseur) à partir du niveau EL1 (le module du noyau) :
- 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;
}
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);
Dans votre code EL1 (
el1.c
), appelez le HVC :pkvm_el2_mod_call(hvc_number);