【Bug】Qoder CN 桌面版(Windows)登录状态文件 ACL 自愈逻辑永不收敛,导致每秒数次文件重写 + icacls/PowerShell 进程风暴,持续占用 CPU
摘要
Qoder CN Windows 桌面端的主进程服务 AuthAppStatusMainService 会把登录态原子写入私有状态文件 .qoder-app-status.json,并在写入后做"内容 + ACL 权限"对账(reconcile)。在中文/工作组环境的 Windows 11 上,新建文件会被系统自动附加 NT AUTHORITY\SYSTEM、BUILTIN\Administrators(或登录会话 SID S-1-5-5-*)等显式 ACE,而应用设置 ACL 用的 icacls /inheritance:r /grant:r <user>:(F) 无法删除这些显式 ACE,导致应用自身的"ACL 是否私有"判定(isAclPrivate,要求所有 ACE 必须且只能是当前用户一条)恒为 false。于是对账永远判定不安全 → 重新原子写 → 文件 watcher 又触发对账 → 死循环、永不收敛。
该循环每秒在磁盘上产生 3~5 次"建目录/写文件/改名/删除",并每秒 spawn 约 1 个 icacls.exe + 4~5 个 powershell.exe 子进程,造成:
-
早期表现为 Windows Defender(Antimalware Service Executable / MsMpEng.exe)CPU 持续高占用(实时扫描大量新建文件);
-
将
.qoder-cn加入 Defender 排除项后,Defender 占用降到 ~0,但 Qoder CN 主进程自身仍持续占用约 50%~60% 单核 CPU(实测 PID 采样 6 秒 CPU=3.47s),且 PowerShell 的 AMSI 扫描开销不受路径排除影响。 -
完全退出 Qoder CN 桌面端后,一切恢复正常。
环境
-
产品:Qoder CN 桌面版(productId
qoder-cn,Electron),package version0.2.1 -
操作系统:Windows 11 中文版(工作组环境,用户
YJM\huaca) -
安装路径:
C:\Program Files\Qoder\Qoder CN\(更新后;旧版本在D:\Program Files\Qoder CN\,两版均复现) -
配置/状态目录:
C:\Users\huaca\.qoder-cn\ -
备注:该问题与"国内版 CN / 国际版"无关——两版本共用同一套 Windows ACL 实现,仅状态目录名(
.qoder-cn/.qoder)和域名不同。CLI 版不运行该 Electron 主进程服务,不复现。
复现
-
在 Windows 11(中文版/工作组或域账户均可)上安装并登录 Qoder CN 桌面版,保持应用运行。
-
观察
C:\Users\<user>\.qoder-cn\目录:持续出现并删除形如
..qoder-app-status.json.<uuid>.private\的临时目录,内含.qoder-app-status.json(约 283 字节)。
实测 8 秒内出现 26 个不同临时目录(约 3~4 个/秒)。 -
进程监控:6 秒采样窗口内新增
icacls.exe7 个、用于 ACL 检查的powershell.exe27 个。 -
CPU:Qoder CN 主进程持续约 50%~60% 单核;未排除目录时 MsMpEng.exe 同样高占用。退出应用后立即恢复。
根因(基于 app.asar 反编译定位)
主进程 AuthAppStatusMainService(依赖 NativePrivateFileMainService)逻辑:
-
定时(默认 5s)+ 文件 watcher 触发
reconcileNow(); -
inspectFile()读取目标文件,校验kind==="regular"且permissions!=="insecure"且contents===desired.contents,任一不满足即requestWrite()重新原子写。
Windows 实现(类 $4e / 平台工厂 e8e):
-
原子写:创建临时目录
.qoder-app-status.json.<uuid>.private\→ 在其中写.qoder-app-status.json→applyAcl()调icacls <path> /inheritance:r /grant:r <DOMAIN>\<user>:(F)→ rename 为正式文件 → 删除临时目录。 -
ACL 校验
isAclPrivate():用 PowerShellGet-Acl读取 ACE,判定条件为
所有 ACE 必须逐条满足identity === <DOMAIN>\<user>(忽略大小写)且AccessControlType==="Allow"且IsInherited===false,即"只允许当前用户一条 ACE"。
问题:在 Windows 上,新建文件时系统会自动附加显式(非继承)ACE。实测:
对目标文件 C:\Users\huaca\.qoder-cn\.qoder-app-status.json:
NT AUTHORITY\SYSTEM Allow IsInherited=False FullControl
S-1-5-5-0-211315 Allow IsInherited=False ReadAndExecute,Synchronize (登录会话 SID)
yjm\huaca Allow IsInherited=False FullControl
对照实验(在仅授权当前用户的目录内新建文件):
新建文件后 ACL:
NT AUTHORITY\SYSTEM Allow inh=False
BUILTIN\Administrators Allow inh=False
yjm\huaca Allow inh=False
执行应用所用命令 icacls <file> /inheritance:r /grant:r "YJM\huaca:(F)" 之后:
NT AUTHORITY\SYSTEM Allow inh=False ← 仍在
BUILTIN\Administrators Allow inh=False ← 仍在
yjm\huaca Allow inh=False
即:
-
/inheritance:r只移除继承 ACE; -
/grant:r只替换同账户的授权; -
二者都不会删除 SYSTEM / Administrators / 登录会话 SID 这些显式 ACE。
因此 isAclPrivate() 恒返回 false(permissions:"insecure")→ reconcile 恒触发重写。文件内容第一次就写对了(临时文件与正式文件逐字节一致,283 字节,snapshot_at 停在启动时刻),纯粹卡在 ACL 判定,且 5s 内任何手动 ACL 修正都会被下一次重写覆盖。
建议修复方向
-
放宽
isAclPrivate()判定:不应要求"仅当前用户一条 ACE"。NT AUTHORITY\SYSTEM、BUILTIN\Administrators、当前登录会话 SID(S-1-5-5-*)是 Windows 新建文件的标准/系统 ACE,应视为安全;只要不存在Everyone/BUILTIN\Users/Authenticated Users/Anonymous等宽泛主体的 Allow,即可判定为私有。 -
或在设置 ACL 时显式移除系统 ACE(
icacls /remove:g "NT AUTHORITY\SYSTEM" ...),但移除 SYSTEM 可能有副作用,不推荐。 -
或在 Windows 上将此类"系统自带 ACE 导致的不匹配"归类为可接受/已降级(
unverified)而非insecure,避免无限重写——核心是不要因为无法满足的 ACL 条件而进入写风暴。
影响面
所有 Windows(尤其中文版/工作组或域账户)桌面端用户都会命中,表现为:持续磁盘 IO、每秒数个短命进程(icacls/powershell)、CPU 长期偏高、笔记本风扇与耗电明显;用户难以自行定位。
附:关键实测数据
-
8 秒内 26 个临时目录(~3~4 个/秒);
-
6 秒内 spawn icacls.exe ×7、ACL 检查 powershell.exe ×27;
-
Qoder CN 主进程 6 秒 CPU=3.47s(≈58% 单核);
-
目标文件与临时文件内容逐字节一致(283 字节),证明仅 ACL 判定失败。