Skip to content

fix: support Noctalia lock lifecycle - #9

Draft
YangYuS8 wants to merge 1 commit into
mainfrom
fix/noctalia-lock-lifecycle
Draft

fix: support Noctalia lock lifecycle#9
YangYuS8 wants to merge 1 commit into
mainfrom
fix/noctalia-lock-lifecycle

Conversation

@YangYuS8

@YangYuS8 YangYuS8 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

问题与复现

Niri 的实际锁屏路径是 Noctalia v5:noctalia msg session lock 只是 IPC 请求,会在会话真正锁定或解锁前返回。旧实现把阻塞式 swaylock 命令的退出当作解锁;若直接替换为 Noctalia IPC 命令,将会在锁定期间过早撤防并伪造 DISARMED

另有两项从回归审查中发现的边界:

  • 既有配置可能绕过 Noctalia 依赖预检,直到布防时才失败;
  • 手动 vigilia-disarm 后,后台 vigilia-arm 可能在后续解锁时再次记录为 unlock-disarm。

Closes #8
Refs #2

实现

  • 引入显式锁后端:默认 noctalia;仅有既有 LOCK_COMMAND 的安装保留 command 兼容路径。
  • Noctalia 路径只在 msg status 已观察到 locked=true、随后观察到 locked=false 后记录 DISARMED reason=unlock backend=noctalia。IPC 请求、超时、状态不可用和请求失败统一记录 LOCK_FAILED,不会伪称解锁。
  • 恢复 ARMED 本地事件和通知;命令后端若在 acquisition delay 前退出则 fail closed。
  • vigilia-disarm 通过 PID 加 /proc/<pid>/stat start-time 双校验终止同一 arm 生命周期,防止 PID 重用误杀,也避免手动撤防后产生第二条 unlock-disarm。
  • 在任何安装写入前检查 awkloginctl 及按实际/默认后端需要的 Noctalia;既有单独 LOCK_COMMAND 配置仍无需 Noctalia。
  • 新增生命周期 fake-command 回归,接入 scripts/verify.sh;更新示例配置、README、架构、硬件测试说明和项目规则。

验证

本地(Arch/CachyOS,Noctalia v5.0.0,Motion 4.7.1):

./scripts/verify.sh
# bash syntax: ok (15 files)
# motion format probe: passed
# lock lifecycle: passed
# default network exposure: disabled
# verification: passed

systemd-analyze --user verify <staged user units>
# passed

ShellCheck 未安装,因此项目脚本按既有行为报告 skipped;没有把它伪装成通过。

受控真机 Noctalia 验收(无旁人摄像头画面):

ARMED → LOCK_ACQUIRED backend=noctalia logind_locked_hint=yes
      → DISARMED reason=unlock backend=noctalia logind_locked_hint=no

最终验收后:vigilia.target、motion/power/inhibit 均为 inactive;Noctalia locked=false/dev/video0 无持有进程;未发现 Motion control/stream listener。Motion journal 记录了 V4L2 打开、解锁后的 SIGTERM 和 /dev/video0 cleanup。操作者还确认锁定期间 LED 亮起、解锁后熄灭。

运行了 ./scripts/collect-diagnostics.sh artifacts/vigilia-diagnostics-noctalia-final.txt;报告为 0600,但含 32 个 home path 和 19 条 capture metadata,已本地审阅、未上传。未发现 Authorization/ntfy topic/token/Bearer 标记。

隐私与安全

  • 未启用麦克风、Motion Web control 或流端口。
  • 未提交用户配置、captures、事件 TSV、诊断报告或任何 ntfy 凭据。
  • 失败/取消状态显式记录 LOCK_FAILED,不将其伪造为解锁。

风险、回滚与限制

  • 变更只涉及锁生命周期、安装预检、测试和文档;不改变 Motion 或 AC 监控的服务边界。
  • 如需回滚,回滚本 PR 即恢复原有阻塞命令行为;但这会重新引入 Noctalia IPC 立即返回的过早撤防缺陷。
  • msg status 目前以无新增依赖的严格键/布尔值正则读取;Noctalia 若未来改变 IPC JSON 契约,需要重新验证。
  • 真机测试覆盖了正常 lock/unlock;故障注入、PID 重用和手动撤防由临时目录回归覆盖。不可重复执行真机以外的隐私敏感动作时,应保留上述本地回归证据。

@YangYuS8

YangYuS8 commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

Review correction — PR is not ready to merge

A later independent final review found two real blockers that the earlier PR description overstated as cleared:

  1. successful-but-unparseable Noctalia msg status output can currently be interpreted as unlock;
  2. vigilia-disarm can race between separate PID/start-time file writes, then arm monitoring after reporting manual disarm.

I am keeping this PR open and will repair both with regression coverage, rerun the complete verification, amend/push the final head, and update the hardware/issue evidence accurately. Do not merge the current head.

@YangYuS8

YangYuS8 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

阻断更新:IPC marker 所有权验证不足,PR 仍不可合入

最新独立只读终审发现,当前本地候选的 Noctalia IPC 进程组回收方案不能安全合入;请勿合并 PR #9 当前远端 head d25c265,也不要把此前的真机 lock/unlock 验收当作后续候选的最终证据。

问题

为回收 Noctalia IPC 后代,本地候选曾引入 ipc.*.pgid marker,vigilia-disarm 会扫描 marker 并对其进程组发送 TERM,最多一秒后发送 KILL。但 marker 只保存 PGID,撤防时只验证其为数字。

若 arm 进程异常终止(例如 SIGKILL)而 marker 残留,或使用持久的 state-runtime fallback,Linux 后续重用该 PGID 时,vigilia-disarm 可能终止同一用户的无关进程组。这是不可接受的所有权边界漏洞。

当前处理状态

  • 未为该实验性进程组回收改动创建提交或推送;远端 PR head 仍是 d25c265
  • 本地后续候选目前处于未完成状态,生命周期回归未通过;不会以此更新 PR。
  • 之前的独立审查发现与本地 fake 回归均表明:正常 Noctalia 状态转换、严格 JSON fail-closed、安装预检、command backend 退出码及 arm/disarm 初始化竞态需要继续保留覆盖;但它们不能抵消本次 IPC 所有权漏洞。
  • 此前受控真机 lock → LED 亮 → unlock → LED 灭验收仅证明当时已安装候选的正常路径,不能证明尚未完成的 IPC 回收设计安全。

后续修复要求

在恢复实现前,IPC cleanup 必须使用可验证的所有权记录:至少原子保存 session leader PID、/proc/<pid>/stat start-time 与 arm token;撤防前验证 marker 文件名 token、PID/start-time、以及 leader 仍是该新 session 的 PGID。任何校验失败只能删除 stale marker,绝不能发信号。

还需要增加回归:构造指向无关、存活独立进程组的 stale marker;运行 vigilia-disarm 后证明该进程及其组成员未收到 TERM/KILL,且 marker 被安全清理。

在该设计和完整回归被重新完成、独立复审放行、并获得新的真机验收前,PR #9 应继续视为 not ready to merge

隐私边界:未上传或提交真实用户配置、事件 TSV、抓拍、诊断或凭据。ShellCheck 仍未安装,不能作为已通过的检查。

Refs #8
Refs #2

@YangYuS8 YangYuS8 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

验收结论:PR #9 当前不可合并,已转为 Draft。

远端 head d25c265 本身已有两个确定阻断:

  1. noctalia_locked() 只区分“匹配 true”和“其他”。Noctalia 命令成功但返回无法解析的内容时,函数返回 1;锁定已获取后的循环会把这个 1 当作 locked=false,错误记录 DISARMED reason=unlock。必须严格区分 truefalse 和 unavailable/unparseable,后者一律 fail closed。
  2. arm PID 与 start-time 分两个文件写入,存在初始化竞态。vigilia-disarm 可能在两个写入之间删除状态并记录手动撤防,随后原 arm 继续写入并启动监控。

后来发现的 ipc.*.pgid 重用误杀问题存在于未推送实验方案,不在当前远端 diff 中;但该方案不应继续推进。这里没有必要维护、扫描并 TERM/KILL Noctalia IPC 进程组。

建议采用更小、更安全的设计:

  • 使用临时文件 + mv 原子发布一份 arm 生命周期记录,至少包含 token、PID、start-time;
  • vigilia-disarm 只停止 vigilia.target 并写入与当前 token 对应的取消标记,不向 arm 或 IPC 进程组发送 TERM/KILL;
  • vigilia-arm 在每次轮询、锁获取后和解锁返回前检查取消标记;已手动撤防时直接退出,不再记录 unlock-disarm;
  • command 后端手动撤防时可以保持锁屏命令运行,待用户正常解锁后退出,但不得重新启动监控或生成第二条 DISARMED;
  • 严格增加 malformed-success status、原子发布竞态、stale marker 指向无关进程、手动撤防后解锁等回归。

修复后请更新 PR 描述,重新运行完整回归和真实 Noctalia lock/unlock 验收,再标记 Ready for review。此前真机正常路径不能替代新实现的最终验收。

YangYuS8 commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Luna,请继续修复 PR #9,但保持 Draft。

要求:不要使用 PGID/进程组 TERM/KILL 回收方案;改为原子生命周期记录 + 手动撤防取消标记,由 vigilia-arm 自己检测并退出。Noctalia 状态必须严格区分 truefalse 和无法解析,无法解析时 fail closed。

补齐竞态与 stale marker 回归后,重新跑完整验证和真实 Noctalia 锁屏测试;确认无误再推送并标记 Ready for review。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix: support the Noctalia v5 lock lifecycle instead of assuming swaylock

1 participant