Runsisi's Blog
不念过去 不畏将来
mount/umount 的 root 检测

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

$ 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:

$ 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
$ 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 的权限检测

// 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 的权限检测

// 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/


最后修改于 2020-12-21