Implementar um módulo de fornecedor pKVM

Esta página explica como implementar um módulo de fornecedor de máquina virtual baseada em kernel (pKVM) protegido. Ao concluir essas etapas, você deverá 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, este código deve declarar uma função init aceitando 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 retornos de chamada para o hipervisor pKVM. Essa estrutura segue as mesmas regras ABI das interfaces GKI.

  2. Crie o hyp/Makefile para construir 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 deste código deve conter uma chamada ao 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 o makefile raiz para unir 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

Tal como acontece com os módulos do fornecedor GKI, os módulos do fornecedor pKVM podem ser carregados usando modprobe. Contudo, por razões de segurança, o carregamento deve ocorrer antes da desprivilegiação. Para carregar um módulo pKVM, você deve garantir que seus módulos estejam incluídos no sistema de arquivos raiz ( initramfs ) e adicionar o seguinte à linha de comando do kernel:

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

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

Se um dos módulos do fornecedor pKVM falhar ao carregar, o sistema será considerado inseguro e não será possível iniciar uma máquina virtual protegida.

Chame uma função EL2 (hipervisor) do EL2 (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 do fornecedor pKVM, um HVC pode ser usado para chamar uma função a ser executada no EL2 (no módulo do hipervisor) a partir do EL1 (o módulo do kernel):

  1. No código EL2 ( el2.c ), declare o manipulador EL2:

    void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
    {
      /* Handle the call */
    
      cpu_reg(ctx, 1) = 0;
    }
    
  2. No seu código EL1 ( el1.c ), registre o manipulador 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);
    
    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);
    
  3. No seu código EL1 ( el1.c ), chame o HVC:

    pkvm_el2_mod_call(hvc_number);