9.1 KiB
feature, status, updated, branch, commits
| feature | status | updated | branch | commits |
|---|---|---|---|---|
| ecat-dcu-flash-crypto-audit | delivered | 2026-09-24 | ecat-dcu-flash-crypto-audit | 28d8426..28d8426 |
EtherCAT 总线刷写 DCU:加密与握手完整性审计
Report
What was built — 对「EtherCAT 总线刷写 DCU」路径完成加密与握手完整性审计(docs-only,无代码改动)。结论:本仓库可见的 APP 交接与布局契约上 无载荷加密(仅镜像尾 CRC32);所谓握手是 0x2502 交接 + 配置页 arm 门,不是密码学握手,且 arm/令牌语义在 BOOT(本仓库不可验证)、APP 擦页会抹掉 word8 令牌字。与电机 OTA(AES-128 + nonce 会话)严格区分。
Verification — 全库 aes128|encrypt 仅命中 sys/aes128.* + motor_ota_relay.cpp(PASS);config.h:333 word 计数仍为 8(PASS,缺陷);ota_boot_request.cpp:198 CAN 路径仍同步 requestEnterBoot(PASS,缺陷);ota_boot_request.cpp 无 gracefulTeardown/fwdgt_*(PASS,缺陷);0x2500 仅注释(PASS,BOOT 盲区);spec self-check PASS。独立审查 FAIL(1 critical 事实错误 + 1 major 范围过宽)→ 修正后 re-review PASS。
Journey log
clearCrashCount()写kOtaAval走writeWord,不会栈越界;OOB 仅擦除路径且 word 下标 ≥8。活缺陷是擦页抹掉 word8/9,不是该调用点。- BOOT
ecatArmed()/0x2500会话只在dc_app.c注释里;凡 BOOT 侧行为必须标「不可验证」,不可用 APP 证据替它下无加密定论。 - 本环境再次拦截
git worktree add,落在分支ecat-dcu-flash-crypto-audit(非隔离 worktree)。 - 工作区另有未跟踪/未提交改动(
esc_lan9252.cpp、config.h、ecat-wkc-periodic-dip.md),与本单无关,未纳入提交。
[S1] Problem
需要确认当前 EtherCAT 总线刷写 DCU 固件 路径是否具备加密,以及握手/鉴权链路是否完整。该路径与「电机 OTA 中继」(AES-128 + nonce 会话)不同,不能混谈。审计对象为本仓库(GD32E508_DCU_APP)可见的 APP 交接入口、共享布局、以及文档/注释中对 BOOT 侧 0x2500/0x2501 直灌会话的契约描述。
[S2] Design
S2.1 路径范围(唯一真源)
主站 EtherCAT
│
│ SDO 0x2502 = 1 ← APP 字典「GD32 boot request」
▼
EcatEnterBootRequestAsync() ← 只置 pending(异步)
│
▼
OtaBootRequest::process() ← 主循环写配置页 + 软复位
│ CHECK=AVAL, AVAL=AVAL, EN=NOT_AVAL
▼
BOOT 起机(10s OTA 窗口)
│ ecatArmed() = !(CHECK==AVAL && EN==AVAL)
│ 交接后 EN=NOT_AVAL ⇒ 直灌解锁(可不依赖 word8 令牌)
▼
BOOT 对象 0x2500 / 0x2501 ← 固件灌写会话(源码不在本仓库)
│
▼
APP 区镜像 + 末尾 CRC32
对照旁路(不在本次范围,仅防混淆):
| 路径 | 加密 | 握手 |
|---|---|---|
| 电机 OTA 中继(CAN 0x100/0x101) | AES-128 ECB + keyed-CRC32 | nonce → K_session → confirm 三步 |
| EtherCAT ESI/EEPROM 刷写 | 无 | 无(仅 SII CRC8) |
| EtherCAT 总线刷写 DCU(本次) | 见 S2.2 | 见 S2.3 |
S2.2 加密结论:本仓库可见路径没有载荷加密
| 检查项 | 结论 | 证据 |
|---|---|---|
| 固件载荷 AES/对称加密(APP 可见代码) | 无 | 全库 `aes128 |
| 传输层机密性(APP 交接 + 布局契约) | 未见 | dc_app.c:305-306 注明直灌会话在 BOOT 的 0x2500/0x2501;本仓库可见路径无载荷加密 |
BOOT 0x2500/0x2501 是否解密/验签 |
本仓库不可验证 | 源码不在本 repo;不能据 APP 侧断言 BOOT 一定无加密 |
| 完整性校验(布局契约) | 仅有镜像尾 CRC32 | shared/ota_layout.h:19 OTA_LAYOUT_APP_MAX_SIZE(216KB-4B,末尾 CRC32) |
| 源码/固件签名 | 未见 | 无 ECDSA/HMAC/secure boot 引用 |
一句话(限定范围):在 本仓库可见的 APP 交接与布局契约 上,EtherCAT 刷写 DCU 是 明文 SDO 灌写意图 + 镜像尾 CRC32,没有载荷加密;BOOT 侧是否另有加密 无法在本仓库证实或证伪。
S2.3 握手结论:不是密码学握手,只是交接 + arm 门;链路不完整
实际存在的「握手」是 交接(handoff)+ 配置页 arm 门,不是 challenge-response:
| 步骤 | 完整性 | 证据 |
|---|---|---|
1. APP 交接入口 0x2502 |
完整(异步) | dc_app.c:329-350 ObjBootReqWrite → EcatEnterBootRequestAsync;ota_boot_request.cpp:257-260 只置 pending;Flash/复位在 process() (:209-248) |
| 2. 配置页 arm 语义 | 注释契约(BOOT 行为,本仓库不可验证) | dc_app.c:316-318 描述 ecatArmed() = !(CHECK==AVAL && EN==AVAL);实现不在 APP |
3. word8 令牌 ECAT_ARM_KEY=0xACA70001 |
按注释可被交接旁路;APP 擦页会毁掉令牌字 | dc_app.c:316-318 称交接后不必 word8;config.h:333 word 计数=8;ota_boot_request.cpp:54-78 擦页只回写 8 字 → 页上 word8/9 被抹 |
| 4. nonce / 会话密钥 / 双向认证 | 无 | 对比 motor_ota_relay.cpp:896-955 的 Req→Nonce→Confirm |
| 5. BOOT 侧会话对象协议 | 本仓库不可验证 | dc_app.c:305-306:0x2500/0x2501 在 BOOT 字典;APP 无实现 |
6. CAN 旁路 0x320+"OTA!" |
不完整 | ota_boot_request.cpp:187-200 handleCanFrame 仍 同步 requestEnterBoot()(VULN-08 未落地) |
一句话:所谓握手 = 「让我进 BOOT」+「配置页门禁」,无加密握手、无会话密钥、无双向认证;门禁语义本身在 BOOT(本仓库不可验证),且 APP 擦页会把 word8 令牌字抹掉。
S2.4 阻断「完整」结论的现存缺陷(APP 侧可证实)
- VULN-11 仍在源码(擦页抹字已证实;栈越界仅限擦除路径):
sys/config.h:333CONFIG_OTA_CONFIG_WORD_COUNT = 8U,应为OTA_LAYOUT_WORD_COUNT(shared/ota_layout.h:48= 10)。requestEnterBoot()对 word2 写NOT_AVAL会走otaConfigSet擦除路径(ota_boot_request.cpp:50-80):只备份/回写 8 字 → 页上 word8ECAT_ARM/ word9CRASH_COUNT被抹成0xFFFFFFFF。栈words[]越界写仅发生在 目标 word 下标 ≥8 且 value==NOT_AVAL 时(:58);clearCrashCount()(:129-131)写的是kOtaAval,走writeWord(:50-51),不会触发该越界。 - VULN-08 仍在源码:
ota_boot_request.cpp:187-200CAN0x320路径在taskCanRxParse上下文同步requestEnterBoot()(内部fmc_page_erase:62,20–30ms),未走requestEnterBootAsync(:175-181)。 - 交接后 Flash 擦写防护未按审计加固落地(对照
docs/audit_ecat_spi_wifi_ota_compatibility.md§3):- 仍调用 Flash 库
fmc_word_program/fmc_page_erase(ota_boot_request.cpp:30,62,73),非 SRAM 寄存器直写; process()在擦写前只关 SPI0/EXTI10_15(:216-217);__disable_irq()仅在复位前(:244),不覆盖fmc_page_erase窗口;- 未见 FWDGT 复位前扩窗(无
fwdgt_*); - 未见 LAN9252 PREOP / CAN STB / EXMC 优雅关停(无
gracefulTeardown)。
- 仍调用 Flash 库
- BOOT 侧盲区:
0x2500/0x2501是否校验 CRC32、是否接受任意明文、是否有会话状态机,本仓库无法证明完整(对象仅出现在dc_app.c:305-306注释)。
S2.5 与电机 OTA 的对照(防误读)
| 维度 | 电机 OTA 中继 | EtherCAT 刷写 DCU |
|---|---|---|
| 载荷加密 | AES-128 ECB 48B/包 | APP 可见路径无(BOOT 不可验证) |
| 密钥协商 | K_master + nonce → K_session |
无 |
| 确认帧 | confirm = AES(nonce⊕0x55) | 无 |
| 完整性 | keyed-CRC32(session‖plain) | 镜像尾 CRC32 |
| 会话绑定 | session_num + pkt_num | 未见 |
| 传输保护 | ARD 单发 + BRS 压制 + 通道锁 | SDO 邮箱;通道锁与本路径无关 |
[S3] Out of Scope
- 电机 OTA 中继的 AES/握手实现质量(另有完整状态机,不在本单)。
- EtherCAT ESI/EEPROM(
gen_eeprom.py/ecat_flash_eeprom.py)刷写。 - BOOT 工程
GD32E50X_BOOT内0x2500/0x2501具体实现(源码不在本仓库)。 - 对上述缺陷的代码修复(本单为「检查」;修复需另开 implement 任务并获确认)。
- 电机/DCU 联合端到端刷写台架实测。
Tasks
- T1: 梳理 EtherCAT 刷写 DCU 全路径与对象边界 — acceptance: S2.1 流程与对象归属可对照源码/注释逐点定位 (covers: S2.1)
- T2: 判定载荷加密有无 — acceptance: S2.2 表中每行有仓库内证据或明确「未见」 (covers: S2.2)
- T3: 判定握手/arm 链完整性 — acceptance: S2.3 表逐步给出完整/不完整/不可验证,并指出门禁旁路与令牌破坏路径 (covers: S2.3)
- T4: 列出阻断「完整」结论的 APP 侧缺陷 — acceptance: S2.4 各条可指到
file:line(covers: S2.4) - T5: 输出与电机 OTA 的对照 — acceptance: S2.5 无混淆两路径的加密/握手结论 (covers: S2.5)