我正在学习 Linux 用户命名空间,并且观察到一种我并不完全清楚的奇怪行为。
我在初始用户命名空间中创建了一系列 UID,我可以通过newuidmap
命令将子用户命名空间中的 UID 映射到这些 UID。这些是我的设置:
$ grep '^woky:' /etc/subuid
woky:200000:10000
$ id -u
1000
然后我尝试创建一个新的用户命名空间并将其 UID 范围映射[0-10000)
到[200000-210000)
父用户命名空间:
第一个航站楼:
$ PS1='% ' unshare -U bash % echo $$ 1337 % id uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
第二航站楼:
$ ps -p 1337 -o uid UID 1000 $ newuidmap 1337 0 200000 10000 $ ps -p 1337 -o uid UID 1000
第一个航站楼:
% id uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
因此,即使newuidmap
成功完成,新用户命名空间内部和外部的 UID 也不会更改。
然后我找到了下面这篇文章 http://www.itinken.com/blog/2016/Sep/exploring-unprivileged-containers/这让我睁开了眼睛。我已经尝试了前面的场景,但使用了以下test-unshare.py
脚本(我从文章中获取并稍作修改),而不是命令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')
第一个航站楼:
$ python3 ./test-unshare.py user id = 65534, process id = 1337 Press Enter to continue...
第二航站楼:
$ ps -p 1337 -o uid UID 1000 $ newuidmap 1337 0 200000 10000 $ ps -p 1337 -o uid UID 1000
第一个航站楼:
<Enter> bash: /home/woky/.bashrc: Permission denied bash-4.4# id uid=0(root) gid=65534(nobody) groups=65534(nobody)
第二航站楼:
$ ps -p 1337 -o uid UID 200000
现在看起来就像我一开始所期望的那样。现在我关于为什么第一个示例中的 UID 没有更改的理论如下:
unshare
被调用execve(2)
的运行无需/bin/bash
先调用setuid(2)
。现在,shell 失去了所有功能(如 中提到的user_namespaces(7)
),并且无法将其 UID 从 65534 更改。在第二种情况下,进程将其 UID 更改为 0,因为它有能力这样做,并且 Linux 将其映射到新的 200000 之外。用户命名空间(根据/proc/1337/uid_map
所写newuidmap
)。这意味着新用户命名空间中的第一个进程必须调用 setuid(START_UID)否则它会在 65534 之后卡住execve(2)
。
这是对的吗?
该文章介绍了我的第一个示例(相当于第一个示例中的 Python 代码),内容如下:
如果您尝试一下,您可能会发现它实际上不太有效,这是因为 uid 映射在 shell 执行之前就已设置。
但我无法从手册页中的信息得出这一结论,手册页也没有明确指出setuid(2)
需要在新用户命名空间的第一个进程中调用它。
然而,在这种情况下,新用户命名空间中的进程不必调用setuid(2)
,但其 UID 已更改:
第一个航站楼:
$ PS1='% ' unshare -U bash % echo $$ 1337 % id uid=65534(nobody) gid=65534(nobody) groups=65534(nobody)
第二航站楼:
$ ps -p 1337 -o uid UID 1000 $ echo '500000 1000 1' >/proc/1337/uid_map $ ps -p 1337 -o uid UID 1000
第一个航站楼:
% id uid=500000 gid=65534(nobody) groups=65534(nobody)
请深入解释所有情况。
当我试图了解该/etc/subuid
文件的用途时,我的旅程开始了。 Docker 和 LXC 使用它,但只有很少的文档对其进行解释。抱歉啰嗦了。我花了很长时间才理解它,但我仍然不完全理解它,所以我把我所知道的都收集在这里。
额外奖励:解释一下/etc/subuid
,它与用户命名空间的关系,为什么 Docker 和 LXC 需要它,以及为什么它是 Linux 发行版上的通用接口。手册页很简短,互联网上的文章主要记录了如何在 LXC/Docker 中运行某些东西。 (实际解释在newuidmap(1)
)。
答案1
看着https://unix.stackexchange.com/a/110245/301641
看看这个答案,你可能会看到一些 UID 为无人,因为它们没有映射。
情况1:“(65534)nobody inside, 1000(woky)outside”作为初始值,newuidmap后仍然没有得到地图(只有[outside200000,outside210000)被映射,但需要outside1000被映射,超出范围) 。所以一切都没有改变。
情况2:“(65534)nobody inside, 1000(woky)outside”作为初始值,newuidmap之后仍然没有得到地图(只有[outside200000,outside210000)被映射,但需要outside1000被映射,超出范围) 。但是你在获取map之后就setuid(inside0)(注意,在写入uid_map之前你永远不能setuid),它在map中,所以UID从溢出值变成了正常的映射值(outside200000,inside0)。
情况3:“(65534)nobody inside, 1000(woky)outside”作为初始值,newuidmap得到映射后(outside1000得到映射),所以UID从溢出值变为正常映射值(outside1000,inside500000)。