为什么非特权用户不能嵌套 FUSE 挂载,但他们可以使用 root_squash 在 NFS 内挂载 FUSE?

为什么非特权用户不能嵌套 FUSE 挂载,但他们可以使用 root_squash 在 NFS 内挂载 FUSE?
$ mkdir mnt

$ bindfs /tmp mnt
fusermount: option allow_other only allowed if 'user_allow_other' is set in /etc/fuse.conf

$ bindfs --no-allow-other /tmp mnt

$ mkdir /tmp/mnt2
$ bindfs --no-allow-other /tmp mnt/mnt2
fusermount: bad mount point /home/alan/mnt/mnt2: Permission denied

fusermount失败,因为它以不同的用户身份运行。

$ sudo ls mnt/
ls: cannot open directory 'mnt/': Permission denied

fusermount是 set-uid root。这是必需的,因为非特权用户无法使用mount()系统调用。

$ ls -l $(which fusermount)
-rwsr-xr-x. 1 root root 32848 Feb  7  2018 /usr/bin/fusermount

   ^ set-uid bit

但是。据报道,FUSE 可以在 NFS 主目录内使用。即使主目录具有模式700- 只能由拥有的用户访问。 NFS 服务器默认为root_squash,这意味着“root 用户将具有与用户 nobody 相同的访问权限”。

为什么这两种情况不同?

我正在Fedora 28上进行测试。有关NFS的报告来自Ubuntu 18.04。这些分布在年龄上非常相似,但可能存在一些差异。

答案1

首先,考虑FUSE的实现no_allow_others

它要求有效的、真实的和保存的UID(用户ID)都匹配。 (GID 也一样)。这是故意阻止 set-UID 程序访问挂载。

https://github.com/torvalds/linux/blob/v4.18/fs/fuse/dir.c#L1024

调用用户控制的文件系统为文件系统
守护进程提供了对当前进程的类似于 ptrace 的功能。这
意味着文件系统守护进程能够记录执行的确切文件系统操作,并且还可以 以其他不可能的方式控制请求者进程的
行为。
例如,
它可以将操作延迟任意长度的时间,从而允许
针对请求者进行 DoS。

现在让我们追踪一下fusermount到底做了什么。我们可以尝试看看

strace -f bindfs ...

sudo perf trace -o trace.txt -a sleep 2; sleep 1; bindfs ...

第一个遇到致命错误“权限被拒绝”,因为 set-UID root 在strace.第二个成功,但无法显示路径等字符串参数。我认为这两条跟踪显示了相同的通用代码路径,直到出现致命错误。这意味着我们可以使用strace结果来填充缺失的字符串参数。

结果中的最后一个调用strace是:

[pid 30609] mount("/home/alan-sysop/mnt", ".", "fuse", MS_NOSUID|MS_NODEV, "default_permissions,fd=5,rootmod"...) = -1 EPERM (Operation not permitted)

有趣的! "."表示当前目录。所以fusermount一定已经在挂载点上运行了......不知何故。此技巧有时可用于访问当前无法使用其绝对路径访问的目录。

如果我们向上滚动,我们可以看到fusermount确实更改为该目录。它还与一些 UID 相关(和 GID 相关)的系统调用一起跳舞。

[pid 30609] getuid()                    = 1000
[pid 30609] setfsuid(1000)              = 1000
[pid 30609] getgid()                    = 1000
[pid 30609] setfsgid(1000)              = 1000
[pid 30609] openat(AT_FDCWD, "/etc/fuse.conf", O_RDONLY) = 6
...
[pid 30609] lstat("/home/alan-sysop/mnt", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
[pid 30609] getuid()                    = 1000
[pid 30609] chdir("/home/alan-sysop/mnt") = 0
[pid 30609] lstat(".", {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0
[pid 30609] access(".", W_OK)           = 0
[pid 30609] getuid()                    = 1000
[pid 30609] setfsuid(1000)              = 1000
[pid 30609] setfsgid(1000)              = 1000

会话中的 UID 结果“错误” strace。我们可以在会议中更好地看到UID舞蹈部分perf trace。 (为了便于阅读,我删除了最左边的列)。

getuid(                                                               ) = 1000
setfsuid(uid: 1000                                                    ) = 0
getgid(                                                               ) = 1000
setfsgid(gid: 1000                                                    ) = 1000
openat(dfd: CWD, filename: 0xa428e2bc                                 ) = 6
    ...
close(fd: 6                                                           ) = 0
lstat(filename: 0xa63882a0, statbuf: 0x7ffe7bd4f6d0                   ) = 0
getuid(                                                               ) = 1000
chdir(filename: 0xa63882a0                                            ) = 0
lstat(filename: 0xa428eca5, statbuf: 0x7ffe7bd4f6d0                   ) = 0
access(filename: 0xa428eca5, mode: W                                  ) = 0
getuid(                                                               ) = 1000
setfsuid(                                                             ) = 1000
setfsgid(gid: 1000                                                    ) = 1000
getuid(                                                               ) = 1000

调用setfsuid()位于drop_privs()restore_privs()函数中熔丝安装程序

chdir()调用偷偷地隐藏在名为 的函数中check_perm()

结论

为什么这在 NFS 上有效?答案:因为 NFS 会查看已设置为非 root UID 的fsuid( 和)。fsgid

为什么这在 FUSE 上不起作用,除非你有allow_others?答案:因为 FUSE 检查“真实”UID,而不是fsuid.

相关内容