En esta página, se explica cómo implementar un módulo de proveedor de máquina virtual basada en kernel (pKVM) protegida. Cuando hayas terminado con estos pasos, deberías tener un árbol de directorios similar al siguiente:
Makefile
el1.c
hyp/
Makefile
el2.c
Agrega el código de hipervisor EL2 (
el2.c
). Como mínimo, este código debe declarar una función init que acepte una referencia al structpkvm_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; }
La API del módulo de proveedor de la pKVM es una struct que encapsula devoluciones de llamada al Hipervisor de la pKVM. Esta struct sigue las mismas reglas de ABI que las interfaces de GKI.
Crea el
hyp/Makefile
para compilar el código del hipervisor:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
Agrega el código del kernel de EL1 (
el1.c
). La sección init de este código debe contener una llamada apkvm_load_el2 module
para cargar el código del hipervisor EL2 del paso 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);
Por último, crea el archivo makefile raíz para vincular el código EL1 y 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
Carga un módulo pKVM
Al igual que con los módulos de proveedores de GKI, los módulos de proveedores de la pKVM se pueden cargar usando modprobe.
Sin embargo, por motivos de seguridad, la carga debe ocurrir antes de quitar los privilegios.
Para cargar un módulo de la pKVM, debes asegurarte de que tus módulos estén incluidos en
el sistema de archivos raíz (initramfs
) y debes agregar lo siguiente a tu
kernel:
kvm-arm.protected_modules=mod1,mod2,mod3,...
Los módulos de proveedores de la pKVM almacenados en initramfs
heredan la firma y la protección de initramfs
.
Si uno de los módulos de proveedores de la pKVM no se carga, el sistema se considera inseguro y no será posible iniciar una máquina virtual protegida.
Llama a una función EL2 (hipervisor) desde EL2 (módulo del kernel)
Una llamada a un hipervisor (HVC) es una instrucción que permite que el kernel lo llame. Con la introducción de los módulos de proveedores de la pKVM, se puede usar un HVC para llamar a que una función se ejecute en EL2 (en el módulo del hipervisor) desde EL1 (el módulo del kernel):
- En el código EL2 (
el2.c
), declara el controlador 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;
}
En tu código EL1 (
el1.c
), registra el controlador EL2 en tu proveedor de pKVM. módulo: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);
En el código EL1 (
el1.c
), llama al HVC:pkvm_el2_mod_call(hvc_number);