Внедрить модуль поставщика pKVM

На этой странице объясняется, как реализовать защищенный модуль поставщика виртуальной машины на основе ядра (pKVM). Когда вы закончите эти шаги, у вас должно получиться дерево каталогов, подобное:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Добавьте код гипервизора EL2 ( el2.c ). Как минимум, этот код должен объявить функцию инициализации, принимающую ссылку на структуру 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;
    }
    

    API модуля поставщика pKVM — это структура, инкапсулирующая обратные вызовы к гипервизору pKVM. Эта структура следует тем же правилам ABI, что и интерфейсы GKI.

  2. Создайте hyp/Makefile для сборки кода гипервизора:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Добавьте код ядра EL1 ( el1.c ). Раздел инициализации этого кода должен содержать вызов pkvm_load_el2 module для загрузки кода гипервизора EL2 с шага 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. Наконец, создайте корневой make-файл, чтобы связать код EL1 и 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
    

Загрузите модуль pKVM

Как и модули поставщиков GKI, модули поставщиков pKVM можно загрузить с помощью modprobe. Однако по соображениям безопасности перед лишением привилегий должна произойти загрузка. Чтобы загрузить модуль pKVM, вы должны убедиться, что ваши модули включены в корневую файловую систему ( initramfs ), и вы должны добавить следующее в командную строку вашего ядра:

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

Модули поставщика pKVM, хранящиеся в initramfs наследуют подпись и защиту initramfs .

Если один из модулей производителя pKVM не загружается, система считается небезопасной и запустить защищенную виртуальную машину будет невозможно.

Вызов функции EL2 (гипервизора) из EL2 (модуля ядра)

Вызов гипервизора (HVC) — это инструкция, которая позволяет ядру вызывать гипервизор. С появлением модулей поставщика pKVM HVC можно использовать для вызова функции для запуска на EL2 (в модуле гипервизора) из EL1 (модуля ядра):

  1. В коде EL2 ( el2.c ) объявите обработчик EL2:

Андроид-14

  void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
  {
    /* Handle the call */

    cpu_reg(ctx, 1) = 0;
  }

Андроид-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. В вашем коде EL1 ( el1.c ) зарегистрируйте обработчик EL2 в вашем модуле поставщика 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. В вашем коде EL1 ( el1.c ) вызовите HVC:

    pkvm_el2_mod_call(hvc_number);
    
,

На этой странице объясняется, как реализовать защищенный модуль поставщика виртуальной машины на основе ядра (pKVM). Когда вы закончите эти шаги, у вас должно получиться дерево каталогов, подобное:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Добавьте код гипервизора EL2 ( el2.c ). Как минимум, этот код должен объявить функцию инициализации, принимающую ссылку на структуру 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;
    }
    

    API модуля поставщика pKVM — это структура, инкапсулирующая обратные вызовы к гипервизору pKVM. Эта структура следует тем же правилам ABI, что и интерфейсы GKI.

  2. Создайте hyp/Makefile для сборки кода гипервизора:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Добавьте код ядра EL1 ( el1.c ). Раздел инициализации этого кода должен содержать вызов pkvm_load_el2 module для загрузки кода гипервизора EL2 с шага 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. Наконец, создайте корневой make-файл, чтобы связать код EL1 и 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
    

Загрузите модуль pKVM

Как и модули поставщиков GKI, модули поставщиков pKVM можно загрузить с помощью modprobe. Однако по соображениям безопасности перед лишением привилегий должна произойти загрузка. Чтобы загрузить модуль pKVM, вы должны убедиться, что ваши модули включены в корневую файловую систему ( initramfs ), и вы должны добавить следующее в командную строку вашего ядра:

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

Модули поставщика pKVM, хранящиеся в initramfs наследуют подпись и защиту initramfs .

Если один из модулей производителя pKVM не загружается, система считается небезопасной и запустить защищенную виртуальную машину будет невозможно.

Вызов функции EL2 (гипервизора) из EL2 (модуля ядра)

Вызов гипервизора (HVC) — это инструкция, которая позволяет ядру вызывать гипервизор. С появлением модулей поставщика pKVM HVC можно использовать для вызова функции для запуска на EL2 (в модуле гипервизора) из EL1 (модуля ядра):

  1. В коде EL2 ( el2.c ) объявите обработчик EL2:

Андроид-14

  void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
  {
    /* Handle the call */

    cpu_reg(ctx, 1) = 0;
  }

Андроид-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. В вашем коде EL1 ( el1.c ) зарегистрируйте обработчик EL2 в вашем модуле поставщика 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. В вашем коде EL1 ( el1.c ) вызовите HVC:

    pkvm_el2_mod_call(hvc_number);