fix(chv-hybrid): p2 第三轮:attach 轮询等挂盘落地 + detach 走 sudo 窄规则 umount
ci / test (push) Failing after 47m51s

第 2 轮复测(0dc8328)p2 仍 FAIL 的两处根因:
1. 挂盘时序竞态:add-disk 返回 06:59:10,root hotplug 路径
   (cloud-init-hotplugd → cloud-final runcmd → chv-data-mount)
   06:59:14–16 才挂上;旧 attach 以 chv 调 chv-data-mount 并判
   rc==0,但 /dev/vd? 属 root:disk、chv 的 blkid/mkfs 必败,
   脚本又静默 exit 0,rc 检查是空洞的成功假象。
2. 卸载权限:挂载是 root 做的,chv 的 plain umount 恒 rc=32
   must be superuser,release 按现写法永远失败、VM 留置。

修法选 (a) 轮询,不选 (b) root 特权挂载:挂盘本身已有 root
路径在跑,host 侧不另开新的 root 挂载通道(新特权面只扩
攻击面);轮询以 chv 只读判定挂载态,零新增特权。
- attach:删 chv 调 chv-data-mount,改为轮询 mountpoint -q
  /home/chv/workspace && test -w(默认 60 秒/2 秒,可经
  CHV_DATA_MOUNT_TIMEOUT_SECONDS / CHV_DATA_MOUNT_INTERVAL_SECONDS
  注入短值);超时记 data_disk_mount_timeout 审计并抛同名
  kind,不写归属标记。attach 返回成功 = 挂盘真实发生。
  resume_seconds 因此真实包含挂盘等待(§2.1 口径),≤2 秒
  目标不改口径、不改目标。
- detach:改走 sudo -n /usr/bin/umount /home/chv/workspace;
  失败即 False,release 留置 VM 不回收(fail-closed)。
- chv-data-mount.sh:盘已找到后 mkfs/mount 失败改 exit 1
  (原全部失败路径 exit 0);无盘仍 exit 0(启动期 no-op,
  不挡 boot)。本脚本只由 root 跑(service/runcmd)。
- build.sh:apt 加 sudo;写 /etc/sudoers.d/chv-data-umount
  窄规则(chv 仅能以 root 跑 umount /home/chv/workspace
  这一条精确 argv,NOPASSWD,无 shell/其它命令/挂载权),
  0440 + visudo -cf 作构建门;buildinfo/lockfile 登记
  sudo 与 chv-data-umount。

特权边界:本轮唯一新增提权面就是上述一条 sudoers 规则;
attach 零提权(只读轮询),挂载权仍只在 root 的
hotplug/service 路径手里。旧镜像(无 sudo/无规则)上
detach 返回 False、release 留置,须重建镜像才恢复回收。

测试:新增 10 项(attach 等三次轮询才成功、超时 loud 失败、
永不以 chv 调 chv-data-mount、detach 精确 sudo argv、
sudo 失败 False、卸载失败 release 留置、客机脚本 exit 码、
build.sh sudo/窄规则/buildinfo 登记);另修 2 处旧断言
(attach 不再调挂载脚本)与 stage4 lockfile 夹具(补 sudo
dpkg 条目),并把 attach 热插段抽为 _hotplug_tenant_disk
(50 行函数上限)。全量 Ran 4688,OK (skipped=15,均为
环境 skip:无 PG DSN/驱动、缺 SearXNG_URL、fallback state
文件在位)。
This commit is contained in:
host committed 2026-11-09 08:17:01 +00:00
1 parent 0dc8328ecc
commit a859d731d5
8 files changed
+351 -37

No files matched your search

+12 -1
View File
@@ -277,7 +277,7 @@ run qemu-img convert -f qcow2 -O raw "$IMG" "$OUT/disk.raw"
# 写进 /app/guest-versions-chromium.txt)。
GT_INSTALL_CMD="curl -fsSL -o /tmp/gt-install.sh https://gitea.com/gitea/cli/raw/branch/main/scripts/install.sh && echo \"${GT_INSTALL_SH_SHA256} /tmp/gt-install.sh\" | sha256sum -c - && GITEA_CLI_VERSION=${GT_VERSION} GITEA_CLI_INSTALL_DIR=/usr/local/bin sh /tmp/gt-install.sh && rm -f /tmp/gt-install.sh && /usr/local/bin/gt version"
VC_ARGS=(-a "$OUT/disk.raw"
--run-command 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends git openssh-server openssh-client cloud-init python3 python3-pip curl ca-certificates xz-utils fonts-wqy-microhei fontconfig systemd-container iproute2 iptables && apt-get clean'
--run-command 'apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends git openssh-server openssh-client cloud-init python3 python3-pip curl ca-certificates xz-utils fonts-wqy-microhei fontconfig systemd-container iproute2 iptables sudo && apt-get clean'
--copy-in "$CODEX_BIN_DIR/codex:/usr/local/bin/"
--copy-in "$CODEX_BIN_DIR/codex-code-mode-host:/usr/local/bin/"
--copy-in "$CODEX_BIN_DIR/codex-resources:/usr/local/bin/"
@@ -298,6 +298,17 @@ VC_ARGS=(-a "$OUT/disk.raw"
--run-command 'mv /usr/local/bin/chv-sync.sh /usr/local/bin/chv-sync && chmod +x /usr/local/bin/chv-sync'
--copy-in "$SCRIPT_DIR/chv-data-mount.sh:/usr/local/bin/"
--run-command 'mv /usr/local/bin/chv-data-mount.sh /usr/local/bin/chv-data-mount && chmod +x /usr/local/bin/chv-data-mount'
# p2 第三轮(2026-11-09 第 2 轮复测根因 2):挂载是 root
# 做的,chv 身份 plain umount 恒 rc=32。唯一提权入口是这条
# sudoers 窄规则:chv 仅能以 root 跑 /usr/bin/umount
# /home/chv/workspace 这一条精确 argv(NOPASSWD,无密码;
# 无 shell、无其它命令、无挂载权——挂载仍只走 root 的
# hotplug/service 路径,host 侧 attach 轮询等它落地,不
# 另开 root 挂载通道)。0440 + visudo -cf 是构建门:
# 规则写错/权限不对即构建失败,不把坏 sudoers 带进镜像
# (坏 sudoers 会让整台客机 sudo 报错)。executor 的
# detach 以 `sudo -n` 调它(-n 绝不交互问密码)。
--run-command 'printf "chv ALL=(root) NOPASSWD: /usr/bin/umount /home/chv/workspace\n" > /etc/sudoers.d/chv-data-umount && chown root:root /etc/sudoers.d/chv-data-umount && chmod 0440 /etc/sudoers.d/chv-data-umount && visudo -cf /etc/sudoers.d/chv-data-umount'
--copy-in "$SCRIPT_DIR/chv-cell.py:/usr/local/bin/"
--copy-in "$SCRIPT_DIR/chv-broker.py:/usr/local/bin/"
--copy-in "$SCRIPT_DIR/chv-browser.py:/usr/local/bin/"
+4
View File
@@ -37,6 +37,10 @@ BASE_CONTENTS = [
"xz-utils", "fonts-wqy-microhei", "fontconfig", "codex",
"codex-code-mode-host", "codex-resources/bwrap",
"/app/host.py", "gt", "chv-sync",
# p2 第三轮:sudo(dpkg 包,版本走 dpkg freeze)+
# chv-data-umount sudoers 窄规则(版本随 repo_commit,
# 见 check/generate_lockfile.py 的 DECLARED_SOURCES)。
"sudo", "chv-data-umount",
# 对齐 Muse P2:nspawn 单元启动器 + broker + 浏览器子代理。
"systemd-container", "chv-cell", "chv-broker", "chv-browser",
# feat-chv-opt-landing(阶段 1):数据盘挂载脚本。
+3
View File
@@ -33,6 +33,9 @@ DECLARED_SOURCES = {
# feat-chv-opt-landing(阶段 1):数据盘挂载脚本,版本随
# repo_commit(与 chv-sync 同款)。
"chv-data-mount": "repo_commit",
# p2 第三轮:卸载 sudoers 窄规则(build.sh 内联写入,
# 内容随仓库走,版本随 repo_commit)。
"chv-data-umount": "repo_commit",
# feat-chv-opt-landing(阶段 2):客机运行时三分服务,版本
# 随 repo_commit(与 chv-sync 同款)。
"chv-daemon": "repo_commit",
+13 -2
View File
@@ -36,13 +36,24 @@ if [ -z "$DEV" ]; then
done
fi
# p2 第三轮退出码契约(2026-11-09 第 2 轮复测):无数据盘
# 即静默退出 0(启动期 no-op:干净池 VM 与 copy 模式旧 VM
# 本来就没有第三块盘,boot 服务绝不能因此失败);但盘已
# 找到后 mkfs/mount 任一步失败即 exit 1 并记日志——旧版
# 全部失败路径 exit 0 是“成功假象”的另一半:host 侧曾以
# chv 调本脚本并判 rc==0,chv 无权读 /dev/vd?、mkfs 必败
# 却被报成成功。host 侧现已改走轮询等 root hotplug 路径
# 落地(见 executor.py attach),不再以 chv 调本脚本;
# 本脚本只由 root 跑(chv-data-mount.service 与
# cloud-final runcmd),失败 loud 不挡启动(runcmd 行带
# || true,service 失败可见于 systemctl status)。
[ -n "$DEV" ] || { log "no data disk found, skip"; exit 0; }
if [ "$(blkid -o value -s LABEL "$DEV" 2>/dev/null || echo "")" != "$LABEL" ]; then
log "formatting $DEV as $LABEL"
mkfs.ext4 -L "$LABEL" "$DEV" || { log "mkfs failed on $DEV"; exit 0; }
mkfs.ext4 -L "$LABEL" "$DEV" || { log "mkfs failed on $DEV"; exit 1; }
fi
mkdir -p "$MOUNT"
mount "LABEL=$LABEL" "$MOUNT" || { log "mount failed"; exit 0; }
mount "LABEL=$LABEL" "$MOUNT" || { log "mount failed"; exit 1; }
chown chv:chv "$MOUNT" || true
# feat-chv-opt-landing(阶段 3):浏览器持久 profile 落数据盘。
# chv-browser 以 chv-browser 身份跑,工作区根属 chv、它写不进;
+3
View File
@@ -54,6 +54,9 @@ DECLARED_SOURCES = {
# feat-chv-opt-landing(阶段 1):数据盘挂载脚本,版本随
# repo_commit(与 chv-sync 同款)。
"chv-data-mount": "repo_commit",
# p2 第三轮:卸载 sudoers 窄规则(build.sh 内联写入,
# 内容随仓库走,版本随 repo_commit)。
"chv-data-umount": "repo_commit",
# feat-chv-opt-landing(阶段 2):客机运行时三分服务,版本
# 随 repo_commit(与 chv-sync 同款)。
"chv-daemon": "repo_commit",