pub fun mask_u8(c: ^u8) ^u8;
将一个秘密的 0/1 标志提升为全 1 的 8 位掩码
0 映射到 0x00,1 映射到 0xFF。首先取低位,因此范围 {0, 1} 之外的标志会根据其低位进行选择,而不会将两个操作数的位混合在一起。
c:秘密标志,0 或 1 ret:0x00 或 0xFF
pub fun mask_u16(c: ^u8) ^u16;
将一个秘密的 0/1 标志提升为全 1 的 16 位掩码
此处涉及的变宽强制转换(widening cast)和 & 1 执行了不同的工作,这四种会变宽的 mask_*(u16/u32/u64/usize)皆遵循此规则。
::^uN 进行零扩展:它只获取标志的低 8 位而没有别的,因此调用者残留的高位无法到达减法。这值得命名,因为在 briar-systems/mach#2373 之前并非如此 —— 秘密到秘密的 :: 过去会降低为改变宽度的 bitcast(一种不进行任何清理的重新解释),而这四个函数之所以正确,仅仅是因为 & 1 刚好在机器寄存器宽度上发射。它们现在正确是因为强制转换执行了转换。
& 1 是对标志的规范化,而不是清理:它将范围 {0, 1} 之外的标志缩减为其低位,这使得 0 - one 能够全为 1 或全为 0,而不是部分混合。仅出于这个原因,它是承载负载的,丢弃它会被 ct: mask reads only the flag's low bit 捕获。
ct: mask_* answer for the value under a dirty flag 将这对属性锁定:无论其周围的寄存器保存什么,这些掩码都必须针对标志的 8 位值给出正确答案。调用者仍然可以传递残留的高位 —— 窄的 0 - x 不会被截断,#2357 对移位消费者进行了规范化而不是生产者 —— 因此这仍然是一个需要守护的活跃属性,而不是历史属性。
c:秘密标志,0 或 1 ret:0x0000 或 0xFFFF
pub fun mask_u32(c: ^u8) ^u32;
将一个秘密的 0/1 标志提升为全 1 的 32 位掩码
强制转换进行零扩展;& 1 规范化标志 —— 参见 mask_u16。
c:秘密标志,0 或 1 ret:0x00000000 或 0xFFFFFFFF
pub fun mask_u64(c: ^u8) ^u64;
将一个秘密的 0/1 标志提升为全 1 的 64 位掩码
强制转换进行零扩展;& 1 规范化标志 —— 参见 mask_u16。
c:秘密标志,0 或 1 ret:0 或 0xFFFFFFFFFFFFFFFF
pub fun mask_usize(c: ^u8) ^usize;
将一个秘密的 0/1 标志提升为全 1 的 usize 掩码
强制转换进行零扩展;& 1 规范化标志 —— 参见 mask_u16。
c:秘密标志,0 或 1 ret:0 或全 1 的 usize
pub fun select_u8(cond: ^u8, a: ^u8, b: ^u8) ^u8;
在两个秘密字节之间进行无分支选择
当标志为 1 时产生 a,为 0 时产生 b,表现为在加宽掩码下的按位混合。两个操作数总是被读取,因此该选择在控制流或内存地址流中不留下任何痕迹。
cond:秘密标志,0 或 1 a:标志为 1 时选择的值 b:标志为 0 时选择的值 ret:被选择的值
pub fun select_u16(cond: ^u8, a: ^u16, b: ^u16) ^u16;
在两个秘密 16 位字之间进行无分支选择
cond:秘密标志,0 或 1 a:标志为 1 时选择的值 b:标志为 0 时选择的值 ret:被选择的值
pub fun select_u32(cond: ^u8, a: ^u32, b: ^u32) ^u32;
在两个秘密 32 位字之间进行无分支选择
cond:秘密标志,0 或 1 a:标志为 1 时选择的值 b:标志为 0 时选择的值 ret:被选择的值
pub fun select_u64(cond: ^u8, a: ^u64, b: ^u64) ^u64;
在两个秘密 64 位字之间进行无分支选择
cond:秘密标志,0 或 1 a:标志为 1 时选择的值 b:标志为 0 时选择的值 ret:被选择的值
pub fun select_usize(cond: ^u8, a: ^usize, b: ^usize) ^usize;
在两个秘密 usize 字之间进行无分支选择
cond:秘密标志,0 或 1 a:标志为 1 时选择的值 b:标志为 0 时选择的值 ret:被选择的值
pub fun is_zero_u8(a: ^u8) ^u8;
无分支测试秘密字节是否为零
a:要测试的值 ret:秘密标志,当 a 为零时为 1
pub fun is_zero_u16(a: ^u16) ^u8;
无分支测试秘密 16 位字是否为零
a:要测试的值 ret:秘密标志,当 a 为零时为 1
pub fun is_zero_u32(a: ^u32) ^u8;
无分支测试秘密 32 位字是否为零
a:要测试的值 ret:秘密标志,当 a 为零时为 1
pub fun is_zero_u64(a: ^u64) ^u8;
无分支测试秘密 64 位字是否为零
a:要测试的值 ret:秘密标志,当 a 为零时为 1
pub fun is_zero_usize(a: ^usize) ^u8;
无分支测试秘密 usize 字是否为零
a:要测试的值 ret:秘密标志,当 a 为零时为 1
pub fun eq_u8(a: ^u8, b: ^u8) ^u8;
无分支比较两个秘密字节是否相等
a:左操作数 b:右操作数 ret:秘密标志,相等时为 1
pub fun eq_u16(a: ^u16, b: ^u16) ^u8;
无分支比较两个秘密 16 位字是否相等
a:左操作数 b:右操作数 ret:秘密标志,相等时为 1
pub fun eq_u32(a: ^u32, b: ^u32) ^u8;
无分支比较两个秘密 32 位字是否相等
a:左操作数 b:右操作数 ret:秘密标志,相等时为 1
pub fun eq_u64(a: ^u64, b: ^u64) ^u8;
无分支比较两个秘密 64 位字是否相等
a:左操作数 b:右操作数 ret:秘密标志,相等时为 1
pub fun eq_usize(a: ^usize, b: ^usize) ^u8;
无分支比较两个秘密 usize 字是否相等
a:左操作数 b:右操作数 ret:秘密标志,相等时为 1
pub fun lt_u8(a: ^u8, b: ^u8) ^u8;
无分支无符号比较两个秘密字节是否小于
a:左操作数 b:右操作数 ret:秘密标志,当 a < b 时为 1
pub fun lt_u16(a: ^u16, b: ^u16) ^u8;
无分支无符号比较两个秘密 16 位字是否小于
a:左操作数 b:右操作数 ret:秘密标志,当 a < b 时为 1
pub fun lt_u32(a: ^u32, b: ^u32) ^u8;
无分支无符号比较两个秘密 32 位字是否小于
a:左操作数 b:右操作数 ret:秘密标志,当 a < b 时为 1
pub fun lt_u64(a: ^u64, b: ^u64) ^u8;
无分支无符号比较两个秘密 64 位字是否小于
a:左操作数 b:右操作数 ret:秘密标志,当 a < b 时为 1
pub fun lt_usize(a: ^usize, b: ^usize) ^u8;
无分支无符号比较两个秘密 usize 字是否小于
a:左操作数 b:右操作数 ret:秘密标志,当 a < b 时为 1
pub fun gt_u8(a: ^u8, b: ^u8) ^u8;
无分支无符号比较两个秘密字节是否大于
a:左操作数 b:右操作数 ret:秘密标志,当 a > b 时为 1
pub fun gt_u16(a: ^u16, b: ^u16) ^u8;
无分支无符号比较两个秘密 16 位字是否大于
a:左操作数 b:右操作数 ret:秘密标志,当 a > b 时为 1
pub fun gt_u32(a: ^u32, b: ^u32) ^u8;
无分支无符号比较两个秘密 32 位字是否大于
a:左操作数 b:右操作数 ret:秘密标志,当 a > b 时为 1
pub fun gt_u64(a: ^u64, b: ^u64) ^u8;
无分支无符号比较两个秘密 64 位字是否大于
a:左操作数 b:右操作数 ret:秘密标志,当 a > b 时为 1
pub fun gt_usize(a: ^usize, b: ^usize) ^u8;
无分支无符号比较两个秘密 usize 字是否大于
a:左操作数 b:右操作数 ret:秘密标志,当 a > b 时为 1
pub fun eq_bytes(a: *^u8, b: *^u8, len: usize) ^u8;
无分支比较两个秘密字节缓冲区是否相等
折叠两个缓冲区的每个字节无论其保存什么,因此运行时间仅取决于公开的 len。累加器是运行差值:当且仅当每个字节都相符时恰好为零。
a:左缓冲区 b:右缓冲区 len:两个缓冲区的字节长度 ret:秘密标志,当缓冲区相等时为 1
pub fun lookup_u8(table: *^u8, len: usize, idx: ^usize) ^u8;
在没有秘密内存地址的情况下读取秘密索引的字节
扫描整个表并合并其公共位置与秘密索引匹配的元素,因此每个索引的地址追踪都是相同的 —— 这替代了语义分析器会拒绝的 table[idx]。成本关于 len 是线性的。
等于或超过 len 的索引不匹配任何内容并产生 0。这是扫描的定义结果,而不是边界检查:没有针对 len 的比较到达控制流,因此越界索引的成本与有效索引完全相同。
table:表基址 len:元素数量,公开的 idx:秘密元素索引 ret:选定的元素,如果索引越界则为 0
pub fun lookup_u32(table: *^u32, len: usize, idx: ^usize) ^u32;
在没有秘密内存地址的情况下读取秘密索引的 32 位字
table:表基址 len:元素数量,公开的 idx:秘密元素索引 ret:选定的元素,如果索引越界则为 0
pub fun lookup_u64(table: *^u64, len: usize, idx: ^usize) ^u64;
在没有秘密内存地址的情况下读取秘密索引的 64 位字
table:表基址 len:元素数量,公开的 idx:秘密元素索引 ret:选定的元素,如果索引越界则为 0
pub fun zeroize(p: *^u8, n: usize);
擦除秘密内存
按照常规规则,对永不再次读取的存储进行的清零循环是死代码(dead),优化器可能会将其删除 —— 从而在读取时表现得像已被擦除的代码后面留下密钥材料。
目前实际上支撑这一点的是什么,诚实地说,因为如果它发生改变,差异至关重要。两件事,且装饰器(decorator)目前还不是其中之一:
* p 是调用者的指针,因此这些存储逃逸出了此函数。在任何流水线中,没有死存储分析可以称它们为死代码。 * 目前 Mach 根本没有死存储消除通道 —— 它的 DCE 仅移除纯未使用的值操作,从不移除存储、内存清零或调用。在 --profile release 下已验证:对公共局部变量进行完全无用的 32 字节填充会完好无损地存活下来。
因此 #[oblivious] 的“不消除清零写入的死存储”条款是真实的但目前是虚设的 —— 没有可供其减去的消除。该装饰器在这个函数上,是为了让这一义务在改变的那一天生效;降低后的 MIR 已经带有未来通道必须咨询的 writes_secret 种子。不要将该条款理解为涵盖寄存器中的秘密:寄存器提升的 var x: ^u8 = k; x = 0; 会被普通的 DCE(无论是否有装饰器)移除,这超出了史诗级项目(epic)的内存地址泄漏模型。这会擦除内存。
p:要擦除的区域基址 n:字节数
pub fun begin() bool;
如果目标架构具备硬件独立于数据的时间模式(data-independent-timing mode),则启用它
类型系统强制执行的时序保证是关于所发射指令的;给定指令的延迟是否独立于数据是硅片的事。一些架构公开了承诺这一点的模式,这就是调用者在秘密工作周围请求它的地方。
目前没有任何目标可以启用它,返回值说明了这一点,而不是声称相反的注释:
* aarch64 —— PSTATE.DIT 可以通过用户代码的 msr DIT, #1 设置,但 msr 不在 Mach 的 aarch64 内联汇编指令集中,因此无法从 Mach 源码中触及该指令(briar-systems/mach#2352)。这是模式真实存在却仅仅无法触及的一个架构。 * x86_64 —— DOITM 存在于 IA32_UARCH_MISC_CTL MSR 中,仅可在特权级 0(ring 0)写入。任何操作系统上的任何用户态程序都无法设置它;它需要由内核配置。 * riscv64 —— Zkt 扩展将其指令列表的数据独立延迟作为架构属性进行说明。没有模式位,因此没有什么可启用的。
调用者无论如何都应该用 begin/end 括起秘密工作:调用点使得 aarch64 路径在以后变成一个函数的更改。
ret:是否启用了硬件独立于数据的时间模式
pub fun end();
释放由 begin 启用的硬件独立于数据的时间模式
在 begin 未启用任何内容的任何地方都是空操作,即当今的所有目标。有关每个架构的原因,请参阅 begin。