E-01修复:poll超长守卫前置st.size根治死码,save输出上限收紧4096,写盘闸拦截与自检留痕

This commit is contained in:
2026-09-22 01:28:10 +08:00
parent 5a154bf9c4
commit d030cb00e3
3 changed files with 120 additions and 20 deletions
+47 -16
View File
@@ -12,6 +12,12 @@
* 移除对 C 库 malloc 的全部依赖(MicroLIB C 堆仅 512B,
* 原 4 处 KB 级 malloc 真机必然失败);增编译期堆峰值预算
* 校验 + 失败上报自检记录
* v1.2 2026-09-21 王建锋 E-01(错误码传播审计 P0):超长文件守卫根治——poll 判定
* 前置到 cfg_store_stat 真实 st.size(旧检查位于截断后的
* len 上恒假=死码,超长文件被截断当有效解析 → 界外静默
* 回工厂默认 → 基线粘住 → 用户配置销毁链);save 输出上限
* 由 CFG_OUT_MAX 收紧至 CFG_FILE_MAX_LEN;s_write_doc 写盘
* 唯一闸口拒写超长文档;load/poll 超长处置补自检留痕
* 函数说明:引擎状态非重入——调用方须收敛同一任务(defaultTask:poll/save,见 P1-3;
* 启动期 load 先于任务竞争);FS 卷并发由 FatFS REENTRANT 卷锁保证
* (见《配置文件设计说明》D15 A+ 方案)
@@ -513,16 +519,24 @@ static int s_load_file(uint8_t **p_pbuf, size_t *p_len) {
}
/*
* 函数功能:经后端原子写配置文档
* 函数功能:经后端原子写配置文档(写盘唯一闸口:模板/外科手术同闸)
* 入口参数:p_buf - 文档数据 const uint8_t *
* len - 数据长度 size_t
* 返回值:0 - 成功;-1 - 写失败 int
* 限定条件:p_buf 非空
* 返回值:0 - 成功;-1 - 写失败或超长拒写 int
* 限定条件:p_buf 非空;len ≤ CFG_FILE_MAX_LEN
* 函数说明:E-01 纵深防御——超长文档一旦落盘,下次开机 load 守卫即判损坏、
* 整份用户配置连同注释/未知段销毁,故在唯一出口硬性拦截,使
* "引擎永不产出超长 config.ini"成为结构性质(调用者新增路径免疫)
*/
static int s_write_doc(const uint8_t *p_buf, size_t len) {
if (p_buf == NULL) {
return -1;
}
if (len > CFG_FILE_MAX_LEN) {
DBG_ERROR("doc: %luB > %uB limit, write rejected",
(unsigned long)len, (unsigned)CFG_FILE_MAX_LEN);
return -1;
}
return cfg_store_write_atomic(cfg_store_config_path(), p_buf, len);
}
@@ -982,13 +996,15 @@ int cfg_file_load(void) {
}
if (st.size > CFG_FILE_MAX_LEN) {
/* 文件超长 = 损坏:留工厂默认 + 重建模板 */
/* 文件超长 = 损坏:留工厂默认 + 重建模板(E-01:补自检留痕——销毁
* 用户文件属重大事件,仅串口 DBG_ERROR 在现场无调试器时不可追溯) */
if (s_write_template() != 0) {
DBG_ERROR("load: corrupt file, template write FAIL");
return -1;
}
DBG_ERROR("load: file %luB > %uB, corrupt, factory template regenerated",
(unsigned long)st.size, (unsigned)CFG_FILE_MAX_LEN);
syslog_post(SYSLOG_SELFTEST, "cfg load oversized");
return 0;
}
@@ -1081,12 +1097,17 @@ int cfg_file_save(void) {
return -1;
}
r = s_surgical((char *)p_in, in_len, p_out, CFG_OUT_MAX, &out_len);
/* E-01:输出上限由 CFG_OUT_MAX(5120) 收紧至 CFG_FILE_MAX_LEN(4096)——
* 旧上限允许把 >4096B 文档合法写盘(= 下一颗雷:下次开机 load 守卫
* 判损坏、整份用户文件连同注释/未知段一起销毁)。超限走下方既有
* 溢出中止路径:原文件不动 + 自检上报,RAM 副本保留(可修复后重试) */
r = s_surgical((char *)p_in, in_len, p_out, CFG_FILE_MAX_LEN, &out_len);
cfg_store_free(p_in);
if (r != 0) {
cfg_store_free(p_out);
DBG_ERROR("save: output overflow, aborted (file untouched)");
DBG_ERROR("save: output > %uB file limit, aborted (file untouched)",
(unsigned)CFG_FILE_MAX_LEN);
syslog_post(SYSLOG_SELFTEST, "cfg save overflow");
return -1;
}
@@ -1140,16 +1161,19 @@ void cfg_file_poll(void) {
/* 已沉降:重载配置文件 */
{
uint8_t *p_buf = NULL;
size_t len = 0u;
int i;
if (s_load_file(&p_buf, &len) != 0) {
return; /* 读失败:保留,下次 poll 重试 */
}
if (len > CFG_FILE_MAX_LEN) {
cfg_store_free(p_buf);
/* E-01(2026-09-21 审计 P0):超长守卫必须基于 cfg_store_stat 的真实
* st.size 且先于 s_load_file——s_load_file 读长钉死 CFG_FILE_MAX_LEN
* (nread ≤ 4096 恒成立),旧检查写在读后的 len 上恒假(死码):
* 超长文件被截断当有效解析 → 界外段/键静默回工厂默认 → 基线粘住 →
* 后续 save 按截断视图重写 → 界外内容永久丢失 → 下次开机 load 守卫
* 判损坏销毁整份用户文件。本分支对齐 load 契约(cfg_file.h 第 4 条):
* 超长 = 损坏 → 工厂默认 + 重建模板 + 自检留痕。
* 刻意不触发段回调:保留现有网络/在线通道(用户仍可经 FTP 修复);
* RAM=工厂 与硬件现状的分歧在下次保存/重启时收敛(重启必然收敛:
* load 读到的已是重建后的合法模板) */
if (st.size > CFG_FILE_MAX_LEN) {
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
if (s_sections[i].used != 0) {
(void)memcpy(s_sections[i].p_data, s_sections[i].p_defaults,
@@ -1157,10 +1181,17 @@ void cfg_file_poll(void) {
}
}
(void)s_write_template();
DBG_ERROR("poll: file corrupt (> %uB), factory template regenerated",
(unsigned)CFG_FILE_MAX_LEN);
DBG_ERROR("poll: file %luB > %uB, corrupt, factory template regenerated",
(unsigned long)st.size, (unsigned)CFG_FILE_MAX_LEN);
syslog_post(SYSLOG_SELFTEST, "cfg poll oversized");
}
else {
uint8_t *p_buf = NULL;
size_t len = 0u;
if (s_load_file(&p_buf, &len) != 0) {
return; /* 读失败:保留,下次 poll 重试 */
}
for (i = 0; i < (int)CFG_FILE_MAX_SECTIONS; i++) {
const cfg_sec_t *p_sec = &s_sections[i];