Estou aprendendo sobre namespaces de usuários Linux e estou observando um comportamento estranho que não está completamente claro para mim.
Eu criei um intervalo de UIDs em namespaces de usuário iniciais para os quais eu posso mapear UIDs no namespace de usuário filho por meio do comando newuidmap
. Estas são minhas configurações:
$ grep '^woky:' /etc/subuid
woky:200000:10000
$ id -u
1000
Depois, tentei criar um novo namespace de usuário e mapear seu intervalo de UID [0-10000)
to [200000-210000)
no namespace de usuário pai:
-
Primeiro terminal:
$ PS1='% ' unshare -U bash
% echo $$
1337
% id
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
-
Segundo terminal:
$ ps -p 1337 -o uid
UID
1000
$ newuidmap 1337 0 200000 10000
$ ps -p 1337 -o uid
UID
1000
-
Primeiro terminal:
% id
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
Assim, os UIDs dentro e fora do novo namespace de usuário não foram alterados, mesmo que o newuidmap
tenha sido concluído com êxito.
Então achei o seguinte artigo link que abriu um pouco meus olhos. Eu tentei o cenário anterior, mas com o seguinte script test-unshare.py
, que tirei do artigo e modifiquei um pouco, em vez do comando unshare
:
#!/usr/bin/python3
import os
from cffi import FFI
CLONE_NEWUSER = 0x10000000
ffi = FFI()
ffi.cdef('int unshare(int flags);')
libc = ffi.dlopen(None)
libc.unshare(CLONE_NEWUSER)
print("user id = %d, process id = %d" % (os.getuid(), os.getpid()))
input("Press Enter to continue...")
# The uid must be set to 0 to avoid loosing capabilities when creating the shell.
os.setuid(0)
os.execlp('/bin/bash', 'bash')
-
Primeiro terminal:
$ python3 ./test-unshare.py
user id = 65534, process id = 1337
Press Enter to continue...
-
Segundo terminal:
$ ps -p 1337 -o uid
UID
1000
$ newuidmap 1337 0 200000 10000
$ ps -p 1337 -o uid
UID
1000
-
Primeiro terminal:
<Enter>
bash: /home/woky/.bashrc: Permission denied
bash-4.4# id
uid=0(root) gid=65534(nobody) groups=65534(nobody)
-
Segundo terminal:
$ ps -p 1337 -o uid
UID
200000
Agora parece que eu esperava desde o começo. Agora, minha teoria sobre por que os UIDs no primeiro exemplo não foram alterados é a seguinte:
O unshare
chamado execve(2)
para executar /bin/bash
sem chamar primeiro setuid(2)
. Agora o shell perdeu todos os seus recursos (como mencionado em user_namespaces(7)
) e não pode alterar seu UID de 65534. No segundo caso, o processo mudou seu UID para 0, porque ele tinha recursos para isso e o Linux o mapeou para 200000 fora do novo namespace de usuário (de acordo com /proc/1337/uid_map
, que newuidmap
escreveu). O que significa que o primeiro processo em um novo namespace de usuário deve chamar setuid (START_UID) ou, caso contrário, ele ficará preso em 65534 após execve(2)
.
Está correto?
O artigo diz sobre o meu primeiro exemplo (que é equivalente ao código Python em seu primeiro exemplo):
If you just try this out, you'll probably find that it doesn't really quite work, this is because the uid map has the be set before the shell is executed.
Mas não posso concluir isso a partir das informações contidas nas páginas man, nem as páginas man afirmam explicitamente que setuid(2)
precisa ser chamado no primeiro processo em um novo namespace de usuário.
No entanto, neste cenário, o processo no novo namespace de usuário não precisou chamar setuid(2)
e, ainda assim, seu UID foi alterado:
-
Primeiro terminal:
$ PS1='% ' unshare -U bash
% echo $$
1337
% id
uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
-
Segundo terminal:
$ ps -p 1337 -o uid
UID
1000
$ echo '500000 1000 1' >/proc/1337/uid_map
$ ps -p 1337 -o uid
UID
1000
-
Primeiro terminal:
% id
uid=500000 gid=65534(nobody) groups=65534(nobody)
Por favor, explique todas as situações em profundidade.
Minha jornada começou quando tentei entender para que serve o arquivo /etc/subuid
. É usado pelo Docker e pelo LXC, mas apenas alguns documentos explicam isso. Desculpe pela verbosidade. Demorei muito tempo para compreendê-lo e ainda não o entendi totalmente, então estou colecionando aqui tudo que sei.
BONUS: Explique /etc/subuid
, sua relação com namespaces de usuários, por que é necessário para Docker e LXC, e por que é uma interface genérica nas distribuições Linux. A página do manual é breve e os artigos na Internet documentam principalmente como fazer algo no trabalho do LXC / Docker. (A explicação real está em newuidmap(1)
).