本文共 3798 字,大约阅读时间需要 12 分钟。
本文假设你已经了解了Linux Container,CGroup和基本的Namespace概念。
User Namespace是Linux 3.8新增的一种namespace,用于隔离安全相关的标识和属性。使用了user namespace之后,进程在namespace内部和外部的uid/gid可以不一样,常用来实现这种效果:进程在namespace外面是一个普通用户,但是在namespace里是root(uid=0),也就是进程在这个namespace里拥有所有的权限,在namespace外面只有普通用户的权限了。
既然都是解决安全方面问题的,就不得不提另外两个Linux安全方面的功能,另外两个常用的是capabilities和LSM (Linux Security Module),其中capabilities和User Namespace关系密切。通过调用带CLONE_NEWUSER
参数的clone方法创建的子进程,自动拥有新User Namespace里所有capabilities。另外,进程通过unshare创建一个新的User Namespace,或者通过setns加入一个已有的Namespace,都自动获取对应User Namespace里所有的capabilities。如果接下来进程通过execve启动了新程序,就要按照capabilities计算规则重新计算新进程的capabilities。
先看一个例子 (from
#define _GNU_SOURCE#include#include #include #include #include #include #include #include #include #include #define STACK_SIZE (1024 * 1024) static char container_stack[STACK_SIZE];char* const container_args[] = { "/bin/bash", NULL}; int pipefd[2]; void set_map(char* file, int inside_id, int outside_id, int len) { FILE* mapfd = fopen(file, "w"); if (NULL == mapfd) { perror("open file error"); return; } fprintf(mapfd, "%d %d %d", inside_id, outside_id, len); fclose(mapfd);} void set_uid_map(pid_t pid, int inside_id, int outside_id, int len) { char file[256]; sprintf(file, "/proc/%d/uid_map", pid); set_map(file, inside_id, outside_id, len);} void set_gid_map(pid_t pid, int inside_id, int outside_id, int len) { char file[256]; sprintf(file, "/proc/%d/gid_map", pid); set_map(file, inside_id, outside_id, len);} int container_main(void* arg){ printf("Container [%5d] - inside the container!\n", getpid()); printf("Container: eUID = %ld; eGID = %ld, UID=%ld, GID=%ld\n", (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid()); /* 等待父进程通知后再往下执行(进程间的同步) */ char ch; close(pipefd[1]); read(pipefd[0], &ch, 1); printf("Container [%5d] - setup hostname!\n", getpid()); //set hostname sethostname("container",10); //remount "/proc" to make sure the "top" and "ps" show container's information mount("proc", "/proc", "proc", 0, NULL); execv(container_args[0], container_args); printf("Something's wrong!\n"); return 1;} int main(){ const int gid=getgid(), uid=getuid(); printf("Parent: eUID = %ld; eGID = %ld, UID=%ld, GID=%ld\n", (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid()); pipe(pipefd); printf("Parent [%5d] - start a container!\n", getpid()); int container_pid = clone(container_main, container_stack+STACK_SIZE, CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL); printf("Parent [%5d] - Container [%5d]!\n", getpid(), container_pid); //To map the uid/gid, // we need edit the /proc/PID/uid_map (or /proc/PID/gid_map) in parent //The file format is // ID-inside-ns ID-outside-ns length //if no mapping, // the uid will be taken from /proc/sys/kernel/overflowuid // the gid will be taken from /proc/sys/kernel/overflowgid (container_pid, 0, uid, 1); set_gid_map(container_pid, 0, gid, 1); printf("Parent [%5d] - user/group mapping done!\n", getpid()); /* 通知子进程 */ close(pipefd[1]); waitpid(container_pid, NULL, 0); printf("Parent - container stopped!\n"); return 0;}
上面的例子创建了一个子进程,其中包含了UTS, PID, MOUNT和USER Namespace。编译并且运行上面的代码
gcc -Wall u.csudo /sbin/setcap all+eip a.out./a.out
运行效果如下
可以看到,在container内部已经是root了。非常神奇的是,原来uid=1000的文件,在container内部自动显示成uid=0,原来uid=0的,自动变成uid=65534 (nobody),很完美。
代码中关键的部分在set_uid_map
中,要设置新创建User Namespace和Parent User Namespace(这里例子中是系统默认的Namespace)中uid和gid的映射,只要写进程对应的两个文件即可:
这里的PID是运行在User Namespace中的进程id。写入的格式是
id-inside-ns id-outside-ns length
有人可能主要到 上面的setcap操作,这一步也很关键,创建username也需要特定的capabilities才行,为了方便,这里直接设置为all
待续
转载地址:http://wdvja.baihongyu.com/