BruceFan's Blog

Stay hungry, stay foolish

0%

User namespace

接下来打算逐个学习一下Linux namespace的作用和用法,顺序不一定科学,随心随性学习法。。第一个学的是User namespace,先学这个是因为在看Chrome沙箱的时候,用到的就是User namespace。
1.命令行使用
通过unshare命令可以启动所有的7种namespace,启动user namespace:

1
2
fanrong@fanrong $ unshare --user -r /bin/bash
root@fanrong #

通过-r选项,将新user namespace中的root用户映射到外面的fanrong用户。
接下来介绍一下映射,使用id命令查看当前用户的ID,使用unshare命令创建一个新user namespace:

1
2
3
fanrong@fanrong $ unshare --user /bin/bash
nobody@fanrong $ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

看到用户和组变成了nobody和nogroup,这是因为没有对父user namespace和子user namespace的ID进行映射。下面我们来进行手动映射,映射ID的方法就是添加映射信息到/proc/NEW_PID/uid_map和/proc/NEW_PID/gid_map文件中。配置信息的格式如下:

1
inside-ID outside-ID length

例如,0 1000 500这条配置就表示子user namespace中的0500映射到父user namespace中的10001500。

在新的user namespace中查看当前进程ID:

1
2
nobody@fanrong $ echo $$
4754

新打开一个终端,将映射配置写入上面说的两个文件。虽然这两个文件都是fanrong用户的,但是如果直接往里写会报Operation not permitted,根本的原因在于当前的 bash 进程没CAP_SETUID和CAP_SETGID的权限:

1
2
3
4
fanrong@fanrong $ Bazel cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000

为/bin/bash添加相关的capabilities:

1
2
3
4
5
6
fanrong@fanrong $ sudo setcap cap_setgid,cap_setuid+ep /bin/bash
fanrong@fanrong $ exec bash
fanrong@fanrong $ cat /proc/$$/status | egrep 'Cap(Inh|Prm|Eff)'
CapInh: 0000000000000000
CapPrm: 00000000000000c0
CapEff: 00000000000000c0

再向文件中写入映射配置:

1
2
fanrong@fanrong $ echo '0 1000 500' > /proc/4754/uid_map
fanrong@fanrong $ echo '0 1000 500' > /proc/4754/gid_map

回到新user namespace的终端,重新加载bash:

1
2
3
nobody@fanrong $ exec bash
root@fanrong # id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)

2.代码实现
代码实现是按照上面映射的方法进行实现的,先执行unshare,再向当前进程的gid_map和uid_map写入映射配置,但是有个问题是运行之后只有uid映射了,gid没有映射,不知道什么原因。需要向setgroups写入deny,gid才能正确映射。完整代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
// gcc -o userns userns.c
#define _GNU_SOURCE
#include <sched.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>

int main()
{
int flags;
int ret;
flags = 0;
flags |= CLONE_NEWUSER;
if (unshare(flags) == -1) {
perror("unshare");
exit(-1);
}
char fname[30];
pid_t pid = getpid();

sprintf(fname, "/proc/%d/setgroups", pid);
int fd = open(fname, O_WRONLY|O_CLOEXEC, 0644);
if (fd == -1) {
printf("Could not open %s\n", fname);
exit(-1);
}
ret = write(fd, "deny", 4);
close(fd);

sprintf(fname, "/proc/%d/gid_map", pid);
fd = open(fname, O_WRONLY|O_CLOEXEC, 0644);
if (fd == -1) {
printf("Could not open %s\n", fname);
exit(-1);
}
ret = write(fd, "0 1000 1", 8);
close(fd);

sprintf(fname, "/proc/%d/uid_map", pid);
fd = open(fname, O_WRONLY|O_CLOEXEC, 0644);
if (fd == -1) {
printf("Could not open %s\n", fname);
exit(-1);
}
ret = write(fd, "0 1000 1", 8);
close(fd);

char *argv[] = {"bash", NULL};
execvp("/bin/bash", argv);
}

reference
https://man7.org/linux/man-pages/man2/unshare.2.html
https://www.cnblogs.com/sparkdev/p/9462838.html