ติดตั้งใช้งานโมดูลของผู้ให้บริการ pKVM

หน้านี้จะอธิบายวิธีใช้โมดูลผู้ให้บริการเครื่องเสมือน (pKVM) ที่ใช้เคอร์เนลที่ได้รับการปกป้อง

สำหรับ android16-6.12 ขึ้นไป เมื่อทำตามขั้นตอนเหล่านี้เสร็จแล้ว คุณควรมีแผนผังไดเรกทอรีที่คล้ายกับแผนผังด้านล่าง

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

ดูตัวอย่างฉบับเต็มได้ที่ สร้างโมดูล pKVM ด้วย DDK .

สำหรับ android15-6.6 ลงไป ให้ทำดังนี้

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. เพิ่มโค้ดไฮเปอร์ไวเซอร์ EL2 (el2.c) โค้ดนี้ต้องประกาศฟังก์ชัน init ที่ยอมรับการอ้างอิงไปยังโครงสร้าง 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) ส่วน init ของโค้ดนี้ต้องมีการเรียก 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. สร้างกฎการสร้าง

    สำหรับ android16-6.12 ขึ้นไป โปรดดู สร้างโมดูล pKVM ด้วย DDK เพื่อสร้าง ddk_library() สำหรับ EL2 และ ddk_module() สำหรับ EL1

    สำหรับ android15-6.6 ลงไป ให้สร้างรูท makefile เพื่อเชื่อมโยงโค้ด 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

โมดูลผู้ให้บริการ pKVM สามารถโหลดได้โดยใช้ modprobe เช่นเดียวกับโมดูลผู้ให้บริการ GKI อย่างไรก็ตาม การโหลดต้องเกิดขึ้นก่อนการลดสิทธิ์ด้วยเหตุผลด้านความปลอดภัย หากต้องการโหลดโมดูล pKVM คุณต้องตรวจสอบว่าได้รวมโมดูลไว้ในระบบไฟล์รูท (initramfs) แล้ว และต้องเพิ่มข้อมูลต่อไปนี้ลงในบรรทัดคำสั่งเคอร์เนล

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

โมดูลผู้ให้บริการ pKVM ที่จัดเก็บไว้ใน initramfs จะรับช่วงการลงนามและการป้องกันของ initramfs

หากโมดูลผู้ให้บริการ pKVM โมดูลใดโมดูลหนึ่งโหลดไม่สำเร็จ ระบบจะถือว่าไม่ปลอดภัยและคุณจะเริ่มเครื่องเสมือนที่ได้รับการปกป้องไม่ได้

เรียกใช้ฟังก์ชัน EL2 (ไฮเปอร์ไวเซอร์) จาก EL1 (โมดูลเคอร์เนล)

การเรียกไฮเปอร์ไวเซอร์ (HVC) เป็นคำสั่งที่อนุญาตให้เคอร์เนลเรียกไฮเปอร์ไวเซอร์ เมื่อมีการเปิดตัวโมดูลผู้ให้บริการ pKVM คุณจะใช้ HVC เพื่อเรียกใช้ฟังก์ชันที่ทำงานที่ EL2 (ในโมดูลไฮเปอร์ไวเซอร์) จาก EL1 (โมดูลเคอร์เนล) ได้ดังนี้

  1. ประกาศตัวจัดการ EL2 ในโค้ด EL2 (el2.c)

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. ลงทะเบียนตัวจัดการ EL2 ในโมดูลผู้ให้บริการ pKVM ในโค้ด EL1 (el1.c)

    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. เรียกใช้ HVC ในโค้ด EL1 (el1.c)

    pkvm_el2_mod_call(hvc_number);
    

ดีบักและโปรไฟล์โค้ด EL2

ส่วนนี้มีตัวเลือกหลายรายการสำหรับดีบักโค้ด EL2 ของโมดูล pKVM

ส่งและอ่านเหตุการณ์การติดตามไฮเปอร์ไวเซอร์

Tracefs รองรับไฮเปอร์ไวเซอร์ pKVM ผู้ใช้รากมีสิทธิ์เข้าถึงอินเทอร์เฟซซึ่งอยู่ใน /sys/kernel/tracing/hypervisor/ ดังนี้

  • tracing_on: เปิดหรือปิดการติดตาม
  • trace: การเขียนลงในไฟล์นี้จะรีเซ็ตการติดตาม
  • trace_pipe: การอ่านไฟล์นี้จะพิมพ์เหตุการณ์ไฮเปอร์ไวเซอร์
  • buffer_size_kb: ขนาดบัฟเฟอร์ต่อ CPU ที่เก็บเหตุการณ์ เพิ่มค่านี้หากเหตุการณ์สูญหาย

เหตุการณ์จะปิดอยู่โดยค่าเริ่มต้น หากต้องการเปิดใช้เหตุการณ์ ให้ใช้ไฟล์ /sys/kernel/tracing/hypervisor/events/my_event/enable ที่เกี่ยวข้องใน Tracefs นอกจากนี้ คุณยังเปิดใช้เหตุการณ์ไฮเปอร์ไวเซอร์ใดก็ได้ในเวลาบูตด้วยบรรทัดคำสั่งเคอร์เนล hyp_event=event1,event2

ก่อนประกาศเหตุการณ์ โค้ด EL2 ของโมดูลต้องประกาศข้อมูลเบื้องต้นต่อไปนี้ โดยที่ pkvm_ops คือ struct pkvm_module_ops * ที่ส่งไปยังฟังก์ชัน init ของโมดูล

  #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

ประกาศเหตุการณ์

ประกาศเหตุการณ์ในไฟล์ .h ของตัวเอง

  $ 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

ส่งเหตุการณ์

คุณสามารถบันทึกเหตุการณ์ในโค้ด EL2 ได้โดยการเรียกใช้ฟังก์ชัน C ที่สร้างขึ้น

  trace_pkvm_driver_event(id);

เพิ่มการลงทะเบียน (Android 15 ลงไป)

สำหรับ Android 15 ลงไป ให้รวมการลงทะเบียนเพิ่มเติมระหว่างการเริ่มต้นโมดูล ซึ่งไม่จำเป็นใน Android 16 ขึ้นไป

  #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;
  }

ส่งเหตุการณ์โดยไม่ต้องประกาศก่อน (Android 16 ขึ้นไป)

การประกาศเหตุการณ์อาจยุ่งยากสำหรับการดีบักอย่างรวดเร็ว trace_hyp_printk() ช่วยให้ผู้เรียกส่งอาร์กิวเมนต์ได้สูงสุด 4 รายการไปยังสตริงรูปแบบโดยไม่ต้องประกาศเหตุการณ์

  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);

นอกจากนี้ยังต้องมีข้อมูลเบื้องต้นในโค้ด EL2 ด้วย trace_hyp_printk() เป็นมาโครที่เรียกใช้ฟังก์ชัน 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

เปิดใช้เหตุการณ์ __hyp_printk ใน /sys/kernel/tracing/hypervisor/events/ หรือ ในเวลาบูตด้วยบรรทัดคำสั่งเคอร์เนล hyp_event=__hyp_printk

เปลี่ยนเส้นทางเหตุการณ์ไปยัง dmesg

พารามิเตอร์บรรทัดคำสั่งเคอร์เนล hyp_trace_printk=1 จะทำให้อินเทอร์เฟซการติดตามไฮเปอร์ไวเซอร์ส่งต่อเหตุการณ์ที่บันทึกไว้แต่ละรายการไปยัง dmesg ของเคอร์เนล ซึ่งจะเป็นประโยชน์ในการอ่านเหตุการณ์เมื่อเข้าถึง trace_pipe ไม่ได้

ดัมพ์เหตุการณ์ระหว่างเคอร์เนลแพนิก (Android 16 ขึ้นไป)

ระบบจะโพลเหตุการณ์ไฮเปอร์ไวเซอร์ ดังนั้นจึงมีช่วงเวลาระหว่างการโพลครั้งสุดท้ายกับเคอร์เนลแพนิกที่เหตุการณ์ถูกส่งออก แต่ไม่ได้ทิ้งลงในคอนโซล ตัวเลือกการกำหนดค่าเคอร์เนล CONFIG_PKVM_DUMP_TRACE_ON_PANIC จะพยายามดัมพ์เหตุการณ์ล่าสุดในคอนโซลหากเปิดใช้ hyp_trace_printk

ตัวเลือกนี้จะปิดอยู่โดยค่าเริ่มต้นสำหรับ GKI

ใช้ Ftrace เพื่อติดตามการเรียกใช้ฟังก์ชันและการแสดงผล (Android 16 ขึ้นไป)

Ftrace เป็นฟีเจอร์เคอร์เนลที่ช่วยให้คุณติดตามการเรียกใช้ฟังก์ชันและการแสดงผลแต่ละรายการได้ ในลักษณะเดียวกัน ไฮเปอร์ไวเซอร์ pKVM มีเหตุการณ์ 2 รายการ ได้แก่ func และ func_ret

คุณสามารถเลือกฟังก์ชันที่ติดตามได้ด้วยบรรทัดคำสั่งเคอร์เนล hyp_ftrace_filter= หรือด้วยไฟล์ tracefs ไฟล์ใดไฟล์หนึ่งต่อไปนี้

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

ตัวกรองใช้การจับคู่ Glob ในรูปแบบเชลล์

ตัวกรองต่อไปนี้จะติดตามฟังก์ชันที่ขึ้นต้นด้วย pkvm_hyp_driver

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

เหตุการณ์ func และ func_ret จะใช้ได้เฉพาะกับ CONFIG_PKVM_FTRACE=y ตัวเลือกนี้จะปิดอยู่โดยค่าเริ่มต้นสำหรับ GKI