Files
ESP32_COMM/docs/review/ESP32_COMM_专家评审_2026-10-19.html
T

160 lines
56 KiB
HTML
Raw Normal View History

2026-10-20 21:13:50 +08:00
<!DOCTYPE html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>ESP32_COMM 专家评审 2026-10-19</title><style>
:root{--ink:#1e293b;--muted:#64748b;--line:#e2e8f0;--bg:#f6f8fb;--card:#fff;--esp:#2563eb}
*{box-sizing:border-box}body{font-family:"Microsoft YaHei","PingFang SC","Segoe UI",Arial,sans-serif;color:var(--ink);background:var(--bg);margin:0;line-height:1.7;font-size:14.5px}
.wrap{max-width:1180px;margin:0 auto;padding:26px 20px 80px}
.hero{background:linear-gradient(135deg,#3b0a0a 0%,#7f1d1d 55%,#b91c1c 100%);color:#fff;border-radius:14px;padding:28px 32px;margin-bottom:22px}
.hero h1{margin:0 0 6px;font-size:25px}.hero p{margin:3px 0;color:#fee2e2;font-size:13.5px}
h2{font-size:20px;margin:40px 0 12px;padding:6px 0 6px 14px;border-left:5px solid #b91c1c;background:linear-gradient(90deg,#fef2f2,transparent);border-radius:0 8px 8px 0}
h3{font-size:16px;margin:22px 0 8px;color:#3b0a0a}
.card{background:var(--card);border:1px solid var(--line);border-radius:12px;padding:16px 20px;margin:12px 0}
table{border-collapse:collapse;width:100%;margin:10px 0;font-size:13px;background:#fff}
th,td{border:1px solid var(--line);padding:6px 9px;text-align:left;vertical-align:top}
th{background:#f1f5f9;font-weight:600;white-space:nowrap}tr:nth-child(even) td{background:#fafcff}
code{background:#eef2f7;border:1px solid #e2e8f0;border-radius:4px;padding:0 4px;font-size:12.5px;font-family:Consolas,monospace;word-break:break-all}
pre{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:14px 16px;overflow-x:auto;font-size:12.5px;line-height:1.5}
.tag{display:inline-block;padding:1px 8px;border-radius:10px;font-size:12px;font-weight:600;white-space:nowrap}
.sev-f{background:#7f1d1d;color:#fff}.sev-s{background:#dc2626;color:#fff}.sev-g{background:#f59e0b;color:#fff}.sev-a{background:#64748b;color:#fff}
.st-ok{background:#dcfce7;color:#166534}.st-part{background:#fef9c3;color:#854d0e}.st-bad{background:#fee2e2;color:#991b1b}.st-na{background:#e2e8f0;color:#334155}
.kpis{display:flex;gap:12px;flex-wrap:wrap;margin:14px 0}.kpi{flex:1;min-width:120px;background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px 14px;text-align:center}
.kpi b{display:block;font-size:26px}.kpi span{color:var(--muted);font-size:12.5px}
.finding{background:#fff;border:1px solid var(--line);border-radius:12px;padding:12px 16px;margin:10px 0}
.finding h4{margin:0 0 6px;font-size:15px}.finding dl{display:grid;grid-template-columns:72px 1fr;gap:4px 10px;margin:0}
.finding dt{color:var(--muted);font-weight:600}.finding dd{margin:0}
.toc a{color:var(--esp);text-decoration:none;margin-right:14px;white-space:nowrap}
.note{color:var(--muted);font-size:13px}
</style></head><body><div class="wrap">
<div class="hero"><h1>ESP32_COMM 专家评审报告</h1>
<p>评审日期 2026-10-19 · 代码基线 master <code style="background:#fff2;color:#fff;border:0">af1f682</code>(固件 v0.5.2,ESP-IDF v6.0,ESP32-C3FH4)</p>
<p>评审方式:多专家视角并行通读 + 主审逐条复核 · 本报告只新增 docs/,未改动任何源码</p>
<p>综合评分 <b style="font-size:20px">5.1 / 10</b></p></div>
<div class="kpis">
<div class="kpi"><b style="color:#7f1d1d">1</b><span>致命</span></div>
<div class="kpi"><b style="color:#dc2626">12</b><span>严重</span></div>
<div class="kpi"><b style="color:#d97706">17</b><span>一般</span></div>
<div class="kpi"><b style="color:#64748b">7</b><span>建议</span></div>
<div class="kpi"><b style="color:#166534">23</b><span>既有条目已修复</span></div>
<div class="kpi"><b style="color:#991b1b">12</b><span>既有条目仍存在</span></div>
</div>
<div class="card toc"><b>目录:</b><a href="#s1">1 项目概述与架构</a><a href="#s2">2 评审范围与方法</a><a href="#s3">3 问题总表</a><a href="#s4">4 问题详情</a><a href="#s5">5 既有评审跟踪</a><a href="#s6">6 修复路线图</a><a href="#s7">7 总结评分</a></div>
<h2 id="s1">1. 项目概述与架构</h2>
<div class="card"><p>ESP32_COMM 是机器人 DCU 板上 ESP32-C3 的通信与 OTA 中枢固件:上位机经 WiFi HTTP / BLE GATT 把固件交给 ESP32;ESP32 自身走 app0/app1 AB 分区升级,GD32E508 固件经 <b>SPI Link v2</b> 六阶段握手 + AES-128 逐包加密中继,电机节点固件再经 GD32 CAN 二次中继;同时提供三路 CANFD 控制面与遥测回收。</p>
<pre>[PC / 手机网页]──WiFi HTTP(22 路由) / BLE GATT(0xFFFF)──▶[ESP32-C3FH4]──SPI Link v2 13.33MHz 5线──▶[GD32E508]──3×CANFD──▶[电机节点]
│ gd32_ota 252KB 缓存 │ Bootloader / APP
│ app0/app1 AB 自升级 │ 电机 OTA CAN 中继</pre>
<table><tr><th>层</th><th>目录 / 模块</th><th>职责</th><th>规模(行)</th></tr>
<tr><td>接入层</td><td><code>server/</code> wifi_ota · http_handlers · ble_ota(_data/_host)</td><td>STA/AP、HTTP 路由、NimBLE GATT、窗口 ACK / reorder</td><td>≈ 6.6k(http_handlers 2832)</td></tr>
<tr><td>协调层</td><td><code>ota/</code> OtaCoordinator · Esp32OtaCore</td><td>9 态单例、唯一权威入口、150 s 忙态看门狗、epoch</td><td>≈ 2.0k</td></tr>
<tr><td>执行层</td><td><code>relay/</code> OtaRelay · <code>storage/</code> Gd32FlashStore · <code>monitor/</code> MotorOtaRelay</td><td>GD32 中继状态机、gd32_ota 分区与 NVS meta、电机 OTA 喂数</td><td>≈ 3.5k</td></tr>
<tr><td>传输层</td><td><code>transport/</code> SpiMaster · protocol · canfd_bridge(+ 仓外 spi_link_v2)</td><td>Link v2 主站(会话/序号/credit/CRC32/重试)、AES/nonce、CAN 0xC0~0xC8</td><td>≈ 4.3k</td></tr>
<tr><td>横切</td><td><code>monitor/</code> health · debug_log · can_feedback · cpu_stats · task_stack 等;<code>core/</code></td><td>健康门控、三路日志、遥测轮询、启动自检位图</td><td>≈ 3.6k</td></tr></table>
<p class="note">任务模型:ota_relay(23) · wifi_ota/ble_ota(22) · health_mon(21) · ble_ota_worker(12) · spi_worker(8,唯一持 SPI) · ota_feed(7) · httpd(5) · can_fb(4) · cpu_stats/uart_rx(1)。</p></div>
<h2 id="s2">2. 评审范围与方法</h2>
<div class="card"><ul>
<li><b>范围</b>:<code>main/</code> 全部 60 个 C/C++ 源文件(≈ 2.0 万行)、<code>components/transport</code>、<code>config/</code>、两份 <code>sdkconfig.defaults</code>、<code>sdkconfig.ci</code>、<code>dependencies.lock</code>、CMake 与 Kconfig、CI workflow、<code>.devcontainer</code>/<code>.vscode</code>/<code>.gitignore</code>、Python 工具脚本、README 与白皮书、git 历史。</li>
<li><b>专家组</b>:① SPI 协议与传输;② OTA 可靠性(协调器 / 本机 OTA / GD32 中继 / 存储 / 电机 OTA);③ 网络与无线安全(HTTP / WiFi / BLE);④ FreeRTOS 实时性 + 构建与仓库卫生 + 代码质量。各组独立通读,主审对每条结论回到当前代码二次核对行号(基线 af1f682)。</li>
<li><b>既有评审</b>:当前树仅有 <code>docs/project_introduction.html</code> §18/§19(含 2026-08-16 安全审计)与 README。git 历史中另有三份已被删除的评审:2026-06-27 架构扫描(af1f682 删除)、2026-06-13 高风险评审与 2026-05-05 通信梳理(5b2c5f8 删除)。本次已原样恢复至 <a href="archive/">docs/review/archive/</a>,并在第 5 节逐条跟踪。</li>
<li><b>严重度</b>:致命=可远程任意控制/变砖/必现崩溃;严重=特定条件下数据损坏、失联、OTA 失败或明显安全缺陷;一般=健壮性/一致性缺陷;建议=可维护性/风格。</li>
<li><b>局限</b>:未上板实测;仓外依赖 <code>spi_link/spi_link_v2.*</code> 不在本仓库,CRC/帧格式部分只能按调用侧判断;0627 评审中约 50 条低风险风格类 P2 条目未逐条复核;网页前端(tools/)已移出仓库,不在范围内。</li></ul></div>
<h2 id="s3">3. 问题总表(37 条)</h2>
<table><tr><th>编号</th><th>严重度</th><th>类别</th><th>问题</th><th>主要位置</th></tr><tr><td><a href='#F-01'>F-01</a></td><td><span class='tag sev-f'>致命</span></td><td>安全</td><td>HTTP 全部端点零认证 + CORS * + /spi/send 原始透传</td><td><code>main/server/http_handlers.cpp:77-83, 1685-1756</code></td></tr><tr><td><a href='#S-01'>S-01</a></td><td><span class='tag sev-s'>严重</span></td><td>构建</td><td>干净克隆构建出的是另一份固件(关键配置文件未生效且自相矛盾)</td><td><code>CMakeLists.txt</code></td></tr><tr><td><a href='#S-02'>S-02</a></td><td><span class='tag sev-s'>严重</span></td><td>OTA</td><td>回滚保护取消过早:mark_app_valid 早于自检判定</td><td><code>main/core/main.cpp:131-147</code></td></tr><tr><td><a href='#S-03'>S-03</a></td><td><span class='tag sev-s'>严重</span></td><td>并发</td><td>看门狗/中止路径与中继任务并发:清会话密钥但中继任务不停</td><td><code>main/ota/ota_coordinator.cpp:625-635</code></td></tr><tr><td><a href='#S-04'>S-04</a></td><td><span class='tag sev-s'>严重</span></td><td>OTA</td><td>忙态看门狗 150 s 短于 GD32 中继合法最坏耗时</td><td><code>main/include/app_config.h:126</code></td></tr><tr><td><a href='#S-05'>S-05</a></td><td><span class='tag sev-s'>严重</span></td><td>健壮性</td><td>WiFi 运行期断线重试 5 次后永不重连,也不回退 AP</td><td><code>main/server/wifi_ota.cpp:94-100, 180-192, 751-777</code></td></tr><tr><td><a href='#S-06'>S-06</a></td><td><span class='tag sev-s'>严重</span></td><td>安全</td><td>GD32 握手 nonce 可预测且时间戳明文入日志</td><td><code>main/relay/ota_relay.cpp:1027-1035</code></td></tr><tr><td><a href='#S-07'>S-07</a></td><td><span class='tag sev-s'>严重</span></td><td>健壮性</td><td>GET /can/mode 查询失败时会把 CAN 接收模式写回 ALL_PASS</td><td><code>main/server/http_handlers.cpp:1020-1030</code></td></tr><tr><td><a href='#S-08'>S-08</a></td><td><span class='tag sev-s'>严重</span></td><td>安全</td><td>凭据与主密钥明文入仓,AP 弱口令</td><td><code>main/include/app_config.h:31-32, 39-40, 161-164</code></td></tr><tr><td><a href='#S-09'>S-09</a></td><td><span class='tag sev-s'>严重</span></td><td>安全</td><td>BLE GATT 无配对/加密,范围内任意设备可 OTA 或重启</td><td><code>main/server/ble_ota_host.cpp:366-376</code></td></tr><tr><td><a href='#S-10'>S-10</a></td><td><span class='tag sev-s'>严重</span></td><td>实时性</td><td>RSSI 更新覆盖过温 SAFE_SHUTDOWN 健康状态</td><td><code>main/monitor/health_monitor.cpp:578-600</code></td></tr><tr><td><a href='#S-11'>S-11</a></td><td><span class='tag sev-s'>严重</span></td><td>实时性</td><td>栈溢出钩子中调用可阻塞的 uart_write_bytes</td><td><code>main/monitor/task_stack.cpp:27-40</code></td></tr><tr><td><a href='#S-12'>S-12</a></td><td><span class='tag sev-s'>严重</span></td><td>安全</td><td>/spi/send 的 timeout_ms 无上限,可长期占死 SPI worker</td><td><code>main/server/http_handlers.cpp:1715</code></td></tr><tr><td><a href='#G-01'>G-01</a></td><td><span class='tag sev-g'>一般</span></td><td>OTA</td><td>完成回调不校验状态与 epoch,可能覆盖新会话</td><td><code>main/ota/ota_coordinator.cpp:372-392, 568-576</code></td></tr><tr><td><a href='#G-02'>G-02</a></td><td><span class='tag sev-g'>一般</span></td><td>并发</td><td>协调器锁超时 100 ms,lock 失败直接跳过状态切换</td><td><code>main/include/app_config.h:124</code></td></tr><tr><td><a href='#G-03'>G-03</a></td><td><span class='tag sev-g'>一般</span></td><td>OTA</td><td>确认 APP 存活前即清除 meta;失败路径连发 3 次 CANCEL</td><td><code>main/relay/ota_relay.cpp:1554, 1573-1579, 1637-1643</code></td></tr><tr><td><a href='#G-04'>G-04</a></td><td><span class='tag sev-g'>一般</span></td><td>OTA</td><td>校验缺口:先置 STORED 后算 SHA;ESP32 finalize 可不带 SHA、不比对实收字节</td><td><code>main/storage/gd32_flash_store.cpp(finishGd32Storag
<h2 id="s4">4. 问题详情</h2><h3>致命(1)</h3><div class="finding" id="F-01"><h4><span class="tag sev-f">致命</span> F-01 · HTTP 全部端点零认证 + CORS * + /spi/send 原始透传 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:77-83, 1685-1756</code></dd>
<dt>现象</dt><dd>22 条路由无任何鉴权;所有响应带 Access-Control-Allow-Origin: *;/spi/send 的 frame_type 直接取自请求(strtol),无白名单,经 SpiMaster::transact 原样下发 GD32,绕过 OtaCoordinator 互斥。</dd><dt>风险</dt><dd>同网段任意主机(或用户浏览的任意网页借 CORS 跨站)可上传/切换固件、重启、发 CAN 帧、向 GD32 注入任意 SPI 命令(含进 Boot、擦写类命令),机器人电机总线可被远程操控。</dd><dt>修改建议</dt><dd>最小改造:① 实施已设计的 Bearer token(NVS 存放,首次配网生成);② CORS 改为白名单来源或去掉;③ /spi/send 加 frame_type 白名单(仅只读诊断类),并在 OTA 忙时拒绝;④ 默认编译期关闭 /spi/send 与 /spi/speedtest。</dd></dl></div><h3>严重(12)</h3><div class="finding" id="S-01"><h4><span class="tag sev-s">严重</span> S-01 · 干净克隆构建出的是另一份固件(关键配置文件未生效且自相矛盾) <span class="note">[构建]</span></h4>
<dl><dt>位置</dt><dd><code>CMakeLists.txt;sdkconfig.defaults;config/sdkconfig.defaults:7-13,23,34,89-90;.gitignore:33</code></dd>
<dt>现象</dt><dd>自定义分区表、4MB Flash、FREERTOS_HZ=1000、APP_ROLLBACK 只写在 config/sdkconfig.defaults,根 CMakeLists 未设置 SDKCONFIG_DEFAULTS,IDF 只读根目录 sdkconfig.defaults;sdkconfig 被 gitignore 未入库。config/ 版本还写着 FLASHMODE_QIO 与 BT_BLUEDROID_ENABLED,与根配置(DIO、NimBLE)冲突。</dd><dt>风险</dt><dd>新环境/CI/同事机器构建得到:单 factory 分区(无 gd32_ota、无 app0/app1,ESP32 自升级不可用)、HZ=100(pdMS_TO_TICKS(1/3/5)=0,SPI 等待退化为零超时/忙让)、无回滚。若有人改为引用 config/ 版本,则 QIO 导致 OTA 读回 SHA 必错。</dd><dt>修改建议</dt><dd>把分区表/Flash 大小/HZ/回滚并入根 sdkconfig.defaults,删除或标注废弃 config/sdkconfig.defaults;代码中加 static_assert(configTICK_RATE_HZ == 1000);CI 增加一次 idf.py build 并校验分区表。</dd></dl></div><div class="finding" id="S-02"><h4><span class="tag sev-s">严重</span> S-02 · 回滚保护取消过早:mark_app_valid 早于自检判定 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/core/main.cpp:131-147</code></dd>
<dt>现象</dt><dd>esp_ota_mark_app_valid_cancel_rollback() 在 SystemBoot::report() 之前无条件调用;随后若 critical_ok 为 false 才 3 秒后 esp_restart()。</dd><dt>风险</dt><dd>新固件关键初始化失败(SPI/WiFi/HTTP 起不来)时已被标记有效,重启后仍运行坏固件,回滚机制形同虚设;在仅能网页升级的现场即“软变砖”。</dd><dt>修改建议</dt><dd>先 SystemBoot::report(),仅 critical_ok 才 mark valid;更稳妥是在 WiFi/HTTP 可达且 SPI L0 通过后(或前端确认后)再 mark,失败时 esp_ota_mark_app_invalid_rollback_and_reboot()。</dd></dl></div><div class="finding" id="S-03"><h4><span class="tag sev-s">严重</span> S-03 · 看门狗/中止路径与中继任务并发:清会话密钥但中继任务不停 <span class="note">[并发]</span></h4>
<dl><dt>位置</dt><dd><code>main/ota/ota_coordinator.cpp:625-635;main/relay/ota_relay.cpp:539-554</code></dd>
<dt>现象</dt><dd>doAbortLocked 在 httpd 任务内、持协调器锁调用 OtaRelay::abortRemoteAndReset(true),其 reset() 清零 m_kSession / m_nonceServer、解除 ring 抑制;而 ota_relay 任务中的 forwardFirmware 没有任何取消标志,仍在用这些数据收发 SPI。OTA_RESULT_ERR_USER_ABORT 从未被触发。</dd><dt>风险</dt><dd>中继后续包以全零密钥加密发往 GD32、两个任务同时发 CANCEL/数据,GD32 半会话残留;与 S-04 叠加时在慢速但合法的中继中可复现。</dd><dt>修改建议</dt><dd>引入 std::atomic&lt;bool&gt; 取消标志 + 事件组:abort 只置标志并等待中继任务自行收尾(带超时),reset() 只能由中继任务自身调用;协调器不在持锁状态下做 1.5 s 级 SPI 操作。</dd></dl></div><div class="finding" id="S-04"><h4><span class="tag sev-s">严重</span> S-04 · 忙态看门狗 150 s 短于 GD32 中继合法最坏耗时 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/app_config.h:126;main/relay/ota_relay.cpp:361-368</code></dd>
<dt>现象</dt><dd>OTA_COORD_BUSY_WATCHDOG_MS=150000;传输阶段自身预算上限 120 s,且只从 TRANSFERRING 开始计;进 Boot(2×12 s+HELLO)、握手 20×1.6 s、SIZE_NEG 最多 120×500 ms、FINISH 10 次+APP 存活轮询都在预算之外。</dd><dt>风险</dt><dd>接近满尺寸固件或总线繁忙时,本可成功的中继会被看门狗中止(并触发 S-03 的并发问题),表现为偶发“OTA 卡在 9x% 后失败”。</dd><dt>修改建议</dt><dd>看门狗改为按阶段续期(中继任务每阶段/每 N 包喂狗),或将阈值设为各阶段上限之和 + 裕量;统一由中继任务上报进度时间戳。</dd></dl></div><div class="finding" id="S-05"><h4><span class="tag sev-s">严重</span> S-05 · WiFi 运行期断线重试 5 次后永不重连,也不回退 AP <span class="note">[健壮性]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/wifi_ota.cpp:94-100, 180-192, 751-777</code></dd>
<dt>现象</dt><dd>重连任务达到 WIFI_STA_MAX_RETRY 后 setStaRetryExhausted(true);该标志只在 STA_START/CONNECTED/初始化时复位(156/168/291 行),断线事件在 exhausted 时不再唤醒重连;AP 回退只在开机流程中执行。</dd><dt>风险</dt><dd>路由器重启或短暂断电超过几次重试窗口后,设备永久失联,只能现场断电;HTTP/OTA/CAN 控制全部不可用。</dd><dt>修改建议</dt><dd>耗尽后改为长周期退避(如 30 s~5 min)持续重连,或运行期回退 AP+STA 并行;同时把耗尽状态纳入 /system/status 与健康监控。</dd></dl></div><div class="finding" id="S-06"><h4><span class="tag sev-s">严重</span> S-06 · GD32 握手 nonce 可预测且时间戳明文入日志 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/relay/ota_relay.cpp:1027-1035;main/transport/protocol.cpp:90-131</code></dd>
<dt>现象</dt><dd>nonce_server = scrambleGenerate(xTaskGetTickCount(), &amp;m_nonceServer, 0x12345678);种子为常量、地址固定,按字节映射使同一次开机内 nonce 第 4~15 字节恒定,仅 4 字节 tick 变化;tick 以 DEBUG_INFO 明文打印(并经 UDP 广播到局域网)。</dd><dt>风险</dt><dd>会话密钥 K_session = AES(MASTER_KEY, nonce) 的新鲜性只剩 32 bit 且可从日志获知,配合硬编码 MASTER_KEY(S-08)可离线复现会话密钥、重放或伪造 OTA 数据。</dd><dt>修改建议</dt><dd>16 字节全部取自 esp_fill_random()(确保 RF 已开或 bootloader_random_enable);删除 timestamp 日志;scrambleGenerate 仅保留为兼容路径。</dd></dl></div><div class="finding" id="S-07"><h4><span class="tag sev-s">严重</span> S-07 · GET /can/mode 查询失败时会把 CAN 接收模式写回 ALL_PASS <span class="note">[健壮性]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:1020-1030</code></dd>
<dt>现象</dt><dd>0xC1 查询失败的回退路径调用 canFdSetRxMode(CAN_RX_MODE_ALL_PASS, &amp;actual, 2000) 作为“查询”,这是一次写操作。</dd><dt>风险</dt><dd>前端轮询 GET 一次失败即静默把用户设定的 FILTER / CAN0→1/2 BRIDGE 模式改回全收,电机总线桥接中断,且无日志可见。GET 有副作用也违背 HTTP 语义。</dd><dt>修改建议</dt><dd>增加只读查询命令(或复用 0xC1 重试),失败时返回 {ok:false} 与缓存值,绝不写入。</dd></dl></div><div class="finding" id="S-08"><h4><span class="tag sev-s">严重</span> S-08 · 凭据与主密钥明文入仓,AP 弱口令 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/app_config.h:31-32, 39-40, 161-164;main/server/wifi_ota.cpp:264</code></dd>
<dt>现象</dt><dd>STA SSID/密码(真实口令)、AP 密码 12345678(WPA/WPA2 混合)、MASTER_KEY = 01 23 45 67 89 AB CD EF FE DC BA 98 76 54 32 10(示例向量)均为源码常量,且已进入 git 历史。</dd><dt>风险</dt><dd>仓库任何读者可接入现场 WiFi 与设备 AP;MASTER_KEY 公开即 GD32 挑战-应答与逐包加密全部失效,可向 GD32 注入任意固件。</dd><dt>修改建议</dt><dd>迁移到 NVS(出厂配置写入)或 Kconfig + 不入库的 sdkconfig;轮换已泄露的 WiFi 口令;每设备派生密钥(MASTER_KEY ⊕ UID KDF);CI 加 gitleaks 扫描。</dd></dl></div><div class="finding" id="S-09"><h4><span class="tag sev-s">严重</span> S-09 · BLE GATT 无配对/加密,范围内任意设备可 OTA 或重启 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/ble_ota_host.cpp:366-376</code></dd>
<dt>现象</dt><dd>CTRL/DATA_IN 特征仅 BLE_GATT_CHR_F_WRITE / WRITE_NO_RSP,未要求 WRITE_ENC/AUTHEN,未配置 SM。</dd><dt>风险</dt><dd>10~30 m 内任意手机可发 0xFA 重启、0xFB 中止、上传固件触发 GD32 中继。</dd><dt>修改建议</dt><dd>启用 LE Secure Connections(Passkey 或 OOB),关键特征加 BLE_GATT_CHR_F_WRITE_ENC | WRITE_AUTHEN;或至少在 CTRL 上加应用层 token。</dd></dl></div><div class="finding" id="S-10"><h4><span class="tag sev-s">严重</span> S-10 · RSSI 更新覆盖过温 SAFE_SHUTDOWN 健康状态 <span class="note">[实时性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/health_monitor.cpp:578-600;main/server/wifi_ota.cpp:853</code></dd>
<dt>现象</dt><dd>温度与 RSSI 共用 m_healthState;updateFromWifiRssi 只要 next != cur 就 setState(SYS_NORMAL/WARNING/CRITICAL),会把温度路径设置的 SYS_SAFE_SHUTDOWN 改写掉。</dd><dt>风险</dt><dd>过温门控被下一次 RSSI 采样清除,高温下仍允许启动 OTA(Flash 擦写 + 射频满负荷),存在硬件损伤与写坏风险。</dd><dt>修改建议</dt><dd>拆分温度/RSSI 子状态,整体状态取两者最严重值;SAFE_SHUTDOWN 只能由温度路径解除。</dd></dl></div><div class="finding" id="S-11"><h4><span class="tag sev-s">严重</span> S-11 · 栈溢出钩子中调用可阻塞的 uart_write_bytes <span class="note">[实时性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/task_stack.cpp:27-40</code></dd>
<dt>现象</dt><dd>vApplicationStackOverflowHook 运行在 vTaskSwitchContext(调度器临界区)内,却调用带互斥锁、可能阻塞的 uart_write_bytes。</dd><dt>风险</dt><dd>溢出发生时可能在钩子内死锁/二次异常,丢失现场信息,甚至无法走到 esp_system_abort。</dd><dt>修改建议</dt><dd>改用 esp_rom_printf / ets_printf(ROM 直写),或只置标志后立即 abort,由 panic handler 输出任务名。</dd></dl></div><div class="finding" id="S-12"><h4><span class="tag sev-s">严重</span> S-12 · /spi/send 的 timeout_ms 无上限,可长期占死 SPI worker <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:1715;main/transport/spi_master.cpp:646</code></dd>
<dt>现象</dt><dd>timeout_ms 由 double 直接强转 uint32;submitJob 内再 ×3+10 s,大数值会回绕或得到极长等待。</dd><dt>风险</dt><dd>一次请求即可让 httpd(单线程)与唯一的 spi_worker 长时间阻塞,CAN 反馈、心跳、OTA 全部停摆。</dd><dt>修改建议</dt><dd>限制 timeout_ms ∈ [10, 5000],超范围 400;submitJob 内做饱和运算。</dd></dl></div><h3>一般(17)</h3><div class="finding" id="G-01"><h4><span class="tag sev-g">一般</span> G-01 · 完成回调不校验状态与 epoch,可能覆盖新会话 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/ota/ota_coordinator.cpp:372-392, 568-576</code></dd>
<dt>现象</dt><dd>notifyRelayComplete / notifyEsp32FinalizeComplete 直接设置 COMPLETE/ERROR,不检查当前状态和 epoch。</dd><dt>风险</dt><dd>看门狗中止后迟到的完成回调会把新开始的 GD32_STORING 会话改成 COMPLETE/ERROR。</dd><dt>修改建议</dt><dd>回调携带启动时的 epoch,不一致即丢弃并记日志。</dd></dl></div><div class="finding" id="G-02"><h4><span class="tag sev-g">一般</span> G-02 · 协调器锁超时 100 ms,lock 失败直接跳过状态切换 <span class="note">[并发]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/app_config.h:124;main/ota/ota_coordinator.cpp</code></dd>
<dt>现象</dt><dd>持锁期间存在远端 CANCEL(≤1.5 s)、NVS 擦写、esp_ota_begin 等长操作;其他调用方 100 ms 拿不到锁即放弃,关键通知(中继完成、恢复 CAN 反馈)被丢。</dd><dt>风险</dt><dd>状态卡住直至 150 s 看门狗中止,前端表现为“完成后长时间不恢复”。</dd><dt>修改建议</dt><dd>长操作移出临界区;关键通知改为投递队列由协调器任务串行处理。</dd></dl></div><div class="finding" id="G-03"><h4><span class="tag sev-g">一般</span> G-03 · 确认 APP 存活前即清除 meta;失败路径连发 3 次 CANCEL <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/relay/ota_relay.cpp:1554, 1573-1579, 1637-1643</code></dd>
<dt>现象</dt><dd>doFinishAndVerify 在 doWaitForAppAlive 成功前清除 gd32 meta;失败时 3 处各发一次 CANCEL(每次最长 1.5 s)。</dd><dt>风险</dt><dd>GD32 新 APP 起不来时无法用已缓存固件重试中继,必须重新上传;失败收尾最长多耗 4.5 s。</dd><dt>修改建议</dt><dd>APP 存活确认后再清 meta;CANCEL 收敛到单一出口。</dd></dl></div><div class="finding" id="G-04"><h4><span class="tag sev-g">一般</span> G-04 · 校验缺口:先置 STORED 后算 SHA;ESP32 finalize 可不带 SHA、不比对实收字节 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/storage/gd32_flash_store.cpp(finishGd32StorageAfterReceive 275-279 附近);main/ota/esp32_ota_core.cpp finalize</code></dd>
<dt>现象</dt><dd>SHA 计算失败时状态仍为 STORED、checksum 全零(中继时会再算一次兜底);/ota/update 请求体缺少 sha256 或 ≥256B 时以 nullptr 跳过比对,且不检查 m_received == 声明大小。</dd><dt>风险</dt><dd>截断上传可能被当作完整固件写入启动分区(依赖 esp_ota_end 的镜像校验兜底)。</dd><dt>修改建议</dt><dd>SHA 成功后再置 STORED;finalize 强制要求 SHA 并校验字节数;请求体超长时排空并 400。</dd></dl></div><div class="finding" id="G-05"><h4><span class="tag sev-g">一般</span> G-05 · OTA 对齐缓冲正常完成后不释放(常驻约 4 KB) <span class="note">[内存]</span></h4>
<dl><dt>位置</dt><dd><code>main/ota/ota_coordinator.cpp:203, 625-635;main/storage/gd32_flash_store.cpp:162-179</code></dd>
<dt>现象</dt><dd>acquireAlignBufForOta() 在 beginGd32Storage 分配;releaseAlignBufForOta() 只在中止路径调用。</dd><dt>风险</dt><dd>成功完成一次 GD32 上传后约 4 KB 内部 DRAM 常驻,与 0.5.0 “上传缓冲 RAII 省 4 KB” 的目标相抵。</dd><dt>修改建议</dt><dd>在 completeGd32Storage / 中继完成路径同样释放,或用 RAII 绑定会话生命周期。</dd></dl></div><div class="finding" id="G-06"><h4><span class="tag sev-g">一般</span> G-06 · 电机 OTA 与 GD32 OTA 共用分区和 meta,互斥不完整 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:2622-2642, 2697-2700, 2748-2776</code></dd>
<dt>现象</dt><dd>/motor/ota 只检查“当前空闲”,不占用协调器;/motor/ota/relay 不区分 meta 来源也不检查协调器状态;注释“relay 复核 SHA”与“relay 不做 SHA 校验”自相矛盾。</dd><dt>风险</dt><dd>与 BLE 的 GD32 存储并发写同一分区;可能把 GD32 镜像中继给电机节点。</dd><dt>修改建议</dt><dd>电机 OTA 纳入 OtaCoordinator 状态机(新增 MOTOR_STORING/RELAYING),meta 增加镜像类型字段并在中继前校验。</dd></dl></div><div class="finding" id="G-07"><h4><span class="tag sev-g">一般</span> G-07 · JSON 截断输出非法 JSON;motor_state 栈占用约 2.5 KB <span class="note">[内存]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:2383, 2493;main/server/http_util.h</code></dd>
<dt>现象</dt><dd>http_json_append 截断时钳制在 cap-1,调用方 len&gt;0 判断失效;/motor/state JSON 每条约 290 B,循环仅留 128 B 余量,超过约 10 个电机必然截断;栈上 motor_state_table_t 约 2.5 KB(httpd 栈 7 KB)。</dd><dt>风险</dt><dd>前端解析失败;httpd 栈余量被压缩。</dd><dt>修改建议</dt><dd>改 chunked 逐条输出;大结构改静态或堆分配。</dd></dl></div><div class="finding" id="G-08"><h4><span class="tag sev-g">一般</span> G-08 · 输入校验不足与未定义行为 <span class="note">[健壮性]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/http_handlers.cpp:323, 1056-1061, 1256, 1340-1341, 1521</code></dd>
<dt>现象</dt><dd>double→uint32/uint8 强转无范围检查;readInt 有符号溢出;http_hex_to_bytes 不校验非 hex 字符;POST /can/mode 非法 mode 静默当 ALL_PASS;send_batch 最多 128 帧×80 ms 阻塞 httpd 约 10 s。</dd><dt>风险</dt><dd>异常输入导致错误下发或 httpd 长时间无响应。</dd><dt>修改建议</dt><dd>统一参数解析 helper(范围、类型、hex 校验),非法即 400;批量发送移到后台任务。</dd></dl></div><div class="finding" id="G-09"><h4><span class="tag sev-g">一般</span> G-09 · suspendStack 跨任务写非原子 BLE 连接状态 <span class="note">[并发]</span></h4>
<dl><dt>位置</dt><dd><code>main/server/ble_ota.cpp:276-279</code></dd>
<dt>现象</dt><dd>在 httpd 任务里直接写 m_bleConnected / m_connHandle,与 NimBLE host 任务竞争。</dd><dt>风险</dt><dd>挂起/恢复期间状态错乱,可能在挂起后被断连事件重新开启广播(待实机确认)。</dd><dt>修改建议</dt><dd>通过 ble_npl 事件投递到 host 任务内修改,或改 atomic。</dd></dl></div><div class="finding" id="G-10"><h4><span class="tag sev-g">一般</span> G-10 · SPI 事务超时返回后 DMA 可能仍在使用栈上缓冲 <span class="note">[内存]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:1001-1019</code></dd>
<dt>现象</dt><dd>spi_device_get_trans_result 重试 1 s 仍超时即返回,栈上的 transaction 与 staging 缓冲可能仍被 DMA 访问。</dd><dt>风险</dt><dd>极端情况下栈数据被改写(use-after-return),表现为随机崩溃。</dd><dt>修改建议</dt><dd>超时后复位 SPI 总线/设备再返回,或改为静态/成员缓冲。</dd></dl></div><div class="finding" id="G-11"><h4><span class="tag sev-g">一般</span> G-11 · tick 比较非回绕安全 <span class="note">[健壮性]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:883-889, 1443-1445, 1560-1572, 2058-2061, 2156-2163, 2544-2556</code></dd>
<dt>现象</dt><dd>多处 now &gt;= deadline / tick &lt;= deadline 直接比较。</dd><dt>风险</dt><dd>运行约 49.7 天(1 kHz)回绕时,个别等待可能变成极长阻塞。</dd><dt>修改建议</dt><dd>统一改用 xTaskCheckForTimeOut 或 (int32_t)(now - deadline) &gt;= 0。</dd></dl></div><div class="finding" id="G-12"><h4><span class="tag sev-g">一般</span> G-12 · SPI 主站边界缺陷(resp_len 越读、out_init_status 未回填、HELLO 失败后会话不一致) <span class="note">[协议]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:175, 622-627, 1692-1697, 2005-2042</code></dd>
<dt>现象</dt><dd>copyFoundFrame 写入未截断长度,0x82 统计按其读取 resp_payload[0..5];submitJob 不回填 out_init_status(当前调用方传 nullptr,潜在);ENTER_BOOT 内 HELLO 重同步失败后 m_v2Active=false 且持有 GD32 未知的新 session。</dd><dt>风险</dt><dd>小缓冲调用方越界读;跨任务查询拿不到结果;失败后一段时间内会话校验失效。</dd><dt>修改建议</dt><dd>长度统一截断到 resp_max;补齐输出字段回填;HELLO 失败恢复旧 session 或强制下一帧 sess0。</dd></dl></div><div class="finding" id="G-13"><h4><span class="tag sev-g">一般</span> G-13 · CI 必然失败且不构建固件 <span class="note">[构建]</span></h4>
<dl><dt>位置</dt><dd><code>.github/workflows/spi-link-selftest.yml</code></dd>
<dt>现象</dt><dd>.github/workflows/spi-link-selftest.yml 检查 ../spi_link 不存在即 exit 1,而没有任何步骤提供该目录;workflow 也不执行 idf.py build。另外 Gitea 上是否启用 Actions 未知。</dd><dt>风险</dt><dd>门禁形同虚设,S-01 一类构建问题无法被发现。</dd><dt>修改建议</dt><dd>把 spi_link 独立成仓并以 submodule 引入(同时解决 components/transport 的 ../../../spi_link 仓外依赖),CI 增加 espressif/idf:v6.0 容器构建。</dd></dl></div><div class="finding" id="G-14"><h4><span class="tag sev-g">一般</span> G-14 · 任务数 &gt; 20 时 CPU 统计冻结并每秒刷 WARN <span class="note">[可维护性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/cpu_stats.cpp:106-117</code></dd>
<dt>现象</dt><dd>uxTaskGetSystemState 在数组不足时返回 0 而非截断,快照冻结;常驻约 19 个任务,加按需任务即超限。</dd><dt>风险</dt><dd>/system/status 的 CPU 数据失真,日志被刷屏。</dd><dt>修改建议</dt><dd>数组按 uxTaskGetNumberOfTasks()+裕量动态分配,WARN 节流。</dd></dl></div><div class="finding" id="G-15"><h4><span class="tag sev-g">一般</span> G-15 · UDP 日志不可关闭、目标硬编码,fd 关闭存在竞态 <span class="note">[安全]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/debug_log.cpp:689-711;main/include/app_config.h:96</code></dd>
<dt>现象</dt><dd>有 IP 即向 192.168.31.3:6666 发送全部日志(含 S-06 的 nonce 时间戳);一个任务读取 socket fd 后另一任务可能将其关闭,fd 被 httpd 复用时日志会写进 HTTP 连接。</dd><dt>风险</dt><dd>信息泄露;极端情况下污染 HTTP 响应流。</dd><dt>修改建议</dt><dd>Kconfig 开关默认关闭;fd 访问加锁或改单写者任务。</dd></dl></div><div class="finding" id="G-16"><h4><span class="tag sev-g">一般</span> G-16 · 日志环满时选择性覆盖导致 /logs 时间序错乱 <span class="note">[可维护性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/debug_log.cpp:341-352</code></dd>
<dt>现象</dt><dd>满环时跳过 ERR/WARN、覆盖 INFO/DBG 槽位。</dd><dt>风险</dt><dd>排查事故时日志顺序失真。</dd><dt>修改建议</dt><dd>分级双环,或保留序号字段由前端排序。</dd></dl></div><div class="finding" id="G-17"><h4><span class="tag sev-g">一般</span> G-17 · OTA_MAX_FIRMWARE_SIZE 注释数值错误 <span class="note">[OTA]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/app_config.h:77</code></dd>
<dt>现象</dt><dd>216*1024 = 221184 = 0x36000,与 GD32 APP_MAX_SIZE 实际对齐;注释却写 “0x35F90”(=221072)。0627 P0-3 对 0x36000 的换算(221296)同样有误。</dd><dt>风险</dt><dd>维护者据注释误判上限,引发两侧不一致。</dd><dt>修改建议</dt><dd>改为 0x36000 字面量 + static_assert,注释删除错误换算。</dd></dl></div><h3>建议(7)</h3><div class="finding" id="A-01"><h4><span class="tag sev-a">建议</span> A-01 · 仓库卫生:gitignore 行内注释失效、被忽略目录仍被跟踪、本机路径入库 <span class="note">[构建]</span></h4>
<dl><dt>位置</dt><dd><code>.gitignore:25,31,89-91;.trae/;.zcode/;.vscode/settings.json</code></dd>
<dt>现象</dt><dd>行内 # 注释使 *.exe、flasher_args.json、*.workspace、*.suo 规则失效;.trae/(5)、.zcode/(11) 过程文件已在 gitignore 中却仍被跟踪;.vscode 含本机 Windows 路径与 COM15(且写成 Robot_v2\DFU)。</dd><dt>风险</dt><dd>噪声与泄露本机信息。</dd><dt>修改建议</dt><dd>注释另起一行;git rm --cached 过程目录;.vscode 改模板。</dd></dl></div><div class="finding" id="A-02"><h4><span class="tag sev-a">建议</span> A-02 · 版本信息与文档漂移 <span class="note">[文档]</span></h4>
<dl><dt>位置</dt><dd><code>main/include/version.h:18-21;README.md</code></dd>
<dt>现象</dt><dd>GIT_HASH=&quot;770ae14&quot; 而 HEAD 为 af1f682(落后 12 个提交);FW_VERSION_STRING 后挂超长变更注释;README/白皮书顶部写“git 770ae14”;README 任务表名称与代码不符(ble_data_worker→ble_ota_worker、motor_feed→ota_feed),缺 httpd/ota_cancel 等任务;引用的 .cache/ 前端快照不在仓库。</dd><dt>风险</dt><dd>现场版本追溯困难。</dd><dt>修改建议</dt><dd>构建时用 git describe 生成 GIT_HASH;变更记录移到 CHANGELOG.md。</dd></dl></div><div class="finding" id="A-03"><h4><span class="tag sev-a">建议</span> A-03 · 提交信息与内容不符,历史评审被静默删除 <span class="note">[构建]</span></h4>
<dl><dt>位置</dt><dd><code>git 历史 af1f682</code></dd>
<dt>现象</dt><dd>af1f682 标为 “docs(readme): 更新文档说明”,实际删除 docs/esp32_architecture_review_2026-06-27.md 等 4 份文档;更早 5b2c5f8 删除了 2026-06-13 评审与 2026-05-05 通信梳理。</dd><dt>风险</dt><dd>评审结论无从追溯,问题被重复发现。</dd><dt>修改建议</dt><dd>评审记录统一归档到 docs/review/(本次已恢复存档),删除须独立提交并说明。</dd></dl></div><div class="finding" id="A-04"><h4><span class="tag sev-a">建议</span> A-04 · CS 前 esp_rom_delay_us 忙等最长 1 ms <span class="note">[实时性]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:946-952</code></dd>
<dt>现象</dt><dd>单核芯片上 prio 8 的 spi_worker 每帧前忙等,期间低优先级任务饥饿。</dd><dt>风险</dt><dd>CPU 利用率与 BLE/WiFi 吞吐受影响。</dd><dt>修改建议</dt><dd>改为基于 esp_timer 的剩余时间判断,&lt; 100 µs 才忙等。</dd></dl></div><div class="finding" id="A-05"><h4><span class="tag sev-a">建议</span> A-05 · 间接优先级倒置 <span class="note">[实时性]</span></h4>
<dl><dt>位置</dt><dd><code>main/core/main.cpp;README 任务表</code></dd>
<dt>现象</dt><dd>ota_relay(23)、health_mon(21) 通过 spi_worker(8) 完成 SPI 事务,等待期间被 9~20 优先级任务(ble_ota_worker 12、lwIP 18 等)抢占。</dd><dt>风险</dt><dd>高优先级任务的时延不可预期。</dd><dt>修改建议</dt><dd>Job 提交时临时提升 worker 优先级(优先级继承)或让 worker 位于调用方之上。</dd></dl></div><div class="finding" id="A-06"><h4><span class="tag sev-a">建议</span> A-06 · 死代码与量程不符 <span class="note">[可维护性]</span></h4>
<dl><dt>位置</dt><dd><code>main/monitor/health_monitor.cpp:389-396, 365;main/monitor/task_stack.cpp:30-39</code></dd>
<dt>现象</dt><dd>g_stackOverflowDetected 置位后钩子立即 abort,health_mon 永远看不到;温度传感器量程配置为 20~80 °C,而 TEMP_CRITICAL=100 °C 超出量程。</dd><dt>风险</dt><dd>告警逻辑失效。</dd><dt>修改建议</dt><dd>删除死分支或改为 RTC_NOINIT 变量跨重启上报;量程改 -10~110。</dd></dl></div><div class="finding" id="A-07"><h4><span class="tag sev-a">建议</span> A-07 · 日志格式串与参数不匹配、注释过期 <span class="note">[可维护性]</span></h4>
<dl><dt>位置</dt><dd><code>main/transport/spi_master.cpp:924-925;main/server/wifi_ota.cpp:403</code></dd>
<dt>现象</dt><dd>格式串 3 个说明符却传 5 个参数;路由注册注释写 35 次,实际 22 路由+21 OPTIONS=43 次。</dd><dt>风险</dt><dd>日志误导。</dd><dt>修改建议</dt><dd>开启 -Wformat 并修正注释。</dd></dl></div>
<h2 id="s5">5. 既有评审结论跟踪</h2>
<div class="card"><p>来源:<b>0816</b>=project_introduction.html §19 安全审计(2026-08-16);<b>0613</b>=<a href="archive/CODE_REVIEW_REPORT_2026-06-13.html">CODE_REVIEW_REPORT_2026-06-13.html</a>(v0.3.12);<b>0627</b>=<a href="archive/esp32_architecture_review_2026-06-27.md">esp32_architecture_review_2026-06-27.md</a>;<b>0505</b>=<a href="archive/COMMUNICATION_AUDIT_2026-05-05.md">COMMUNICATION_AUDIT_2026-05-05.md</a>。</p>
<p>统计:已修复 23 · 部分修复 5 · 仍存在/结论不成立 12 · 未逐条核对 2。总体看,历史上的并发、函数拆分、资源清理类问题大多已整改;<b>安全类结论(认证、凭据、签名、ECB、nonce)一条都没有关闭</b>。</p></div>
<table><tr><th>来源</th><th>编号</th><th>原结论</th><th>当前状态</th><th>当前代码证据</th></tr><tr><td>0816</td><td>①</td><td>HTTP 22 端点零认证</td><td><span class='tag st-bad'>仍存在</span></td><td>http_handlers.cpp:77-83 CORS *,无鉴权;见 F-01</td></tr><tr><td>0816</td><td>②</td><td>WiFi 凭据与 MASTER_KEY 明文入仓</td><td><span class='tag st-bad'>仍存在</span></td><td>app_config.h:31-40,161-164;见 S-08</td></tr><tr><td>0816</td><td>③</td><td>无固件签名</td><td><span class='tag st-bad'>仍存在</span></td><td>仅 SHA-256 完整性;未启用 Secure Boot / 签名校验</td></tr><tr><td>0816</td><td>④</td><td>AES-ECB 确定性模式</td><td><span class='tag st-bad'>仍存在</span></td><td>ota_relay.cpp 609-620, 836-855 esp_aes_crypt_ecb</td></tr><tr><td>0816</td><td>缓解</td><td>nonce 每会话随机绑定防重放</td><td><span class='tag st-bad'>结论不成立</span></td><td>nonce 仅 tick 4 字节变化且明文入日志;见 S-06</td></tr><tr><td>0613</td><td>#1</td><td>SPI 总线无并发保护</td><td><span class='tag st-ok'>已修复</span></td><td>spi_master.cpp:588-821 单 worker + Job 池 + m_mutex</td></tr><tr><td>0613</td><td>#2</td><td>硬编码占位密钥</td><td><span class='tag st-bad'>仍存在</span></td><td>同 0816 ②</td></tr><tr><td>0613</td><td>#3</td><td>Flash 写入无对齐检查</td><td><span class='tag st-ok'>已修复</span></td><td>gd32_flash_store.cpp:199-276 flashWriteChunk 头尾 pad read-modify-write</td></tr><tr><td>0613</td><td>#4</td><td>BLE/WiFi 双通道 ESP32 OTA 冲突</td><td><span class='tag st-ok'>已修复</span></td><td>Esp32OtaCore s_occupied + OtaCoordinator 单例互斥</td></tr><tr><td>0613</td><td>#5</td><td>SPI 帧 XOR 伪校验</td><td><span class='tag st-ok'>已修复(迁移)</span></td><td>otaCrc8 已移除,Link v2 用 CRC32-MPEG2(实现在仓外 spi_link,未能直接核对)</td></tr><tr><td>0613</td><td>#6</td><td>0xA5 常量碰撞</td><td><span class='tag st-ok'>已修复</span></td><td>spi_master.h:33 SPI_APP_ENTER_BOOT_ACK_OK=0xB5</td></tr><tr><td>0613</td><td>#7</td><td>OTA 会话无全局超时</td><td><span class='tag st-part'>部分修复</span></td><td>传输阶段 120 s 预算 + 150 s 看门狗,但看门狗短于最坏耗时且无法停止中继任务;见 S-03/S-04</td></tr><tr><td>0613</td><td>#8</td><td>CORS 允许所有来源</td><td><span class='tag st-bad'>仍存在</span></td><td>http_handlers.cpp:79</td></tr><tr><td>0613</td><td>#11</td><td>OtaRelay 静态状态无锁</td><td><span class='tag st-part'>部分修复</span></td><td>成员已 private + StateLockGuard,reset() 仍可被外部任务调用;见 S-03</td></tr><tr><td>0613</td><td>#16</td><td>nonce 仅用 esp_random 一次/可预测</td><td><span class='tag st-bad'>仍存在(形式变化)</span></td><td>现为 tick+固定地址+常量种子;见 S-06</td></tr><tr><td>0613</td><td>#17</td><td>ota_meta checksum 从未填充</td><td><span class='tag st-ok'>已修复</span></td><td>metaFinalizeChecksum + metaVerifyChecksum(gd32_flash_store.cpp:555-620)</td></tr><tr><td>0613</td><td>#18</td><td>ESP32 OTA 无回滚验证</td><td><span class='tag st-part'>部分修复</span></td><td>代码调用 mark_app_valid,但调用过早且回滚开关所在配置未生效;见 S-01/S-02</td></tr><tr><td>0613</td><td>#19</td><td>SPI 忽略 timeout</td><td><span class='tag st-ok'>已修复</span></td><td>spi_master.cpp:981-1019 有界等待(残留 G-10)</td></tr><tr><td>0613</td><td>#20</td><td>esp_ota_write 失败未 abort</td><td><span class='tag st-ok'>已修复</span></td><td>esp32_ota_core finalizeCleanupAndReturn/beginCleanupAndReturn</td></tr><tr><td>0613</td><td>#22</td><td>SPI 无就绪信号</td><td><span class='tag st-ok'>已修复</span></td><td>DATA_READY GPIO10 ISR + credit 流控</td></tr><tr><td>0627</td><td>P0-1</td><td>metaVerifyChecksum 全零跳过校验</td><td><span class='tag st-ok'>已修复</span></td><td>gd32_flash_store.cpp:563-569 返回 ESP_ERR_INVALID_STATE</td></tr><tr><td>0627</td
<h2 id="s6">6. 优先级修复路线图</h2>
<table><tr><th>阶段</th><th>目标</th><th>条目</th><th>预估</th></tr>
<tr><td><span class="tag sev-f">P0 · 本周</span></td><td>堵住远程控制面,恢复可复现构建</td><td>F-01(token + CORS 白名单 + /spi/send 白名单/默认关闭)、S-12、S-01(合并 sdkconfig.defaults + HZ static_assert)、S-08(轮换 WiFi 口令,凭据迁 NVS)</td><td>2~3 人日</td></tr>
<tr><td><span class="tag sev-s">P1 · 2 周内</span></td><td>消除变砖与失联路径</td><td>S-02(mark_app_valid 后移)、S-03 + S-04(中继取消标志与分阶段看门狗)、S-05(断线长周期重连)、S-07、S-10、S-11、G-01、G-02</td><td>4~6 人日,需 GD32 全链路 OTA 回归</td></tr>
<tr><td><span class="tag sev-g">P2 · 1 个月</span></td><td>加固密码学与无线面</td><td>S-06(esp_fill_random nonce)、S-09(BLE LESC + 加密特征)、0816 ③④(签名 / AES-CTR 或 GCM,需三工程同步)、G-06、G-15</td><td>5~8 人日(含 GD32 侧)</td></tr>
<tr><td><span class="tag sev-a">P3 · 持续</span></td><td>工程卫生</td><td>G-13(spi_link submodule + 固件构建 CI)、G-03~G-05、G-07~G-12、G-14、G-16、G-17、A-01~A-07</td><td>随迭代</td></tr></table>
<h2 id="s7">7. 总结评分</h2>
<table><tr><th>维度</th><th>评分</th><th>理由</th></tr><tr><td>固件架构与分层</td><td><b>7</b> / 10</td><td>接入/协调/执行/传输分层清晰,协调器单例 + 单 SPI worker 设计合理</td></tr><tr><td>SPI 协议与传输</td><td><b>6</b> / 10</td><td>会话/序号/credit/CRC32 完备,诊断充分;边界与回绕细节待补</td></tr><tr><td>OTA 可靠性</td><td><b>5</b> / 10</td><td>互斥与 epoch 进步明显;中止协作、看门狗预算、回滚时机存在变砖级缺口</td></tr><tr><td>FreeRTOS 与实时性</td><td><b>6</b> / 10</td><td>任务优先级经事故调优;钩子与健康状态逻辑有真实缺陷</td></tr><tr><td>网络与无线安全</td><td><b>2</b> / 10</td><td>零认证 + CORS * + 原始 SPI 透传 + 硬编码密钥 + BLE 无加密</td></tr><tr><td>代码质量与可维护性</td><td><b>6</b> / 10</td><td>历史评审的大函数/重复样板多数已整改;超长文件(http_handlers 2.8k 行)仍需拆分</td></tr><tr><td>构建与仓库卫生</td><td><b>3</b> / 10</td><td>干净克隆不可复现、CI 必败、仓外依赖、过程文件入库</td></tr><tr><td>文档</td><td><b>6</b> / 10</td><td>白皮书详尽,但版本/任务表漂移,历史评审被静默删除</td></tr>
<tr><td><b>综合</b></td><td><b>5.1 / 10</b></td><td>链路与 OTA 工程扎实、诊断能力强,历史评审的结构性问题大多已整改;但“可信局域网”前提下的零认证控制面、不可复现的构建配置、回滚时机与中止协作缺陷,使其距离量产/现场部署仍有明显距离。</td></tr></table>
<div class="card"><b>结论</b>:建议在下一个版本(0.5.3)前完成 P0 全部条目,并把本报告纳入 <code>docs/project_introduction.html</code> §19 的跟踪;后续评审请统一归档到 <code>docs/review/</code>,不要随文档整理删除。</div>
<p class="note">本报告由 Hark 专家评审生成 · 2026-10-19 · 基线 af1f682。行号以该提交为准,后续提交可能漂移。</p>
</div></body></html>