新的 Linux 用户命名空间中的第一个进程需要调用 setuid()?

新的 Linux 用户命名空间中的第一个进程需要调用 setuid()?

我正在学习 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)。

相关内容