接下来打算逐个学习一下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
| #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