mount/umount 命令在应用层做了强制的权限检测,通过赋予普通用户进程 CAP_SYS_ADMIN capability 不能解决普通用户进程没有权限执行 mount/umount 命令的问题,需要为普通用户添加 sudo 权限。

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
$ export LIBMOUNT_DEBUG=0xffff
$ umount /media
74375: libmount: don't print memory addresses (SUID executable).
74375: libmount: INIT: library debug mask: 0x100ffff
74375: libmount: INIT: library version: 2.34.0
74375: libmount: INIT: feature: selinux
74375: libmount: INIT: feature: smack
74375: libmount: INIT: feature: btrfs
74375: libmount: INIT: feature: namespaces
74375: libmount: INIT: feature: assert
74375: libmount: INIT: feature: debug
Available "LIBMOUNT_DEBUG=<name>[,...]|<mask>" debug masks:
all [0xffff] : info about all subsystems
cache [0x0004] : paths and tags cache
cxt [0x0200] : library context (handler)
diff [0x0400] : mountinfo changes tracking
fs [0x0040] : FS abstraction
help [0x0001] : this help
locks [0x0010] : mtab and utab locking
loop [0x2000] : loop devices routines
options [0x0008] : mount options parsing
tab [0x0020] : fstab, mtab, mountinfo routines
update [0x0080] : mtab, utab updates
utils [0x0100] : misc library utils
monitor [0x0800] : mount tables monitor
btrfs [0x1000] : btrfs specific routines
74375: libmount: CXT: ----> allocate [RESTRICTED]
74375: libmount: CXT: umount: /media
74375: libmount: CXT: umount: lookup FS for '/media'
...

构建 mount/umount 命令行需要使能 libblkid 与 libmount:

1
2
3
4
5
6
$ git clone https://github.com/karelzak/util-linux.git
$ cd util-linux/
$ git co v2.34
$ ./autogen.sh
$ ./configure CFLAGS="-g -O0" --prefix=/usr --disable-all-programs --enable-libblkid --enable-libmount --enable-mount
$ make
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ cd .libs
$ ls
libblkid.a libblkid.lai libblkid.so.1 libcommon.a libmount.a libmount.lai libmount.so.1 libmount.so.1.1.0T libtcolors.la umount
libblkid.la libblkid.so libblkid.so.1.1.0 libcommon.la libmount.la libmount.so libmount.so.1.1.0 libtcolors.a mount
$ export LD_LIBRARY_PATH=$PWD
$ gdb ./umount
(gdb) set args /media
(gdb) b mnt_context_prepare_umount
Function "mnt_context_prepare_umount" not defined.
Make breakpoint pending on future shared library load? (y or [n]) y
Breakpoint 1 (mnt_context_prepare_umount) pending.
(gdb) r
Starting program: /home/runsisi/working/cpp/util-linux/.libs/umount /media
[Detaching after fork from child process 76658]

Breakpoint 1, mnt_context_prepare_umount (cxt=0x555555560538) at libmount/src/context_umount.c:877
877 {

虽然 CAP_SYS_ADMIN capability 权限允许调用 mount/umount 系统调用,但是 mount/umount 命令自身会进行 uid 检测,也就是说,必须 root 用户才能执行 mount/umount 操作,以 util-linux v2.34 版本为例。

mount 的权限检测

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
// libmount/src/context.c

struct libmnt_context *mnt_new_context(void)
{
struct libmnt_context *cxt;
uid_t ruid, euid;

cxt = calloc(1, sizeof(*cxt));
if (!cxt)
return NULL;

// 非 root 用户操作受限

/* if we're really root and aren't running setuid */
cxt->restricted = (uid_t) 0 == ruid && ruid == euid ? 0 : 1;

return cxt;
}

// sys-utils/mount.c

main
if (argc == 2 && !mnt_context_get_source(cxt)
&& !mnt_context_get_target(cxt)) {
// 非 root 用户不允许进行 mount 操作

/*
* D) mount <source> <target>
*/
if (mnt_context_is_restricted(cxt))
exit_non_root(NULL);

mnt_context_set_source(cxt, argv[0]);
mnt_context_set_target(cxt, argv[1]);
}

umount 的权限检测

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// libmount/src/context_umount.c

static int evaluate_permissions(struct libmnt_context *cxt)
{
struct libmnt_table *fstab;
unsigned long u_flags = 0;
const char *tgt, *src, *optstr;
int rc = 0, ok = 0;
struct libmnt_fs *fs;

// root 用户直接允许 umount
if (!mnt_context_is_restricted(cxt))
return 0; /* superuser mount */

// 非 root 用户必须要检测 /etc/fstab 中是否定义了 user, users, owner, group 之类的选项
// 选项在如下的文件中进行定义:
// https://github.com/karelzak/util-linux/blob/v2.34/libmount/src/optmap.c

/*
* User mounts have to be in /etc/fstab
*/
rc = mnt_context_get_fstab(cxt, &fstab);
if (rc)
return rc;

tgt = mnt_fs_get_target(cxt->fs);
src = mnt_fs_get_source(cxt->fs);

fs = mnt_table_find_pair(fstab, src, tgt, MNT_ITER_FORWARD);
if (!fs) {
/*
* It's possible that there is /path/file.img in fstab and
* /dev/loop0 in mtab -- then we have to check the relation
* between loopdev and the file.
*/
fs = mnt_table_find_target(fstab, tgt, MNT_ITER_FORWARD);
if (fs) {
struct libmnt_cache *cache = mnt_context_get_cache(cxt);
const char *sp = mnt_fs_get_srcpath(cxt->fs); /* devname from mtab */
const char *dev = sp && cache ? mnt_resolve_path(sp, cache) : sp;

if (!dev || !is_associated_fs(dev, fs))
fs = NULL;
}
if (!fs) {
DBG(CXT, ul_debugobj(cxt,
"umount %s: mtab disagrees with fstab",
tgt));
goto eperm;
}
}

// /etc/fstab 中找到了相关的 mount 条目,接下来检测 user, users, owner, group 之类的选项

/*
* User mounting and unmounting is allowed only if fstab contains one
* of the options `user', `users' or `owner' or `group'.
*
* The option `users' allows arbitrary users to mount and unmount -
* this may be a security risk.
*
* The options `user', `owner' and `group' only allow unmounting by the
* user that mounted (visible in mtab).
*/
optstr = mnt_fs_get_user_options(fs); /* FSTAB mount options! */
if (!optstr)
goto eperm;

if (mnt_optstr_get_flags(optstr, &u_flags,
mnt_get_builtin_optmap(MNT_USERSPACE_MAP)))
goto eperm;

eperm:
// 不满足权限检测要求,返回错误 umount failed: Operation not permitted.

DBG(CXT, ul_debugobj(cxt, "umount is not allowed for you"));
return -EPERM;
}

参考资料

Privilage elevation in Processes

https://nick.readthedocs.io/en/latest/Security/elevated_privilages/