V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
wniming
V2EX  ›  Linux

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

  •  
  •   wniming · 3 天前 · 3147 次点击

    主要是想用普通用户执行 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 用户来管理虚拟机的磁盘镜象 )。

    33 条回复    2024-07-01 10:34:18 +08:00
    xscit
        1
    xscit  
       3 天前
    setuid
    sduoduo233
        2
    sduoduo233  
       3 天前 via Android
    https://github.com/proot-me/proot 我没试过,不过应该可以。android termux 就用 proot 实现免 root 的 chroot
    cccer
        3
    cccer  
       3 天前
    你提到可以用 docker 里的 root 执行,可实际上 docker 容器本身就需要 root 来启动,将用户加到 docker 组里面就相当于给了 root 权限,后面的同理。
    wniming
        4
    wniming  
    OP
       3 天前
    @cccer 不是的,我用的 docker 是完全不需要 root 权限的:

    https://docs.docker.com/engine/security/rootless/

    docker 服务都是用普通用户来运行的:

    d@develop:~$ systemctl --user start docker
    d@develop:~$ systemctl --user status docker
    ● docker.service - Docker Application Container Engine (Rootless)
    Loaded: loaded (/home/d/.config/systemd/user/docker.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/user/service.d
    └─10-timeout-abort.conf
    Active: active (running) since Sat 2024-06-29 18:44:10 CST; 19h ago
    Docs: https://docs.docker.com/go/rootless/
    Main PID: 5727 (rootlesskit)
    Tasks: 170
    Memory: 912.4M (peak: 1006.0M)
    CPU: 1min 17.179s
    CGroup: /user.slice/user-1000.slice/[email protected]/app.slice/docker.service
    wniming
        5
    wniming  
    OP
       3 天前
    @xscit 能具体一些吗?我就是想当伸手党才来问的,哈哈
    dhb233
        6
    dhb233  
       3 天前
    把 dnf 命令加到 sudo 列表里,普通用户用 sudo 运行
    wniming
        8
    wniming  
    OP
       3 天前 via Android
    @dhb233 不行的,因为我是用

    fuse2fs -o fakeroot ~/fedora.raw /path/to/fedora_rootfs

    这种方式来挂载虚拟机的磁盘镜像的,这种方式就只能用普通用户来读写挂载目录的文件,用 root 用户反而没有读写的权限(所以用 sudo 肯定也不行)。

    我觉得用基于命名空间的 root 用户是可以读写的,因为我把/path/to/fedora_rootfs 目录共享给 docker ,在 docker 内能用 root 用户读写这个目录,docker 的 root 用户就是通过命名空间技术映射到 host 的普通用户的。
    wniming
        9
    wniming  
    OP
       3 天前
    @0o0O0o0O0o fakeroot 的不是基于命名空间技术实现的,虽然可以用这个命令来骗过 dnf 命令, 让 dnf 命令不报那个需要特权用户的错,但执行到一半还是会报错:

    d@develop:~$ fakeroot dnf --installroot=/home/d/.local/mnt/0 --releasever=/ --config /etc/dnf/dnf.conf install tcpdump
    Last metadata expiration check: 0:04:10 ago on Sun 30 Jun 2024 02:58:04 PM CST.
    Dependencies resolved.
    ===================================================================================================================================================================================================================
    Package Architecture Version Repository Size
    ===================================================================================================================================================================================================================
    Installing:
    tcpdump x86_64 14:4.99.4-6.fc40 fedora 501 k

    Transaction Summary
    ===================================================================================================================================================================================================================
    Install 1 Package

    Total size: 501 k
    Installed size: 1.2 M
    Is this ok [y/N]: y
    Downloading Packages:
    [SKIPPED] tcpdump-4.99.4-6.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:~$
    wniming
        10
    wniming  
    OP
       3 天前
    @sduoduo233 这个不提供 x86_64 架构下的安装包,而且我比较倾向于用 fedora 官方支持的工具来实现。
    fugu37
        11
    fugu37  
       3 天前
    不想用 docker 就用 podman ,这个发行版仓库内就有
    0o0O0o0O0o
        12
    0o0O0o0O0o  
       3 天前
    @wniming #9 只看了标题。。。那 bubblewrap ?不过可能需要调试出合适的参数

    `bwrap --ro-bind / / --unshare-all --uid 0 id -u`
    leonshaw
        13
    leonshaw  
       3 天前
    user namespace 应该可以,但是你需要一个 owner 是这个用户的 dnf 。比如说先 unshare ,再另装一套 dnf ,但是这可能又需要 chroot 或者 mount namespace... 最后可能还不如用容器简单。
    yanqiyu
        14
    yanqiyu  
       3 天前
    我一般就直接 podman unshare -- ... 来实现了,这样还能获得一个完整的 uid 命名空间,因为做镜像之类的时候搞不好要用到除了 0 之外的 uid
    SenLief
        15
    SenLief  
       3 天前
    podman
    wniming
        16
    wniming  
    OP
       3 天前
    @yanqiyu 你的这种方法完美解决了我的问题:

    podman unshare dnf --installroot=/home/d/.local/mnt/0/ --releasever=/ --config /etc/dnf/dnf.conf install tar
    crazyliu
        17
    crazyliu  
       3 天前
    setuid, 只要打上这个标记就行了
    xscit
        18
    xscit  
       3 天前
    @wniming chmod +s 就可以让其他用户执行 root 的东西,具体可以搜一下
    0o0O0o0O0o
        19
    0o0O0o0O0o  
       3 天前 via iPhone
    #16 所以你接受依赖 podman ,但不接受依赖 docker rootless 吗…
    wniming
        20
    wniming  
    OP
       3 天前
    @0o0O0o0O0o #19 用 podman 比 docker rootless 更好,主要有以下 2 点

    1 ,podman 的安装比 docker rootless 更方便,一条 dnf 命令搞定
    2 ,docker 貌似没有类似 podman 的 unshare 子命令,用 docker 的话我就必须再依赖一个 fedora 的 container , 还要做个目录共享,例如:

    docker run --name fedora4 -itd -p 2223:22 -v /home/d/.local/mnt/0/:/mnt/0 fedora:latest

    然后用如下命令来实现:

    docker exec -it fedora4 dnf --installroot=/mnt/0/ --releasever=/ --setopt=reposdir=/etc/yum.repos.d/ --setopt=cachedir=/var/cache/dnf --config /etc/dnf/dnf.conf install tar


    如果用 bwrap 也能做到类似 podman unshare 的效果的话我也很乐意尝试一下,但暂时不打算研究了。
    guo4224
        21
    guo4224  
       3 天前 via iPhone
    1 楼就是标准答案
    yanqiyu
        22
    yanqiyu  
       3 天前
    @guo4224 dnf 是个脚本,你要是 setuid 那就只能安排给 python 了
    yanqiyu
        23
    yanqiyu  
       3 天前
    @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
        24
    yanqiyu  
       3 天前
    草按错了,接上文:
    podman unshare -- cat /proc/self/uid_map 输出的是
    0 1000 1
    1 100000 65536

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

    虽然技术上靠 unshare 也能实现,但是我更倾向于用 podman unshare 糊弄
    wniming
        25
    wniming  
    OP
       3 天前
    @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
        26
    churchmice  
       3 天前 via Android
    setuid 不适用于脚本,只能搞 binary ,setuid 如果支持脚本是有安全风险的
    但是用 c 语言做下 wrap 就可以了
    wniming
        27
    wniming  
    OP
       3 天前
    @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) <[email protected]>"
    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
        28
    wniming  
    OP
       3 天前
    @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
        29
    zbinlin  
       3 天前   ❤️ 1
    简单地用 `unshare --map-auto --map-root-user COMMAND` 能不能解决?
    wniming
        30
    wniming  
    OP
       3 天前
    @zbinlin #29 可以,在我的这个使用场景下效果和 podman unshare 一样,你这个方法才是我发帖时最想得到的答案
    yinmin
        31
    yinmin  
       3 天前
    我推荐一种方式,在 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
        32
    kuanat  
       3 天前   ❤️ 1
    主要问题是这个报错 `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
        33
    wniming  
    OP
       3 天前
    @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 。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5391 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 30ms · UTC 03:18 · PVG 11:18 · LAX 20:18 · JFK 23:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.