在 UID 命名空间中,我可以将 uid 设置为未映射的 UID 吗?

Tan*_*ett 6 linux-kernel namespace

假设我是用户 ID 1000。我在 UID 命名空间内运行一个程序,将命名空间内的 UID 0 映射到命名空间外的 UID 1000。然后程序尝试将其自己的 UID 更改为 50,这在命名空间之外没有对应的 UID。

程序是否成功更改了自己的(内核)UID?如果是这样,它的 UID 从命名空间之外看起来是什么?它仍然是1000,还是别的什么?

Tan*_*ett 9

我(最终)在 Fedora Rawhide 中对其进行了测试。看起来我的初步印象是正确的:如果您seteuid(50)在名称空间内运行,但名称空间内的 uid 50 与名称空间外的 uid 不对应,那么对 seteuid 的调用就会失败。

在这里,我将使用“内核 uid”来指代内核(以及用户命名空间之外的大多数进程)看到的进程的 uid,并使用“主观 uid”来指代自己看到的进程的 uid(和命名空间内的其他进程)。

测试 1

首先,让我们看看当 root 调用seteuid(50). 我们打开 Python,看看它的 pid 是什么:

[fedora@ip-0-0-0-0 ~]$ sudo python                             
Python 2.7.6 (default, Feb  4 2014, 15:36:52)                         
[GCC 4.8.2 20140120 (Red Hat 4.8.2-14)] on linux2                     
Type "help", "copyright", "credits" or "license" for more information.
>>> import os                                                         
>>> os.getpid()                                                       
1081                                                                  
Run Code Online (Sandbox Code Playgroud)

在第二个终端中,我们可以看到 python 的内核 uid 实际上是 0(root):

[fedora@ip-0-0-0-0 ~]$ ls -ld /proc/1081
dr-xr-xr-x. 9 root root 0 Feb  7 03:27 /proc/1081
Run Code Online (Sandbox Code Playgroud)

当然,主观 uid 也是 0:

>>> os.geteuid()  
0                 
Run Code Online (Sandbox Code Playgroud)

所以现在我们将 uid 改为 50:

>>> os.seteuid(50)
Run Code Online (Sandbox Code Playgroud)

在第二个终端中,我们看到内核uid确实变成了50:

[fedora@ip-0-0-0-0 ~]$ ls -ld /proc/1081
dr-xr-xr-x. 9 50 root 0 Feb  7 03:27 /proc/1081
Run Code Online (Sandbox Code Playgroud)

测试 2

好的,现在让我们使用“unshare”来创建一个新的用户命名空间。

[fedora@ip-0-0-0-0 ~]$ unshare --user python                   
Python 2.7.6 (default, Feb  4 2014, 15:36:52)                         
[GCC 4.8.2 20140120 (Red Hat 4.8.2-14)] on linux2                     
Type "help", "copyright", "credits" or "license" for more information.
>>> import os                                                         
>>> os.getpid()                                                       
1084                                                                  
Run Code Online (Sandbox Code Playgroud)

在第二个终端中,我们看到这个进程的内核 uid 是 1000 (fedora):

[fedora@ip-0-0-0-0 ~]$ ls -ld /proc/1084
dr-xr-xr-x. 9 fedora fedora 0 Feb  7 03:30 /proc/1084
Run Code Online (Sandbox Code Playgroud)

但是在第一个终端中,我们看到它的主观uid是65534(未映射):

>>> os.geteuid()
65534           
Run Code Online (Sandbox Code Playgroud)

所以,在第二个终端中,我们给python一个映射:

[fedora@ip-0-0-0-0 ~]$ echo '0 1000 1' > /proc/1084/uid_map
Run Code Online (Sandbox Code Playgroud)

在第一个终端中,我们看到python的主观uid现在为0:

>>> os.geteuid()                     
0                                    
Run Code Online (Sandbox Code Playgroud)

我们尝试将 python 的 uid 设置为 50,但这会因“无效参数”(大概是 EINVAL)而失败,因为主观 uid 50 没有映射到内核 uid:

>>> os.seteuid(50)                   
Traceback (most recent call last):   
  File "<stdin>", line 1, in <module>
OSError: [Errno 22] Invalid argument 
Run Code Online (Sandbox Code Playgroud)

在第二个终端中,我们看到进程的内核 uid 没有变化:

[fedora@ip-0-0-0-0 ~]$ ls -ld /proc/1084
dr-xr-xr-x. 9 fedora fedora 0 Feb  7 03:30 /proc/1084
Run Code Online (Sandbox Code Playgroud)

在第一个终端中,我们看到进程的主观 uid 也没有改变:

>>> os.geteuid()
0               
Run Code Online (Sandbox Code Playgroud)

测试 3

好的,如果我们定义一个包含多个 uid 的映射会发生什么?根据 LWN.net 文章“操作中的命名空间,第 5 部分:用户命名空间”,我们需要映射 uid运行 python 。(我在运行 python 之前没有映射 uid 就尝试了这个,并且 seteuid 操作给出了“不允许操作”。)所以首先,我们在第二个新用户命名空间中运行一个 shell:

[fedora@ip-0-0-0-0 ~]$ unshare --user  
id: cannot find name for user ID 65534        
id: cannot find name for group ID 65534       
id: cannot find name for user ID 65534        
[I have no name!@ip-10-239-133-144 ~]$ echo $$
1798                                          
Run Code Online (Sandbox Code Playgroud)

然后,在第二个终端中,我们需要以 root 身份来定义映射:

[root@ip-10-239-133-144 fedora]# echo '0 1000 2' > /proc/1798/uid_map
Run Code Online (Sandbox Code Playgroud)

根据whoami命令,我们的主观uid现在是root,所以我们打开Python,将我们的主观uid设置为1:

[I have no name!@ip-0-0-0-0 ~]$ whoami                         
root                                                                  
[I have no name!@ip-0-0-0-0 ~]$ python                         
Python 2.7.6 (default, Feb  4 2014, 15:36:52)                         
[GCC 4.8.2 20140120 (Red Hat 4.8.2-14)] on linux2                     
Type "help", "copyright", "credits" or "license" for more information.
>>> import os                                                         
>>> os.seteuid(1)                                                     
>>> os.getpid()                                                       
1837                                                                  
Run Code Online (Sandbox Code Playgroud)

在第二个终端中,我们看到我们的内核 uid 现在是 1001,正如预期的那样:

[root@ip-10-239-133-144 fedora]# ls -ld /proc/1837
dr-xr-xr-x. 9 1001 fedora 0 Feb  7 04:00 /proc/1837
Run Code Online (Sandbox Code Playgroud)

再次测试2?

在测试 3 中,我们需要在运行 Python 之前定义映射,以便成功调用 seteuid()。在测试 2 中,我们没有这样做。所以,显而易见的问题是,如果我们在运行 Python 之前确实定义了映射,对 seteuid() 的调用会起作用吗?事实证明,答案是否定的。第一个终端:

[fedora@ip-0-0-0-0 ~]$ unshare --user  
id: cannot find name for user ID 65534        
id: cannot find name for group ID 65534       
id: cannot find name for user ID 65534        
[I have no name!@ip-0-0-0-0 ~]$ echo $$
1861                                          
Run Code Online (Sandbox Code Playgroud)

第二个终端:

[fedora@ip-0-0-0-0 ~]$ echo '0 1000 1' > /proc/1861/uid_map
Run Code Online (Sandbox Code Playgroud)

第一个终端:

[I have no name!@ip-0-0-0-0 ~]$ whoami                         
root                                                                  
[I have no name!@ip-0-0-0-0 ~]$ python                         
Python 2.7.6 (default, Feb  4 2014, 15:36:52)                         
[GCC 4.8.2 20140120 (Red Hat 4.8.2-14)] on linux2                     
Type "help", "copyright", "credits" or "license" for more information.
>>> import os                                                         
>>> os.geteuid()                                                      
0                                                                     
>>> os.seteuid(50)                                                    
Traceback (most recent call last):                                    
  File "<stdin>", line 1, in <module>                                 
OSError: [Errno 22] Invalid argument                                  
Run Code Online (Sandbox Code Playgroud)

结论

为了成功调用用户命名空间内的 seteuid,传递给 seteuid 的 uid必须具有用户命名空间之外的映射。否则,调用将失败并显示 EINVAL。