Implementar um módulo de fornecedor pKVM

Esta página explica como implementar um módulo de fornecedor de máquina virtual protegida com base em kernel (pKVM). Ao concluir essas etapas, você terá uma árvore de diretórios semelhante a:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Adicione o código do hipervisor EL2 (el2.c). No mínimo, esse código precisa declarar uma função init que aceita uma referência à estrutura 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;
    }
    

    A API do módulo do fornecedor pKVM é uma estrutura que encapsula os callbacks para o pKVM. Esse struct segue as mesmas regras de ABI das interfaces GKI.

  2. Crie o hyp/Makefile para criar o código do hipervisor:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Adicione o código do kernel EL1 (el1.c). A seção init do código precisa conter uma chamada para pkvm_load_el2 module a fim de carregar o código do hipervisor EL2 da etapa 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 fim, crie o makefile raiz para unir os códigos EL1 e 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
    

Carregar um módulo pKVM

Assim como nos módulos de fornecedor de GKI, os módulos de fornecedor pKVM podem ser carregados usando modprobe. No entanto, por motivos de segurança, o carregamento precisa ocorrer antes da remoção. Para carregar um módulo pKVM, verifique se os módulos estão incluídos no no sistema de arquivos raiz (initramfs) e adicione o seguinte ao seu linha de comando do kernel:

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

Os módulos do fornecedor pKVM armazenados em initramfs herdam a assinatura e a proteção de initramfs.

Se um dos módulos do fornecedor pKVM não for carregado, o sistema será considerado não seguro, e não será possível iniciar uma máquina virtual protegida.

Chamar uma função EL2 (hipervisor) no EL2 (módulo kernel)

Uma chamada de hipervisor (HVC, na sigla em inglês) é uma instrução que permite que o kernel chame o hipervisor. Com a introdução dos módulos do fornecedor pKVM, um HVC pode ser usado para chamar uma função para ser executada no EL2 (no módulo do hipervisor) do EL1 (o módulo do kernel):

  1. No código do EL2 (el2.c), declare o gerenciador do 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. No código EL1 (el1.c), registre o gerenciador EL2 no fornecedor 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);
    
  2. No código EL1 (el1.c), chame o HVC:

    pkvm_el2_mod_call(hvc_number);