从“以管理员打开”简要讲述 Linux 的凭据
本文主要讲解传统的 Unix 凭据和能力(Caps)。
众所周知,GNOME 的文件应用里有以管理员打开这个选项。点击后,经过一次 polkit 鉴权就可以访问原本需要 root 才能访问的目录。这是怎么做到的?
查看一下 /usr/share/polkit-1/actions
目录,发现 nautilus 的文件操作 action 来自 org.gtk.vfs.file-operations.policy
。根据注释 <!-- This action is used to authorize gvfsd-admin operations. -->
,推测可能和 gvfsd-admin
有关。
然而有一个问题。党务 sudo ps -ef
尝试查看 gvfsd-admin
是否在运行是,发现 1
2
3
4
5
6
7
8
9
10
11
12$ sudo ps -ef | grep gvfsd
Sving10+ 1790 1723 0 13:36 ? 00:00:00 /usr/lib/gvfsd
Sving10+ 1863 1723 0 13:36 ? 00:00:00 /usr/lib/gvfsd-fuse /run/user/1000/gvfs -f
Sving10+ 2339 1723 0 13:36 ? 00:00:00 /usr/lib/gvfsd-metadata
Sving10+ 2843 1790 0 13:36 ? 00:00:00 /usr/lib/gvfsd-trash --spawner :1.12 /org/gtk/gvfs/exec_spaw/0
Sving10+ 2970 1790 0 13:36 ? 00:00:00 /usr/lib/gvfsd-network --spawner :1.12 /org/gtk/gvfs/exec_spaw/1
Sving10+ 2976 1790 0 13:36 ? 00:00:00 /usr/lib/gvfsd-dnssd --spawner :1.12 /org/gtk/gvfs/exec_spaw/2
Sving10+ 2987 1723 0 13:36 ? 00:00:00 python3 /usr/bin/wsdd --no-host --discovery --listen /run/user/1000/gvfsd/wsdd
Sving10+ 3472 1790 0 13:37 ? 00:00:00 /usr/lib/gvfsd-admin --spawner :1.12 /org/gtk/gvfs/exec_spaw/5 --address unix:path=/run/user/1000/bus --dir /run/user/1000
Sving10+ 6230 1790 0 13:54 ? 00:00:00 /usr/lib/gvfsd-http --spawner :1.12 /org/gtk/gvfs/exec_spaw/6
Sving10+ 18518 3017 0 15:43 pts/0 00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox --exclude-dir=.venv --exclude-dir=venv gvfsdUID
是 Sving1024
而不是 root
?那它是如何访问需要 root
权限才能访问的目录?
翻阅 gvfs 的 man page,发现 $XDG_DATA_DIRS/gvfs/mounts
里有 daemon 的信息。找到 admin.mount
: 1
2
3
4$ cat /usr/share/gvfs/mounts/admin.mount
...
Exec=/bin/sh -c 'pkexec /usr/lib/gvfsd-admin "$@" --address $DBUS_SESSION_BUS_ADDRESS --dir $XDG_RUNTIME_DIR' gvfsd-admin
...gvfsd-admin
是通过 pkexec
运行的,但是为什么 ps -ef
里显示 UID
是 Sving1024
。
查阅源代码。这个文件中的 check_permission
函数用 polkit 对发送 org.gtk.vfs.file-operations
的程序做了鉴权。之后定义了一些文件操作,似乎都直接访问了对应的文件。看到最后 g_vfs_backend_admin_pre_setup
发现调用了 acquire_caps
函数,猜测可能和这个现象有关。
UID 与 GID
acquire_caps
首先出现了 seteuid
和 setfsuid
两个函数。顾名思义,是设置 euid
和 fsuid
。那么这两个是干什么的?
通常来说,大多数进程携带了传统的 Unix 凭据。这包括
- 一组 uid(用户 id)。其中有
- ruid(真实用户 id),标识了那个用户启动了这个进程。
- euid(有效用户 id),这个 id 用于大多数的权限检查,当我们
ps -ef
时,显示的 uid 就是 euid。 - suid(保存用户 id),用于使程序临时提升权限。
- fsuid(文件系统用户 id),用于文件操作的权限检查。
- 一组 gid(组 id),意义和 uid 基本一样,包括
- rgid
- egid
- sgid
- fsgid
可以看到,在 acquire_caps
中,先调用了 seteuid
,将 euid
设置为了用户的 uid
使其可以连接至会话总线,接下来设置 fsuid
为 root
权限。可以通过 ps
命令查看进程的各种 id。 1
2$ ps -ax -o euid -o fsuid -o pid -o cmd | grep gvfsd-admin
1000 0 12500 /usr/lib/gvfsd-admin --spawner :1.12 /org/gtk/gvfs/exec_spaw/5 --address unix:path=/run/user/1000/bus --dir /run/user/1000
然后,在 acquire_caps
中还可以看到 capset
函数和注释中提到的 capabilities
,这些是什么?
能力(capabilities)
传统 Unix 凭据的缺点在于,一旦一个进程携带了 root
的 uid,这个进程就可以获取完整的 root
权限。然而 root
权限涵盖的内容是很广的,大多数程序只需要其中的一部分。例如,加入你是一个操作文件的程序,你就不需要可以设置 fwmark 的权限。为了防止进程利用完整的 root
权限为所欲为,能力就被引入了。
能力标识了一个进程超出一般进程的权限。给予进程其需要的最小的能力可以防止 root
权限的滥用。在 acquire_caps
中调用了 capset
就使 gvfsd-admin 丢弃了它不需要的能力,仅保留文件操作的能力。你可以在 capabilities 的手册页找到能力的完整列表。
看起来,linux 的安全机制已经相当完善了,可以有效的避免一个进程滥用 root
权限,对吗?
从此过上了幸福的生活?
我们刚刚说过,gvfsd-admin 连接至了会话总线,gfvsd-admin 处理总线上发来的消息,进行对应的文件操作。那么,那些进程可以发送消息给 gvfsd-admin 呢?答案是,所有连接至会话总线的进程都可以。只要一个进程想,它可以随机发送消息给 gfvsd-admin,要求其进行需要 root
权限才能进行的文件操作(当然,一般需要过一次 polkit
鉴权)。在会话总线提供其它操作的 daemon 也一样可以被任何连接至会话总线的进程要求使用对应的 root
权限。这些连接至会话总线的进程的权限根本就没有被限制。不过,你可以配置那些进程可以访问那些接口,或者要求访问对应接口时过一次 polkit
鉴权来限制进程的权限。此外,也可以使用 apparmor 来进一步限制进程的权限。