為什麼非特權使用者不能嵌套 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-uidroot
。這是必需的,因為非特權使用者無法使用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。這些分佈在年齡上非常相似,但可能存在一些差異。
首先,考慮 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 ...
第一個遇到致命錯誤“Permission denied”,因為 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
strace
會話中的 UID 結果是“錯誤的” 。perf trace
我們可以在會話中更好地看到 UID 舞蹈部分。(為了便於閱讀,我刪除了最左邊的列)。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()
呼叫在fusermount.cdrop_privs()
的andrestore_privs()
函式中。該
chdir()
呼叫被偷偷地隱藏在被呼叫的函式中check_perm()
。結論
為什麼這適用於 NFS?答:因為 NFS 會查看已設置為非根 UID的
fsuid
(and )。fsgid
為什麼這在 FUSE 上不起作用,除非你有
allow_others
?答:因為 FUSE 檢查的是“真實”的 UID,而不是fsuid
.