Implementa un módulo de proveedores de la pKVM

En esta página, se explica cómo implementar un módulo del proveedor de máquinas virtuales protegidas basadas en el kernel (pKVM).

Para android16-6.12 y versiones posteriores, cuando termines con estos pasos, deberías tener un árbol de directorios similar al siguiente:

BUILD.bazel
el1.c
hyp/
    BUILD.bazel
    el2.c

Para ver un ejemplo completo, consulta Cómo compilar un módulo de pKVM con el DDK.

Para android15-6.6 y versiones anteriores:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Agrega el código del hipervisor EL2 (el2.c). Como mínimo, este código debe declarar una función init que acepte una referencia a la estructura 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;
    }
    

    La API del módulo de proveedores de la pKVM es una estructura que encapsula devoluciones de llamada al hipervisor de la pKVM. Esta estructura sigue las mismas reglas de ABI que las interfaces de GKI.

  2. 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
    
  3. Agrega el código del kernel de EL1 (el1.c). La sección de inicialización de este código debe contener una llamada a pkvm_load_el2 module para cargar el código del hipervisor de 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);
    
  4. Por último, crea las reglas de compilación.

    Para android16-6.12 y versiones posteriores, consulta Cómo compilar un módulo de pKVM con el DDK para crear ddk_library() para EL2 y ddk_module() para EL1.

    Para android15-6.6 y versiones anteriores, crea el archivo makefile raíz para vincular el código de 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
    

Cómo cargar un módulo de pKVM

Al igual que con los módulos del proveedor de GKI, los módulos del proveedor de pKVM se pueden cargar con modprobe. Sin embargo, por motivos de seguridad, la carga debe ocurrir antes de la reducción de privilegios. Para cargar un módulo de 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 la línea de comandos del 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 del proveedor de pKVM no se carga, el sistema se considera inseguro y no será posible iniciar una máquina virtual protegida.

Llama a una función de EL2 (hipervisor) desde EL1 (módulo del kernel)

Una llamada al hipervisor (HVC) es una instrucción que permite que el kernel llame al hipervisor. Con la introducción de los módulos de proveedores de la pKVM, se puede usar un HVC para llamar a una función que se ejecute en EL2 (en el módulo del hipervisor) desde EL1 (el módulo del kernel):

  1. En el código de EL2 (el2.c), declara el controlador de 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. En tu código de EL1 (el1.c), registra el controlador de EL2 en tu módulo de proveedor de la 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. En tu código de EL1 (el1.c), llama al HVC:

    pkvm_el2_mod_call(hvc_number);