<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>嵌入式 on TiJet</title><link>https://www.tainuohc.com/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/</link><description>Recent content in 嵌入式 on TiJet</description><generator>Hugo</generator><language>zh</language><lastBuildDate>Wed, 22 Jul 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://www.tainuohc.com/categories/%E5%B5%8C%E5%85%A5%E5%BC%8F/index.xml" rel="self" type="application/rss+xml"/><item><title>工控固件安全实战：从裸奔到硬件信任根的完整链路</title><link>https://www.tainuohc.com/blog/2026-07-22-industrial-security/</link><pubDate>Wed, 22 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-07-22-industrial-security/</guid><description>&lt;h2 id="1-为什么要搞安全启动">1. 为什么要搞安全启动？&lt;/h2>
&lt;p>在工控领域，固件被篡改不只是数据泄露的问题——它是物理安全威胁。被篡改的固件可能禁用看门狗、改写热关断阈值，或悄无声息地窃取核心运动控制参数。&lt;/p>
&lt;p>然而现实是，大多数工业物联网设备出厂时 JTAG 全开，固件完整性校验仅靠 CRC32。CRC32 能抓比特翻转，但对有动机的攻击者毫无作用。&lt;/p>
&lt;p>2025 年中，我们对泰诺固件供应链做了安全审计，发现多处缺口：OTA 载荷未签名、无版本回滚防护、生产设备调试接口未关闭。这篇文章记录完整修复方案。&lt;/p>
&lt;h2 id="2-信任链设计">2. 信任链设计&lt;/h2>
&lt;p>我们的安全启动实现了三级信任链：&lt;/p>
&lt;pre tabindex="0">&lt;code>ROM Boot Code（不可变，掩膜 ROM 固化）
 ↓ 验证 ECDSA 签名
第一级：Bootloader（公钥哈希烧录在 OTP eFuse 中）
 ↓ 验证 ECDSA 签名
第二级：应用固件
 ↓ 验证 ECDSA 签名
第三级：FPGA 比特流 + 标定数据
&lt;/code>&lt;/pre>&lt;p>每一级的公钥内嵌于上一级，信任根锚定于 i.MX RT1064 的一次性可编程（OTP）eFuse。公钥哈希一旦烧入 OTP，除非物理更换芯片，否则无法修改。&lt;/p>
&lt;p>所有签名使用 &lt;strong>ECDSA with NIST P-256&lt;/strong>。选择 ECDSA 而非 RSA 基于两个考量：签名体积更小（64 字节 vs. 同等安全强度的 256+ 字节），以及在 Cortex-M 核上的验证速度更快（无需硬件加密加速器即可获得可接受的性能）。&lt;/p>
&lt;h2 id="3-ota-防回滚">3. OTA 防回滚&lt;/h2>
&lt;p>仅有签名是不够的。攻击者可以截获一个有效但过时的 OTA 包，将其重放以将设备降级到存在已知漏洞的旧版本。&lt;/p>
&lt;p>我们的防回滚机制简单且硬件支持：&lt;/p>
&lt;ul>
&lt;li>每个固件镜像在其签名元数据中携带一个单调递增的&lt;strong>安全版本号&lt;/strong>。&lt;/li>
&lt;li>当前安全版本号存储在 eFuse 实现的&lt;strong>单调计数器&lt;/strong>中（只能烧 1，不能清 0）。&lt;/li>
&lt;li>Bootloader 拒绝任何安全版本号小于 eFuse 计数值的镜像。&lt;/li>
&lt;li>提升安全版本的 OTA 更新会烧录新的 eFuse 比特——这是一个物理上不可逆的操作，即使全片擦除也无法撤销。&lt;/li>
&lt;/ul>
&lt;h2 id="4-cicd-中的密钥管理">4. CI/CD 中的密钥管理&lt;/h2>
&lt;p>密钥管理是大多数安全启动方案在实际落地中最薄弱的环节。密钥存在开发者的笔记本上，就意味着迟早会泄露。&lt;/p></description></item><item><title>裸机 Rust 实战：重写 Bootloader，零内存错误的背后</title><link>https://www.tainuohc.com/blog/2026-07-19-rust-embedded/</link><pubDate>Sun, 19 Jul 2026 00:00:00 +0000</pubDate><guid>https://www.tainuohc.com/blog/2026-07-19-rust-embedded/</guid><description>&lt;h2 id="1-bootloader-的困局">1. Bootloader 的困局&lt;/h2>
&lt;p>泰诺的工业控制板卡上搭载一颗 MCU，负责电源时序管理、看门狗和 OTA 固件升级。Bootloader 约 8000 行 C 代码，已&amp;quot;稳定&amp;quot;运行多年——但在嵌入式语境下，&amp;ldquo;稳定&amp;quot;往往意味着&amp;quot;已知的坑都补上了，未知的等待下一次现场翻车&amp;rdquo;。&lt;/p>
&lt;p>每隔 6 到 9 个月，新的 OTA 边界场景就会浮现：固件镜像校验时的缓冲区溢出、Flash 擦除例程的 off-by-one、CAN 总线命令分发器中的 use-after-free。每一次修复都需要全量 OTA 推送，而针对已部署的工控设备群，一次全量 OTA 是以周为单位的痛苦过程。&lt;/p>
&lt;h2 id="2-为什么要在裸机上用-rust">2. 为什么要在裸机上用 Rust&lt;/h2>
&lt;p>2026 年初，团队决定用 Rust 重写 Bootloader，目标平台为 &lt;code>thumbv7em-none-eabihf&lt;/code>（ARM Cortex-M7）。这个决定不是出于技术信仰，而是工程经济学的考量：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>编译期内存安全&lt;/strong>：借用检查器在编译阶段就消灭了 use-after-free、double-free 和缓冲区溢出——这几类问题占据了我们 Bootloader CVE 的约 70%。&lt;/li>
&lt;li>&lt;strong>零成本抽象&lt;/strong>：Rust 的 &lt;code>no_std&lt;/code> 生态提供了 RAII、类型化状态机和穷尽模式匹配，却不引入任何运行时和分配器。&lt;/li>
&lt;li>&lt;strong>生态成熟度&lt;/strong>：&lt;code>embedded-hal&lt;/code>、&lt;code>cortex-m&lt;/code>、&lt;code>cortex-m-rt&lt;/code>、&lt;code>probe-rs&lt;/code> 已达到生产级水准，有活跃维护和商业支持。&lt;/li>
&lt;/ul>
&lt;h2 id="3-架构设计">3. 架构设计&lt;/h2>
&lt;p>我们的 Bootloader 被建模为一个类型级强制的有限状态机：&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-rust" data-lang="rust">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">enum&lt;/span> &lt;span style="color:#a6e22e">BootState&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> PowerOn(Peripherals),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> VerifyFirmware(Peripherals, FlashHandle),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ReadyToBoot(VerifiedImage),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Fallback(Error),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">impl&lt;/span> BootState {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">fn&lt;/span> &lt;span style="color:#a6e22e">transition&lt;/span>(self) -&amp;gt; &lt;span style="color:#a6e22e">Self&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">match&lt;/span> self {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Self::PowerOn(p) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> self.init_peripherals(p),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Self::VerifyFirmware(p, f) &lt;span style="color:#f92672">=&amp;gt;&lt;/span> self.verify_image(p, f),
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 编译器保证每个状态都被处理
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>核心设计原则：&lt;strong>非法状态不可表达&lt;/strong>。你不可能在镜像校验尚未完成时意外调用 &lt;code>jump_to_firmware()&lt;/code>，因为该函数只接受 &lt;code>VerifiedImage&lt;/code> 类型——而构造该类型的唯一路径就是通过校验流程。&lt;/p></description></item></channel></rss>