nix - 直接运行二进制文件工作正常,但 execve 失败

标签 nix execve

我有一个非常好奇的案例,我花了一周时间尝试调试但无济于事。对于上下文,我想开始使用 nix 包管理器,并且我从 Alpine 基础镜像之一为自己创建了一个小型 chroot 环境。我已经成功安装了 nix 包管理器,如下所示:

# nix --version
nix (Nix) 2.9.2

我已经执行了通常的操作

# nix-channel --add "https://nixos.org/channels/nixpkgs-unstable"

并通过运行验证其是否成功运行:

# nix-channel --list
nixpkgs https://nixos.org/channels/nixpkgs-unstable

但是,当我尝试运行 nix-channel --update 时,我得到:

# nix-channel --update
unpacking channels...
error: executing '/usr/bin/nix-env': No such file or directory
error: program '/usr/bin/nix-env' failed with exit code 1

好吧,它说 nix-env 不可用,但是:

# /usr/bin/nix-env --version
nix-env (Nix) 2.9.2
# type -p nix-env
/usr/bin/nix-env
# nix-env --version
nix-env (Nix) 2.9.2

它确实存在,所以我开始深入挖掘,并进行我的 strace(相关摘录):

[pid 30861] vfork(strace: Process 30880 attached
 <unfinished ...>
[pid 30880] prctl(PR_SET_PDEATHSIG, SIGKILL) = 0
[pid 30880] dup2(12, 1)                 = 1
[pid 30880] rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0
[pid 30880] getcwd("/", 4096)           = 2
[pid 30880] setns(3, CLONE_NEWNS)       = 0
[pid 30880] chdir("/")                  = 0
[pid 30880] prlimit64(0, RLIMIT_STACK, NULL, {rlim_cur=65536*1024, rlim_max=RLIM64_INFINITY}) = 0
[pid 30880] prlimit64(0, RLIMIT_STACK, {rlim_cur=8192*1024, rlim_max=RLIM64_INFINITY}, NULL) = 0
[pid 30880] mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7efc7fa67000
[pid 30880] execve("/usr/bin/nix-env", ["/usr/bin/nix-env", "--profile", "/nix/var/nix/profiles/per-user/r"..., "--file", "/tmp/nix.pmkgai", "--install", "--remove-all", "--from-expression", "f: f { name = \"nixpkgs\"; channel"..., "--quiet"], 0x7ffc38f7aac0 /* 11 vars */) = -1 ENOENT (No such file or directory)

相当奇怪。出于好奇,我获取了 nix 源代码,并在 execve 调用周围添加了一些调试信息。手动运行时,抓取执行的命令行可以在控制台上正常工作。将对 /usr/bin/nix-env 的调用替换为(稍微修改的版本)/bin/echo,它成功执行并产生了我期望的结果(nix 家伙)确实应该考虑提供更好的运行时调试信息,因为目前 nix-env is execved with --quiet flag 且输出为 not even propagated back to the user ,因此不可能知道发生了什么,但这是另一天的 GitHub 问题 /rant off)。

无论如何,我有点离题了。我的问题是,这可能是什么原因造成的?为什么 nix-env 在命令行中工作得很好,但在 execve 下却失败了?

我按照惯例检查了预期的解释器:

# readelf -l /usr/bin/nix-env | grep interpreter
      [Requesting program interpreter: /lib/ld-musl-x86_64.so.1]
# ls -la /lib/ld-musl-x86_64.so.1
-rwxr-xr-x    1 root     root        604704 Apr  8 05:38 /lib/ld-musl-x86_64.so.1

以及确保所有动态库都得到正确解析:

# ldd /usr/bin/nix-env 
    /lib/ld-musl-x86_64.so.1 (0x7fc0b3d20000)
    libnixexpr.so => /usr/lib/libnixexpr.so (0x7fc0b378a000)
    libgc.so.1 => /usr/lib/libgc.so.1 (0x7fc0b3721000)
    libnixmain.so => /usr/lib/libnixmain.so (0x7fc0b36d7000)
    libnixfetchers.so => /usr/lib/libnixfetchers.so (0x7fc0b35dd000)
    libnixstore.so => /usr/lib/libnixstore.so (0x7fc0b3229000)
    libnixutil.so => /usr/lib/libnixutil.so (0x7fc0b30eb000)
    libnixcmd.so => /usr/lib/libnixcmd.so (0x7fc0b3027000)
    libstdc++.so.6 => /usr/lib/libstdc++.so.6 (0x7fc0b2dd9000)
    libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x7fc0b2dba000)
    libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7fc0b3d20000)
    libboost_context.so.1.80.0 => /usr/lib/libboost_context.so.1.80.0 (0x7fc0b2db5000)
    libsqlite3.so.0 => /usr/lib/libsqlite3.so.0 (0x7fc0b2cbd000)
    libcurl.so.4 => /usr/lib/libcurl.so.4 (0x7fc0b2c3f000)
    libsodium.so.23 => /usr/lib/libsodium.so.23 (0x7fc0b2bed000)
    libseccomp.so.2 => /usr/lib/libseccomp.so.2 (0x7fc0b2bd1000)
    libcrypto.so.3 => /usr/lib/libcrypto.so.3 (0x7fc0b2817000)
    libbrotlidec.so.1 => /usr/lib/libbrotlidec.so.1 (0x7fc0b280b000)
    libbrotlienc.so.1 => /usr/lib/libbrotlienc.so.1 (0x7fc0b2787000)
    libarchive.so.13 => /usr/lib/libarchive.so.13 (0x7fc0b26e0000)
    libcpuid.so.15 => /usr/lib/libcpuid.so.15 (0x7fc0b26c5000)
    libeditline.so.1 => /usr/lib/libeditline.so.1 (0x7fc0b26ba000)
    libnghttp2.so.14 => /usr/lib/libnghttp2.so.14 (0x7fc0b2691000)
    libssl.so.3 => /usr/lib/libssl.so.3 (0x7fc0b25fc000)
    libz.so.1 => /lib/libz.so.1 (0x7fc0b25e2000)
    libbrotlicommon.so.1 => /usr/lib/libbrotlicommon.so.1 (0x7fc0b25bf000)
    libacl.so.1 => /lib/libacl.so.1 (0x7fc0b25b5000)
    libexpat.so.1 => /usr/lib/libexpat.so.1 (0x7fc0b2590000)
    liblzma.so.5 => /usr/lib/liblzma.so.5 (0x7fc0b256d000)
    libzstd.so.1 => /usr/lib/libzstd.so.1 (0x7fc0b24f7000)
    liblz4.so.1 => /usr/lib/liblz4.so.1 (0x7fc0b24d8000)
    libbz2.so.1 => /usr/lib/libbz2.so.1 (0x7fc0b24c9000)

任何正确方向的指示都将受到高度赞赏。

编辑:添加 strace -v 运行以查看传递给 nix-env 调用的环境(如其中所建议的)评论):

[pid 18800] execve("/usr/bin/nix-env", ["/usr/bin/nix-env", "--profile", "/nix/var/nix/profiles/per-user/r"..., "--file", "/tmp/nix.aCocaf", "--install", "--remove-all", "--from-expression", "f: f { name = \"nixpkgs\"; channel"..., "--quiet"], ["CHARSET=UTF-8", "PWD=/root", "HOME=/root", "LANG=C.UTF-8", "TMPDIR=/tmp", "SHLVL=1", "PAGER=less", "PS1=\\h:\\w\\$ ", "LC_COLLATE=C", "PATH=/nix/var/nix/profiles/defau"..., "OLDPWD=/root", "_=/usr/bin/strace"]) = -1 ENOENT (No such file or directory)

不幸的是,没有 LD_* 环境变量被操作或传递给 nix-env 调用。

编辑:进一步挖掘,我构建了一个非常小的静态链接 Rust 应用程序,该应用程序在执行时将命令行和环境写入日志文件,并替换了 nix- env 与它。遇到同样的问题。

最佳答案

你说你使用的是chroot环境。您的 strace 日志包含 setns(CLONE_NEWNS),这意味着 nix 使用挂载命名空间。通过查看nix源代码,似乎当nix启动时,它会保存当前的挂载命名空间,并通过setns调用将其恢复到 fork 子级中。也许,这样 nix 可以从 chroot 逃逸到一个你的二进制文件不可用的地方。这可以解释为什么 nix-env 和 Rust 静态二进制文件都无法执行,但 /bin/echo 可以(前提是您有 /bin/echo 也在你的 chroot 之外)。

关于nix - 直接运行二进制文件工作正常,但 execve 失败,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73401455/

相关文章:

python - 试图从/nix/store 中删除一个包,现在系统出现错误,如何修复?

nix - 如何在 Nix 环境中安装 PostGIS

haskell - 使用 Reflex Platform 编译带有本地包的堆栈项目

linux - 为什么 do_execve() 会分两步而不是一步执行可执行验证检查?

c - linux execve,段错误(strcmp_sse42)

nix - Nix 中的 buildPackages 是什么?

ruby - 如何使用 Nix 而不是 Homebrew 在 OS X 上设置 Ruby?

c++ - fork() 之后,如何在 for() 循环中继续运行 execve()?

C execve() 参数 [生成一个 shell 示例]

linux - execve后,前一个进程addr中的内存是否被释放?