在ARMv9架构引入的Realm Management Extension(RME)中,Granule保护检查(Granule Protection Check,简称GPC)是一项关键的安全特性。它通过在传统的内存管理单元(MMU)之外增加额外的保护层,为系统提供了更细粒度的内存访问控制。
Granule是ARM架构中内存管理的基本单位,通常对应页表的最小粒度(如4KB、16KB或64KB)。GPC机制的核心目标是在物理内存层面实施强制访问控制,确保:
与传统MMU基于虚拟地址的访问控制不同,GPC工作在物理地址层面,即使攻击者成功操纵了页表,也无法绕过这层保护。
GPC机制主要涉及以下核心组件:
典型的内存访问流程如下:
code复制物理地址生成 → MMU地址翻译 → GPC检查 → 内存访问
从提供的伪代码可以看出,GranuleProtectionCheck()函数是GPC的核心实现。其主要逻辑如下:
c复制func GranuleProtectionCheck(addrdesc : AddressDescriptor, accdesc : AccessDescriptor) => GPCFRecord
begin
// 1. 特性检查
assert IsFeatureImplemented(FEAT_RME);
// 2. 获取物理地址
let address = addrdesc.paddress;
// 3. 检查是否启用bypass模式
if GPCCR_EL3().GPC == '0' then
return GPCNoFault();
end;
// 4. 寄存器一致性检查
if !GPCRegistersConsistent() then
return GPCFault(GPCF_Walk, 0);
end;
// 5. GPC2特性相关检查
if IsFeatureImplemented(FEAT_RME_GPC2) then
// 检查各PASpace的访问禁用位
var access_disabled : boolean;
case address.paspace of
when PAS_Secure => access_disabled = GPCCR_EL3().SPAD == '1';
when PAS_NonSecure => access_disabled = GPCCR_EL3().NSPAD == '1';
when PAS_Realm => access_disabled = GPCCR_EL3().RLPAD == '1';
when PAS_Root => access_disabled = FALSE;
otherwise => unreachable;
end;
if access_disabled then
return GPCFault(GPCF_Fail, 0);
end;
end;
// 6. 地址大小检查
if AbovePPS(address.address) then
if (address.paspace == PAS_NonSecure ||
(IsFeatureImplemented(FEAT_RME_GPC2) && GPCCR_EL3().APPSAA == '1')) then
return GPCNoFault();
else
return GPCFault(GPCF_Fail, 0);
end;
end;
// 7. 检查GPC bypass窗口
if (IsFeatureImplemented(FEAT_RME_GPC3) && GPCCR_EL3().GPCBW == '1' &&
PAWithinGPCBypassWindow(address.address)) then
return GPCNoFault();
end;
// 8. GPT基地址检查
let gpt_base : bits(56) = ZeroExtend{}(GPTBR_EL3().BADDR::Zeros{12});
if AbovePPS(gpt_base) then
return GPCFault(GPCF_AddressSize, 0);
end;
// 9. GPT查找
var (gpcf, gpt_entry) = GPTWalk(address.address, accdesc);
if gpcf.gpf != GPCF_None then
return gpcf;
end;
// 10. GPI检查
let permitted : boolean = GPICheck(address.paspace, gpt_entry.gpi, accdesc.ss);
if !permitted then
gpcf = GPCFault(GPCF_Fail, gpt_entry.level);
return gpcf;
end;
// 11. 检查通过
return GPCNoFault();
end;
在支持FEAT_RME_GPC2的情况下,系统可以通过GPCCR_EL3寄存器的SPAD、NSPAD和RLPAD位分别禁用安全、非安全和领域空间的访问:
c复制if IsFeatureImplemented(FEAT_RME_GPC2) then
case address.paspace of
when PAS_Secure => access_disabled = GPCCR_EL3().SPAD == '1';
when PAS_NonSecure => access_disabled = GPCCR_EL3().NSPAD == '1';
when PAS_Realm => access_disabled = GPCCR_EL3().RLPAD == '1';
// ...
end;
end;
这种设计允许系统在检测到安全威胁时,快速锁定特定安全域的所有内存访问。
GPTWalk函数负责在Granule保护表中查找目标地址对应的条目。这个过程类似于页表遍历,但有几个关键区别:
GPICheck函数根据以下参数验证访问权限:
典型的GPI编码如下:
c复制constant GPT_Secure : bits(4) = '1000';
constant GPT_NonSecure : bits(4) = '1001';
constant GPT_Root : bits(4) = '1010';
constant GPT_Realm : bits(4) = '1011';
// ...
FEAT_RME_GPC3引入了GPC bypass窗口特性,允许特定范围的物理地址绕过GPC检查:
c复制func PAWithinGPCBypassWindow(pa_in : bits(56)) => boolean
begin
let pa : bits(26) = pa_in[55:30];
let gpcbwl : integer{} = UInt(GPCBW_EL3().BWSIZE);
let gpcbwu : integer{} = 9 + UInt(GPCBW_EL3().BWSTRIDE);
return pa[gpcbwu:gpcbwl] == GPCBW_EL3().BWADDR[gpcbwu:gpcbwl];
end;
这个特性对性能关键的内存区域(如DMA缓冲区)特别有用,可以避免GPC检查带来的延迟。
IsGranuleProtectionCheckedAccess函数定义了哪些内存访问需要经过GPC检查:
c复制func IsGranuleProtectionCheckedAccess(accdesc : AccessDescriptor) => boolean
begin
// 标签访问的数据传输跳过GPC
if accdesc.datafortagaccess then
return FALSE;
end;
// DC操作的处理由实现定义
if accdesc.acctype == AccessType_DC then
return ImpDefBool("GPC Fault on DC operations");
end;
return TRUE;
end;
这种设计确保了内存标签管理(如MTE)不会受到GPC的干扰。
在虚拟化场景下,GPC与Stage-2页表协同工作:
这种分层设计既保持了虚拟化的灵活性,又提供了硬件级的安全保障。
注意:启用GPC会导致平均内存访问延迟增加5-15%,具体取决于实现。在安全关键场景中,这种开销通常是可接受的。
| 故障类型 | 可能原因 | 调试方法 |
|---|---|---|
| GPCF_Walk | GPT寄存器配置错误 | 检查GPTBR_EL3和GPCCR_EL3 |
| GPCF_Fail | 权限不足 | 检查GPI与PASpace匹配性 |
| GPCF_AddressSize | 地址超出范围 | 验证物理地址有效性 |
问题1:系统在启用GPC后频繁触发GPCF_Fault
排查步骤:
问题2:GPC导致性能显著下降
优化建议:
在真实项目中,我曾遇到一个案例:某系统在领域世界访问共享缓冲区时随机性失败。最终发现是GPT中一个块描述符错误地将部分区域标记为安全专属。这个案例凸显了GPT配置验证的重要性。
随着ARM架构演进,GPC机制可能会:
对于开发者而言,理解当前GPC实现的同时,保持对架构更新的关注也很重要。