Primeiro, considere a implementação do FUSE no_allow_others
.
Requer que os UIDs (IDs de usuário) efetivos, reais e salvos sejam correspondentes. (E o mesmo para o GID). Isso é deliberadamente impedir que um programa set-UID acesse a montagem.
Calling into a user-controlled filesystem gives the filesystem
daemon ptrace-like capabilities over the current process. This
means, that the filesystem daemon is able to record the exact
filesystem operations performed, and can also control the behavior
of the requester process in otherwise impossible ways. For example
it can delay the operation for arbitrary length of time allowing
DoS against the requester.
Agora vamos rastrear o que o fusermount
faz. Nós podemos tentar olhar para
strace -f bindfs ...
e
sudo perf trace -o trace.txt -a sleep 2; sleep 1; bindfs ...
O primeiro atinge um erro fatal "Permissão negada", porque a raiz do set-UID não funciona quando executado em strace
. O segundo é bem-sucedido, mas não pode mostrar parâmetros de string, como caminhos. Eu acho que os dois traços mostram o mesmo caminho de código geral até o erro fatal. Isso significa que podemos usar os resultados strace
para preencher os parâmetros de string ausentes.
Essa última chamada nos resultados strace
é:
[pid 30609] mount("/home/alan-sysop/mnt", ".", "fuse", MS_NOSUID|MS_NODEV, "default_permissions,fd=5,rootmod"...) = -1 EPERM (Operation not permitted)
Interessante! "."
significa o diretório atual. Então fusermount
já deve estar rodando no ponto de montagem ... de alguma forma. Às vezes, esse truque pode ser usado para acessar um diretório que você atualmente não pode acessar usando seu caminho absoluto.
Se rolarmos para cima, podemos ver que fusermount
realmente foi alterado para esse diretório. E também dançava com algumas chamadas de sistema relacionadas a UID (e relacionadas a GIDs).
[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
Os resultados do UID estão "errados" na sessão strace
. Podemos ver melhor a parte de dança do UID na sessão perf trace
. (Eu removi as colunas mais à esquerda para facilitar a leitura).
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
As chamadas setfsuid()
estão nas funções drop_privs()
e restore_privs()
em fusermount.c .
A chamada chdir()
está sorrateiramente oculta na função chamada check_perm()
.
Conclusão
Por que isso funciona no NFS? Resposta: porque o NFS examina os fsuid
(e fsgid
), que foram definidos para o UID não-raiz.
Por que isso não funciona no FUSE, a menos que você tenha allow_others
? Resposta: porque o FUSE verifica o UID "real", e não o fsuid
.