为什么不允许使用非特权递归unshare(CLONE_NEWUSER)?

5

我使用的是Ubuntu 17.04。

单个非特权unshare挂载命名空间有效。您可以尝试使用unshare(1)命令:

$ unshare -m -U /bin/sh
#

但是不允许在未共享的空间中再次进行未共享操作:

$ unshare -m -U /bin/sh
# unshare -m -U /bin/sh
unshare: Operation not permitted
#

以下是一个基本的 C 程序,其实现与上述功能类似:
#define _GNU_SOURCE
#include <stdio.h>
#include <sched.h>
#include <sys/mount.h>
#include <unistd.h>

int
main(int argc, char *argv[])
{
    if(unshare(CLONE_NEWUSER|CLONE_NEWNS) == -1) {
        perror("unshare");
        return -1;
    }
    if(unshare(CLONE_NEWUSER|CLONE_NEWNS) == -1) {
        perror("unshare2");
        return -1;
    }
    return 0;
}

为什么不允许这样做?我在哪里可以找到相关文档?我在unshare或clone手册以及内核unshare文档中找不到此信息。

是否有系统设置可以允许此操作?

我想要实现的目标:

第一个unshare:我想用自己的版本来屏蔽系统上的一些二进制文件。

第二个unshare:非特权的chroot。

1个回答

4

我有点猜测,但我认为原因是UID映射。为了执行它,必须满足某些条件(来自user_namespaces手册):

   In  order  for  a process to write to the /proc/[pid]/uid_map (/proc/[pid]/gid_map) file, all of the following require‐
   ments must be met:

   1. The writing process must have the CAP_SETUID (CAP_SETGID) capability in the user namespace of the process pid.

   2. The writing process must either be in the user namespace of the process pid or be in the parent  user  namespace  of
      the process pid.

   3. The mapped user IDs (group IDs) must in turn have a mapping in the parent user namespace.

我认为所发生的是,第一次运行时,映射与父UID相匹配。然而,第二次运行时不匹配,这会导致系统调用失败。

来自unshare(2)手册页面:

   EPERM  CLONE_NEWUSER was specified in flags, but either the effective user ID or the effective group ID of  the  caller
          does not have a mapping in the parent namespace (see user_namespaces(7)).

这会使得非特权递归unshare通常不可能吗?第一个是允许的,因为有CLONE_NEWUSER。但是你不能像你写的那样做第二个。 - Hadrian Węgrzynowski
我将检查是否可以做与nsenter(1)相同的事情。我在两个不同的进程中调用unshare来生成工作进程,我认为也许我可以绕过它。也许我可以先回到父命名空间,然后再次进行unshare,因为我在第一次unshare之后进行的掩码对于chroot来说不再必要。 - Hadrian Węgrzynowski
可能不可能。如setns(2)手册所述:重新关联到用户命名空间的进程必须在目标用户命名空间中具有CAP_SYS_ADMIN功能。 - Hadrian Węgrzynowski
我相信这是可能的。你只需要确保两次创建兼容的映射。CAP_SYS_ADMIN没有问题,因为在unshare(CLONE_NEWUSER)之后立即获得完整的能力集。 - Shachar Shemesh
1
谢谢。我通过在第一次unshare之后放置fprintf(/proc/self/uid_map, "%d %d 1\n", uid, uid); fputs("deny", /proc/self/setgroups); fprintf(/proc/self/gid_map, "%d %d 1\n", gid, gid); 并在此之前获取uid和gid 来完成它。 - Hadrian Węgrzynowski

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接