为什么 CAP_NET_ADMIN 没有足够的 ioctl(TUNSETIFF) 权限?

为什么 CAP_NET_ADMIN 没有足够的 ioctl(TUNSETIFF) 权限?

我正在尝试用 Rust 编写一个 tun/tap 程序。由于我不希望它以 root 身份运行,因此我已将 CAP_NET_ADMIN 添加到二进制文件的功能中:

$sudo setcap cap_net_admin=eip target/release/tunnel
$getcap target/release/tunnel
target/release/tunnel = cap_net_admin+eip

然而,这是行不通的。我读到的所有内容都表明这是创建 tun 所需的唯一功能,但程序在 ioctl 上获得了 EPERM。在 strace 中,我看到这个错误:

openat(AT_FDCWD, "/dev/net/tun", O_RDWR|O_CLOEXEC) = 3
fcntl(3, F_GETFD)                       = 0x1 (flags FD_CLOEXEC)
ioctl(3, TUNSETIFF, 0x7ffcdac7c7c0)     = -1 EPERM (Operation not permitted)

我已经验证二进制文件可以在完全 root 权限下成功运行,但我不希望这需要 sudo 才能运行。为什么 CAP_NET_ADMIN 在这里不够用?

作为参考,我Linux version 4.15.0-45发现这个 ioctl 只有几种方法可以在内核中返回 EPERM(https://elixir.bootlin.com/linux/v4.15/source/drivers/net/tun.c#L2194)并且至少其中之一似乎感到满意。我不知道如何探究其他人:

if (!capable(CAP_NET_ADMIN))
    return -EPERM;
...
if (tun_not_capable(tun))
    return -EPERM;
...
if (!ns_capable(net->user_ns, CAP_NET_ADMIN))
    return -EPERM;

答案1

我猜想您的target/release/tunnel二进制文件所在的文件系统是使用该nosuid选项安装的。这也会影响文件功能,而不仅仅是 setuid 位。

此外,您将无法像您那样跟踪 set-capability 或 setuid 二进制文件——execve()ptraced 进程调用时,内核将忽略文件功能:

$ getcap tapy
tapy = cap_net_admin+eip
$ ./tapy
tapy: {tap1}
^C
$ strace -e trace=ioctl ./tapy
ioctl(3, TUNSETIFF, 0x7ffdc5b2fef0)     = -1 EPERM (Operation not permitted)
tapy: ioctl TUNSETIFF: Operation not permitted
+++ exited with 1 +++

答案2

我在编写一个 Rust 程序时遇到了同样的问题,该程序生成了一个tunctl创建和管理 TUN/TAP 接口的过程。

例如:

let tunctl_status = Command::new("tunctl")
            .args(&["-u", "user", "-t", "tap0"])
            .stdout(Stdio::null())
            .status()?;

失败了:

$ ./target/debug/nio
TUNSETIFF: Operation not permitted
tunctl failed to create tap network device.

即使NET_ADMIN设置了文件功能:

$ sudo setcap cap_net_admin=+ep ./target/debug/nio
$ getcap ./target/debug/nio                       
./target/debug/nio cap_net_admin=ep

手册指出:

由于当以非 root 用户身份运行时,可继承的功能通常不会在 execve(2) 中保留,因此希望运行具有提升功能的帮助程序的应用程序应考虑使用环境功能,如下所述。

execve()为了涵盖系统调用的情况,我使用了环境功能。

环境(自 Linux 4.3 起)这是在非特权程序的 execve(2) 中保留的一组功能。环境能力集遵循以下不变量:如果不能同时允许且可继承,则任何能力都不能是环境能力。

解决方案示例:为了方便起见,我使用caps-rs图书馆。

// Check if `NET_ADMIN` is in permitted set.
let perm_net_admin = caps::has_cap(None, CapSet::Permitted, Capability::CAP_NET_ADMIN);
match perm_net_admin {
    Ok(is_in_perm) => {
        if !is_in_perm {
            eprintln!("Error: The capability 'NET_ADMIN' is not in the permitted set!");
            std::process::exit(1)
        }
    }
    Err(e) => {
        eprintln!("Error: {:?}", e);
        std::process::exit(1)
    }
}


// Note: The ambient capability set obeys the invariant that no capability can ever be ambient if it is not both permitted and inheritable.
caps::raise(
    None,
    caps::CapSet::Inheritable,
    caps::Capability::CAP_NET_ADMIN,
)
.unwrap_or_else(fail_due_to_caps_err);

caps::raise(None, caps::CapSet::Ambient, caps::Capability::CAP_NET_ADMIN)
    .unwrap_or_else(fail_due_to_caps_err);

最后,设置NET_ADMIN文件功能就足够了:

$ sudo setcap cap_net_admin=+ep ./target/debug/nio

答案3

虽然CAP_NET_ADMIN允许您修改网络配置(例如,接口等)以进行实际窃听和网络 I/O 读取,但除 之外,iptables您还必须添加该功能。CAP_NET_RAWCAP_NET_ADMIN

他们将共同赋予您所寻求的能力。

相关内容