Fix tun on: recognize setcap capability and block sudo on user install (#267)

Problem 1 - can_manage_tun_safely() missed setcap grants:
- Add kernel_binary_has_cap_net_admin(): use getcap to detect file
  capability cap_net_admin on the kernel binary, so a user who ran
  'setcap cap_net_admin,cap_net_raw+ep mihomo' is allowed through.
- Extend can_manage_tun_safely() to call this check after the existing
  capsh (current-shell) check.
- In cmd_tun_on(), add a process-level fallback via the existing
  tun_process_has_cap_net_admin() for cases where getcap is unavailable
  but the running process already holds the capability.

Problem 2 - sudo clashctl corrupts runtime file ownership:
- Add guard_sudo_on_user_install(): detects root + SUDO_USER +
  stored install scope == user, refuses with a clear message and
  instructs the user to run as the install user directly.
- Call the guard at the entry of both cmd_tun_on() and cmd_tun_off()
  so neither write path can corrupt runtime/ file ownership.
This commit is contained in:
Arvin
2026-06-16 16:40:19 +08:00
parent 5f30fcdfef
commit 2511b1285b
2 changed files with 54 additions and 6 deletions
+19 -6
View File
@@ -4916,6 +4916,8 @@ cmd_tun_on() {
local verify_result
local container_mode risk_reason
guard_sudo_on_user_install "on" || return 1
prepare
container_mode="$(tun_container_mode 2>/dev/null || echo unknown)"
@@ -4944,12 +4946,21 @@ cmd_tun_on() {
esac
if ! can_manage_tun_safely; then
echo
echo "❗ Tun 模式无法开启"
echo "🚨 原因:当前环境不满足基础 Tun 条件"
echo "👉 下一步:clashctl tun doctor"
echo
return 1
# Fallback: check if the running mihomo process already has CAP_NET_ADMIN
# (covers the case where setcap was applied after the binary check fails
# due to getcap being unavailable, or the process received the capability
# through another mechanism).
local _fb_backend
_fb_backend="$(runtime_backend 2>/dev/null || echo unknown)"
if ! tun_process_has_cap_net_admin "$_fb_backend" 2>/dev/null; then
echo
echo "❗ Tun 模式无法开启"
echo "🚨 原因:当前环境不满足基础 Tun 条件"
echo "💡 若已通过 setcap 授权 mihomo,请确认 getcap 已安装并重试"
echo "👉 下一步:clashctl tun doctor"
echo
return 1
fi
fi
case "$(tun_kernel_support_level 2>/dev/null || echo unknown)" in
@@ -4996,6 +5007,8 @@ cmd_tun_on() {
cmd_tun_off() {
local verify_result
guard_sudo_on_user_install "off" || return 1
prepare
if ! sync_tun_target_state "off" "false"; then
+35
View File
@@ -2030,6 +2030,14 @@ has_ip_command() {
command -v ip >/dev/null 2>&1
}
kernel_binary_has_cap_net_admin() {
local _bin
_bin="$(runtime_kernel_bin 2>/dev/null || true)"
[ -n "${_bin:-}" ] && [ -x "${_bin}" ] || return 1
command -v getcap >/dev/null 2>&1 || return 1
getcap "$_bin" 2>/dev/null | grep -q 'cap_net_admin'
}
can_manage_tun_safely() {
if ! tun_device_exists; then
return 1
@@ -2039,10 +2047,37 @@ can_manage_tun_safely() {
return 0
fi
# Current shell has CAP_NET_ADMIN
if has_cap_net_admin; then
return 0
fi
# Kernel binary has file capability cap_net_admin (setcap)
if kernel_binary_has_cap_net_admin; then
return 0
fi
return 1
}
# Guard: refuse to run a tun action as root via sudo when the installation
# was done in user scope. Writing runtime files as root corrupts their
# ownership and breaks subsequent systemctl --user operations.
guard_sudo_on_user_install() {
local _action="${1:-on}"
is_root_user || return 0
[ -n "${SUDO_USER:-}" ] || return 0
local _stored_scope
_stored_scope="$(install_env_scope 2>/dev/null || true)"
[ "${_stored_scope:-}" = "user" ] || return 0
echo
echo "❗ 操作被拒绝:user 安装模式不支持以 sudo 运行"
echo "🚨 原因:sudo 会将 runtime 文件写成 root:root,导致 systemctl --user 无法访问"
echo "👤 请以安装用户(${SUDO_USER})直接执行:"
echo " clashctl tun ${_action}"
echo
return 1
}