☰
Tock 核心组会议纪要解读:AppID Padding 凭据设计、TBF 术语统一与 mut_imut_buffer 取舍(2021-10-22)
2026/10/10 2:37:02 网站建设 项目流程
  • 操作系统
  • 嵌入式
  • 嵌入式OS

【免费下载链接】tock

A secure embedded operating system for microcontrollers

项目地址:https://gitcode.com/gh_mirrors/to/tock
点击查看免费下载

本篇技术指南以 Tock 嵌入式操作系统核心工作组 2021 年 10 月 22 日的会议纪要(core-notes-2021-10-22.md)为主体,逐条还原并深入解析当时围绕应用凭据(Application Credentials)可事后追加的设计、TBF(Tock Binary Format)术语体系、UART TRD 合并节奏、mut_imut_buffer抽象取舍等关键议题。读者在阅读后,将能理解 Tock 如何通过"Padding Credentials TLV"在固定大小的 TBF 对象中预留空间以便事后写入签名与凭据、TBF v2 头中TbfHeaderV2Program/TbfFooterV2Credentials的底层结构,以及核心组在"命名体系"与"Rust 风格"上的工程权衡。文中所有实现细节均与当前仓库源码(如 types.rs、process_checker.rs、mut_imut_buffer.rs)逐一对照,保证事实可查、可复现。

会议背景与议题总览

本次会议由 Tock 核心工作组(Core Working Group)的 10 位成员参与,包括 Amit Levy、Brad Campbell、Hudson Ayers、Johnathan Van Why、Leon Schuermann、Pat Pannuto、Philip Levis、Vadim Sukhomlinov、Alexandru Radovici 等。会议围绕当时正在推进的 Tock 2.0 相关设计展开,主要议题为:

  1. 更新与进展:rust-lang/rust 的 panic 位置信息补丁被接收;libtock-rs 2.0 的 allow/subscribe 接口设计定案。
  2. AppID —— Padding Credentials TLV:如何为 TBF 对象预留空间,使其能在"事后"追加或校验应用凭据,而不破坏完整性校验。
  3. TBF 术语统一:TBF header、TLV、element、field、section 等名词的层级命名讨论。
  4. UART TRD 合并节奏:先实现还是先合并规范。
  5. mut_imut_buffer:可同时容纳可变/不可变缓冲的抽象是否值得合入主线。

这些讨论并非孤立的会议记录——它们大多已被沉淀为当前仓库中的正式文档与实现,例如 trd-appid.md(AppID、凭据与进程加载规范)和 tock-tbf 库中的 TBF 解析代码。因此,本文在还原讨论的同时,会同步给出"设计落地后的代码模样",帮助读者建立从设计决策到实现形态的完整认知。

更新与进展:上游补丁与 libtock-rs 2.0 接口

panic 位置信息补丁

Hudson Ayers 提交到 rust-lang/rust 的"panic location 细节控制"补丁被上游接受,并已进入 bors 合并流程。这意味着新的 nightly 编译器在数日内即可在下游项目中使用该能力——即使 Tock 上游暂不打算直接采用,它也为后续的调试信息控制提供了自由度。

libtock-rs 2.0 的 allow/subscribe 设计

Johnathan Van Why 汇报了 libtock-rs 2.0 的 allow/subscribe 接口方案:采用其在 issue #338 中描述的Pin-based 设计。选择该设计的出发点是让库尽快兼容 Tock 2.0 的系统调用接口;如果未来出现更优方案,再切换也不迟。他计划于下一周提交包含完整实现的 PR。

这一决策体现了 Tock 生态"先让库可用、再持续演进"的务实节奏:接口层(syscall.rs 对应的内核侧系统调用处理)与用户态库(libtock-rs)的适配可以并行推进。

AppID 设计核心:Padding Credentials TLV

动机:凭据需要"事后"追加

Phil(Philip Levis)与一位深耕嵌入式认证场景(围绕 Root of Trust 芯片等)的专家交流后,发现一个现实需求:TBF 对象可能需要在写入设备之后,再追加或校验凭据(credentials)。例如,厂商先在设备上部署应用二进制,随后才决定由某个密钥签发——此时不能重新生成整个二进制,而应在已部署的对象中"预留空间,之后再插入凭据"。

会议上提出了两种候选方案:

  • Padding 方案(Phil 提出):在凭据类型中增加一个 padding 字段,用 TLV 形式在 TBF 中预留空间。核心思路是"凭据不参与完整性校验"(spec 明确声明 credentials 不在 integrity check 范围内),因此只要新增一种 header 类型,修改它天然不会改变完整性校验结果。
  • 动态大小列表方案(Alex 提出):动态增长的列表,但被担心会引入比 padding 方案更多的复杂度。

会议最终倾向 padding 方案,Pat Pannuto 的一句总结点明了权衡的本质:"Flash 比内存便宜,这里很容易选 yes"(即用固定的 flash 空间换取免于重算的简单性)。Phil 同时强调"代码体积确实重要"——在嵌入式场景中,每个新增的解析器都会占用 flash,这是后续 TBF 术语讨论中反复出现的主线。

工作机制:如何用 padding 预留空间并事后替换

Leon 对机制进行了精确复述:追加签名/凭据的过程,就是用另一种类型的 header 替换掉 padding 的过程。Phil 给出了具体例子:

假设有一个 6KB 的 padding credential。要写入一个 4096 位 RSA 签名时,把这个 padding credential 替换为 4096 位 RSA 签名(4096/8 = 512 字节),再追加一个新的 padding credential,长度为 6KB − 512 字节。

关键设计约束包括:

  • 二进制大小固定:只要 TBF 对象整体被完整性校验覆盖,替换 padding 后总大小保持不变(Alex 确认"如果被完整性检查覆盖,二进制大小就是固定的")。
  • 完整性计算按 0 处理:Phil 强调,完整性校验(哈希或签名)覆盖整个 TBF 对象,但凭据 header 一律视为全 0,而不是"跳过"。"重要的是把它当作 0,而不是跳过它"——这保证了凭据内容的改动不影响校验值,从而允许事后追加/替换。
  • 多个凭据可共存:TBF 中可以同时存在"padding 为 0 且携带真实签名"的凭据 TLV,与"padding 非 0 且不含签名"的占位 TLV。Brad 的疑问(单个 TLV 带多个签名 vs. TLV 多次出现)得到的答案是:多个 TLV,每个 TLV 要么全是 padding,要么是其他东西。
  • 凭据格式类型可扩展:Vadim 询问算法是否被指定,Phil 确认已在 PR 中规定,当时已列有多种类型,未来可继续增加。

遗留问题:凭据覆盖凭据(credential-over-credential)

Phil 还提出了一个前瞻性需求:能否允许"某些凭据 TLV 被另一些凭据覆盖",这需要引入新的 header 类型。Leon 的担忧很有代表性:如果现在不设计"一个凭据覆盖另一个凭据"的机制,未来就需要实现两个独立的 header 与两套解析器,对 flash 使用不友好;而规范一旦定稿就无法再修改。Alex 则建议:也许 padding 可以做成比"仅凭据专用"更通用的 header 类型,只要规范声明"padding header 也被跳过"即可。

会议结论是:这属于细节问题,Phil 将先动手实现,在实践中暴露问题;"该 header 是否覆盖该用例"的更大问题可以稍后定夺。Phil 计划继续打磨相关 trait,并在数周后回到该议题。

落地形态:TBF 中的 Program Header 与 Credentials Footer

会议讨论的设计最终沉淀在 TBF 格式与 AppID 规范中,当前仓库的 types.rs 给出了完整实现:

Program Header(TbfHeaderV2Program)与 Main Header 的区别在于:Main Header 不指定二进制终点,而 Program Header 通过binary_end_offset告诉 Verifier "凭据 header 从哪里开始",二进制终点到 TBF 对象末尾之间的区域预留给 Credentials Footer:

/// The v2 Program Header for apps. /// /// All apps must have either a Main Header or a Program Header. Without /// either, the TBF object is considered padding. Main and Program Headers /// differ in whether they specify the endpoint of the process binary; Main /// Headers do not, while Program Headers do. A Program Header includes /// the binary end offset so that a Verifier knows where Credentials Headers /// start. The region between the end of the binary and the end of the TBF /// is reserved for Credentials Footers. pub struct TbfHeaderV2Program { init_fn_offset: u32, protected_trailer_size: u32, minimum_ram_size: u32, binary_end_offset: u32, version: u32, }

(types.rs 中的源码注释同时说明:带 Main Header 的 TBF 不允许有 Credentials Footer,带 Program Header 的 TBF 才可以有,这与会议中"完整性计算排除凭据"的设计一脉相承。)

Credentials Footer(TbfFooterV2Credentials)是可变的 TLV:4 字节format字段 + 变长data。其类型枚举在源码中与会议提到的"算法已指定、可扩展"完全对应:

pub enum TbfFooterV2CredentialsType { Reserved = 0, Rsa3072Key = 1, Rsa4096Key = 2, SHA256 = 3, SHA384 = 4, SHA512 = 5, EcdsaNistP256 = 6, }

解析时,每个类型都有固定的数据长度(如Rsa4096Key为 1024 字节、SHA256为 32 字节、EcdsaNistP256为 64 字节,见 types.rs 的TryFrom<&'static [u8]> for TbfFooterV2Credentials实现);未知的format值直接报BadTlvEntry。而"因为length字段指明了长度,不理解某种凭据类型并不妨碍解析其他凭据"(trd-appid.md 5.2 节)这一特性,正是 Padding Credentials TLV 能够与真实凭据混合共存的前提。

完整性区域(Integrity Region):凭据中的哈希/签名值必须只覆盖"从 TBF 对象起点到binary_end_offset"的区域,即 TBF Header + Userspace Binary,不得包含 Footer 内容(trd-appid.md 5.3 节)。这与会议中"凭据 header 在完整性计算中被视为 0"的表述一致——二者共同保证了"事后替换 padding 为凭据"不会使原有签名失效。

落地形态:内核侧凭据检查流程

会议讨论的"添加/检查凭据"能力,在内核侧由 process_checker.rs 实现。其中ProcessCheckerMachine逐 footer 扫描凭据,并把结果交给AppCredentialsPolicy判定:

  • CheckResult::Accept(metadata):接受该凭据并运行二进制,同时可附带一个不透明的usize元数据(例如"用第几个公钥验证通过"),供后续分配 ShortId 使用;
  • CheckResult::Pass:尝试下一个凭据 footer;
  • CheckResult::Reject:拒绝该二进制,不加载;
  • 若所有凭据都 Pass 且没有 Reject/Accept,则回落到require_credentials():返回true则不加载,返回false则加载(trd-appid.md 第 6 节;实现见 process_checker.rs)。

ProcessCheckerMachine::check_footer中的解析循环调用tock_tbf::parse::parse_tbf_footer(parse.rs),只接受TbfHeaderTypes::TbfFooterCredentials(类型值 128)的 TLV,其余类型返回BadTlvEntry。这印证了会议中"新增一种 header 类型即可"的设计:内核侧解析器只需识别凭据 TLV,padding TLV 与真实凭据 TLV 共用同一类型通道,靠内容区分。

TBF 术语统一:header、TLV、element、field 还是 section?

会议第二大议题是 TBF 各层结构的命名。争论的起因是:既有代码把整个 TBF 头叫 "TBF header",又把其中的每个可选 TLV 块也叫 "header",产生了英文单词碰撞,新读者(如 Alex 初读 TBF spec 时)难以区分。

各方观点梳理:

  • Brad(原作者视角):整个 TBF 头是一体化的 header;其中有一个必需的 base,其余可选结构称为 elements;version这类基础类型是 field 而非 element。Brad 坦言当时命名"没有太纠结"("I didn't agonize over the naming"),欢迎改进。
  • Phil:primitive 类型叫 field、而 primitive 的集合叫 element 的划分令人困惑,不如统一叫 "optional fields"。
  • Alex:建议把 TBF 文件划分为headers section / code section / padding section,headers section 中的每个个体就是 TLV element;或者"base header + optional headers"。
  • Leon:认为"headers section 里装 header"依然令人困惑。
  • Phil 的收尾:结合 Brad 提供的历史背景(命名是代码演进的遗留),Phil 表示会去调研其他项目如何处理这种层级命名,再回来定案;Alex 表示愿意协助。

从当前仓库看,这一命名讨论的最终形态是:TbfTlv承载"TLV 头(T 和 L)",TbfHeaderTypes枚举列出各可选块类型(Main=1、WriteableFlashRegions=2、PackageName=3、FixedAddresses=5、Permissions=6、StoragePermissions=7、KernelVersion=8、Program=9、ShortId=10、FooterCredentials=128),未知类型统一归入Unknown并被安全跳过(types.rs)。也就是说,TBF v2 最终采用"base + 可选 TLV 元素"的模型,未采用"section"命名——命名虽未大改,但"未知 TLV 按长度跳过而非报错"的宽容解析设计(Unknown变体及其注释)正是从这类讨论中沉淀出的工程结论。

UART TRD:先实现还是先合并?

Brad 询问 UART TRD(技术规范文档)的合并状态。Phil 表示已吸收全部评审意见,但主张先实现再合并规范("merge it then implement, but I think we should implement then merge"),而不是先合并规范再实现。Brad 认可这一节奏,只是想确认该事项卡在谁那里。Phil 表示当前负载过重,但该事项没有阻塞风险。

仓库中对应文档为 trd-uart.md。这一讨论体现了 Tock 工作组的规范流程原则:TRD 需要与实现同步演进,避免规范先行导致实现与文档脱节。

mut_imut_buffer:值得为 RSA 合入吗?

mut_imut_buffer是一个可同时容纳可变引用或不可变引用缓冲区的 enum。会议讨论的焦点是:在 RSA 密钥相关代码真正用到它之前,是否值得合入主线。

  • Brad:看不到合入的必要性——万一 RSA 最终没用上它呢?
  • Phil:Alistair(OpenTitan 相关)已经非常耐心地等待;如果没有更好的方案就只能合并,但它"有违 Rust 精神"(goes against the Rust spirit),应尽量避免。
  • Brad:大家对这个抽象还没有完全想清楚。
  • Pat:会在 PR 上留言总结当前状态。

从当前仓库的实现(mut_imut_buffer.rs)看,这一抽象最终被合入,且其文档注释恰好记录了会议中讨论的动机:

公钥常以常量值形式存放在内核镜像的 flash 中(例如用于验证签名),这要求它们是不可变的;把这些大密钥(4096 位 RSA 密钥即 512 字节)复制到 RAM 代价高昂。与此同时,某些客户端使用动态生成或接收的密钥,存放在可变 RAM 中。MutImutBuffer让实现可以同时存放可变与不可变缓冲。OTBN(OpenTitan Big Number 加速器)就是该类型的一个用例。

同时注释也保留了会议中"有违 Rust 精神"的顾虑的落地版本:该类型需要运行时动态检查类型匹配,不应出现在任何 HIL 或标准外部 API 中,仅限实现内部使用。这正是"Rust 精神"与嵌入式实际需求之间的一种折中:把它限定在内部,避免污染对外接口的静态类型保证。

PR triage:长驻 PR 的梳理与合并

会议最后,Pat Pannuto 列举了几个长期未动的 PR,工作组逐一讨论其现状,并当场合并了其中一部分。这一例行环节保证了 PR 队列不会因"无人认领"而无限期滞留。

总结:从会议决策到仓库实现

本次会议的三条主线,在今天仓库中都能找到明确落点:

会议议题讨论要点当前仓库落地
Padding Credentials TLV凭据不参与完整性校验、按 0 处理;padding 可事后替换为真实凭据TbfFooterV2Credentials、TbfHeaderV2Program::binary_end_offset(types.rs)、parse_tbf_footer(parse.rs)
TBF 术语header/TLV/element/field 命名碰撞,最终维持"base + 可选 TLV"模型TbfHeaderTypes+TbfTlv,未知类型Unknown宽容跳过(types.rs)
mut_imut_buffer避免"有违 Rust 精神",但公钥场景确有需要MutImutBuffer限定内部实现使用(mut_imut_buffer.rs)

对于希望深入了解 Tock 进程加载与安全策略的读者,建议进一步阅读 trd-appid.md(AppID、凭据与进程加载的完整 TRD,含AppCredentialsPolicy、AppUniqueness、Compress三大 trait 及五个典型使用案例)与 process_checker.rs(内核侧凭据检查状态机实现)。会议纪要本身(core-notes-2021-10-22.md)则是观察 Tock 设计演进过程的一手资料,可与 2021-10-29、2022-01-21 等后续纪要(doc/wg/core/notes 目录)对照阅读,看到 Padding Credentials 议题的后续走向。

  • 操作系统
  • 嵌入式
  • 嵌入式OS

【免费下载链接】tock

A secure embedded operating system for microcontrollers

项目地址:https://gitcode.com/gh_mirrors/to/tock
点击查看免费下载

相关推荐

上一篇:golang.org/x/sys/unix 构建体系全解:从 C 头文件到 Go 系统调用代码生成
下一篇:WezTerm Lua 配置中的 JSON 序列化:`wezterm.serde.json_encode` 用法与底层实现解析

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询