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 pKVM é uma struct que encapsula callbacks para o hipervisor 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 conter 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 para criar ddk_library() para EL2 e ddk_module() para EL1.

    Para android15-6.6 e versões anteriores, crie o makefile raiz para vincular o código 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 do fornecedor GKI, os módulos do fornecedor 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 pKVM armazenados em initramfs herdam a assinatura e a proteção de initramfs.

Se um dos módulos do fornecedor do 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 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 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 do fornecedor 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), chame o HVC:

    pkvm_el2_mod_call(hvc_number);