Esta página explica como implementar um módulo de fornecedor de máquina virtual protegida baseada no kernel (pKVM).
Para android16-6.12 e versões mais recentes, depois de concluir estas etapas, você terá uma árvore de diretórios semelhante a:
BUILD.bazel
el1.c
hyp/
BUILD.bazel
el2.c
Para um exemplo completo, consulte Criar um módulo pKVM com DDK .
Para android15-6.6 e versões anteriores:
Makefile
el1.c
hyp/
Makefile
el2.c
Adicione o código do hipervisor EL2 (
el2.c). No mínimo, esse código precisa declarar uma função de inicialização que aceite uma referência à structpkvm_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; }A API do módulo de fornecedor de pKVM é uma struct que encapsula callbacks para o hipervisor da pKVM. Essa struct segue as mesmas regras de ABI das interfaces do GKI.
Crie o
hyp/Makefilepara criar o código do hipervisor:hyp-obj-y := el2.o include $(srctree)/arch/arm64/kvm/hyp/nvhe/Makefile.moduleAdicione o código do kernel EL1 (
el1.c). A seção de inicialização desse código precisa ter uma chamada parapkvm_load_el2 modulepara carregar o código do hipervisor EL2 da etapa 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);Crie as regras de build.
Para android16-6.12 e versões mais recentes, consulte Criar um módulo pKVM com DDK para criar
ddk_library()para EL2 eddk_module()para EL1.Para android15-6.6 e versões anteriores, crie o makefile raiz para vincular os códigos EL1 e 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
Carregar um módulo pKVM
Assim como os módulos de fornecedor de GKI, os módulos de fornecedor de pKVM podem ser carregados usando o modprobe.
No entanto, por motivos de segurança, o carregamento precisa ocorrer antes da remoção de privilégios.
Para carregar um módulo pKVM, verifique se os módulos estão incluídos no sistema de arquivos raiz (initramfs) e adicione o seguinte à linha de comando do kernel:
kvm-arm.protected_modules=mod1,mod2,mod3,...
Os módulos de fornecedor de pKVM armazenados em initramfs herdam a assinatura e a proteção de initramfs.
Se um dos módulos de fornecedor de pKVM não for carregado, o sistema será considerado inseguro e não será possível iniciar uma máquina virtual protegida.
Chamar uma função EL2 (hipervisor) do EL1 (módulo do kernel)
Uma chamada de hipervisor (HVC) é uma instrução que permite ao kernel chamar o hipervisor. Com a introdução dos módulos de fornecedor de pKVM, uma HVC pode ser usada para chamar uma função que será executada no EL2 (no módulo do hipervisor) do EL1 (o módulo do kernel):
- No código EL2 (
el2.c), declare o gerenciador EL2:
Android 14
void pkvm_driver_hyp_hvc(struct kvm_cpu_context *ctx)
{
/* Handle the call */
cpu_reg(ctx, 1) = 0;
}
Android 15 ou mais recente
void pkvm_driver_hyp_hvc(struct user_pt_regs *regs)
{
/* Handle the call */
regs->regs[0] = SMCCC_RET_SUCCESS;
regs->regs[1] = 0;
}
No seu código EL1 (
el1.c), registre o gerenciador EL2 no módulo de fornecedor de 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);No seu código EL1 (
el1.c), faça a HVC:pkvm_el2_mod_call(hvc_number);
Depurar e criar perfil do código EL2
Esta seção contém várias opções para depurar o código EL2 do módulo pKVM.
Emitir e ler eventos de rastreamento do hipervisor
O Tracefs oferece suporte ao hipervisor pKVM. O usuário raiz tem acesso à interface, que está localizada em /sys/kernel/tracing/hypervisor/:
tracing_on: ativa ou desativa o rastreamento.trace: a gravação nesse arquivo redefine o rastreamento.trace_pipe: a leitura desse arquivo imprime os eventos do hipervisor.buffer_size_kb: o tamanho do buffer por CPU que contém eventos. Aumente esse valor se os eventos forem perdidos.
Por padrão, os eventos estão desativados. Para ativar eventos, use o arquivo /sys/kernel/tracing/hypervisor/events/my_event/enable correspondente no Tracefs. Também é possível
ativar qualquer evento do hipervisor no momento da inicialização com a linha de comando do kernel de
hyp_event=event1,event2.
Antes de declarar um evento, o código EL2 do módulo precisa declarar o seguinte boilerplate, em que pkvm_ops é o struct pkvm_module_ops * transmitido à função init do módulo:
#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
Declarar eventos
Declare eventos no próprio arquivo .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
Emitir eventos
É possível registrar eventos no código EL2 chamando a função C gerada:
trace_pkvm_driver_event(id);
Adicionar registro extra (Android 15 ou versões anteriores)
Para o Android 15 e versões anteriores, inclua um registro extra durante a inicialização do módulo. Isso não é necessário no Android 16 e versões mais recentes.
#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;
}
Emitir eventos sem declaração anterior (Android 16 e versões mais recentes)
Declarar eventos pode ser complicado para uma depuração rápida. trace_hyp_printk()
permite que o autor da chamada transmita até quatro argumentos para uma string de formato sem nenhuma declaração de evento:
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);
Um boilerplate no código EL2 também é necessário. trace_hyp_printk() é uma macro que chama a função 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
Ative o evento __hyp_printk em /sys/kernel/tracing/hypervisor/events/ ou
no momento da inicialização com a linha de comando do kernel hyp_event=__hyp_printk.
Redirecionar eventos para dmesg
O parâmetro de linha de comando do kernel hyp_trace_printk=1 faz com que a interface de rastreamento do hipervisor encaminhe cada evento registrado para o dmesg do kernel. Isso é útil para ler eventos quando trace_pipe está inacessível.
Despejar eventos durante um kernel panic (Android 16 e versões mais recentes)
Os eventos do hipervisor são pesquisados. Portanto, há uma janela entre a última pesquisa e um kernel panic em que os eventos foram emitidos, mas não despejados no console.
A opção de configuração do kernel CONFIG_PKVM_DUMP_TRACE_ON_PANIC tenta despejar os eventos mais recentes no console se hyp_trace_printk tiver sido ativado.
Essa opção está desativada por padrão para o GKI.
Usar o Ftrace para rastrear a chamada e o retorno da função (Android 16 e versões mais recentes)
O Ftrace é um recurso do kernel que permite rastrear cada chamada e retorno de função.
Da mesma forma, o hipervisor pKVM oferece dois eventos func e
func_ret.
É possível selecionar as funções rastreadas com a linha de comando do kernel hyp_ftrace_filter= ou com um dos arquivos tracefs:
/sys/kernel/tracing/hypervisor/set_ftrace_filter/sys/kernel/tracing/hypervisor/set_ftrace_notrace
Os filtros usam a correspondência de glob no estilo shell.
O filtro a seguir rastreia as funções que começam com pkvm_hyp_driver:
echo "__kvm_nvhe_pkvm_hyp_driver*" > /sys/kernel/tracing/hypervisor/set_ftrace_filter
Os eventos func e func_ret estão disponíveis apenas com CONFIG_PKVM_FTRACE=y.
Essa opção está desativada por padrão para o GKI.