SpiMaster 是单例但没有任何互斥锁。多个 FreeRTOS 任务直接调用 SPI 方法:ota_relay 任务 (优先级 MAX-2)、wifi_ota 任务 (MAX-3)、health_monitor 任务 (MAX-4)。协议操作是多步非原子的(如 otaTransfer = 发送帧 + 轮询响应,CS在两次传输之间释放),高优先级任务可在协议序列中间抢占。
SemaphoreHandle_t m_mutex,所有公共方法入口加锁。对于多步协议操作(otaTransfer等),使用统一的锁粒度保护整个请求-响应序列。
MASTER_KEY 使用 NIST AES 测试向量,SCRAMBLE_CONST 使用简单递增序列,GD32_CHIP_UID 使用 0x00-0x0B。注释标注"需填入实际密钥"但从未替换。
两个调用路径产生非对齐值:(1) WiFi OTA: gd32_offset 从 httpd_req_recv 返回值累加,最后一个chunk是固件余数;(2) BLE OTA: data_len = recv_len - 11(7字节头+4字节CRC),不保证4字节对齐。esp_partition_write 要求 offset 和 size 均4字节对齐。
BleOta 和 WifiOta 各自维护独立的 m_esp32OtaActive、m_esp32OtaHandle、m_esp32UpdatePartition,但底层 esp_ota_begin/write/end 操作的是同一个OTA分区。两个通道同时触发时:第二个 esp_ota_begin 会失败或覆盖句柄;esp_ota_write 可能交错写入同一分区;导致固件损坏+变砖。
XOR校验无法检测:偶数个相同位置的位翻转、字节交换、全零段丢失。固件数据的静默损坏几乎不可检测。
同一值 0xA5 表示两种完全不同的语义。GD32在OTA窗口回复ACK时,ESP32可能将ACK字节误解析为OTA帧起始,导致协议状态机混乱。
waitUntilNotWait() 每次调用有 max_wait_rounds 上限,但整个 forwardFirmware() 流程没有全局超时。GD32 持续返回 WAIT 时,单个 SEND_SIZE阶段可挂起 ~78秒,完整流程可挂起数分钟。OTA_RESULT_ERR_USER_ABORT 已定义但从未使用。
任何网页都可以向 ESP32 发起 OTA 请求。结合占位密钥问题,攻击链完整:恶意网页 → 上传伪造固件 → 通过占位密钥认证 → 烧录恶意GD32固件。
| # | 严重度 | 文件 | 问题 | 影响 |
|---|---|---|---|---|
| 9 | HIGH | wifi_ota.cpp:121 | WiFi事件回调中调用 esp_wifi_connect() 重连 | 在系统事件任务中调用可能死锁 |
| 10 | HIGH | health_monitor.cpp | healthState 静态变量无锁跨任务读写 | 竞态导致健康判断错误 |
| 11 | HIGH | ota_relay.h:80-84 | 5个静态变量(relayState等)无锁跨任务访问 | 协议状态不一致 |
| 12 | HIGH | wifi_ota.h:33-47 | WifiOta 所有成员 public 且无锁 | HTTP回调与WiFi事件回调竞争 |
| 13 | HIGH | ble_ota.h | BleOta 成员无锁,BLE GATT回调与主任务竞争 | BLE OTA 数据损坏 |
| 14 | HIGH | debug_log.cpp:296 | log() 栈分配 msg[512] + log_buf[600] = 1112字节 | 低栈任务中栈溢出 |
| 15 | HIGH | wifi_ota.cpp:1008 | spi_send_handler 栈分配 1728+ 字节 | HTTP server栈仅8KB,接近溢出 |
| 16 | HIGH | ota_relay.cpp:456 | sendNonceAndAuthenticate() nonce 仅用 esp_random() 一次 | 认证会话密钥可预测 |
| 17 | HIGH | ota_relay.h:25 | ota_meta_t.checksum[32] 从未填充/验证 | 固件完整性无保障 |
| 18 | HIGH | wifi_ota.cpp:429-444 | ESP32 OTA 完成→重启,无回滚验证 | 新固件若无法启动将变砖 |
| 19 | HIGH | spi_master.cpp:258 | syncTransfer/recv 忽略 timeout_ms 参数 | SPI事务可能无限等待 |
| 20 | HIGH | wifi_ota.cpp:599-629 | esp_ota_write 失败后 break,未调用 esp_ota_abort | OTA分区处于不一致状态 |
| 21 | HIGH | ble_ota.cpp:439-444 | BLE OTA 帧预留4字节CRC但减去计算不匹配 | 最后几个字节被误截断 |
| 22 | HIGH | spi_master.h | SPI仅4线,无硬件流控/就绪信号 | 高速传输时GD32来不及处理 |
项目中有 5个静态全局变量 (relayState, relaySeq, relayTotalPackets, gd32AppAlive, gd32Version) 和 大量public成员 (WifiOta/BleOta所有状态),跨越3+个FreeRTOS任务和多个ISR/callback上下文访问,没有任何一个被mutex/atomic/critical section保护。这是系统性问题,不是个别bug。
SpiMaster 单例是唯一的SPI通信通道,但没有任何仲裁机制。WiFi/BLE/健康监控/OTA中继四个功能模块各自直接调用,协议级操作(请求-响应对)不是原子的。需要从架构层面引入SPI总线仲裁器。
认证握手存在但密钥硬编码→形同虚设;固件校验字段存在但未填充→形同虚设;CORS开放→任何人可触发OTA。安全功能"看起来有"但实际上不提供保护,这种"安全假象"比没有安全功能更危险,因为它给了虚假的信心。
| 优先级 | 修复项 | 工作量 | 紧急度 | 进度 |
|---|---|---|---|---|
| P0 | 添加 SPI 总线互斥锁 | 小 | 极高 — 生产环境必崩 | |
| P0 | 替换硬编码密钥为安全存储 | 中 | 极高 — 安全漏洞 | |
| P0 | Flash 写入对齐检查 | 小 | 极高 — 数据损坏 | |
| P1 | 添加全局 OTA 超时+中止机制 | 中 | 高 — 设备挂死 | |
| P1 | ESP32 OTA 添加回滚验证 | 中 | 高 — 变砖风险 | |
| P1 | 替换 XOR 为 CRC-8/16 校验 | 小 | 高 — 固件损坏 | |
| P1 | 消除 0xA5 值碰撞 | 极小 | 高 — 协议混乱 | |
| P2 | 添加 OTA 单源互斥 (BLE/WiFi) | 中 | 中高 | |
| P2 | 为所有跨任务状态添加锁/atomic | 大 | 中高 | |
| P2 | 修复 WiFi 事件回调中的重连调用 | 小 | 中 | |
| P3 | 修复大栈分配问题 | 小 | 中 | |
| P3 | 限制 CORS 来源 | 小 | 中 | |
| P3 | 实现 ota_meta_t 校验填充与验证 | 中 | 中 |
| 原始严重度 | 问题描述 | 驳回原因 |
|---|---|---|
| CRITICAL | SPI DMA缓冲区栈分配导致数据损坏/硬Fault | ESP-IDF 6.0 的 spi_master 驱动内置 setup_dma_priv_buffer() 函数自动检测并对齐DMA缓冲区,栈分配仅导致性能损失,不引发数据损坏 |
| HIGH | TOCTOU: relayState 检查后 reset() 无锁 | 虽然模式存在,但当前代码流程中检查和重置在同一线程同步执行,实际竞态窗口极小,风险被高估 |