Mengimplementasikan modul vendor pKVM

Halaman ini menjelaskan cara menerapkan modul vendor virtual machine berbasis kernel (pKVM) yang dilindungi.

Untuk android16-6.12 dan yang lebih baru, setelah menyelesaikan langkah-langkah ini, Anda akan memiliki pohon direktori yang mirip dengan:

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

Untuk contoh lengkapnya, lihat Membangun modul pKVM dengan DDK .

Untuk android15-6.6 dan yang lebih lama:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. Tambahkan kode hypervisor EL2 (el2.c). Setidaknya, kode ini harus mendeklarasikan fungsi init yang menerima referensi ke 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;
    }
    

    API modul vendor pKVM adalah struct yang merangkum callback ke hypervisor pKVM. Struktur ini mengikuti aturan ABI yang sama dengan antarmuka GKI.

  2. Buat hyp/Makefile untuk membangun kode hypervisor:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. Tambahkan kode kernel EL1 (el1.c). Bagian init kode ini harus berisi panggilan ke pkvm_load_el2 module untuk memuat kode hypervisor EL2 dari langkah 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. Buat aturan build.

    Untuk android16-6.12 dan yang lebih baru, lihat Membangun modul pKVM dengan DDK untuk membuat ddk_library() untuk EL2 dan ddk_module() untuk EL1.

    Untuk android15-6.6 dan yang lebih lama, buat file makefile root untuk mengikat kode EL1 dan 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
    

Memuat modul pKVM

Seperti modul vendor GKI, modul vendor pKVM dapat dimuat menggunakan modprobe. Namun, karena alasan keamanan, pemuatan harus terjadi sebelum penghapusan hak istimewa. Untuk memuat modul pKVM, Anda harus memastikan modul disertakan dalam sistem file root (initramfs) dan Anda harus menambahkan kode berikut ke command line kernel:

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

Modul vendor pKVM yang disimpan di initramfs mewarisi tanda tangan dan perlindungan initramfs.

Jika salah satu modul vendor pKVM gagal dimuat, sistem dianggap tidak aman dan mesin virtual yang dilindungi tidak dapat dimulai.

Memanggil fungsi EL2 (hypervisor) dari EL1 (modul kernel)

Panggilan hypervisor (HVC) adalah instruksi yang memungkinkan kernel memanggil hypervisor. Dengan diperkenalkannya modul vendor pKVM, HVC dapat digunakan untuk memanggil fungsi agar berjalan di EL2 (dalam modul hypervisor) dari EL1 (modul kernel):

  1. Dalam kode EL2 (el2.c), deklarasikan pengendali EL2:

Android 14

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

     cpu_reg(ctx, 1) = 0;
   }

Android 15 atau yang lebih tinggi

   void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
   {
     /* Handle the call */

     regs->regs[0] = SMCCC_RET_SUCCESS;
     regs->regs[1] = 0;
   }
  1. Dalam kode EL1 Anda (el1.c), daftarkan handler EL2 di modul vendor pKVM Anda:

    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. Dalam kode EL1 (el1.c), panggil HVC:

    pkvm_el2_mod_call(hvc_number);
    

Men-debug dan memprofilkan kode EL2

Bagian ini berisi beberapa opsi untuk men-debug kode EL2 modul pKVM.

Memancarkan dan membaca peristiwa rekaman aktivitas hypervisor

Tracefs mendukung hypervisor pKVM. Pengguna root memiliki akses ke antarmuka, yang berada di /sys/kernel/tracing/hypervisor/:

  • tracing_on: Mengaktifkan atau menonaktifkan pelacakan.
  • trace: Menulis ke file ini akan mereset rekaman aktivitas.
  • trace_pipe: Membaca file ini akan mencetak peristiwa hypervisor.
  • buffer_size_kb: Ukuran buffer per-CPU yang menyimpan peristiwa. Tingkatkan nilai ini jika peristiwa hilang.

Secara default, peristiwa dinonaktifkan. Untuk mengaktifkan peristiwa, gunakan file /sys/kernel/tracing/hypervisor/events/my_event/enable yang sesuai di Tracefs. Anda juga dapat mengaktifkan peristiwa hypervisor apa pun saat waktu booting dengan command line kernel hyp_event=event1,event2.

Sebelum mendeklarasikan peristiwa, kode EL2 modul harus mendeklarasikan boilerplate berikut, dengan pkvm_ops adalah struct pkvm_module_ops * yang diteruskan ke fungsi init modul:

  #include "events.h"
  #define HYP_EVENT_FILE ../../../../relative/path/to/hyp/events.h
  #include <nvhe/define_events.h>

  #ifdef CONFIG_TRACING
  void *tracing_reserve_entry(unsigned long length)
  {
      return pkvm_ops->tracing_reserve_entry(length);
  }

  void tracing_commit_entry(void)
  {
      pkvm_ops->tracing_commit_entry();
  }
  #endif

Mendeklarasikan acara

Deklarasikan peristiwa dalam file .h-nya sendiri:

  $ cat hyp/events.h
  #if !defined(__PKVM_DRIVER_HYPEVENTS_H_) || defined(HYP_EVENT_MULTI_READ)
  #define __PKVM_DRIVER_HYPEVENTS_H_

  #ifdef __KVM_NVHE_HYPERVISOR__
  #include <nvhe/trace.h>
  #endif

  HYP_EVENT(pkvm_driver_event,
          HE_PROTO(u64 id),
          HE_STRUCT(
                  he_field(u64, id)
          ),
          HE_ASSIGN(
                  __entry->id = id;
          ),
          HE_PRINTK("id=0x%08llx", __entry->id)
  );
  #endif

Memancarkan peristiwa

Anda dapat mencatat peristiwa dalam kode EL2 dengan memanggil fungsi C yang dihasilkan:

  trace_pkvm_driver_event(id);

Menambahkan pendaftaran tambahan (Android 15 atau yang lebih rendah)

Untuk Android 15 dan yang lebih rendah, sertakan pendaftaran tambahan selama inisialisasi modul. Hal ini tidak diperlukan di Android 16 dan yang lebih tinggi.

  #ifdef CONFIG_TRACING
  extern char __hyp_event_ids_start[];
  extern char __hyp_event_ids_end[];
  #endif

  int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops)
  {
  #ifdef CONFIG_TRACING
      ops->register_hyp_event_ids((unsigned long)__hyp_event_ids_start,
                                        (unsigned long)__hyp_event_ids_end);
  #endif

      /* init module ... */

      return 0;
  }

Memancarkan peristiwa tanpa deklarasi sebelumnya (Android 16 dan yang lebih tinggi)

Mendeklarasikan peristiwa bisa merepotkan untuk proses debug cepat. trace_hyp_printk() memungkinkan pemanggil meneruskan hingga empat argumen ke string format tanpa deklarasi peristiwa:

  trace_hyp_printk("This is my debug");
  trace_hyp_printk("This is my variable: %d", (int)foo);
  trace_hyp_printk("This is my address: 0x%llx", phys);

Boilerplate dalam kode EL2 juga diperlukan. trace_hyp_printk() adalah makro yang memanggil fungsi trace___hyp_printk():

  #include <nvhe/trace.h>

  #ifdef CONFIG_TRACING
  void trace___hyp_printk(u8 fmt_id, u64 a, u64 b, u64 c, u64 d)
  {
          pkvm_ops->tracing_mod_hyp_printk(fmt_id, a, b, c, d);
  }
  #endif

Aktifkan peristiwa __hyp_printk di /sys/kernel/tracing/hypervisor/events/ atau saat waktu booting dengan command line kernel hyp_event=__hyp_printk.

Mengarahkan peristiwa ke dmesg

Parameter command line kernel hyp_trace_printk=1 membuat antarmuka pelacakan hypervisor meneruskan setiap peristiwa yang dicatat ke dmesg kernel. Hal ini berguna untuk membaca peristiwa saat trace_pipe tidak dapat diakses.

Mengekspor peristiwa selama kernel panik (Android 16 dan yang lebih baru)

Peristiwa hypervisor di-polling. Oleh karena itu, ada jeda antara polling terakhir dan kernel panic saat peristiwa telah dipancarkan, tetapi tidak di-dump ke konsol. Opsi konfigurasi kernel CONFIG_PKVM_DUMP_TRACE_ON_PANIC mencoba mencatat peristiwa terbaru di konsol jika hyp_trace_printk telah diaktifkan.

Opsi ini dinonaktifkan secara default untuk GKI.

Menggunakan Ftrace untuk melacak panggilan dan kembalinya fungsi (Android 16 dan yang lebih tinggi)

Ftrace adalah fitur kernel yang memungkinkan Anda melacak setiap panggilan dan kembalinya fungsi. Dengan cara yang sama, hypervisor pKVM menawarkan dua peristiwa func dan func_ret.

Anda dapat memilih fungsi yang dilacak dengan command line kernel hyp_ftrace_filter= atau dengan salah satu file tracefs:

  • /sys/kernel/tracing/hypervisor/set_ftrace_filter
  • /sys/kernel/tracing/hypervisor/set_ftrace_notrace

Filter menggunakan pencocokan glob gaya shell.

Filter berikut melacak fungsi yang dimulai dengan pkvm_hyp_driver:

  echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter

Acara func dan func_ret hanya tersedia dengan CONFIG_PKVM_FTRACE=y. Opsi ini dinonaktifkan secara default untuk GKI.