14

修志龙_ZenonXiu · 2023年09月09日 · 上海市浦东新区

Arm CCA支持的Linux KVM

之前的文章介绍了arm CCA的构架。
Arm机密计算架构技术(Armv9 CCA) 白皮书
Confidential Compute Architecture - Arm构架的TEE新模式
本文简要介绍一下如何在KVM上使用了CCA RME虚拟机。

之前介绍了CCA设计的一个目的是让non secure hypervisor可以创建和调度RME虚拟机和vCPU,并且给它分配内存等资源,但一旦这些资源分配给RME虚拟机之后,hypervisor就失去了访问这些内存内容的权限。这是通过EL3 monitor软件修改GPT表项实现的。从而实现虚拟机的机密计算。

Arm和开源社区提供一个参考设计,其中包括包括:
1. Linux kernel KVM模块的支持:

  • 支持由RME通过RMI接口创建,运行虚拟机vCPU,可以服务RME虚拟的RSI host call
  • 增加了KVM UABI用于管理Realms
  • 增加了Linux kernel作为CCA的Host, 也可以做为CCA的Guest(运行于RME虚拟机中,支持RSI接口)

目前运行hypervisor的host Linux运行在non secure EL2, 并且运行于VHE模式。代码在https://gitlab.arm.com/linux-... 

2. 开源的RMM实现:TF-RMM,主要是服务host hypervisor发过来的RMI请求,创建在Realm状态下对应host Linux KVM里“struct kvm”的虚拟机的描述和状态,和对应host Linux vCPU的虚拟CPU状态和上下文的结构,并且负责Realm中vCPU的context的save/restore. RMM还负责通过Realm的stage 2实现多个Realm虚拟机间的隔离。https://github.com/TF-RMM/tf-rmm

3. 扩展现有的 Arm Trusted Firmware,使它支持RME引入的4个secure状态和它们间切换时的context switching,增加了RMI调用的支持和GPT页表的管理。https://github.com/ARM-softwa...
image.png

TF-RMM采用了类似Hafnium, OPTEE的工作方式,主要目的是将原来在host Linux kernel做的部分事情(如和vCPU context,stage 2页表配置相关的)通过RMI调用转到Realm world实现,如果这部分事情放在host Linux中,可以导致host能获得修改VM的信息和控制VM的执行。但是TF-RMM和Hafnium, OPTEE一样,TF-RMM没有调度和复杂的内存管理功能,TF-RMM的执行主要是服务Host Linux的RMI调用和Guest Linux的RSI调用。

RMI的调用主要包括:

CommandDescription
VersionQuery RMI ABI version.
Granule.DelegateChange granule (from NS) to Delegated.
Granule.UndelegateChange granule (from Delegated) to NS.
Realm.CreateCreate Realm Descriptor (RD).
Realm.DestroyDestroy Realm identified by RD.
Realm.ActivateChange Realm (from New) to Active.
REC.CreateCreate Realm Execution Context (REC).
REC.DestroyDestroy REC.
REC.RunEnter REC (i.e. run VCPU).
Data.CreateUnknownChange granule to Data with unknown content.
Data.CreateChange granule to Data, copy NS content.
Data.DestroyChange Data granule to Delegated, zeroed.
RTT.CreateCreate Realm Translation Table (RTT).
RTT.DestroyDestroy RTT.
RTT.MapProtectedMap Data granule in RTT.
RTT.UnmapProtectedRemove mapping from RTT.
RTT.MapUnprotectedMap NS granule in RTT.
RTT.UnmapUnprotectedRemove NS mapping from RTT.
RTT.ReadEntryReturn content of an RTT entry.

概况

image.png
Host Linux支持创建普通的虚拟机和Realm虚拟机,现有的KVM API还是可以使用。Host Linux通过KVM_CAP_ARM_RME告诉user space的VMM,平台是否支持RME的KVM。如果要创建Realm虚拟机,VMM可以通过kvm module的ioctl KVM_ENABLE_CAP调用并传入.cap = KVM_CAP_ARM_RME参数。Host Linux kernel为之创建的“struct kvm”结构中的kvm->arch.is_realm记录其为Realm VM,后面kernel通过kvm_is_realm(kvm)检查是否为Realm VM. KVM_CAP_ARM_RME会导致KVM调用

kvm_create_realm->realm_create_rd->rmi_realm_create 

这会引起RMI Realm.Create的SMC调用,ATF会将这个调用dispatch给TF-RMM处理,TF-RMM会为之创建Realm Descriptor (RD,“struct rd”)的结构,对应于host Linux kernel的“struct kvm"结构。TF-RMM的rmm_handler通过一下handler来服务这些RMI调用:

static const struct smc_handler smc_handlers[] = {
    HANDLER(VERSION,        0, 0, smc_version,         true, true),
    HANDLER(FEATURES,        1, 1, smc_read_feature_register, true,  true),
    HANDLER(GRANULE_DELEGATE,    1, 0, smc_granule_delegate,     false, true),
    HANDLER(GRANULE_UNDELEGATE,    1, 0, smc_granule_undelegate,     false, true),
    HANDLER(REALM_CREATE,        2, 0, smc_realm_create,         true,  true),
    HANDLER(REALM_DESTROY,        1, 0, smc_realm_destroy,     true,  true),
    HANDLER(REALM_ACTIVATE,        1, 0, smc_realm_activate,     true,  true),
    HANDLER(REC_CREATE,        3, 0, smc_rec_create,         true,  true),
    HANDLER(REC_DESTROY,        1, 0, smc_rec_destroy,         true,  true),
    HANDLER(REC_ENTER,        2, 0, smc_rec_enter,         false, true),
    HANDLER(DATA_CREATE,        5, 0, smc_data_create,         false, false),
    HANDLER(DATA_CREATE_UNKNOWN,    3, 0, smc_data_create_unknown,     false, false),
    HANDLER(DATA_DESTROY,        2, 0, smc_data_destroy,         false, true),
    HANDLER(RTT_CREATE,        4, 0, smc_rtt_create,         false, true),
    HANDLER(RTT_DESTROY,        4, 0, smc_rtt_destroy,         false, true),
    HANDLER(RTT_FOLD,        4, 0, smc_rtt_fold,         false, true),
    HANDLER(RTT_MAP_UNPROTECTED,    4, 0, smc_rtt_map_unprotected,     false, false),
    HANDLER(RTT_UNMAP_UNPROTECTED,    3, 0, smc_rtt_unmap_unprotected, false, false),
    HANDLER(RTT_READ_ENTRY,        3, 4, smc_rtt_read_entry,     false, true),
    HANDLER(PSCI_COMPLETE,        2, 0, smc_psci_complete,     true,  true),
    HANDLER(REC_AUX_COUNT,        1, 1, smc_rec_aux_count,     true,  true),
    HANDLER(RTT_INIT_RIPAS,        3, 0, smc_rtt_init_ripas,     false, true),
    HANDLER(RTT_SET_RIPAS,        5, 0, smc_rtt_set_ripas,     false, true)

然后VMM通过KVM_CREATE_VCPU的kvm ioctl调用

kvm_vm_ioctl_create_vcpu ->kvm_arch_vcpu_create

创建vCPU, 之后,对于Realm VM还需要通过KVM_ARM_VCPU_FINALIZE kvm_arch_vcpu_ioctl系统调用,

kvm_arm_vcpu_finaliz->kvm_create_rec->rmi_rec_create

通过RMI REC_CREATE调用(通过SMC指令)到ATF,ATF再将此调用dispatch到TF-RMM,TF-RMM通过smc_rec_create在Realm状态创建对应于host Linux KVM "struct kvm_vcpu"的"struct rec"结构体。Host kernel KVM通过vcpu_is_rec()检查vCPU是否运行在Realm中。

struct rec {
    struct granule *g_rec;    /* the granule in which this REC lives */
    unsigned long rec_idx;    /* which REC is this */
    bool runnable;

    unsigned long regs[31];

    /*
     * PAuth state of Realm.
     * Note that we do not need to save NS state as EL3 will save this as part of world switch.
     */
    struct pauth_state pauth;

    unsigned long pc;
    unsigned long pstate;

    struct sysreg_state sysregs;
    struct common_sysreg_state common_sysregs;

    struct {
        unsigned long start;
        unsigned long end;
        unsigned long addr;
        enum ripas ripas;
    } set_ripas;
    /*
     * Common values across all RECs in a Realm.
     */
    struct {
        unsigned long ipa_bits;
        int s2_starting_level;
        struct granule *g_rtt;
        struct granule *g_rd;
        bool pmu_enabled;
        unsigned int pmu_num_cnts;
        bool sve_enabled;
        uint8_t sve_vq;
    } realm_info;

    struct {
        /*
         * The contents of the *_EL2 system registers at the last time
         * the REC exited to the host due to a synchronous exception.
         * These are the unsanitized register values which may differ
         * from the value returned to the host in rec_exit structure.
         */
        unsigned long esr;
        unsigned long hpfar;
        unsigned long far;
    } last_run_info;

    /* Pointer to per-cpu non-secure state */
    struct ns_state *ns;

    struct {
        /*
         * Set to 'true' when there is a pending PSCI
         * command that must be resolved by the host.
         * The command is encoded in rec->regs[0].
         *
         * A REC with pending PSCI is not schedulable.
         */
        bool pending;
    } psci_info;

    /* Number of auxiliary granules */
    unsigned int num_rec_aux;

    /* Addresses of auxiliary granules */
    struct granule *g_aux[MAX_REC_AUX_GRANULES];
    struct rec_aux_data aux_data;

    unsigned char rmm_realm_token_buf[SZ_1K];
    size_t rmm_realm_token_len;

    struct token_sign_ctx token_sign_ctx;

    /* Buffer allocation info used for heap init and management */
    struct {
        struct buffer_alloc_ctx ctx;
        bool ctx_initialised;
    } alloc_info;

    struct {
        unsigned long vsesr_el2;
        bool inject;
    } serror_info;

    /* True if host call is pending */
    bool host_call;
};

然后VMM通过KVM_RUN 的ioctl运行vCPU,对于Realm VM,

kvm_arch_vcpu_ioctl_run ->kvm_rec_enter->rmi_rec_enter

rmi_rec_enter通过RMI REC_ENTER的调用(通过SMC指令)到ATF,ATF将它dispatch到TF-RMM,TF-RMM调用smc_rec_enter,然后通过rec_run_loop将物理CPU的执行转交给Realm EL0&EL1的Realm VM。
完整的vCPU运转图如下:
image.png
由此看出,对于host Linux KVM, 与正常的KVM VM, pKVM VM类似,Realm虚拟机的vCPU的创建是由host Linux上的VMM管理的,每个Realm vCPU对于host Linux来说也只是一个线程,也是由host Linux负责调度的。只是Realm vCPU运行于Realm EL0&EL1状态,因而Realm VM和Realm vCPU的信息host Linux是看不到的,这点与正常的KVM VM不同。

从上图也可以看出,Realm vCPU执行的进入和退出需要经过更长的路径,如进入vCPU的执行需要经过host Linux KVM->ATF->TF-RMM->Realm VM,每个步骤都涉及到context save/restore. 但根据初步的研究发现,这个过程中带来的overhead并不是很明显。
image.png

Stage 2 table的管理

RMM的stage 2用于Realm VM之间的隔离。在TF-RMM中维护一个RTT,相当于Normal KVM VM中的stage 2页表,对于Realm VM, 这是由TF-RMM管理的RTT.
Realm VM的IPA空间包括一个受保护的地址范围(PAR),RMM确保只能将其映射到Realm PAS。对于在PAR内的访问,RMM保证给Realm的机密性和完整性;在PAR之外,hypervisor可以自由地映射NS PAS内存页或模拟访问。这为在Realm VM上运行的OS提供了一个可靠的机制来确定它是访问自己的私有内存,还是可以与不受信任的其他部分共享的内存。

在Realm创建期间,hypervisor可以将一个内存页分配给Realm,在PAR内的特定IPA处,并从NS内存页复制数据到它。IPA和数据经过加密哈希,哈希包含在Realm的认证令牌中。认证令牌允许Realm的所有者推理其初始状态和内容。一旦Realm被激活,只能将内存添加到其他未使用的IPA。
hypervisor可以随时从Realm回收内存。RMM在回收内存页并将其返回给hypervisor之前将此内存页清零。后续访问Realm回收内存的IPA会导致RMM的stage 2异常,这会阻止Realm进一步执行并保持Arm CCA的完整性保证。

在Host Linux KVM中它可以请求map或是unmap Realm VM的stage 2映射,让RMM管理Realm stage 2页表,但它不能获取修改到Realm RMM的stage 2页表内容。例如Host Linux KVM可以通过unmap_stage2_range来请求unmap一些stage 2映射,这个过程是:

unmap_stage2_range->__unmap_stage2_range->kvm_realm_unmap_range->realm_tear_down_rtt_range, 

在这里会通过RMI的RTT_DESTROY,RTT_CREAT调用(通过SMC指令)到ATF, ATF再将它dispatch到TF-RMM的smc_rtt_create,smc_rtt_create进行RMM RTT页表操作。

中断处理

目前没有GIC没有专门对Realm VM的虚拟中断支持,因此Realm VM的中断需要在host Linux kernel中通过其vgic中断产生虚拟中断。

由代码可知,RMM在rec_run_loop中准备运行Realm时(参见前面Realm vCPU运行流程图),它调用restore_realm_state

static void restore_realm_state(struct rec *rec)
{
    write_cnthctl_el2(rec->sysregs.cnthctl_el2);
    isb();

    restore_sysreg_state(&rec->sysregs);

    write_elr_el2(rec->pc);
    write_spsr_el2(rec->pstate);
    write_hcr_el2(rec->sysregs.hcr_el2);

设置Realm EL2的HCR_EL2寄存器的值为

HCR_FLAGS     (HCR_FWB | HCR_E2H | HCR_RW | HCR_TSC | HCR_AMO |HCR_BSU_IS | HCR_IMO | HCR_FMO | HCR_PTW | HCR_SWIO | HCR_VM | HCR_TID3 | HCR_TEA | HCR_API | HCR_APK)

这个设置中HCR_IMO, HCR_FMO让当运行在Realm EL0&EL1 (Realm VM)时产生的IRQ和FIQ会route到Realm EL2 (RMM)中处理。
RMM对于这些route到Realm EL2的处理为

el2_irq_lel:
    stp    x0, x1, [sp, #-16]!
    mov    x0, #ARM_EXCEPTION_IRQ_LEL
    b    realm_exit
ENDPROC(el2_sync_lel)

el2_fiq_lel:
    stp    x0, x1, [sp, #-16]!
    mov    x0, #ARM_EXCEPTION_FIQ_LEL
    b    realm_exit
ENDPROC(el2_sync_lel)

realm_exit使执行回到handle_realm_exit,然后继续退回到ATF,ATF继续退回到Host Linux KVM, 再在host Linux kernel里进行这个物理中断的处理,如果要产生一个虚拟中断给Realm VM,host Linux kernel利用其GIC driver的vGIC产生一个虚拟中断,然后通过ATF进入到RMM,RMM再回到Realm VM的执行,这是Realm VM就能处理这个pending的虚拟中断。
image.png

ATF的支持

ATF对CCA支持主要包括:
1. 加载RMM Image和启动RMM
image.png
2. 负责Root,Non Secure,Secure和Realm四个secure world的切换,增加了RMM的dispatcher
image.png
image.png

3. 管理GPT页表
Host Linux KVM可以通过alloc_delegated_page ,free_delegated_page 等函数来请求ATF改变一个物理内存页在GPT页表中的PAS, 这是通过host Linux kvm调用rmi_granule_delegate, rmi_granule_undelegate也就是RMI GRANULE_UNDELEGATE ,RMI GRANULE_UNDELEGATE的RMI调用(通过SMC指令),这会进入到ATF的rmmd_rmi_handler进行处理,它将这个RMI请求forward到RMM中处理,RMM处理之后,RMM可以调用RMM_GTSI_DELEGATE或SMC_RMM_GTSI_UNDELEGATE调用(通过SMC)回到ATF,在ATF中的rmmd_rmm_el3_handler中调用gpt_delegate_pas或是gpt_undelegate_pas来修改GPT页表,最终修改了此物理页对应的PAS。
image.png
不受信任的hypervisor总能停止调度一个Realm,并总是可以回收分配给Realm的内存,但在任何情况下,它都不能访问Realm的CPU或内存状态。这是通过一个简单但功能强大delegate(分批/委托)来实现的。hypervisor将内存delegate给Realm world,和回收到non secure world。所有由Realms使用的内存首先必须由hypervisor delegate;RMM本身不管理Realm的内存池。一旦内存被delegate给Realm world,hypervisor可以请求RMM将其用于各种目的,例如保存Realm的元数据或数据。当一个内存页被delegate给Realm world但未被RMM使用时,RMM确保该内存页清零,从而降低了内存页被重用或回收时意外信息泄露风险。
RMM为hypervisor提供了一个Realm管理接口(RMI),要求RMM delegate内存、创建Realms、执行Realms和为Realms分配内存。每一个RMI命令都是作为一个SMC实现的,所以当hypervisor调用该命令时,它会陷入到EL3 Monitor,然后切换执行到Realm world的RMM来处理命令。在RMI命令完成后,RMM向EL3 Monitor发出一个SMC,将执行切换回NS world的hypervisor。
为了维护Realms的安全保证,RMM必须知道系统上每个内存页的状态,它通过维护自己的内存页状态表(GST)来实现,以跟踪每个内存页的delegate状态和使用情况。当hypervisor delegate一个内存页时,RMM检查其GST,以确认内存页尚未被delegate,RMM然后向EL3 Monitor发出一个SMC请求更改Realm PAS。EL3 Monitor检查该内存页当前是否位于NS PAS,然后更新GPT将其移至Realm PAS。最后,RMM更新其GST以记录该内存页已被delegate。如果hypervisor试图delegate已被delegate的内存页,或回收RMM正在使用的内存页,RMM会返回一个错误代码给不受信任的hypervisor。与GPT不同,GST不是由硬件检查的,而只是一个软件记录机制。

目前的限制

  1. 目前没有GIC没有专门对Realm VM的虚拟中断支持,因此Realm VM的中断需要在host Linux kernel中通过其vgic中断产生虚拟中断。未来的GIC会支持Realm虚拟中断的注入
  2. 暂时没有 Device Assignment的支持,随着SMMU的进化和软件的开放,后面会支持
  3. 目前在arm构架上的KVM有四种不同的支持方式:armv7-a/armv8.0-a的KVM支持方式,自armv8.1-a开始支持的VHE方式,Google推的pKVM方式,Realm支持的方式。软件构架上需要提供更clear的实现。

参考

https://www.trustedfirmware.o...
https://lore.kernel.org/lkml/...
https://static.sched.com/host...

推荐阅读
关注数
8629
内容数
51
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息