Linux一般权限介绍
Linux中我们最熟知的权限应该就是文件的读写执行权限了。某个用户是否能操作某个文件是这个文件的权限决定的。除了文件的操作还有其他的操作也需要权限的,而这些需要权限的操作大多是需要调用内核。如果我们用bc做数字计算,那这不需要特殊权限,任何普通用户都能做。但如果我们要用nc命令监听80端口,这就需要特殊权限了。普通用户则会出现下面的权限限制提示:
$ nc -l 80
nc: Permission denied
除了网络相关的操作,还有很多其他的操作也需要特殊权限。比如执行chroot来更改root目录, 用hostname修改主机名等等。不同的操作需要的权限也不完全一样,监听80端口需要`CAP_NET_BIND_SERVICE`权限, chroot需要`CAP_SYS_CHROOT`权限,而修改主机名则需要CAP_SYS_ADMIN。而普通用户默认没有这些权限所以当我们执行这些操作的时候会提示我们权限不足。root用户默认则拥有所有这些权限。当我们执行操作的时候系统验证权限并不是直接验证用户权限,而是验证执行操作的进程/线程的权限。我们执行任何操作都是需要敲命令的,而命令则是启动进程。比如我们用vim去编辑`/etc/shadow`文件就会启动一个vim的进程,直接去操作文件的是这个vim进程。所以系统会验证这个vim进程是不是有操作`/etc/shadow`的权限,而不是验证执行vim命令的用户。下面我们来说一说如何看进程有哪些权限。
进程有4个和权限比较相关的属性: uid, gid, euid, egid。
- uid指的是执行命令启动这个进程的用户
- gid是启动进程的用户所属的用户组
- euid叫做effective uid,系统就是根据这个id来判定进程是否对文件有操作权限
- egid叫做effective gid,这也是系统检查文件操作权限的依据
通常情况下euid和uid一样,而egid和gid一样。所以只要用户对某个文件有操作权限那他就可以通过vim去操作这个文件。`/etc/shadow`文件的权限如下,所以root可以修改这个文件而其他普通用户这不行。
$ sudo ls -l /etc/shadow
-rw-r----- 1 root shadow 1635 11月 26 15:49 /etc/shadow
但我们知道`/etc/shadow`这个文件是存用户密码的文件,而普通用户也能修改自己的密码。也就是说普通修改自己密码的过程中其实也修改了`/etc/shadow`这个文件了。结合上面的内容,我们可以猜想到的是:普通用户没有操作shadow文件的权限,但普通用户修改密码的时候执行的这个进程有权限修改shadow文件。那实际上也就是这么回事。`passwd`文件被设置了suid,导致passwd起的进程可以通过seteuid把euid设置成passwd文件的拥有者root,而不是uid。
$ ls -l `which passwd`
-rwsr-xr-x 1 root root 54256 5月 17 2017 /usr/bin/passwd
其实ping这个我们经常用到的命令,也是需要特殊权限的。
$ ls -l `which ping`
-rwsr-xr-x 1 root root 44168 5月 8 2014 /bin/ping
但我们一边执行ping一边用ps命令观察ping进程的euid可能会发现它的euid并不是root的id 0,还是执行者的uid. 这是因为ping程序本身在不需要权限的时候用seteuid把euid改了以加强安全。其实还有些版本的ping并没有使用suid位,但普通用户也能执行ping命令。其实ping不一定需要root权限,它真正需要的是`CAP_NET_RAW`这个capability。所以在这些版本的ping中,它赋予了ping这个可执行文件`CAP_NET_RAW`,这样ping程序起来的进程也有`CAP_NET_RAW`。
Linux user namespace 和capability
上面提到了Linux的一些capability,在某些特殊情况下需要用到一些capability。其实Linux中的capability种类加起来有几十种,它们都是`CAP_*`这样的格式。普通用户默认没有任何capability,而root用户则拥有所有的这些capability。这一节我们说一下capability和user namespace。除了user namespace在linux中有好几种namespace:network namespace, mount namespace等等。Linux中每一个进程/线程都会关联一组namespaces。举例来说,我们现在有两个 network namespace,那我在不同的network namespace里面执行ifconfig命令看到的结果是不一样的。因为ifconfig进程关联的network namespace不一样。
我们可以用unshare命令创建一个新的user namespace。在新的user namespace中我们可以看到这个user的id是65534名字是nobody。这里我们说`在新的user namespace中`准确的说是在关联新的user namespace的进程中。
cheng@chengdev2:~$ unshare --user bash
nobody@chengdev2:~$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)
下面我们给unshare多加一个参数,这使得新的user namespace里面的user的id是0,名字是root。同时这个user namespace里面的root和外面的`cheng`这个用户做关联。这样当关联新的user namespace的进程想要访问文件的时候,系统就会依据`cheng`这个关联的用户来判断是否有权限。
cheng@chengdev2:~$ unshare --user -r bash
root@chengdev2:~# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
当我们创建一个新的user namespace的时候,在这个新的user namspace中的第一个进程在这个user namespace管辖范围内有所有的capability,因为它在自己的王国里是root(我们可以通过`capsh --print` 或者`getcaps $$`来查看进程拥有的权限)。但实际上这时候我们试着用`nc -l 80`来监听80端口还是会报权限不足。这是因为我们执行nc的地方不是这个新的user namespace管辖范围内,所以这个新root失败了。
cheng@chengdev2:~$ unshare --user -r bash
root@chengdev2:~# id
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)
root@chengdev2:~# nc -l 80
nc: Permission denied
那怎么才算是我们新的user namespace管辖范围呢。刚才我们说了系统中可能有很多的network namespace,而每个network namespace都会有一个所属的user namespace。这个user namespace就是创建该network namespace的user所在的user namespace。一个进程要在一个network namespace中执行`nc -l 80`需要满足下面两个条件:
- 这个进程有capability
- 这个进程要操作的network namespace属于该进程关联的user namespace
回到刚才失败的那个例子,新root要操作的network namespace在user namespace创建之前就已经存在了。那显然这个network namespace并不属于这个新创建的user namespace。下面我们看另外一个例子,我们先创建一个user namespace,然后在用新root去创建一个network namespace,这时候就成功了。
cheng@chengdev2:~$ unshare --user -r bash
root@chengdev2:~# unshare --net bash
root@chengdev2:~# nc -l 80
^C
root@chengdev2:~#
引用
- lwn.net/Articles/532593/