একটি pKVM ভেন্ডর মডিউল প্রয়োগ করুন

এই পৃষ্ঠাটি ব্যাখ্যা করে কিভাবে একটি সুরক্ষিত কার্নেল-ভিত্তিক ভার্চুয়াল মেশিন (pKVM) বিক্রেতা মডিউল বাস্তবায়ন করতে হয়।

android16-6.12 এবং তার পরবর্তী সংস্করণের জন্য, এই ধাপগুলি সম্পন্ন করার পরে, আপনার কাছে একটি ডিরেক্টরি ট্রি থাকা উচিত:

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

সম্পূর্ণ উদাহরণের জন্য, DDK দিয়ে একটি pKVM মডিউল তৈরি করুন দেখুন।

android15-6.6 এবং তার আগের ভার্সনের জন্য:

Makefile
el1.c
hyp/
    Makefile
    el2.c
  1. EL2 হাইপারভাইজার কোড ( el2.c ) যোগ করুন। কমপক্ষে, এই কোডটিকে pkvm_module_ops struct-এর রেফারেন্স গ্রহণ করে একটি init ফাংশন ঘোষণা করতে হবে:

    #include <asm/kvm_pkvm_module.h>
    
    int pkvm_driver_hyp_init(const struct pkvm_module_ops *ops)
    {
      /* Init the EL2 code */
    
      return 0;
    }
    

    pKVM ভেন্ডর মডিউল API হল একটি স্ট্রাক্ট যা pKVM হাইপারভাইজারে কলব্যাকগুলিকে এনক্যাপসুলেট করে। এই স্ট্রাক্টটি GKI ইন্টারফেসের মতো একই ABI নিয়ম অনুসরণ করে।

  2. হাইপারভাইজার কোড তৈরি করতে hyp/Makefile তৈরি করুন:

    hyp-obj-y := el2.o
    include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.module
    
  3. EL1 কার্নেল কোড ( el1.c ) যোগ করুন। ধাপ ১ থেকে EL2 হাইপারভাইজার কোড লোড করার জন্য এই কোডের init বিভাগে pkvm_load_el2 module একটি কল থাকতে হবে।

    #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 এবং পরবর্তী সংস্করণের জন্য, EL2 এর জন্য ddk_library() এবং EL1 এর জন্য ddk_module() তৈরি করতে DDK দিয়ে একটি pKVM মডিউল তৈরি করুন দেখুন।

    android15-6.6 এবং তার আগের সংস্করণগুলির জন্য, 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 ব্যবহার করে লোড করা যেতে পারে। তবে, নিরাপত্তার কারণে, deprivileging এর আগে লোডিং অবশ্যই ঘটতে হবে। একটি pKVM মডিউল লোড করার জন্য, আপনাকে নিশ্চিত করতে হবে যে আপনার মডিউলগুলি রুট ফাইল সিস্টেমে ( initramfs ) অন্তর্ভুক্ত রয়েছে এবং আপনাকে আপনার কার্নেল কমান্ড-লাইনে নিম্নলিখিতগুলি যোগ করতে হবে:

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

initramfs এ সংরক্ষিত pKVM বিক্রেতা মডিউলগুলি initramfs এর স্বাক্ষর এবং সুরক্ষা উত্তরাধিকার সূত্রে প্রাপ্ত হয়।

যদি pKVM বিক্রেতা মডিউলগুলির একটি লোড করতে ব্যর্থ হয়, তাহলে সিস্টেমটি অনিরাপদ বলে বিবেচিত হবে এবং একটি সুরক্ষিত ভার্চুয়াল মেশিন চালু করা সম্ভব হবে না।

EL1 (কার্নেল মডিউল) থেকে একটি EL2 (হাইপারভাইজার) ফাংশন কল করুন

হাইপারভাইজার কল (HVC) হল একটি নির্দেশ যা কার্নেলকে হাইপারভাইজার কল করতে দেয়। pKVM ভেন্ডর মডিউল প্রবর্তনের সাথে সাথে, একটি HVC ব্যবহার করে EL1 (কার্নেল মডিউল) থেকে EL2 (হাইপারভাইজার মডিউলে) এ চালানোর জন্য একটি ফাংশন কল করা যেতে পারে:

  1. EL2 কোডে ( el2.c ), EL2 হ্যান্ডলারটি ঘোষণা করুন:

অ্যান্ড্রয়েড ১৪

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

     cpu_reg(ctx, 1) = 0;
   }

অ্যান্ড্রয়েড ১৫ বা তার উচ্চতর

   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 ) এ, আপনার pKVM বিক্রেতা মডিউলে EL2 হ্যান্ডলারটি নিবন্ধন করুন:

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

ডিবাগ এবং প্রোফাইল EL2 কোড

এই বিভাগে pKVM মডিউল EL2 কোড ডিবাগ করার জন্য বেশ কয়েকটি বিকল্প রয়েছে।

হাইপারভাইজার ট্রেস ইভেন্ট নির্গত করে এবং পড়ে

Tracefs pKVM হাইপারভাইজার সমর্থন করে। রুট ব্যবহারকারীর ইন্টারফেসে অ্যাক্সেস আছে, যা /sys/kernel/tracing/hypervisor/ এ অবস্থিত:

  • tracing_on : ট্রেসিং চালু বা বন্ধ করে।
  • trace : এই ফাইলে লেখা ট্রেসটিকে রিসেট করে।
  • trace_pipe : এই ফাইলটি পড়লে হাইপারভাইজার ইভেন্টগুলি প্রিন্ট হয়।
  • buffer_size_kb : প্রতি-CPU বাফার হোল্ডিং ইভেন্টের আকার। ইভেন্ট হারিয়ে গেলে এই মান বাড়ান।

ডিফল্টরূপে, ইভেন্টগুলি বন্ধ থাকে। ইভেন্টগুলি সক্রিয় করতে Tracefs-এ সংশ্লিষ্ট /sys/kernel/tracing/hypervisor/events/my_event/enable ফাইলটি ব্যবহার করুন। আপনি 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

ইমিট ইভেন্ট

আপনি জেনারেটেড C ফাংশনটি কল করে EL2 কোডে ইভেন্ট লগ করতে পারেন:

  trace_pkvm_driver_event(id);

অতিরিক্ত নিবন্ধন যোগ করুন (Android 15 বা তার কম)

অ্যান্ড্রয়েড ১৫ এবং তার পরবর্তী ভার্সনের জন্য, মডিউল ইনিশিয়ালাইজেশনের সময় একটি অতিরিক্ত নিবন্ধন অন্তর্ভুক্ত করুন। অ্যান্ড্রয়েড ১৬ এবং তার পরবর্তী ভার্সনে এটির প্রয়োজন নেই।

  #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() কলারকে কোনও ইভেন্ট ঘোষণা ছাড়াই একটি ফর্ম্যাট স্ট্রিংয়ে চারটি পর্যন্ত আর্গুমেন্ট পাস করতে দেয়:

  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

/sys/kernel/tracing/hypervisor/events/ এ অথবা বুট করার সময় কার্নেল কমান্ড-লাইন hyp_event=__hyp_printk ব্যবহার করে __hyp_printk ইভেন্টটি সক্রিয় করুন।

ইভেন্টগুলিকে dmesg-এ পুনঃনির্দেশ করুন

কার্নেল কমান্ড-লাইন প্যারামিটার hyp_trace_printk=1 হাইপারভাইজার ট্রেসিং ইন্টারফেসকে প্রতিটি লগ করা ইভেন্টকে কার্নেলের dmesg এ ফরোয়ার্ড করে। trace_pipe অ্যাক্সেসযোগ্য না থাকলে ইভেন্টগুলি পড়ার জন্য এটি কার্যকর।

কার্নেল প্যানিকের সময় ইভেন্টগুলি ডাম্প করুন (অ্যান্ড্রয়েড 16 এবং উচ্চতর)

হাইপারভাইজার ইভেন্টগুলি পোল করা হয়। অতএব, শেষ পোলের মাঝখানে একটি উইন্ডো থাকে এবং একটি কার্নেল প্যানিক থাকে যেখানে ইভেন্টগুলি নির্গত হয় কিন্তু কনসোলে ডাম্প করা হয় না। কার্নেল কনফিগারেশন বিকল্প CONFIG_PKVM_DUMP_TRACE_ON_PANIC যদি hyp_trace_printk সক্রিয় থাকে তবে কনসোলে সাম্প্রতিক ইভেন্টগুলি ডাম্প করার চেষ্টা করে।

এই বিকল্পটি GKI-এর জন্য ডিফল্টরূপে নিষ্ক্রিয় থাকে।

ফাংশন কল এবং রিটার্ন ট্রেস করতে Ftrace ব্যবহার করুন (Android 16 এবং উচ্চতর)

Ftrace হল একটি কার্নেল বৈশিষ্ট্য যা আপনাকে প্রতিটি ফাংশন কল এবং রিটার্ন ট্রেস করতে দেয়। একইভাবে, pKVM হাইপারভাইজার দুটি ইভেন্ট func এবং func_ret অফার করে।

আপনি কার্নেল কমান্ড-লাইন hyp_ftrace_filter= অথবা tracefs ফাইলগুলির একটি দিয়ে ট্রেস করা ফাংশনগুলি নির্বাচন করতে পারেন:

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

ফিল্টারগুলি শেল-স্টাইলের গ্লোব ম্যাচিং ব্যবহার করে।

নিম্নলিখিত ফিল্টারটি pkvm_hyp_driver দিয়ে শুরু হওয়া ফাংশনগুলি ট্রেস করে:

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

func এবং func_ret ইভেন্টগুলি শুধুমাত্র CONFIG_PKVM_FTRACE=y এর সাথে উপলব্ধ। GKI এর জন্য এই বিকল্পটি ডিফল্টরূপে নিষ্ক্রিয় করা আছে।