目录

Linux Kernel | Linux 权限介绍

进程权限控制是指进程能否有权限访问某个文件、能否访问其他进程、能否进行某些操作,以及进程能否被其他项目组访问。task_struct 中关于进程权限的成员变量有如下这些,其中 cred 表示我这个进程可以操作谁,实质上就是我操作别人时具有的权限是什么;real_cred 表示谁能操作我这个进程

操作其实就是一个对象对另一个对象进行某些动作。当动作要实施的时候,需要审核权限,当两边的权限匹配上了,那么就可以实施操作。

1
2
3
4
5
6
// include\linux\sched.h

/* Objective and real subjective task credentials (COW): */
const struct cred __rcu         *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu         *cred;

其中 struct cred 的定义如下所示:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// include\linux\cred.h

struct cred {
......
        kuid_t          uid;            /* real UID of the task */
        kgid_t          gid;            /* real GID of the task */
        kuid_t          suid;           /* saved UID of the task */
        kgid_t          sgid;           /* saved GID of the task */
        kuid_t          euid;           /* effective UID of the task */
        kgid_t          egid;           /* effective GID of the task */
        kuid_t          fsuid;          /* UID for VFS ops */
        kgid_t          fsgid;          /* GID for VFS ops */
......
        kernel_cap_t    cap_inheritable; /* caps our children can inherit */
        kernel_cap_t    cap_permitted;  /* caps we're permitted */
        kernel_cap_t    cap_effective;  /* caps we can actually use */
        kernel_cap_t    cap_bset;       /* capability bounding set */
        kernel_cap_t    cap_ambient;    /* Ambient capability set */
......
} __randomize_layout;

主要分为两大块:

  • 第一块是关于用户和用户所属的用户组信息;
  • 第二块是 capabilities 相关的信息;

1. 用户和用户组

uid、gid 一般情况下是谁启动的进程,就是谁的 ID。但是权限审核的时候,又往往不比较这两个,也就是说不大起作用。

euid、egid 这两个是真正起作用的。当一个进程要操作消息队列、共享内存、信号量等对象的时候,就是在比较这个。

fsuid、fsgid(filesystem user/group id),这个是对文件操作时会审核的权限。

suid、sgid 是用来干什么的呢?一般来说 uid、euid、fsuid 这三者是一样的,gid、egid、fsgid 也是一样的,因为一般谁启动的进程,那么就应该审核启动的用户到底有没有这个权限。但是,也有特殊情况。一个程序是用户 B的,这个程序所拥有的权限是 rwxr-xr-x。这个程序会操作一个文件,而这个文件的权限是 rw-r–r–。此时,用户 A 可以运行该程序,A 用户启动的进程中 uid、euid、fsuid 都是用户 A 的。这个时候,启动的进程无法写这个文件。此时,我们可以通过 chmod u+s program 给这个程序设置 set-user-ID 的标识位,也就是把程序的权限设置为 rwsr-xr-x。那么,用户 A 再次启动这个程序的时候,uid 还是用户 A,但是 euid、fsuid 就不是用户 A 了,因为看到了 set-user-ID 标识,所以 euid、fsuid 就变成了程序的所有者,也就是用户 B 的 ID 了。

在 Linux 里面,一个进程可以随时通过 setuid 来设置 uid。程序的拥有者 ID 会被保存在一个地方,这就是 suid、sgid(saved uid、saved gid)。这样,就可以使用 setuid 来方便地设置 uid 了。

https://img.dawnguo.cn/Linux/image-20201102203415672.png

2. Linux capabilities

除了使用用户和用户组控制权限,Linux 的另一个机制就是 capabilities。capabilities 相关的成员变量也在 struct cred 中。那么为什么需要 capabilities 呢?

在 Linux capabilities 出现前,进程的权限可以简单分为两类,第一类是特权用户的进程(进程的有效用户 ID 是 0,简单来说,你可以认为它就是 root 用户的进程),第二类是非特权用户的进程(进程的有效用户 ID 是非 0,可以理解为非 root 用户进程)。特权用户进程可以执行 Linux 系统上的所有操作,而非特权用户在执行某些操作的时候就会被内核限制执行(其实这个概念,也是我们通常对 Linux 中 root 用户与非 root 用户的理解)。然而这个时候 root 用户权限太大,而普通用户权限太小。有时候,一个普通用户只想做一点高权限的事情,但是却必须给他整个 root 的权限,这有点不安全。

从 kernel 2.2 开始,Linux 把特权用户所有的这些“特权”做了更详细的划分,从而可以更加细粒度地给进程赋予不同权限,而被划分出来的每个单元就被称为 capability。对于一个 capability,有的对应一个特权操作,有的可以对应很多个特权操作。而每个特权操作都有一个对应的 capability。所有的 capabilities 都在 Linux capabilities 手册列出来了,你也可以在内核的文件capability.h 中看到所有 capabilities 的定义。

这样,对于任意一个进程,在做任意一个特权操作的时候,都需要有这个特权操作对应的 capability,也就是说当有相应权限的时候,就能做这些操作;没有的时候,就不能做,从而把权限的粒度给减小了很多。比如说,运行 iptables 命令,对应的进程需要有 CAP_NET_ADMIN 这个 capability。如果要 mount 一个文件系统,那么对应的进程需要有 CAP_SYS_ADMIN 这个 capability(需要注意的是:CAP_SYS_ADMIN 这个 capability 里允许了大量的特权操作,包括文件系统,交换空间,还有对各种设备的操作,以及系统调试相关的调用等等)。

“Linux Programmer’s Manual"中关于Linux capabilities的描述:https://man7.org/linux/man-pages/man7/capabilities.7.html

2.1. capabilities 表示

capabilities 机制使用位图来表示权限

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// include\uapi\linux\capability.h

#define CAP_CHOWN            0
#define CAP_KILL             5
#define CAP_NET_BIND_SERVICE 10
#define CAP_NET_RAW          13
#define CAP_SYS_MODULE       16
#define CAP_SYS_RAWIO        17
#define CAP_SYS_BOOT         22
#define CAP_SYS_TIME         25
#define CAP_AUDIT_READ       37
#define CAP_LAST_CAP         CAP_AUDIT_READ

task_struct 中关于 capabilities 的成员变量的解释如下:

  • cap_permitted 表示进程能够使用的权限,但是真正起作用的是 cap_effective。cap_permitted 中包含了 cap_effective 中没有的权限。一个进程可以在必要的时候,放弃自己的某些权限,从而可以更加安全。

  • cap_bset(capability bounding set),是系统中所有进程允许保留的权限。如果这个集合中不存在某个权限,那么系统中的所有进程都没有这个权限,即使以 root 用户执行的进程也是一样的。

  • cap_inheritable 表示当可执行文件的扩展属性设置了 inheritable 位时,调用 exec 执行该程序时会继承调用者的 inheritable 集合,并将其加入到 permitted 集合中。但是在非 root 用户下执行 exec 时,通常不会保留 inheritable 集合。(但是往往是非 root 用户才想保留权限,所以非常鸡肋)。

  • cap_ambient 是比较新加入内核的,主要是为了解决 cap_inheritable 鸡肋的情况。也就是当一个非 root 用户进程使用 exec 执行一个程序的时候,如何保留权限的问题。当执行 exec 的时候,cap_ambient 会被添加到 cap_permitted 中,同时设置到 cap_effective。

2.2. root 和非 root 用户

在普通 Linux 节点上,非 root 用户启动的进程缺省没有任何 Linux capabilities,而 root 用户启动的进程缺省包含了所有的 Linux capabilities。

但是,对于 root 用户启动的进程来说,如果将某个 capability 去掉,那么需要相应权限的命令也是无法执行的。比如,对于 root 用户启动的进程,如果把 CAP_NET_ADMIN 这个 capability 移除,那么是无法正常执行 iptables 的。如下所示,我们使用 capsh(https://man7.org/linux/man-pages/man1/capsh.1.html) 这个工具来进行试验。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# sudo /usr/sbin/capsh --keep=1 --user=root   --drop=cap_net_admin  --   -c './iptables -L;sleep 100'
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
 
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
 
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
iptables: Permission denied (you must be root).

可以看到,即使是 root 用户,如果把 root 用户的 “CAP_NET_ADMIN"给移除了,那么在执行 iptables 的时候就会看到"Permission denied (you must be root).“的提示信息。

2.3. capabilities 查看

另外通过 /proc 文件系统,我们可以查看进程对应的 capabilties 的设置,见 /proc/[pid]/status 文件。这个文件里面有 5 个 cap 参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
# ps -ef | grep sleep
root     22603 22275  0 19:44 pts/1    00:00:00 sudo /usr/sbin/capsh --keep=1 --user=root --drop=cap_net_admin -- -c ./iptables -L;sleep 100
root     22604 22603  0 19:44 pts/1    00:00:00 /bin/bash -c ./iptables -L;sleep 100
 
# cat /proc/22604/status | grep Cap
CapInh:            0000000000000000
CapPrm:          0000003fffffefff
CapEff:             0000003fffffefff
CapBnd:          0000003fffffefff
CapAmb:         0000000000000000

其中,对于当前进程,直接影响某个特权操作是否可以被执行的参数,是"CapEff”,也就是"Effective capability sets”,这是一个 bitmap,每一个 bit 代表一项 capability 是否被打开。而每一位代表的意思见文件 capability.h。比如,Linux 内核capability.h里把 CAP_NET_ADMIN 的值定义成 12,所以我们可以看到"CapEff"的值是"0000003fffffefff”,第 4 个数值是 16 进制的"e",而不是 f。因此,这个进程是没有执行 iptable 命令的权限了。

而对于其他几个 capabilities 相关的参数,它们可以和应用程序文件属性中的 capabilities 协同工作,确定新启动的进程最终的 capabilities 参数的值。比如,要新启动一个程序,在 Linux 里的过程就是先通过 fork() 来创建出一个子进程,然后调用 execve() 系统调用读取文件系统里的程序文件,把程序文件加载到进程的代码段中开始运行。那么这个新运行的进程里的相关 capabilities 参数的值,是由它的父进程以及程序文件中的 capabilities 参数值计算得来的。示意图如下所示:

https://img.dawnguo.cn/Linux/906a996776f84d8f856cc7f62589095c.jpeg

具体计算过程,可以看 “Linux Programmer’s Manual” 中的描述(https://man7.org/linux/man-pages/man7/capabilities.7.html),也可以参考网上这两篇文章:

  • Capabilities: Why They Exist and How They Work:https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work
  • Linux Capabilities in Practice:https://blog.container-solutions.com/linux-capabilities-in-practice

综上来说,文件中可以设置 capabilities 参数值,并且这个值会影响到最后运行它的进程。比如,我们如果把 iptables 的应用程序加上 CAP_NET_ADMIN 的 capability,那么即使是非 root 用户也有执行 iptables 的权限了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
$ id
uid=1000(centos) gid=1000(centos) groups=1000(centos),10(wheel)
$ sudo setcap cap_net_admin+ep ./iptables
$ getcap ./iptables
./iptables = cap_net_admin+ep
$./iptables -L
Chain INPUT (policy ACCEPT)
target     prot opt source               destination
 
Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
DOCKER-USER  all  --  anywhere             anywhere
DOCKER-ISOLATION-STAGE-1  all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere             ctstate RELATED,ESTABLISHED
DOCKER     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
…