Linux 有没有什么工具或方法能实现用普通用户运行本来是需要用 root 用户才能执行的程序?

97 天前
 wniming

主要是想用普通用户执行 dnf --installroot=/path/to/fedora_rootfs 这个命令,这个命令如果不用 root 用户执行会有如下报错:

Error: This command has to be run with superuser privileges (under the root user on most systems).

这个命令可以在非特权的 docker 容器内用容器的 root 用户执行运行,这样是可以实现不使用 host 的 root 用户运行的需求,但依赖 docker ,不太方便,我想找到一种用 nsenter 或 unshare 之类的命令来运行的方法。

本质上是想使用 linux 的 user namespace 功能来造一个 root 用户,这个 root 用户映射到系统的普通用户,想用这个 root 用户执行 dnf --installroot=/path/to/fedora_rootfs 来从外部给 fedora 虚拟机安装 rpm 包(我的虚拟机的磁盘镜象是 ext4 文件系统,可以用 fuse2fs -o fakeroot ~/fedora.raw /path/to/fedora_rootfs 命令实现用普通用户挂载,而且可以用普通用户去读写磁盘镜象内属于 root 用户的文件, 如果能实现用普通用户运行 dnf --installroot=/path/to/fedora_rootfs 的话,我就可以避免用 host 的 root 用户来管理虚拟机的磁盘镜象 )。

4492 次点击
所在节点    Linux
34 条回复
guo4224
96 天前
1 楼就是标准答案
yanqiyu
96 天前
@guo4224 dnf 是个脚本,你要是 setuid 那就只能安排给 python 了
yanqiyu
96 天前
@wniming
bwrap 能达到很“类似”的效果:
bwrap --dev-bind / / --unshare-user --uid 0 --gid 0 $SHELL
但是不一样的是 bwrap --dev-bind / / --unshare-user --uid 0 --gid 0 cat /proc/self/uid_map 输出是 0 1000 1
但是
yanqiyu
96 天前
草按错了,接上文:
podman unshare -- cat /proc/self/uid_map 输出的是
0 1000 1
1 100000 65536

这样 podman 创建的 uidmap 有完整的 uid range ,有些程序可能需要这个。

虽然技术上靠 unshare 也能实现,但是我更倾向于用 podman unshare 糊弄
wniming
96 天前
@guo4224 #21
@yanqiyu #22

实际上 setuid 是无法满足我的这个需求的,原因跟我 #8 楼说的一样,比如我给 python 设置了 s 标志:

chmod u+s /usr/bin/python3.12

然后执行 dnf:

d@develop:~$ dnf --installroot=/home/d/.local/mnt/0/ --releasever=/ --config /etc/dnf/dnf.conf install tar
Config error: [Errno 13] Permission denied: '/home/d/.local/mnt/0/var': '/home/d/.local/mnt/0/var'
d@develop:~$
churchmice
96 天前
setuid 不适用于脚本,只能搞 binary ,setuid 如果支持脚本是有安全风险的
但是用 c 语言做下 wrap 就可以了
wniming
96 天前
@guo4224 #21
@yanqiyu #22

刚才又试了一下不用 fuse2fs -o fakeroot 这种方式挂载的目录,就用普通的目录作为 rootfs 会怎样,结果还是不行:

-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Total 1.6 MB/s | 11 MB 00:06
Fedora 40 - x86_64 1.6 MB/s | 1.6 kB 00:00
Importing GPG key 0xA15B79CC:
Userid : "Fedora (40) <fedora-40-primary@fedoraproject.org>"
Fingerprint: 115D F9AE F857 853E E844 5D0A 0727 707E A15B 79CC
From : /etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-40-x86_64
Is this ok [y/N]: y
Key imported successfully
Running transaction check
Transaction check succeeded.
Running transaction test
RPM: error: Unable to change root directory: Permission denied
[Errno 13] Permission denied: '/home/d/.local/mnt/2/var/lib/dnf/rpmdb_lock.pid'
The downloaded packages were saved in cache until the next successful transaction.
You can remove cached packages by executing 'dnf clean packages'.
Failed to store expired repos cache: [Errno 13] Permission denied: '/home/d/.local'
[Errno 13] Permission denied: '/home/d/.local'
d@develop:~$
wniming
96 天前
@yanqiyu #24

dnf --installroot 应该是只需要 root 用户的,但是实测是不行的,不管是 fuse2fs -o fakeroot 还是普通的目录都报一样的错:

d@develop:~$ bwrap --dev-bind / / --unshare-user --uid 0 --gid 0 dnf --installroot=/home/d/.local/mnt/1/ --releasever=/ --config /etc/dnf/dnf.conf install vi
Last metadata expiration check: 0:05:21 ago on Sun 30 Jun 2024 05:57:21 PM CST.
Dependencies resolved.
===================================================================================================================================================================================================================
Package Architecture Version Repository Size
===================================================================================================================================================================================================================
Installing:
vim-minimal x86_64 2:9.1.158-1.fc40 fedora 806 k
Installing dependencies:
vim-data noarch 2:9.1.158-1.fc40 fedora 23 k

Transaction Summary
===================================================================================================================================================================================================================
Install 2 Packages

Total size: 829 k
Installed size: 1.6 M
Is this ok [y/N]: y
Downloading Packages:
[SKIPPED] vim-data-9.1.158-1.fc40.noarch.rpm: Already downloaded
[SKIPPED] vim-minimal-9.1.158-1.fc40.x86_64.rpm: Already downloaded
Running transaction check
Transaction check succeeded.
Running transaction test
RPM: error: Unable to change root directory: Operation not permitted
The downloaded packages were saved in cache until the next successful transaction.
You can remove cached packages by executing 'dnf clean packages'.
Error: Transaction test error:
Errors occurred during test transaction.

d@develop:~$
zbinlin
96 天前
简单地用 `unshare --map-auto --map-root-user COMMAND` 能不能解决?
wniming
96 天前
@zbinlin #29 可以,在我的这个使用场景下效果和 podman unshare 一样,你这个方法才是我发帖时最想得到的答案
yinmin
96 天前
我推荐一种方式,在 linux 的 ~/.profile 或者 ~/.bashrc 加一条 alias

alias ddnf='docker run --rm -v /home/d/.local/mnt/0/:/mnt/0 fedora:latest fedora4 dnf --installroot=/mnt/0/ --releasever=/ --setopt=reposdir=/etc/yum.repos.d/ --setopt=cachedir=/var/cache/dnf --config /etc/dnf/dnf.conf'

然后在 linux 命令行可以直接使用 ddnf install tar
kuanat
96 天前
主要问题是这个报错 `RPM: error: Unable to change root directory: Permission denied` 因为 dnf 是脚本,最终还要调用 rpm 的。dnf 的参数 `--installroot` 根据文档的描述是类似 chroot 之后 dnf 的,这个过程里 dnf 提前设置一下 rootdir 用到的配置文件等等。这个 chroot 的操作实际上是由 rpm 完成的。上面的报错是说 rpm 没有获得 CAP_SYS_CHROOT 这个权限导致的。(另外实际安装的时候也应该也需要 CAP_CHOWN 权限,也就是说单独给 chroot 权限不够,不过 chown 权限比较复杂,后面说)

同样因为 dnf 是个脚本,所以它只是机械地检查自身是否为 root 来判断自己能否执行。所以在不改动 dnf 的前提下,只能把 dnf 放到 root/uid0 去执行(不管是不是真的)。

fakeroot 的原理是通过 LD_PRELOAD 拦截并替换需要 root 权限的 syscall ,让被调用的程序认为自己是 root 。#9 失败的原因我猜测是 dnf 是个脚本,如果是 subprocess 的方式去调用 rpm ,那么 rpm 不会实际获得 chroot 权限。

顺便说一下 fuse 的原理,它是用户空间的实现,如果用 `-o fakeroot` 的话,相当于这个文件系统还是 ext4 ,原本所有操作都应该检查 ext4 里面的权限,开启 fakeroot 之后会强制可读可写,即 RWX 那一套不起控制作用。虽然名字也是 fakeroot 但不是一个实现方式。

所以理论上,只需要运行 `unshare -m -r dnf ...` 就可以创建一个 ns 让 dnf 以 root 权限运行,`-m` 创建 ns ,`-r` 将 ns 内的 uid 映射为 root 。

我个人不建议用上面的方式来运行,因为 `unshare -m -r dnf ...` 执行的是宿主机的 dnf 所以你需要传递一些 dnf 相关的参数,尽管 dnf 是做了很多工作,但还是有可能出问题。我更建议的方式是 `unshare -m -r chroot /path/to/fedora_rootfs dnf ...` 这样直接 chroot 进虚拟机系统,然后用虚拟机的 `dnf` 完成包管理。

另外实际上这样做还是太粗糙了,有几个问题:

- ns 里面环境变量不一定正确
- 一些特殊的 /proc 之类的文件系统不会绑定
- chown 需要额外的 uid/gid 映射表才能正常工作

所以一般会用 `podman unshare` 来解决上面这些麻烦。这个命令的实现来自于 `buildah unshare`,buildah 是 RH 系的构建系统,podman 也是 RH 开发的,所以就把这个功能用同样的方式实现了。

之所以要用 `podman unshare` 而不是直接用 `unshare` 是因为你自己处理 uid/gid 映射是比较麻烦且容易出错的。而 chown 的调用在 rpm 包中很常见,rpm 在安装包的时候会执行创建用户/组以及更改权限等相关的脚本。

当以非特权用户执行 unshare 的时候,只能够映射自身 uid 或者将自身映射为 fakeroot 。这个设计的初衷是防止 ns 内部冒充宿主的 root 以避免被漏洞提权。实现方式也比较简单粗暴,比如宿主机上 uid 范围是 0~65535 那么 ns 里面就是 100000~165535 这样,ns 里面的 0 对应的是宿主机上的用户。

所以实际上要满足 ns 中支持多 uid/gid ,那么宿主机上普通用户权限是不够的,但是又不希望以 root 权限创建 ns ,于是就通过具有 CAP_SETUID 权限的 /usr/bin/newuidmap 来实现。如果你要自己手动通过 `unshare` 来完成上述工作,就需要借助 newuidmap/newgidmap 。
wniming
96 天前
@kuanat #32

dnf --installroot=/path/to/fedora_rootfs 这个确实不如 chroot /path/to/fedora_rootfs dnf ,dnf --installroot= 这种方式会导致 dnf history 时显示的 Command line 会包含 installroot 相关的几个参数,如果我一次性在命令行指定很多个包时后面的包会显示不出来,dnf --installroot= 更适合在空目录创建全新的 base rootfs 。
0x5c0f
87 天前
好像没有看到人说用 sudoers 来处理权限管理,是这个不适用么

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1053687

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX