From eb5c4aa1b40ec452e2335f0f2616ed3891b6969d Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 25 Sep 2026 06:33:23 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=20env-defaults.sh=20=E8=87=AA?= =?UTF-8?q?=E5=B7=B1=E7=9A=84=E4=B8=80=E4=B8=AA=E5=81=87=E7=BB=BF:=20HOME?= =?UTF-8?q?=20=E5=8F=AF=E7=94=A8=E6=80=A7=E5=88=A4=E6=8D=AE=E4=BB=8E=20`[?= =?UTF-8?q?=20-w=20]`=EF=BC=88=E6=9D=83=E9=99=90=E4=BD=8D=EF=BC=89?= =?UTF-8?q?=E6=94=B9=E6=88=90**=E7=9C=9F=E5=86=99=E6=8E=A2=E9=92=88**=20?= =?UTF-8?q?=E2=80=94=E2=80=94=20=E4=B8=8E=E6=88=91=E8=87=AA=E5=B7=B1?= =?UTF-8?q?=E4=B8=8A=E5=B0=81=E9=82=A3=E4=B8=AA=E9=94=99=E5=90=8C=E4=B8=80?= =?UTF-8?q?=E5=BD=A2=E7=8A=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ★ 缺陷(我在认自己 `[ -w ]` 假绿时顺带查出来的,就在我的 scope 里): 196 行 `mkdir -p "$_am_home" && [ -w "$_am_home" ]` 179 行 `... || [ ! -w "$_am_home" ]` 两处都只看**权限位**。uid=0 时对任何 root 属主目录都返真,而**实际拦截可能在另一层** (本会话每次 shell 都带 `landlock-run: partial enforcement`)。本机实测: [ -w /root ] ⇒ true / touch /root/x ⇒ Permission denied [ -w /opt/agentmail ] ⇒ true / touch /opt/agentmail/x ⇒ Permission denied ⇒ 而且 `mkdir -p` 对**已存在**的目录不写任何东西(rc=0),所以它**接不住**这个假绿 —— 文件 177 行原本就写了"mkdir -p 会返回成功,所以必须再单独判",但"那个单独判"选错了层。 ★ 后果正撞在本文件存在的理由上(**误指对象**): HOME 空 + uid=0 ⇒ `_am_home=/root` 被误判可用 ⇒ HOME=/root 放行 ⇒ ② 段取 `$HOME/.cache/go-build` 当 GOCACHE ⇒ 那里用的是**真写探针**(297 行)⇒ 失败 ⇒ 报出「**GOCACHE** 不可写」,而根因是 **HOME** 不可写 ⇒ 实测该路径可复现(`env -u HOME bash -c 'source deploy/lib/env-defaults.sh'`) ★ 修法: 加 `_am_can_write()` —— **真去写一个探针文件**(与 297 行同一办法), 179/196 两处都用它。**必须两处一起改**: 196 的 else 是 `exit 2`(不是 fallback), 只改 196 会让 /root 走到 `exit 2` 而不是改判。 ★ 顺带把 WARN 的括注从"(非 root / passwd 里没有可用目录)"改成 "(已实测真写探针;uid=%s)" —— 原括注是**按 uid 猜的原因**,而判据已不按 uid 判, uid=0 也会走到这里。**不报没验过的原因**。 ⚠️ 前半句 `按身份推出来的 HOME=%s 不可用` **一个字都没动** —— 它是 `plugins/pi-mail-bridge/test/env-guard.test.mjs:268` 的 assert.match 锚 (我第一版把它改成了"写不进去",**实测把 pi 的测试弄红了 16/17**,已改回)。 ★ 变异测试: 把 `_am_can_write` 改成恒真 ⇒ **被抓住**,且复现的正是上面那条误指 (HOME 空时不再改判 ⇒ 直接报「GOCACHE 不可写」)✓ ★ 反空真: 探针对 /tmp、/home/program/agentmail 返真,对 /root、/opt/agentmail、 /nonexistent、空串返假 ⇒ 有区分力(非恒真非恒红)✓ ★ 验证: pi 的 env-guard 17/17 绿、criteria-hygiene 7/7 绿、三处 source 者 bash -n 全过、 install.sh/redeploy-*.sh 未改。**生产未动**。 --- deploy/lib/env-defaults.sh | 42 +++++++++++++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 5 deletions(-) diff --git a/deploy/lib/env-defaults.sh b/deploy/lib/env-defaults.sh index ecf5b69..eaa6b25 100644 --- a/deploy/lib/env-defaults.sh +++ b/deploy/lib/env-defaults.sh @@ -160,6 +160,28 @@ unset _am_self_missing _am_c # 实测:`setpriv --reuid=65534 env -i HOME=/root` ⇒ `touch $HOME/probe` 被拒。 # 以 **root** 跑时 `/root` 写得进去,所以这条只在非 root 下触发 —— # 判据落在"**能不能写**",不落在"路径长得像不像"。 +# +# ★★ 但"验证可写"**不能用 `[ -w ]`**(dsh 实测 2026-09-25,修本文件自己的一个假绿): +# `[ -w ]` 查的是**权限位**。以 uid=0 跑时它对任何 root 属主的目录都返回**真**, +# 而**实际拦截可能在另一层**。本机实测(同一台、同一 shell): +# [ -w /root ] ⇒ true +# touch /root/x ⇒ Permission denied ← 能写才算数 +# [ -w /opt/agentmail ] ⇒ true +# touch /opt/agentmail/x ⇒ Permission denied +# (本会话每次 shell 都带着 `landlock-run: partial enforcement` —— 拦截在沙箱层, +# 权限位那层**看不出来**。) +# ⇒ 后果正是本文件要防的那件事,而且更绕:`_am_home=/root` 被**误判为可用** ⇒ +# `HOME=/root` 放行 ⇒ ② 段拿 `$HOME/.cache/go-build` 当 GOCACHE ⇒ 那里用的是 +# **真写探针**(297 行)⇒ 探针失败 ⇒ 报出**「GOCACHE 不可写」**。 +# ⇒ 根因是 HOME,报出来的是 GOCACHE —— **误指了对象**,与本文件存在的理由正好相反。 +# ⇒ 所以这里也改用**真写探针**(与 297 行同一个办法,不再一处真写、一处只看权限位)。 +_am_can_write() { + [ -n "${1:-}" ] || return 1 + [ -d "$1" ] || return 1 + { : > "$1/.agentmail-wprobe-$$"; } 2>/dev/null || return 1 + rm -f "$1/.agentmail-wprobe-$$" + return 0 +} _am_uid="$(id -u 2>/dev/null || echo 0)" _am_home_given=0 if [ -z "${HOME:-}" ]; then @@ -174,9 +196,10 @@ else _am_home="$HOME" fi # 目录必须落在**调用者写得进去**的地方,否则就是把问题往后推。 -# 注意:`mkdir -p` 对已存在的不可写目录会**返回成功**,所以必须再单独判 `-w`。 +# 注意:`mkdir -p` 对已存在的不可写目录会**返回成功**,所以必须再单独判可写 —— +# 而那个"单独判"**必须真去写**(见上面 ★★:`[ -w ]` 在沙箱/ACL 那一层会假绿)。 _am_fallback="${TMPDIR:-/tmp}/agentmail-home-$_am_uid" -if [ -z "$_am_home" ] || [ ! -d "$_am_home" ] || [ ! -w "$_am_home" ]; then +if [ -z "$_am_home" ] || [ ! -d "$_am_home" ] || ! _am_can_write "$_am_home"; then if [ "$_am_home_given" = "1" ] && [ -n "$_am_home" ]; then # 调用者显式给了 HOME 却不可写:**说清楚**再改判,不静默换目录 # (静默改 HOME 会让"东西写到哪去了"变成一个谜)。 @@ -188,12 +211,21 @@ if [ -z "$_am_home" ] || [ ! -d "$_am_home" ] || [ ! -w "$_am_home" ]; then # 或 passwd 里的 home 不存在,例如 nobody 的 /nonexistent)。 # 原先只报"调用者给的不可写",于是这条**悄悄换了 HOME**: # 东西写到哪去了变成一个谜 —— 与本文件存在的理由(把环境问题说清楚)正好相反。 - printf ' [WARN] 按身份推出来的 HOME=%s 不可用(非 root / passwd 里没有可用目录);改判到 %s\n' \ - "${_am_home:-(空)}" "$_am_fallback" >&2 + # + # ★ 括注原写"(非 root / passwd 里没有可用目录)"—— 那是**按 uid 猜的原因**, + # 而判据已改成**真写探针**(见上 ★★)⇒ 现在 uid=0 也可能走到这里 + # (能写与否取决于沙箱/ACL,不取决于身份)。**不要再报一个没验过的原因**: + # 只报"写不进去"这个已测事实,并附上身份供人判断。 + # + # ⚠️ 文案前半句 `按身份推出来的 HOME=%s 不可用` **是 pi 的判据匹配的锚** + # (`plugins/pi-mail-bridge/test/env-guard.test.mjs:268` 用 assert.match 锁它, + # 理由是"兜底路径改了 HOME 就必须留痕")。⇒ **锚不许动**,只把括注改准。 + printf ' [WARN] 按身份推出来的 HOME=%s 不可用(已实测真写探针;uid=%s);改判到 %s\n' \ + "${_am_home:-(空)}" "$_am_uid" "$_am_fallback" >&2 fi _am_home="$_am_fallback" fi -if mkdir -p "$_am_home" 2>/dev/null && [ -w "$_am_home" ]; then +if mkdir -p "$_am_home" 2>/dev/null && _am_can_write "$_am_home"; then if [ "$_am_home" != "${HOME:-}" ]; then HOME="$_am_home" export HOME