Na tej stronie dowiesz się, jak wdrożyć moduł dostawcy chronionej maszyny wirtualnej opartej na jądrze (pKVM).
W przypadku wersji android16-6.12 i nowszych po wykonaniu tych czynności powinna być widoczna struktura katalogów podobna do tej:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Pełny przykład znajdziesz w artykule Tworzenie modułu pKVM za pomocą DDK.
W przypadku Androida 15–6.6 i starszych:
Makefile
el1.c
hyp/
Makefile
el2.c
Dodaj kod hiperwizora EL2 (
el2.c). Musi on zawierać co najmniej deklarację funkcji init, która akceptuje odwołanie do strukturypkvm_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; }Interfejs API modułu dostawcy pKVM to struktura zawierająca wywołania zwrotne do hiperwizora pKVM. Ta struktura jest zgodna z tymi samymi regułami interfejsu ABI co interfejsy GKI.
Utwórz
hyp/Makefile, aby skompilować kod hiperwizora:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleDodaj kod jądra EL1 (
el1.c). Sekcja inicjowania tego kodu musi zawierać wywołanie funkcjipkvm_load_el2 module, aby wczytać kod hiperwizora EL2 z kroku 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);Utwórz reguły kompilacji.
W przypadku Androida 16–6.12 i nowszych zapoznaj się z artykułem Tworzenie modułu pKVM za pomocą DDK, aby utworzyć
ddk_library()dla EL2 iddk_module()dla EL1.W przypadku Androida 15-6.6 i starszych wersji utwórz główny plik makefile, aby połączyć kod EL1 i 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
Wczytywanie modułu pKVM
Podobnie jak w przypadku modułów dostawcy GKI, moduły dostawcy pKVM można wczytywać za pomocą polecenia modprobe.
Ze względów bezpieczeństwa wczytywanie musi jednak nastąpić przed ograniczeniem uprawnień.
Aby załadować moduł pKVM, musisz się upewnić, że moduły są uwzględnione w głównym systemie plików (initramfs), i dodać do wiersza poleceń jądra te elementy:
kvm-arm.protected_modules=mod1,mod2,mod3,...
Moduły dostawcy pKVM przechowywane w initramfs dziedziczą podpis i ochronę initramfs.
Jeśli nie uda się wczytać jednego z modułów dostawcy pKVM, system zostanie uznany za niezabezpieczony i nie będzie można uruchomić chronionej maszyny wirtualnej.
Wywoływanie funkcji EL2 (hiperwizora) z EL1 (modułu jądra)
Wywołanie hypervisora (HVC) to instrukcja, która umożliwia jądru wywołanie hypervisora. Wraz z wprowadzeniem modułów dostawcy pKVM wywołanie HVC może służyć do wywoływania funkcji do uruchomienia na poziomie EL2 (w module hipernadzorcy) z poziomu EL1 (w module jądra):
- W kodzie EL2 (
el2.c) zadeklaruj moduł obsługi EL2:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 lub nowszy
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
W kodzie EL1 (
el1.c) zarejestruj moduł obsługi EL2 w module dostawcy 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);W kodzie EL1 (
el1.c) wywołaj HVC:pkvm_el2_mod_call(hvc_number);
Debugowanie i profilowanie kodu EL2
Ta sekcja zawiera kilka opcji debugowania kodu EL2 modułu pKVM.
Wysyłanie i odczytywanie zdarzeń logu czasu hiperwizora
Tracefs obsługuje hiperwizor pKVM. Użytkownik root ma dostęp do interfejsu, który znajduje się w /sys/kernel/tracing/hypervisor/:
tracing_on: włącza lub wyłącza śledzenie.trace: zapisanie danych w tym pliku powoduje zresetowanie śladu.trace_pipe: Odczytanie tego pliku powoduje wydrukowanie zdarzeń hiperwizora.buffer_size_kb: rozmiar bufora na procesor przechowującego zdarzenia. Zwiększ tę wartość, jeśli zdarzenia są tracone.
Domyślnie zdarzenia są wyłączone. Aby je włączyć, użyj odpowiedniego pliku /sys/kernel/tracing/hypervisor/events/my_event/enable w Tracefs. Możesz też włączyć dowolne zdarzenie hipernadzorcy w momencie uruchamiania za pomocą wiersza poleceń jądra hyp_event=event1,event2.
Przed zadeklarowaniem zdarzenia kod EL2 modułu musi zadeklarować poniższy tekst standardowy, gdzie pkvm_ops to struct pkvm_module_ops * przekazane do funkcji modułu 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
Deklarowanie zdarzeń
Deklarowanie zdarzeń w osobnym pliku .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
Emitowanie zdarzeń
Zdarzenia możesz rejestrować w kodzie EL2, wywołując wygenerowaną funkcję C:
trace_pkvm_driver_event(id);
Dodawanie dodatkowej rejestracji (Android 15 lub starszy)
W przypadku Androida 15 i starszych wersji dodaj dodatkową rejestrację podczas inicjowania modułu. Nie jest to wymagane w Androidzie 16 i nowszym.
#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;
}
Wysyłanie zdarzeń bez wcześniejszej deklaracji (Android 16 i nowszy)
Deklarowanie zdarzeń może być uciążliwe w przypadku szybkiego debugowania. trace_hyp_printk()
umożliwia przekazywanie do ciągu formatującego maksymalnie 4 argumentów bez deklaracji zdarzenia:
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);
W kodzie EL2 wymagany jest też tekst standardowy. trace_hyp_printk() to makro, które wywołuje funkcję 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
Włącz zdarzenie __hyp_printk w /sys/kernel/tracing/hypervisor/events/ lub podczas uruchamiania za pomocą wiersza poleceń jądra hyp_event=__hyp_printk.
Przekierowywanie zdarzeń do dmesg
Parametr wiersza poleceń jądra hyp_trace_printk=1 sprawia, że interfejs śledzenia hiperwizora przekazuje każde zarejestrowane zdarzenie do dmesg jądra. Jest to przydatne do odczytywania zdarzeń, gdy trace_pipe jest niedostępny.
Zrzucanie zdarzeń podczas paniki jądra (Android 16 i nowszy)
Zdarzenia hiperwizora są odpytywane. Dlatego między ostatnim odpytywaniem a błędem krytycznym jądra istnieje okres, w którym zdarzenia zostały wyemitowane, ale nie zostały zrzucone do konsoli.
Opcja konfiguracji jądra CONFIG_PKVM_DUMP_TRACE_ON_PANIC próbuje zrzucić najnowsze zdarzenia w konsoli, jeśli włączono hyp_trace_printk.
W przypadku GKI ta opcja jest domyślnie wyłączona.
Używanie Ftrace do śledzenia wywołań i powrotów funkcji (Android 16 i nowszy)
Ftrace to funkcja jądra, która umożliwia śledzenie każdego wywołania funkcji i każdego powrotu z niej.
Podobnie hiperwizor pKVM oferuje 2 zdarzenia: func i func_ret.
Funkcje śledzone możesz wybrać za pomocą wiersza poleceń jądrahyp_ftrace_filter= lub jednego z plików tracefs:
/sys/kernel/tracing/hypervisor/set_ftrace_filter/sys/kernel/tracing/hypervisor/set_ftrace_notrace
Filtry używają dopasowywania wzorców glob w stylu powłoki.
Ten filtr śledzi funkcje zaczynające się od pkvm_hyp_driver:
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
Zdarzenia func i func_ret są dostępne tylko w przypadku CONFIG_PKVM_FTRACE=y.
W przypadku GKI ta opcja jest domyślnie wyłączona.