Zero-OS 合规模块
源文件:
kernel/compliance/lib.rs
Crate:compliance(v0.1.0)
环境:#![no_std]
目录
- 概述
- 架构与设计原则
- 加固配置
- FIPS 模式
- 密码算法策略
- FIPS 已知答案测试(KAT)
- 审计集成
- 与安全模块的集成
- 合规报告
- 关键数据结构
- 并发与安全性
- 关键函数逐行解析
- 启动流程集成
- 缺陷修复历史
概述
compliance 模块是 Zero-OS 内核安全态势的集中式策略引擎。它管理系统加固的两个正交维度:
-
加固配置 —— 在启动时选择安全性与性能之间的权衡方案(安全 / 均衡 / 性能)。每个配置映射到一个具体的
SecurityConfig,用于控制 W^X 强制执行、Spectre 缓解措施、内核指针混淆以及审计缓冲区大小。 -
FIPS 模式 —— 一个可在运行时激活的、粘性的(单向的,重启前不可撤销)标志,用于强制执行 FIPS 140-2 / 140-3 合规性。启用后,仅允许使用经 FIPS 批准的密码算法,且所有密码操作均会被审计。激活前需通过一组 NIST 已知答案测试(KAT)。
该模块专为裸机 no_std 内核环境设计。它仅使用无锁原子操作管理全局状态,从不分配堆内存,并与 audit、security 和 livepatch crate 集成。
依赖项
| Crate | 作用 |
|---|---|
security | 接收由当前加固配置生成的 SecurityConfig |
audit | 提供 SHA-256 / HMAC-SHA256 密码原语及事件发送 API |
livepatch | 提供 ECDSA P-256 验证器及其 KAT(run_kat_if_needed) |
spin | 自旋锁原语(列于 Cargo.toml 中,当前源码未直接使用) |
x86_64 | VirtAddr 类型,用于物理内存偏移 |
架构与设计原则
合规模块遵循以下安全工程原则:
-
失败即关闭(Fail-Closed):对原子值的每个 match 匹配都包含一个通配符分支(
_),回退到最严格的状态。损坏的配置值默认为Secure;损坏的 FIPS 值默认为Failed。这确保内存损坏或位翻转永远不会削弱安全态势。 -
粘性状态(Sticky State):FIPS 模式一旦启用便无法禁用。
FIPS_MODE原子变量只能从Disabled -> Enabled或Disabled -> Failed转换,不存在反向路径。 -
单一事实来源(Single Source of Truth):合规模块不重复实现密码算法,而是将 KAT 执行委托给所属子系统(
audit::crypto负责 SHA-256/HMAC,livepatch::ecdsa_p256负责 ECDSA),以防止实现偏差。 -
零分配(Zero Allocation):整个模块无需堆分配即可运行,因此在分配器初始化之前的早期启动阶段也可安全调用。
-
无锁并发(Lock-Free Concurrency):所有全局状态均使用
core::sync::atomic类型,并带有显式的Ordering标注。正常操作期间不持有任何互斥锁或自旋锁。
加固配置
枚举定义
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum HardeningProfile {
Secure = 0,
Balanced = 1,
Performance = 2,
}
#[repr(u8)] 确保该枚举具有稳定的单字节表示,适合原子存储。
配置对比矩阵
| 特性 | 安全(Secure) | 均衡(Balanced) | 性能(Performance) |
|---|---|---|---|
| W^X 强制执行 | 严格(违规时 panic) | 仅警告 | 禁用 |
| NX 强制执行 | 是 | 是 | 是 |
| Spectre 缓解措施(IBRS/IBPB/STIBP) | 全部启用 | 全部启用 | 禁用 |
| 内核指针混淆(kptr guard) | 是 | 是 | 禁用 |
| 恒等映射清理 | RemoveWritable | RemoveWritable | |
| RNG 初始化 | 是 | 是 | 是 |
| 安全自检 | 是 | 否 | 否 |
| 审计环容量 | 256 条 | 128 条 | 64 条 |
| 推荐用途 | 生产环境、高安全性 | 开发环境、通用场景 | 基准测试、低延迟 |
配置解析
from_str 方法接受不区分大小写的别名:
| 配置 | 可接受的字符串 |
|---|---|
| Secure | secure、strict、hardened |
| Balanced | balanced、default、normal |
| Performance | performance、perf、fast |
解析使用固定的 16 字节栈缓冲区进行大小写转换,避免堆分配。超过 16 字节的字符串会被静默截断。
默认配置
Default 实现返回 Balanced,为开发环境提供合理的安全性/性能权衡。
SecurityConfig 生成
每个配置通过 security_config(&self, phys_offset) 方法生成完整的 security::SecurityConfig 结构体。phys_offset 参数(x86_64::VirtAddr 类型)必须由内存子系统提供,表示物理内存恒等映射的虚拟地址。
各配置在生成的 SecurityConfig 中的关键差异:
- Secure:
strict_wxorx = true(W^X 违规时 panic),run_security_tests = true - Balanced:
strict_wxorx = false(警告但继续运行),run_security_tests = false - Performance:
validate_wxorx = false,enable_kptr_guard = false,enable_spectre_mitigations = false
三种配置共享:cleanup_strategy = RemoveWritable,enforce_nx = true,initialize_rng = true。
FIPS 模式
状态机
FIPS 模式建模为三状态状态机:
enable_fips_mode()
Disabled --------------------------> Enabled
|
| (自检失败)
+--------------> Failed
- 未知/损坏的原子值映射为
Failed(失败即关闭)。 Enabled和Failed是终态(重启前不可转出)。
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum FipsState {
Disabled = 0,
Enabled = 1,
Failed = 2,
}
状态转换:
Disabled -> Enabled:所有 KAT 通过后成功调用enable_fips_mode()。Disabled -> Failed:调用enable_fips_mode()时某个 KAT 失败。Enabled -> (无转换):FIPS 模式具有粘性,无法禁用。Failed -> (无转换):失败状态在重启前是永久的。
粘性语义
一旦 FIPS_MODE 被设置为 Enabled,就没有 API 可以将其恢复为 Disabled。退出 FIPS 模式的唯一方式是系统重启,届时 AtomicU8 会被重新初始化为 0(Disabled)。
错误类型
pub enum FipsError {
AlreadyEnabled, // FIPS 模式已处于激活状态
EnableFailed, // FIPS 模式此前已失败(永久性)
NotPermitted, // 操作被 FIPS 策略阻止
SelfTestFailed, // 启用过程中 KAT 失败
AccessDenied, // 权限检查失败
}
密码算法策略
算法注册表
pub enum CryptoAlgorithm {
// 哈希函数
Sha256, Sha384, Sha512, Blake2b,
// 消息认证码
HmacSha256,
// 签名
EcdsaP256, EcdsaP384, Ed25519,
// 加密
Aes128Gcm, Aes256Gcm, ChaCha20, ChaCha20Poly1305,
}
FIPS 批准矩阵
| 算法 | FIPS 批准 | 标准 |
|---|---|---|
| SHA-256 | 是 | FIPS 180-4 |
| SHA-384 | 是 | FIPS 180-4 |
| SHA-512 | 是 | FIPS 180-4 |
| Blake2b | 否 | RFC 7693 |
| HMAC-SHA256 | 是 | FIPS 198-1 |
| ECDSA P-256 | 是 | FIPS 186-4 |
| ECDSA P-384 | 是 | FIPS 186-4 |
| Ed25519 | 否 | RFC 8032 |
| AES-128-GCM | 是 | FIPS 197 / SP 800-38D |
| AES-256-GCM | 是 | FIPS 197 / SP 800-38D |
| ChaCha20 | 否 | RFC 8439 |
| ChaCha20-Poly1305 | 否 | RFC 8439 |
策略执行逻辑
is_algorithm_permitted(algorithm) 函数实现三路判定:
- FIPS 已禁用:所有算法均被允许(立即返回
true)。 - FIPS 已启用:仅允许经 FIPS 批准的算法(通过匹配表检查)。
- FIPS 已失败:不允许任何算法(立即返回
false)。这就是失败即关闭行为——如果 FIPS 自检失败,所有密码操作均被阻止。
FIPS 已知答案测试(KAT)
fips_kat 子模块实现了 NIST 规定的已知答案测试,这些测试必须在 FIPS 模式激活前全部通过。
测试套件
| 测试 | 标准 | 测试向量 | 实现 |
|---|---|---|---|
| SHA-256 | FIPS 180-4 / CAVP | 空字符串、"abc"、100 万个 'a' | audit::crypto::sha256_digest / StreamingSha256 |
| HMAC-SHA256 | NIST CSRC / RFC 4231 | 64 字节密钥、32 字节密钥、100 字节密钥 | audit::crypto::hmac_sha256_digest |
| ECDSA P-256 | RFC 6979 A.2.5 | 确定性签名验证 | livepatch::ecdsa_p256::run_kat_if_needed |
SHA-256 KAT 详情
验证三个测试向量:
- 空消息:
SHA-256("") = e3b0c442...—— 测试无输入数据的基本情况。 - 短消息:
SHA-256("abc") = ba7816bf...—— 测试单块输入。 - 长消息(流式):
SHA-256("a" * 1,000,000) = cdc76e5c...—— 通过以 64 字节块(与 SHA-256 内部块大小一致)逐步输入数据来测试流式哈希接口。这在不进行堆分配的情况下测试了多块处理和填充逻辑。
HMAC-SHA256 KAT 详情
按照 NIST CSRC 规范测试三种密钥长度场景:
- 密钥 = 块长度(64 字节):密钥
0x00..0x3f,消息"Sample message for keylen=blocklen"。测试密钥恰好填满一个块的标准路径。 - 密钥 < 块长度(32 字节):密钥
0x00..0x1f,消息"Sample message for keylen<blocklen"。测试零填充路径。 - 密钥 > 块长度(100 字节):密钥
0x00..0x63,消息"Sample message for keylen=blocklen"。测试密钥哈希路径,即 HMAC 必须先对密钥进行哈希以将其缩减到块大小。
ECDSA P-256 KAT 详情
委托给 livepatch::ecdsa_p256::run_kat_if_needed() 执行,该函数运行 RFC 6979 附录 A.2.5 中的确定性 ECDSA 测试向量:
- 私钥:
d = 0xC9AFA9D845BA75166B5C215767B1D6934E50C3DB36E89B127B8A622B120F6721 - 消息:
"sample"(使用 SHA-256 预哈希) - 签名
(r, s)根据从d派生的公钥进行验证。
常量时间比较
所有 KAT 结果比较均使用常量时间相等函数(ct_eq)以防止时序侧信道攻击:
fn ct_eq(a: &[u8], b: &[u8]) -> bool {
if a.len() != b.len() {
return false;
}
let mut diff = 0u8;
for i in 0..a.len() {
diff |= a[i] ^ b[i];
}
diff == 0
}
该函数对所有字节累积异或差异,仅在最后检查结果,确保比较时间与不匹配位置无关。
审计集成
FIPS 状态变更通过审计子系统记录。emit_fips_audit_event 函数构造的审计事件包含:
- 类别(Kind):
AuditKind::Security(类别 5) - 结果(Outcome):
AuditOutcome::Success或AuditOutcome::Error - 主体(Subject):
AuditSubject::kernel()(内核本身是操作者) - 对象(Object):
AuditObject::None(无目标资源) - 参数(Args):
[event_type, success_flag],其中event_type = 1(FIPS 启用事件),success_flag为1(成功)或0(失败) - 错误码(Errno):
0(无错误码) - 时间戳(Timestamp):
0(由审计子系统填充)
审计事件在两种情况下发送:
- FIPS 激活成功(
success = true, message = "enabled") - 因 KAT 失败导致 FIPS 激活失败(
success = false, message = "self-test failed")
emit 调用的结果被显式丢弃(let _ = ...),因为审计失败不应阻止 FIPS 状态转换——FIPS 状态变更是主要操作,审计日志记录是尽力而为的。
与安全模块的集成
合规模块是高层策略(加固配置)与底层执行(security crate)之间的桥梁。集成流程如下:
启动命令行 --> compliance::HardeningProfile::from_str()
|
v
compliance::set_profile(profile)
|
v
profile.security_config(phys_offset)
|
v
security::SecurityConfig { ... }
|
v
security::init(config, &mut frame_allocator)
|
v
compliance::lock_profile() // 重启前不可变
SecurityConfig 结构体字段及其含义:
| 字段 | 类型 | 描述 |
|---|---|---|
phys_offset | VirtAddr | 物理内存映射的虚拟地址 |
cleanup_strategy | IdentityCleanupStrategy | 如何处理引导加载程序的恒等映射(始终为 RemoveWritable) |
enforce_nx | bool | 在数据页上设置 NX 位 |
validate_wxorx | bool | 检查 W^X 违规 |
strict_wxorx | bool | W^X 违规时 panic(而非仅警告) |
initialize_rng | bool | 初始化 RDRAND/RDSEED + ChaCha20 CSPRNG |
enable_kptr_guard | bool | 在日志中混淆内核指针 |
enable_spectre_mitigations | bool | 启用 IBRS/IBPB/STIBP |
run_security_tests | bool | 运行运行时安全自检 |
合规报告
ComplianceStatus 结构体提供当前合规态势的快照:
pub struct ComplianceStatus {
pub profile: HardeningProfile,
pub profile_locked: bool,
pub fips_state: FipsState,
}
通过 compliance::status() 获取,该结构体聚合了:
- 当前活跃的加固配置(从
ACTIVE_PROFILE原子变量读取) - 配置是否已锁定(从
PROFILE_LOCKED原子变量读取) - 当前 FIPS 状态(从
FIPS_MODE原子变量读取,采用失败即关闭语义)
这对于运行时自省、健康检查和合规仪表板非常有用。
关键数据结构
全局原子变量
| 静态变量 | 类型 | 初始值 | 用途 |
|---|---|---|---|
ACTIVE_PROFILE | AtomicU8 | 1(Balanced) | 存储当前活跃的加固配置 |
PROFILE_LOCKED | AtomicBool | false | 防止启动后修改配置 |
FIPS_MODE | AtomicU8 | 0(Disabled) | 存储 FIPS 状态 |
FIPS_ENABLING | AtomicBool | false | 序列化并发的 FIPS 启用尝试 |
枚举
| 枚举 | 表示 | 变体 | 用途 |
|---|---|---|---|
HardeningProfile | u8 | Secure(0)、Balanced(1)、Performance(2) | 安全级别选择 |
FipsState | u8 | Disabled(0)、Enabled(1)、Failed(2) | FIPS 模式状态机 |
FipsError | -- | AlreadyEnabled、EnableFailed、NotPermitted、SelfTestFailed、AccessDenied | 错误报告 |
CryptoAlgorithm | -- | 12 个变体(哈希、MAC、签名、加密) | 算法标识 |
结构体
| 结构体 | 字段 | 用途 |
|---|---|---|
ComplianceStatus | profile、profile_locked、fips_state | 运行时合规快照 |
并发与安全性
内存排序
模块全程使用显式的原子排序:
-
Ordering::Acquire用于所有加载操作:确保对原子值的读取能看到对应Release存储之前发生的所有写入。 -
Ordering::Release用于所有存储操作:确保存储对其他核心上后续的Acquire加载可见。 -
Ordering::AcqRel用于compare_exchange:用于 FIPS 启用序列化锁(FIPS_ENABLING)。
FIPS 启用序列化
enable_fips_mode() 函数通过 FIPS_ENABLING 使用自旋锁模式来序列化并发的启用尝试:
线程 A:CAS(FIPS_ENABLING, false -> true) --> 成功,运行 KAT
线程 B:CAS(FIPS_ENABLING, false -> true) --> 失败,自旋等待
线程 A:KAT 通过,存储 FIPS_MODE = Enabled
线程 A:EnableGuard 析构,存储 FIPS_ENABLING = false
线程 B:CAS 成功,重新检查 FIPS_MODE --> AlreadyEnabled,返回错误
EnableGuard RAII 结构体确保 FIPS_ENABLING 始终被释放,即使在 KAT 执行期间发生 panic 也是如此。
TOCTOU 防护
enable_fips_mode() 函数两次检查 fips_state():
- 获取启用锁之前(快速路径拒绝)
- 获取启用锁之后(防止 TOCTOU 竞态条件)
配置锁定
配置锁是一个单向闩锁:PROFILE_LOCKED 从 false 转换为 true 后不再回退。这是安全的,因为 set_profile() 仅在早期单线程启动阶段调用,而 lock_profile() 紧随其后立即调用。
关键函数逐行解析
enable_fips_mode()
pub fn enable_fips_mode() -> Result<(), FipsError> {
公共入口点。FIPS 激活成功时返回 Ok(()),否则返回类型化错误。
match fips_state() {
FipsState::Enabled => return Err(FipsError::AlreadyEnabled),
FipsState::Failed => return Err(FipsError::EnableFailed),
FipsState::Disabled => {}
}
快速路径检查(第 249-253 行):在尝试获取开销较大的自旋锁和执行 KAT 之前,如果 FIPS 已启用或永久失败,则立即拒绝。
while FIPS_ENABLING
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
.is_err()
{
core::hint::spin_loop();
}
自旋锁获取(第 256-261 行):原子地尝试将 FIPS_ENABLING 从 false 设置为 true。如果另一个线程持有该锁,则通过 core::hint::spin_loop() 自旋等待(在 x86 上发出 PAUSE 指令)。
struct EnableGuard;
impl Drop for EnableGuard {
fn drop(&mut self) {
FIPS_ENABLING.store(false, Ordering::Release);
}
}
let _guard = EnableGuard;
RAII 守卫(第 264-270 行):定义一个零大小类型,其 Drop 实现释放启用锁。这保证了在所有退出路径上锁都会被释放。
match fips_state() {
FipsState::Enabled => return Err(FipsError::AlreadyEnabled),
FipsState::Failed => return Err(FipsError::EnableFailed),
FipsState::Disabled => {}
}
TOCTOU 重新检查(第 273-277 行):获取锁之后,重新读取 FIPS 状态以防止竞态条件。
if !run_fips_self_tests() {
FIPS_MODE.store(FipsState::Failed as u8, Ordering::Release);
emit_fips_audit_event(false, "self-test failed");
return Err(FipsError::SelfTestFailed);
}
KAT 执行(第 280-284 行):运行所有 FIPS 已知答案测试。如果任何测试失败,FIPS 状态将被永久设置为 Failed。
FIPS_MODE.store(FipsState::Enabled as u8, Ordering::Release);
emit_fips_audit_event(true, "enabled");
Ok(())
激活(第 287-290 行):所有 KAT 均已通过。将 FIPS 模式设置为 Enabled(粘性),发送成功审计事件,并返回 Ok。
current_profile()
pub fn current_profile() -> HardeningProfile {
match ACTIVE_PROFILE.load(Ordering::Acquire) {
0 => HardeningProfile::Secure,
1 => HardeningProfile::Balanced,
2 => HardeningProfile::Performance,
_ => HardeningProfile::Secure, // 失败即关闭
}
}
通配符分支(_)实现了失败即关闭原则:如果原子值被损坏,系统默认回退到最严格的 Secure 配置。
fips_state()
pub fn fips_state() -> FipsState {
match FIPS_MODE.load(Ordering::Acquire) {
0 => FipsState::Disabled,
1 => FipsState::Enabled,
2 => FipsState::Failed,
_ => FipsState::Failed, // 失败即关闭
}
}
失败即关闭的通配符分支将损坏的值映射为 Failed,这会导致 is_algorithm_permitted() 阻止所有算法。
is_algorithm_permitted()
两阶段判定:首先检查 FIPS 状态(对 Disabled/Failed 进行短路处理),然后将特定算法与 FIPS 批准列表进行比对。Failed 状态会阻止一切。
fips_kat::ct_eq()
常量时间字节比较。对每对字节进行异或运算,并将结果按位或累积到一个累加器中。无论不匹配出现在哪个位置,循环始终执行完整长度,从而防止时序侧信道攻击。
启动流程集成
合规模块按如下方式集成到 Zero-OS 启动流程中(来自 kernel/src/main.rs):
[2.6/3] 应用安全加固...
1. 选择配置: compliance::HardeningProfile::Balanced
2. 设置配置: compliance::set_profile(profile)
3. 生成配置: profile.security_config(phys_offset)
4. 打印信息: "Profile: Balanced (audit_capacity: 128)"
5. 初始化安全模块:security::init(sec_config, &mut frame_allocator)
6. 锁定配置: compliance::lock_profile()
7. 打印: "Profile locked (immutable until reboot)"
调用 lock_profile() 之后,任何后续对 set_profile() 的调用都会返回 false 且不产生任何效果。
目前,内核主函数中配置被硬编码为 Balanced。生产部署时应解析启动命令行(例如 profile=secure),并使用 HardeningProfile::from_str() 选择相应的配置。
缺陷修复历史
| 修复编号 | 描述 | 影响 |
|---|---|---|
| R93-1 | 使用 FIPS_ENABLING 自旋锁和 TOCTOU 重新检查来序列化 FIPS 启用尝试 | 防止并发 enable_fips_mode() 调用之间的竞态条件 |
| R93-1 | 在 is_algorithm_permitted() 中对 FIPS 自检失败实施失败即关闭策略 | FipsState::Failed 阻止所有算法,而非允许它们通过 |
| R93-14 | 实现真正的 FIPS 140-2/140-3 已知答案测试(SHA-256、HMAC-SHA256、ECDSA P-256) | 用 NIST 规定的测试向量替换占位自检 |
| R94-3 | 对 FIPS 状态损坏实施失败即关闭策略(_ => FipsState::Failed) | 损坏的原子值不再回退到 Disabled |
| R94-4 | 对配置损坏实施失败即关闭策略(_ => HardeningProfile::Secure) | 损坏的原子值不再回退到 Balanced |