Files
DCU_APP/docs/compose/spec/ecat-dcu-flash-crypto-audit.md
T

9.1 KiB
Raw Blame History

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 侧可证实)

  1. VULN-11 仍在源码(擦页抹字已证实;栈越界仅限擦除路径):sys/config.h:333 CONFIG_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 字 → 页上 word8 ECAT_ARM / word9 CRASH_COUNT 被抹成 0xFFFFFFFF。栈 words[] 越界写仅发生在 目标 word 下标 ≥8 且 value==NOT_AVAL 时(:58);clearCrashCount()(:129-131)写的是 kOtaAval,走 writeWord(:50-51),不会触发该越界。
  2. VULN-08 仍在源码:ota_boot_request.cpp:187-200 CAN 0x320 路径在 taskCanRxParse 上下文同步 requestEnterBoot()(内部 fmc_page_erase :62,20–30ms),未走 requestEnterBootAsync(:175-181)。
  3. 交接后 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)。
  4. 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)