之前为了最小化进程权限,主导设计了DAC进程降权方案并在全业务线进行推广,对所有进程的进行了降权,解决了所有进程以root运行的乱象,对于个别需要特权操作的进程通过启动时赋予capibilities能力解决。此方案运行良好很长一段时间,最近有业务线反馈说需要使用mount,但是配置capibilities为ALL都没有用,必须使用root启动。当时我把震惊了,之前针对capibilities做了深入研究分析,只有root进程能执行而配置了capibilities的普通进程的不能执行的操作是有,但非常少,而mount需要root才能执行让我震惊,决定花个把小时分析一把。
在ubuntu简单复现一下错误现象,先用root成功运行
# 生成镜像并格式化(普通用户可操作)
dd if=/dev/zero of=test.img bs=1M count=100
mkfs.ext4 test.img
# 挂载时需使用 sudo(关键步骤)
sudo mkdir -p /mnt/image
sudo mount -o loop test.img /mnt/image # "-o loop" 需 root 权限
# 卸载时同样需要 sudo
sudo umount /mnt/image
再用非root报错
mount -o loop test.img /mnt/image
错误输出如下
mount: only root can use "--options" option
给进程赋予所有capibilities再看
cp /usr/bin/mount .
sudo setcap all=ep ./mount
getcap ./mount
输出: ./mount =ep #表示所有cap设置成功,可用交互终端bash替换mount运行来验证,cat /proc/pid/status
mount -o loop test.img /mnt/image #再次运行
输出: mount: only root can use "--options" option #还是不行
以上从直接运行mount命令看,确实只有root才能运行带选项mount命令,但真的是这样吗?
用strace分析一下有无sudo运行的差异
未使用sudo跟踪系统调用如下
strace mount -o loop test.img /mnt/image
输出:
...
getuid() = 1000
geteuid() = 1000
getuid() = 1000
geteuid() = 1000
write(2, "mount: ", 7mount: ) = 7
write(2, "only root can use \"--options\" op"..., 36only root can use "--options" option) = 36
...
使用sudo跟踪系统调用如下
sudo strace mount -o loop test.img /mnt/image
输出:
...
geteuid() = 0
getegid() = 0
getuid() = 0
getgid() = 0
access("/run/mount/utab", R_OK|W_OK) = 0
mount("/dev/loop15", "/mnt/image", "ext4", 0, NULL) = 0
...
对比两个系统调用,很明显,未使用sudo根本没有运行到mount这个系统调用就报错了,而且结合前面有getuid和geteuid的调用,猜想是在用户态程序判断当前不是root,直接就拒绝往下运行了。
查看内核源码mount系统调用需要CAP_SYS_ADMIN能力
// fs/namespace.c 文件搜
if (!ns_capable(sb->s_user_ns, CAP_SYS_ADMIN))
写个代码验证一下。
步骤 1:检查可用的循环设备
# 查看当前已使用的循环设备
losetup -a
# 找到第一个可用的循环设备(如 /dev/loop15)
losetup -f
输出:/dev/loop15
步骤 2:将 test.img 绑定到循环设备
# 将 test.img 绑定到 /dev/loop15(需要 root 权限)
sudo losetup /dev/loop15 test.img
步骤 3:用带cap的程序挂载循环设备到目标目录
// 创建挂载点(如 /mnt/image)
// sudo mkdir -p /mnt/image
// 用程序挂载 /dev/loop15(文件系统类型为 ext4)
// sudo mount /dev/loop15 /mnt/image -t ext4 如下程序类似实现这个替代吗命令
// mount_demo.c
#include <sys/mount.h>
#include <stdio.h>
#include <stdlib.h>
int main() {
// 挂载 /dev/loop15 到 /mnt/image (ext4)
if (mount("/dev/loop15", "/mnt/image", "ext4", 0, NULL) == 0) {
printf("挂载成功!\n");
} else {
perror("挂载失败");
return 1;
}
return 0;
}
//编译程序
//gcc mount_demo.c -o mount_demo
//未设置权限直接运行
//$./mount_demo
//输出:挂载失败: Operation not permitted
//给程序添加 CAP_SYS_ADMIN 能力(需要 root 权限)
//sudo setcap CAP_SYS_ADMIN=ep mount_demo
//getcap mount_demo
//输出:mount_demo = cap_sys_admin+ep
//再运行 ./mount_demo
//输出:挂载成功!
从以上输出可以看出使用CAP_SYS_ADMIN能力直接调用moutn接口运行成功了。而且mount没权限提示的是“Operation not permitted”错误。
步骤 4:验证挂载
# 查看挂载内容
ls /mnt/image
输出:lost+found
步骤 5:卸载与清理
完成后需依次卸载挂载点和循环设备:
# 卸载挂载点
sudo umount /mnt/image
# 断开循环设备与 test.img 的绑定
# 如下也可以非root用CAP_SYS_ADMIN,CAP_DAC_OVERRIDE两个cap来解决
sudo losetup -d /dev/loop15
//...
while ((c = getopt_long(argc, argv, "aBcfFhilL:Mno:O:rRsU:vVwt:T:",
longopts, NULL)) != -1) {
/* only few options are allowed for non-root users */
if (mnt_context_is_restricted(cxt) && //如果是非root
!strchr("hlLUVvrist", c) && //非这些选项
c != MOUNT_OPT_TARGET &&
c != MOUNT_OPT_SOURCE)
exit_non_root(option_to_longopt(c, longopts)); //输出非root提示
//...
static void __attribute__((__noreturn__)) exit_non_root(const char *option)
{
const uid_t ruid = getuid();
const uid_t euid = geteuid();
if (ruid == 0 && euid != 0) {
/* user is root, but setuid to non-root */
if (option)
errx(MOUNT_EX_USAGE, _("only root can use \"--%s\" option "
"(effective UID is %u)"),
option, euid);
errx(MOUNT_EX_USAGE, _("only root can do that "
"(effective UID is %u)"), euid);
}
if (option)
errx(MOUNT_EX_USAGE, _("only root can use \"--%s\" option"), option);
errx(MOUNT_EX_USAGE, _("only root can do that"));
}
mnt_context_is_restricted & mnt_new_context
/**
* mnt_context_is_restricted:
* @cxt: mount context
*
* Returns: 0 for an unrestricted mount (user is root), or 1 for non-root mounts
*/
int mnt_context_is_restricted(struct libmnt_context *cxt)
{
return cxt->restricted; //返回这个成员变量,从函数注释可以看到是root判断
}
/**
* mnt_new_context:
*
* Returns: newly allocated mount context
*/
struct libmnt_context *mnt_new_context(void)
{
struct libmnt_context *cxt;
uid_t ruid;
cxt = calloc(1, sizeof(*cxt));
if (!cxt)
return NULL;
ruid = getuid();
mnt_context_reset_status(cxt);
cxt->ns_orig.fd = -1;
cxt->ns_tgt.fd = -1;
cxt->ns_cur = &cxt->ns_orig;
cxt->map_linux = mnt_get_builtin_optmap(MNT_LINUX_MAP);
cxt->map_userspace = mnt_get_builtin_optmap(MNT_USERSPACE_MAP);
INIT_LIST_HEAD(&cxt->hooksets_hooks);
INIT_LIST_HEAD(&cxt->hooksets_datas);
/* if we're really root and aren't running setuid */
//这边根据root来设置
cxt->restricted = (uid_t) 0 == ruid && !is_privileged_execution() ? 0 : 1;
cxt->noautofs = 0;
DBG(CXT, ul_debugobj(cxt, "----> allocate %s",
cxt->restricted ? "[RESTRICTED]" : ""));
return cxt;
}
到此,结论很明显,mount系统调用只需要CAP_SYS_ADMIN能力就可以了,而linux系统自带的mount命令工具自作聪明,在用户态程序中做了一些不合适的判断,只要检测到非root就退出了,未能继续往下执行了。因此,只要重写mount程序,或者自己写程序直接调用即可。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。