是的,你的理解非常准确!BL31 作为 ARM Trusted Firmware-A (TF-A) 的运行时服务,常驻于 EL3,任何非 EL3 的代码想要请求 BL31 的服务,都必须通过 SMC 指令进入。

以下是详细的机制解析:


一、BL31 的定位与运行级别

EL3 固件

安全世界

非安全世界

SMC

SMC

SVC -> Kernel -> SMC

EL0: 应用程序

EL1: Linux Kernel

EL1: OP-TEE/Trusty

BL31: Runtime Services
PSCI/Secure Monitor

BL31 的核心特征:

  • 运行级别:EL3(最高特权级)
  • 生命周期:系统启动后常驻内存,永不退出
  • 职责:充当安全监控器,管理世界切换和安全服务

二、为什么必须用 SMC 调用?

1. 特权级隔离原则

调用者 当前 EL 目标 EL 必须使用的指令
Linux Kernel EL1 EL3 SMC
OP-TEE EL1 (Secure) EL3 SMC
Hypervisor EL2 EL3 SMC
U-Boot (BL33) EL2/EL1 EL3 SMC

原因

  • SVC 只能从 EL0 切换到 EL1
  • HVC 只能从 EL0/EL1 切换到 EL2
  • 只有 SMC 能切换到 EL3

2. SMC 调用标准(SMCCC)

ARM 定义了 SMC Calling Convention (SMCCC),规定了如何通过寄存器传递参数:

// SMC 调用约定
x0 = Function ID (功能号,标识请求的服务)
x1 ~ x7 = 参数
SMC #0
// 返回时
x0 ~ x4 = 返回值

三、BL31 服务调用完整流程

以 Linux 内核请求 CPU 休眠(PSCI 服务)为例:

EL3: SMC Handler EL3: BL31 Runtime EL1: Linux Kernel EL3: SMC Handler EL3: BL31 Runtime EL1: Linux Kernel 准备参数 x0=PSCI_FN_CPU_SUSPEND x1=power_state psci_cpu_suspend() SMC 保存 EL1 状态 smc_handler64() 解析 x0 判断功能 执行 PSCI 操作 设置返回值 x0 ERET (返回 EL1) 继续执行

四、BL31 提供的典型服务

服务类型 Function ID 范围 典型功能
PSCI 0x8400xxxx / 0xC400xxxx CPU 开关机、休眠、系统复位
SiP 0x8200xxxx 厂商自定义服务
OEM 0x8300xxxx OEM 厂商服务
Standard 0x8600xxxx 标准 Secure Service

示例:系统复位

// Linux 内核中的调用
void psci_system_reset(void)
{
    // x0 = PSCI_FN_SYSTEM_RESET (0x84000009)
    // x1 = reset_type (复位类型)
    asm volatile(
        "mov x0, #0x84000009\n"
        "mov x1, #0\n"          // 冷复位
        "smc #0\n"
    );
    // 永远不会执行到这里
}

五、BL31 内部的处理机制

// BL31 的 SMC 处理流程(简化)
void smc_handler64(void) {
    uint64_t smc_fid = get_smc_fid(x0);  // 获取功能号
    
    // 根据功能号分发
    if (is_psci_fid(smc_fid)) {
        ret = psci_smc_handler(smc_fid, x1, x2, x3);
    } 
    else if (is_sip_fid(smc_fid)) {
        ret = sip_smc_handler(smc_fid, x1, x2, x3);
    }
    else {
        ret = SMC_UNK;  // 未知服务
    }
    
    // 设置返回值
    x0 = ret;
    eret();  // 返回调用者
}

六、特殊情况说明

1. 中断也可以进入 EL3

除了 SMC,以下异步异常也会进入 BL31:

异常类型 触发源 用途
IRQ 外部中断 EL3 处理安全中断
FIQ 快速中断 TrustZone 安全中断
SError 系统错误 致命错误处理

2. BL31 内部调用不需要 SMC

如果代码已经在 EL3(BL31 内部),直接函数调用即可:

// 在 BL31 内部
void bl31_internal_call(void) {
    // 已经在 EL3,直接调用
    psci_cpu_on(target_cpu, entrypoint);
    // 不需要 SMC
}

七、总结

EL0/EL1/EL2

EL3

想要调用 BL31 服务

当前在哪个EL?

必须使用 SMC 指令

直接函数调用

触发同步异常

硬件切换到 EL3

BL31 处理请求

ERET 返回

核心结论:

  1. BL31 常驻 EL3,是安全世界的守门人
  2. SMC 是唯一标准入口,从非 EL3 进入 BL31 必须用 SMC
  3. 遵循 SMCCC 规范,通过 x0 传递功能号,x1-x7 传参
  4. BL31 处理后通过 ERET 返回,恢复调用者状态

这就是为什么在 Linux 内核中,所有 PSCI(电源管理)相关操作最终都会执行 smc 指令的原因。

这是一个非常敏锐的安全问题!你的担忧很有道理:如果随便发个中断就能进 EL3,那安全隔离岂不是形同虚设?

答案是:无法绕过。

虽然中断能进入 EL3,但它和 SMC 调用有本质区别。简单来说:SMC 是“办事窗口”,中断是“应急通道”。


一、为什么中断无法绕过安全检查?

1. 执行流不可控

特性 SMC 调用 中断进入
触发方式 软件主动执行 SMC 指令 硬件外部信号触发
目标代码 根据 x0(功能号) 查表跳转 跳转到 固定的中断向量
能否指定服务 ✅ 可以(我想执行 PSCI 关机) ❌ 不可以(只能执行中断处理逻辑)
能否传参 ✅ 可以(x1-x7 自由设置) ❌ 不可以(寄存器保存的是被打断的上下文)

2. 中断处理逻辑是固定的

当 IRQ 触发进入 EL3 时,CPU 跳转到 VBAR_EL3 + Offset,执行的是 BL31 预先写死的代码:

// BL31 中断处理流程(伪代码)
void el3_irq_handler(void) {
    // 1. 保存上下文(此时 x0-x7 是被打断进程的数据,不是服务参数)
    save_context();

    // 2. 查询中断控制器(GIC),看是谁发的中断
    uint32_t intid = gic_get_pending_irq();

    // 3. 根据中断号分发(这是硬件中断号,不是 SMC 功能号)
    switch (intid) {
        case SECURE_TIMER_IRQ:
            handle_secure_timer();
            break;
        case SGI_FOR_WORLD_SWITCH:
            // 可能用于 OP-TEE 与 Linux 的通信调度
            handle_world_switch_request(); 
            break;
        default:
            panic(); // 未知中断,直接报错
    }
    
    // 4. 恢复上下文返回
    eret();
}

关键点:你在 EL1/EL0 触发中断后,只能执行 BL31 允许你执行的那几段固定逻辑(比如处理定时器、维护调度),无法像 SMC 那样请求“CPU 下电”或“修改安全内存”。


二、中断路由控制:大部分中断进不了 EL3

ARM 架构通过 GIC(中断控制器) 严格控制谁能接收中断:

路由配置

Group 0: 安全中断

Group 1: 非安全中断

外部中断信号

GIC: 中断控制器

路由判断

EL3: BL31 处理

EL1: Linux Kernel 处理

配置示例

BL31 在启动时会配置 GIC:

// BL31 初始化代码
void gic_setup(void) {
    // 将 UART 中断路由给 Linux (EL1)
    gic_set_group(UART_IRQ, GICV3_GROUP1_NS); 
    
    // 将安全定时器中断路由给 EL3
    gic_set_group(SEC_TIMER_IRQ, GICV3_GROUP0);
    
    // 将 SGI (软件中断) 配置为只能由 EL3 接收
    // 即使 EL1 尝试触发 SGI,也只会路由到 EL1 自己
}

结论:EL1/EL0 的大部分外设中断(网卡、磁盘、UART)根本进不了 EL3,只能去 EL1。


三、对比:SMC vs 中断进入 EL3

场景:恶意代码想关机

❌ 尝试 1:通过中断绕过
// 恶意代码在 EL1
mov x0, #PSCI_SYSTEM_OFF  // 想关机
// 尝试触发一个 IRQ?
// 问题:无法把 x0 传给中断处理函数
// 问题:中断处理函数根本不解析 x0,也不提供关机服务
trigger_irq(); 
// 结果:EL3 只是处理了中断,然后返回继续执行,根本没关机
✅ 正确方式:通过 SMC
// 正常代码在 EL1
mov x0, #PSCI_SYSTEM_OFF
smc #0
// 结果:EL3 解析 x0,执行关机逻辑

四、那中断进入 EL3 有什么用?

既然不能“办事”,为什么还要让中断进 EL3?

用途 说明
世界调度 OP-TEE (Secure) 和 Linux (Non-Secure) 切换时,通过 SGI 中断通知 EL3
安全定时器 EL3 自己的定时器中断,用于实现可信时间源
电源管理事件 比如系统休眠唤醒的硬件信号,需要 EL3 第一时间处理
错误处理 SError(总线错误)可能需要 EL3 收集故障信息

五、安全模型总结

请求服务

触发中断

非安全世界 EL1/EL0

想进入 EL3

SMC 同步异常

IRQ/FIQ 异步异常

✅ 可控入口

解析参数 x0-x7

执行请求的服务

❌ 固定流程

查询硬件中断号

执行预设的处理函数

不解析用户参数
不执行任意服务

核心结论

  1. 中断只能“进”,不能“用”:中断可以触发 EL3 代码执行,但执行什么完全由 BL31 预先定义,攻击者无法注入意图。
  2. SMC 是唯一的“服务接口”:只有 SMC 允许非安全世界传递参数(Function ID)并请求特定操作。
  3. 路由隔离:绝大多数中断被 GIC 拦截在 EL1,根本无法触及 EL3。

所以,中断机制不会破坏 EL3 的安全隔离,反而通过硬件路由和固定处理流程,确保了 EL3 的安全边界。

更多推荐