Implementar um módulo de fornecedor pKVM

Nesta página, explicamos como implementar um módulo de fornecedor de máquina virtual baseada em kernel protegida (pKVM).

Para android16-6.12 e versões mais recentes, depois de concluir essas etapas, você terá uma árvore de diretórios semelhante a esta:

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

Para um exemplo completo, consulte Criar um módulo pKVM com DDK .

Para android15-6.6 e versões anteriores:

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 de inicialização que aceite uma referência à struct 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 de fornecedor de pKVM é uma struct que encapsula callbacks para o hipervisor da pKVM. Essa struct segue as mesmas regras de ABI das interfaces do 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 de inicialização desse código precisa ter uma chamada para pkvm_load_el2 module para 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 as regras de build.

    Para android16-6.12 e versões mais recentes, consulte Criar um módulo pKVM com DDK. Nesse artigo você entende como criar ddk_library() para EL2 e ddk_module() para EL1.

    Para android15-6.6 e versões anteriores, crie o makefile raiz para vincular 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 os módulos de fornecedor de GKI, os módulos de fornecedor de pKVM podem ser carregados usando o modprobe. No entanto, por motivos de segurança, o carregamento precisa ocorrer antes da remoção de privilégios. Para carregar um módulo pKVM, verifique se os módulos estão incluídos no sistema de arquivos raiz (initramfs) e adicione o seguinte à linha de comando do kernel:

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

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

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

Chamar uma função EL2 (hipervisor) de EL1 (módulo do kernel)

Uma chamada de hipervisor (HVC) é uma instrução que permite ao kernel chamar o hipervisor. Com a introdução dos módulos de fornecedor de pKVM, uma HVC pode ser usada para chamar uma função que será executada no EL2 (no módulo do hipervisor) do EL1 (o módulo do kernel):

  1. No código EL2 (el2.c), declare o gerenciador 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 seu código EL1 (el1.c), registre o gerenciador EL2 no módulo de fornecedor de 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. No seu código EL1 (el1.c), faça a HVC:

    pkvm_el2_mod_call(hvc_number);