From 8303fa33598f4aa6572ab00ccfef760368449c0a Mon Sep 17 00:00:00 2001 From: liuzhuohua <29739020+liuzhuohua@users.noreply.github.com> Date: Sat, 10 Oct 2026 16:29:19 +0800 Subject: [PATCH] =?UTF-8?q?fix(update):=20=E6=9B=B4=E6=96=B0=E5=89=8D?= =?UTF-8?q?=E8=87=AA=E5=8A=A8=E5=A4=87=E4=BB=BD=E4=BB=A5=20drouter=20?= =?UTF-8?q?=E8=BA=AB=E4=BB=BD=E8=B7=91=E6=97=B6=E5=86=99=E4=B8=8D=E8=BF=9B?= =?UTF-8?q?=E5=A4=87=E4=BB=BD=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 真机报错:更新功能「备份失败,已中止更新」—— [Errno 13] Permission denied: /opt/drouter/backups/xxx.part。 根因:/opt/drouter/backups 是 root:root drwxr-xr-x,而「更新前备份」这条链 由 drouter-web(drouter 用户)spawn drouter-backupd.py --run-now,backupd 的 call() 用 subprocess 直接跑 helper、没提权 → helper 继承 drouter 身份 → 写备份包 .part 被拒。定时器那条链是 root 跑的,所以平时备份正常, 只有「更新触发的备份」踩雷。 修法:backupd 的 call() 里 helper 调用加 sudo -n 前缀。sudoers 已放行 drouter 免密跑 helper 白名单,root(定时器)/ drouter(更新)两种身份都通。 判据:t-update.py 新增「backupd call() 用 sudo -n 提权」(查命令数组语义), t-update-inject.py 加对应反向验证注入(34 例 0 异常)。 真机验收:sudo -u drouter backupd --run-now → BK_CREATED files=21 size=23767。 --- _dev/t-update-inject.py | 8 ++++++++ _dev/t-update.py | 9 +++++++++ backend/drouter-backupd.py | 14 +++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/_dev/t-update-inject.py b/_dev/t-update-inject.py index 8c2d623..bcf83e4 100644 --- a/_dev/t-update-inject.py +++ b/_dev/t-update-inject.py @@ -98,6 +98,14 @@ CASES = [ " import drouter_update as U", 'web.py 用 importlib'), + # backupd call() 去掉 sudo 前缀:drouter 身份(更新前备份)写不进 + # /opt/drouter/backups → [Errno 13] Permission denied(2026-10-10 真机修)。 + ('backupd call() 去掉 sudo(drouter 身份写不进备份目录)', + 'backend/drouter-backupd.py', + " ['sudo', '-n', '/usr/bin/python3', HELPER, action,", + " ['/usr/bin/python3', HELPER, action,", + 'backupd call() 用 sudo -n 提权'), + ('/api/update 路由被摘掉', 'backend/drouter-web.py', " if p.startswith('/api/update'):", diff --git a/_dev/t-update.py b/_dev/t-update.py index 34c1060..67b563f 100644 --- a/_dev/t-update.py +++ b/_dev/t-update.py @@ -792,6 +792,15 @@ ck('_do_backup 校验 BK_CREATED(不只看 returncode)', '(returncode==0 在「自动备份未开启」时也会出现,' '只看它会把「什么都没备份」当成「备份成功」)') ck('_do_backup 用 --run-now 调用', "'--run-now'" in uc) +# ⚠️ backupd 的 call() 必须用 sudo -n 提权(2026-10-10 真机修的 bug): +# 「更新前备份」由 drouter-web(drouter 用户)spawn backupd,这条链不带 sudo +# 时 helper 继承 drouter 身份,写 /opt/drouter/backups(root:root)直接 +# [Errno 13] Permission denied,更新被卡死在备份这步。sudoers 已放行 +# drouter 免密跑 helper 白名单,所以 root/drouter 两种身份都通。 +# 判据查**语义**(subprocess 命令数组里真有 sudo 前缀),不能只查「出现 sudo 这个词」。 +_bkd = read(P('backend', 'drouter-backupd.py')) +ck('backupd call() 用 sudo -n 提权(否则 drouter 身份写不进备份目录)', + re.search(r"\['sudo',\s*'-n',\s*'/usr/bin/python3',\s*HELPER", _bkd) is not None) # ④ P0-4:apply 必须有白名单且在写库前 ck('apply_module 有模块白名单', 'APPLY_MODULES' in w) diff --git a/backend/drouter-backupd.py b/backend/drouter-backupd.py index b0c00fb..3018cac 100644 --- a/backend/drouter-backupd.py +++ b/backend/drouter-backupd.py @@ -74,9 +74,21 @@ def emit(line): def call(action, payload=None, timeout=CALL_TIMEOUT): + """调用 helper 的备份能力。 + + ⚠️ 这里用 sudo -n 提权(2026-10-10 修): + helper 写备份包要进 /opt/drouter/backups(root:root drwxr-xr-x), + 而 backupd 有两种运行身份 —— + · 定时器(systemd timer)→ root,本来就能写; + · 「更新前自动备份」→ 由 drouter-web(drouter 用户)spawn, + 这条链不带 sudo 时 helper 也继承 drouter 身份,写 .part 直接 + [Errno 13] Permission denied,更新被卡在备份这步。 + sudoers 已放行 drouter ALL=(root) NOPASSWD: python3 .../drouter-helper.py, + 所以 root 跑 sudo -n 免密、drouter 跑也走白名单提权,两种身份都通。 + """ try: p = subprocess.run( - ['/usr/bin/python3', HELPER, action, + ['sudo', '-n', '/usr/bin/python3', HELPER, action, json.dumps(payload or {}, ensure_ascii=False)], capture_output=True, text=True, timeout=timeout, errors='replace') return json.loads(p.stdout or '{}')