CSV加密代码分析

1.0 .load_state = ram_load 在内存加密中的实现流程

在支持内存加密的虚拟机迁移(如 AMD SEV、Intel TDX)场景中,ram_load 负责将加密的内存数据安全地加载到目标虚拟机,并确保解密过程的完整性和机密性。以下是其核心流程与技术细节:


1.1. 加密内存迁移的基本架构
源端虚拟机(加密内存) → 传输加密数据 → 目标端虚拟机(解密并加载)
  • 核心挑战
    迁移过程中需保证内存数据的 端到端加密,且密钥不暴露给宿主机或传输通道。

1.2. ram_load 的加密处理流程
步骤 1:接收加密内存页
  • 从迁移流(Migration Stream)中读取 加密后的内存页数据元数据(如加密算法标识、完整性校验值)。
  • 代码逻辑示例
    static int ram_load(QEMUFile *f, void *opaque, int version_id) {
        // 从迁移流中读取加密内存块
        uint8_t *encrypted_data = qemu_get_buffer(f, length);
        // 提取元数据(如IV、MAC)
        struct encryption_metadata meta = parse_metadata(f);
        ...
    }
    
步骤 2:密钥注入与解密环境准备
  • 密钥来源
    密钥通常通过 安全信道(如云平台的密钥管理服务 KMS)预先注入目标虚拟机的安全模块(如 AMD SEV 的 PSP 或 Intel TDX 的 TEE)。
  • 解密触发
    调用硬件加密模块(如 SEV API 或 TDX 指令)进行解密:
    // 伪代码:调用硬件解密接口
    sev_decrypt_memory(target_ram_addr, encrypted_data, meta.iv, meta.key_id);
    
步骤 3:内存解密与完整性验证
  • 解密操作
    加密内存数据在目标虚拟机的 安全隔离环境(如 SEV 加密内存区域)中解密,宿主机无法访问明文。
  • 完整性校验
    验证内存数据的哈希或数字签名(如 HMAC),防止篡改:
    if (!verify_integrity(decrypted_data, meta.mac)) {
        error_report("内存完整性校验失败!");
        return -EINVAL;
    }
    
步骤 4:写入目标虚拟机内存
  • 将解密后的明文数据直接写入虚拟机的 加密内存区域,确保运行时内存始终受硬件加密保护。
  • 内存映射
    依赖硬件特性(如 SEV 的 C-bit)自动加密内存访问:
    // 将解密后的数据写入目标 RAM(硬件自动加密)
    memcpy(target_ram_addr, decrypted_data, page_size);
    

1.3. 关键技术依赖
a. 硬件加密支持
  • AMD SEV
    使用 AES-128 加密内存,密钥由安全处理器(PSP)管理,迁移时通过 VMSA 结构传递密钥句柄。
  • Intel TDX
    基于 MK-TME(Multi-Key Total Memory Encryption),每个虚拟机拥有独立密钥,迁移时通过 TDX 模块更新密钥。
b. 安全传输协议
  • TLS 加密通道
    迁移数据通过 TLS 传输,防止网络窃听(即使内存已加密,仍需保护传输层)。
  • 临时密钥协商
    使用 Diffie-Hellman 或量子安全算法协商临时密钥,用于加密迁移元数据。
c. 零信任设计
  • 宿主机不可信
    解密操作仅在目标虚拟机的安全环境中执行,宿主机只能处理密文。
  • 防重放攻击
    在元数据中包含随机数(Nonce)或时间戳,避免旧数据被重复注入。

1.4. 与 ConfidentialGuestMemoryEncryptionOps 的协作

在支持保密计算的虚拟机中,ram_load 会与加密模块协同工作:

ram_load 触发解密请求 → ConfidentialGuestMemoryEncryptionOps 提供硬件接口 → 安全硬件完成解密
  • 示例交互
    static void ram_load(...) {
        // 调用加密模块的解密回调
        if (csv_encryption_ops && csv_encryption_ops->decrypt_page) {
            csv_encryption_ops->decrypt_page(encrypted_data, target_ram_addr);
        } else {
            // 默认处理(非加密场景)
            memcpy(target_ram_addr, encrypted_data, size);
        }
    }
    

1.5. 典型问题与解决方案
问题场景解决方案
密钥未同步中断迁移,通过 KMS 重新注入密钥
解密后数据校验失败触发回滚(Rollback),重新请求迁移页
硬件加密模块不可用拒绝迁移,记录错误日志(error_report("SEV 解密不可用")
性能瓶颈启用并行解密(如分块多线程处理)

1.6. 代码流程总结
ram_load 入口
   ├─ 读取加密内存页数据
   ├─ 解析元数据(IV、MAC、密钥ID)
   ├─ 请求安全模块解密(通过 ConfidentialGuestMemoryEncryptionOps)
   ├─ 验证数据完整性
   ├─ 写入目标虚拟机加密内存
   └─ 返回加载状态(成功/失败)
深入方向
  • 动态密钥轮换:迁移过程中更新密钥以增强安全性。
  • 混合加密策略:对敏感数据使用硬件加密,非敏感数据使用软件加密(如 AES-NI)。
  • 性能优化:通过硬件加速(如 SEV-SNP 的 VMPL 层级)减少解密延迟。

如果需要分析具体硬件(如 SEV 或 TDX)的解密实现细节,可进一步说明目标平台。

2.0 结构体组成与功能

struct ConfidentialGuestMemoryEncryptionOps csv_memory_encryption_ops = {
.save_setup = sev_save_setup,
.save_outgoing_page = NULL,
.load_incoming_page = csv_load_incoming_page,
.is_gfn_in_unshared_region = NULL,
.save_outgoing_shared_regions_list = sev_save_outgoing_shared_regions_list,
.load_incoming_shared_regions_list = sev_load_incoming_shared_regions_list,
.queue_outgoing_page = csv_queue_outgoing_page,
.save_queued_outgoing_pages = csv_save_queued_outgoing_pages,
.queue_incoming_page = NULL,
.load_queued_incoming_pages = NULL,
.save_outgoing_cpu_state = csv_save_outgoing_context,
.load_incoming_cpu_state = csv_load_incoming_context,
};
2.1. 保存相关操作
  • .save_setup = sev_save_setup
    初始化迁移前的准备工作(如SEV加密虚拟机的证书校验)
  • .save_outgoing_page = NULL
    发送单个内存页的加密操作(当前未实现)
  • .save_outgoing_shared_regions_list = sev_save_outgoing_shared_regions_list
    记录共享内存区域列表(用于SEV的共享内存管理)
2.2. 加载相关操作
  • .load_incoming_page = csv_load_incoming_page
    接收加密内存页的解密操作(csv可能指代Confidential Secure Virtualization)
  • .load_incoming_shared_regions_list = sev_load_incoming_shared_regions_list
    加载共享内存区域列表
2.3. 批量页面队列操作
  • .queue_outgoing_page = csv_queue_outgoing_page
    将待发送的加密内存页加入队列(异步处理优化)
  • .save_queued_outgoing_pages = csv_save_queued_outgoing_pages
    批量处理队列中的加密内存页
2.4. CPU状态管理
  • .save_outgoing_cpu_state = csv_save_outgoing_context
    保存虚拟机CPU加密上下文(如寄存器中的密钥)
  • .load_incoming_cpu_state = csv_load_incoming_context
    恢复CPU加密上下文
2.5. 未实现接口
  • is_gfn_in_unshared_region, queue_incoming_page, load_queued_incoming_pages 设为 NULL
    表示当前场景下无需或暂未支持共享区域检查、接收队列功能

2.6 典型应用场景

此结构体主要用于:

  1. 虚拟机热迁移(Live Migration)
    加密内存页在迁移过程中的安全传输
  2. 内存快照(Snapshot)
    加密状态的持久化保存
  3. 跨安全域通信
    通过共享内存区域列表管理不同信任域的数据交互

2.7 技术背景推测
  • SEV (AMD Secure Encrypted Virtualization):函数前缀 sev_ 表明支持AMD的机密计算技术
  • CSV (可能指 Confidential Secure Virtualization)csv_ 开头的函数可能对应Intel TDX或其他自定义实现
  • 混合架构设计:通过函数指针实现多加密后端的统一抽象(如同时支持SEV和TDX)

2.8 设计特点
  1. 模块化:通过函数指针解耦具体加密算法实现
  2. 按需实现:仅实现必要接口(如不涉及接收队列时不实现相关函数)
  3. 安全隔离:严格区分内存页的加密/解密路径(outgoing vs incoming
static SaveVMHandlers savevm_ram_handlers = {
.save_setup = ram_save_setup,
.save_live_iterate = ram_save_iterate,
.save_live_complete_postcopy = ram_save_complete,
.save_live_complete_precopy = ram_save_complete,
.has_postcopy = ram_has_postcopy,
.save_live_pending = ram_save_pending,
.load_state = ram_load,
.save_cleanup = ram_save_cleanup,
.load_setup = ram_load_setup,
.load_cleanup = ram_load_cleanup,
.resume_prepare = ram_resume_prepare,
};

3.0 结构体 SaveVMHandlers 解析

此结构体用于定义虚拟机快照(Snapshot)与热迁移(Live Migration)过程中 RAM 状态管理的核心操作接口,属于 QEMU/KVM 虚拟化平台的核心模块。以下是逐项解析:


3.1. 保存(Save)阶段
  • .save_setup = ram_save_setup
    初始化 RAM 保存流程(如分配缓冲区、注册内存区域)
  • .save_live_iterate = ram_save_iterate
    迭代传输内存脏页(在 Precopy 阶段持续发送被修改的内存页)
  • .save_live_pending = ram_save_pending
    计算剩余待传输的脏页数量(用于判断迁移进度)
  • .save_cleanup = ram_save_cleanup
    释放保存过程中占用的资源
3.2. 完成(Complete)阶段
  • .save_live_complete_postcopy = ram_save_complete
    Postcopy 迁移模式完成后的收尾操作(边迁移边恢复)
  • .save_live_complete_precopy = ram_save_complete
    Precopy 迁移模式完成后的收尾操作(先全量传输再增量同步)
3.3. 加载(Load)阶段
  • .load_setup = ram_load_setup
    准备恢复 RAM 状态(如映射目标内存区域)
  • .load_state = ram_load
    实际加载内存页数据到目标虚拟机
  • .load_cleanup = ram_load_cleanup
    清理加载过程中的临时资源
3.4. 恢复准备
  • .resume_prepare = ram_resume_prepare
    在恢复前准备虚拟机运行环境(如重建内存映射表)
3.5. 迁移模式支持
  • .has_postcopy = ram_has_postcopy
    检测当前是否支持 Postcopy 迁移模式(依赖硬件和配置)

3.6 关键机制说明
Precopy vs Postcopy
模式特点
Precopy先全量复制内存,再增量同步脏页;停机时间短,但总迁移时间长
Postcopy先启动目标机,按需传输内存页;总迁移时间短,但可能因缺页导致性能波动
脏页跟踪
  • 通过 KVM 的 脏页位图(Dirty Bitmap) 标记修改的内存页
  • .save_live_iterate 会遍历脏页并重置位图,.save_live_pending 统计剩余脏页数量

设计特点
  1. 模块化生命周期管理
    明确划分 Setup → Iterate/Pending → Complete → Cleanup 阶段
  2. 双模式兼容
    通过 .save_live_complete_postcopy/precopy 支持两种迁移策略
  3. 资源安全释放
    .save_cleanup.load_cleanup 确保异常场景下的资源回收
  4. 动态适应性
    .has_postcopy 允许运行时根据环境决定迁移模式

ConfidentialGuestMemoryEncryptionOps 的关联
  • 功能互补性
    SaveVMHandlers 负责通用 RAM 迁移,而 ConfidentialGuestMemoryEncryptionOps 处理加密内存的特殊操作(如密钥同步)
  • 协作流程
    在加密虚拟机迁移时,两者可能通过钩子函数(Hook)协同工作(例如先由 savevm_ram_handlers 传输密文内存,再由 csv_memory_encryption_ops 解密)

4.0 dma_direct_alloc_pages函数概述(这是内核直接实现的)

dma_direct_alloc_pages 是 Linux 内核中用于为 DMA(直接内存访问) 分配物理连续内存的核心函数。它处理内存加密、缓存一致性、高端内存限制等复杂场景,最终返回符合设备要求的 DMA 内存地址和内核虚拟地址(若需要)。


函数参数说明
  • struct device *dev:目标设备(用于 DMA 寻址)
  • size_t size:请求分配的内存大小
  • dma_addr_t *dma_handle:输出参数,存储 DMA 总线地址
  • gfp_t gfp:内存分配标志(如 GFP_KERNEL
  • unsigned long attrs:DMA 属性标志(如 DMA_ATTR_NO_KERNEL_MAPPING

执行流程分析
4.1. 分配物理页面
page = __dma_direct_alloc_pages(dev, size, dma_handle, gfp, attrs);
if (!page)
    return NULL;
  • 调用底层函数 __dma_direct_alloc_pages 分配物理连续的页面。
  • 若分配失败,直接返回 NULL

4.2. 处理无内核映射的场景
if ((attrs & DMA_ATTR_NO_KERNEL_MAPPING) && !force_dma_unencrypted(dev)) {
    if (!PageHighMem(page))
        arch_dma_prep_coherent(page, size); // 清理缓存一致性
    *dma_handle = phys_to_dma(dev, page_to_phys(page)); // 物理地址转 DMA 地址
    return page; // 返回物理页描述符(而非虚拟地址)
}
  • 条件:当属性包含 DMA_ATTR_NO_KERNEL_MAPPING 且无需强制解密时。
  • 行为
    • 若非高端内存,调用架构相关函数 arch_dma_prep_coherent 确保缓存一致性。
    • 直接返回物理页指针 page 作为句柄,不建立内核虚拟地址映射。

4.3. 拒绝高端内存
if (PageHighMem(page)) {
    dev_info(dev, "Rejecting highmem page from CMA.\n");
    __dma_direct_free_pages(dev, size, page);
    return NULL;
}
  • 问题:高端内存(HighMem)无法直接通过内核虚拟地址访问。
  • 处理:若 CMA 分配器返回高端内存,记录错误并释放页面,确保调用者获得低端内存。

4.4. 获取内核虚拟地址
ret = page_address(page); // 获取页面对应的内核虚拟地址
if (force_dma_unencrypted(dev)) {
    set_memory_decrypted((unsigned long)ret, 1 << get_order(size)); // 设置加密标识
    *dma_handle = __phys_to_dma(dev, page_to_phys(page));
} else {
    *dma_handle = phys_to_dma(dev, page_to_phys(page));
}
memset(ret, 0, size); // 清零内存
  • 虚拟地址映射:通过 page_address 获取低端内存的内核虚拟地址。
  • 内存加密:若设备要求强制解密(如某些安全场景),调用 set_memory_decrypted
  • DMA 地址转换:将物理地址转换为设备可识别的 DMA 地址。

4.5. 处理非缓存内存
if (IS_ENABLED(CONFIG_ARCH_HAS_UNCACHED_SEGMENT) &&
    dma_alloc_need_uncached(dev, attrs)) {
    arch_dma_prep_coherent(page, size); 
    ret = uncached_kernel_address(ret); // 转换为非缓存地址
}
  • 条件:内核支持非缓存段且设备需要非缓存内存。
  • 行为
    • 调用 arch_dma_prep_coherent 确保缓存一致性。
    • 将虚拟地址映射到非缓存区域(避免 CPU 缓存干扰 DMA 操作)。

关键点总结
  1. 物理连续性:通过 CMA 或 Buddy 分配器保证内存物理连续,满足 DMA 要求。
  2. 属性处理
    • DMA_ATTR_NO_KERNEL_MAPPING:节省虚拟地址空间,适用于仅设备访问的内存。
    • force_dma_unencrypted:处理加密内存的设备兼容性。
  3. 架构适配:通过 arch_* 函数抽象硬件差异(如缓存操作、地址转换)。
  4. 错误处理:严格拒绝高端内存,避免内核无法直接访问的问题。

典型应用场景
  • 设备驱动:在 dma_alloc_attrs() 中调用此函数,为设备分配 DMA 缓冲区。
  • 加密设备:通过 force_dma_unencrypted 确保内存可被设备直接访问。
  • 高性能场景:使用非缓存内存避免缓存一致性开销。

该函数体现了 Linux 内核在 DMA 内存管理上的灵活性和对硬件特性的深度适配。

5、SEV 加密位开启

原回答中提到的 KVM_MEM_ENCRYPT_OP 已过时,AMD SEV 在 Linux KVM 中的实际操作基于以下 ioctl 命令

  • KVM_SEV_INIT:初始化普通 SEV 环境
  • KVM_SEV_ES_INIT:初始化 SEV-ES(Encrypted State,增强安全模式)
  • KVM_SEV_LAUNCH_START:启动虚拟机加密会话
5.1、C-bit 设置全流程(更新版)
1. 硬件初始化阶段
// QEMU 源码关键路径(target/i386/sev.c)
sev_guest_init(const char *id)
{
    struct kvm_enable_cap cap = {
        .cap = KVM_CAP_VM_MEM_ENC_OP,
        .args[0] = KVM_MEMORY_ENCRYPT_OP // 此处实际已被新接口替代
    };
    kvm_vm_enable_cap(kvm_state, &cap); // 旧接口示例(仅作对比)
}

更新后的内核接口(≥5.10)直接通过 KVM_SEV_INIT 完成初始化。

2. SEV 会话启动(修正后的核心步骤)
// 实际调用流程(QEMU → KVM → AMD PSP)
sev_launch_start(SEVState *s)
{
    struct kvm_sev_cmd cmd = {
        .id = KVM_SEV_INIT,            // 或 KVM_SEV_ES_INIT
        .data = (unsigned long)&start, // 指向 kvm_sev_launch_start 结构
        .error = 0
    };

    struct kvm_sev_launch_start start = {
        .policy = s->policy,
        .handle = 0,
        .cbitpos = s->cbitpos,          // C-bit 物理地址位(通常 47)
        .reduced_phys_bits = s->reduced_phys_bits
    };

    // 调用 KVM ioctl
    ret = kvm_vm_ioctl(kvm_state, KVM_MEMORY_ENCRYPT_OP, &cmd);
}

参数传递逻辑

  • cbitpos:通过 CPUID 获取(AMD EPYC 默认为 47)
  • reduced_phys_bits:计算公式为:
    reduced_phys_bits = 物理地址宽度 − ( cbitpos + 1 ) \text{reduced\_phys\_bits} = \text{物理地址宽度} - (\text{cbitpos} + 1) reduced_phys_bits=物理地址宽度(cbitpos+1)
    例如:48 位地址空间下,cbitpos=47 → reduced_phys_bits=0
3. 内核侧处理(KVM 模块)
// Linux 内核源码(arch/x86/kvm/svm/sev.c)
static int svm_sev_guest_init(struct kvm *kvm)
{
    struct kvm_sev_info *sev = &to_kvm_svm(kvm)->sev_info;
    int asid, ret;

    ret = sev_platform_init(&error);
    if (ret)
        return ret;

    asid = sev_asid_new();
    if (asid < 0)
        return -ENOMEM;

    sev->active = true;
    sev->asid = asid;
    sev->cbitpos = start->cbitpos;      // 从 QEMU 传递的 cbitpos
    sev->reduced_phys_bits = start->reduced_phys_bits;

    /* 设置 C-bit 掩码 */
    sev->asid_cache = __sme_set(asid);
    sev->areas = NULL;
    return 0;
}
5.2、物理地址加密触发机制

当虚拟机访问内存时:

  1. 硬件自动检测:CPU 根据物理地址的 cbitpos 位判断
    • 若该位为 1 → 触发加密内存访问,使用 VM 专属密钥(VEK)
    • 若为 0 → 明文访问
  2. 地址转换规则
    加密物理地址 = ( 原始物理地址  &   ∼ ( 1 U L L < < c b i t p o s ) )   ∣   ( 密文标识 < < c b i t p o s ) \text{加密物理地址} = (\text{原始物理地址} \ \& \ \sim(1ULL << cbitpos)) \ | \ (\text{密文标识} << cbitpos) 加密物理地址=(原始物理地址 & (1ULL<<cbitpos))  (密文标识<<cbitpos)
5.3、验证方法更新
  1. 内核日志检查

    dmesg | grep -i sev
    # 正确输出示例:
    # kvm_amd: SEV supported: 255 ASIDs
    # kvm_amd: SEV-ES supported: 255 ASIDs
    
  2. QEMU 调试参数

    QEMU_OPTIONS += -D qemu_sev.log -d guest_errors
    # 日志中应出现:
    # "sev_launch_start cbitpos=47 reduced_phys_bits=0"
    
5.4、版本兼容性对照表
内核版本对应 IOCTL 命令QEMU 最低版本功能范围
4.16 ~ 5.9KVM_MEMORY_ENCRYPT_OP (旧封装)2.12基础 SEV
≥5.10KVM_SEV_INIT/KVM_SEV_ES_INIT5.0SEV + SEV-ES
≥5.15KVM_SEV_SNP_INIT7.0SEV-SNP(最新扩展)
5.5、常见配置错误排查
  1. ioctl 失败

    # 错误示例:ioctl(KVM_MEMORY_ENCRYPT_OP) failed: 22
    # 原因:内核未启用 CONFIG_KVM_AMD_SEV 或 QEMU 版本过旧
    
  2. C-bit 位冲突

    # 若 dmesg 出现 "SEV: C-bit position conflict with hypervisor"
    # 需检查 BIOS 中 SME 设置是否与 cbitpos 参数一致
    

建议参考 AMD 官方文档《SEV API Spec rev3.34》第 4.3 节,其中明确规定了 KVM_SEV_INIT 的调用时序和参数结构。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值