当构建上下文位于由另一名用户创建、权限为0700的临时目录中时,Podman的 RUN --mount=type=bind会出现“权限被拒绝”的错误

后端开发 2026-07-08

我通过一个以root身份运行的podman systemd套接字来执行podman build (CONTAINER_HOST=unix:///var/run/podman/podman.sock)。构建使用带有 RUN --mount=type=bind 的Dockerfile:

  FROM quay.io/centos/centos:stream9
  USER 0
  RUN --mount=type=bind,source=utils/dnf-helper.sh,target=/utils/dnf-helper.sh,ro \
      /utils/dnf-helper.sh install perl

构建上下文是由Python的 tempfile.TemporaryDirectory() 创建的临时目录,该目录设置了0700的权限:

  drwx------ runner runner /home/runner/.local/share/containers/tmpdir/tmpXXXXXX/
  drwxr-xr-x runner runner /home/runner/.local/share/containers/tmpdir/tmpXXXXXX/utils/
  -rwxr-xr-x runner runner /home/runner/.local/share/containers/tmpdir/tmpXXXXXX/utils/dnf-helper.sh

构建在 RUN --mount=type=bind 步骤失败:

  /bin/sh: line 1: /utils/dnf-helper.sh: Permission denied

构建命令包含 --security-opt label=disable。当直接以sudo podman build调用(非通过套接字)时,同样的Dockerfile能成功构建。

环境:

  • Ubuntu 24.04的 GitHub Actions运行器
  • 通过Homebrew (Linuxbrew) 安装的Podman,以root权限的systemd服务运行
  • CONTAINER_HOST=unix:///var/run/podman/podman.sock

我已验证:

  • 文件有0755的权限且可执行
  • sudo ls -laR 关于tmpdir显示的权限正确
  • Root可以直接访问该文件(DAC不是问题)
  • 使用sudo podman build(非通过套接字)构建成功
  • 相同设置在Fedora 44上也能通过 --security-opt label=disable 构建成功
  • SELinux不相关(Ubuntu使用AppArmor)

我的假设:当podman客户端通过套接字把构建上下文发送给以root身份运行的podman服务器时,服务器进程可能无法遍历0700权限的临时目录,因为它运行在与直接sudo podman调用不同的挂载命名空间,或具有不同的进程凭据。

问题:为什么以root身份运行的podman服务在访问被其他用户拥有、权限为0700的目录中的文件时会失败,而root应该绕过DAC权限检查?是否存在某种podman配置或systemd服务设置,会限制服务器的文件系统访问?

解决方案

根本原因是在 /var/tmp 上的noexec,而不是目录权限

关于0700目录遍历的假设是误导性的。Root可以绕过DAC,错误信息证明绑定挂载成功——容器内确实存在该文件。内核阻塞的是 execve(),而不是文件访问。

实际原因:podman守护进程将远程构建上下文提取到 /var/tmp,如果该文件系统带有noexec挂载标志,就会通过 RUN --mount=type=bind 传播到容器中。

远程API的工作原理

即便使用本地Unix套接字,通过 CONTAINER_HOST 构建也始终:

  1. 客户端把构建上下文目录打包成tar
  2. 通过HTTP POST将 tarball发送给守护进程
  3. 守护进程把它解包到一个临时目录(默认值为 /var/tmp
  4. 构建在提取出的副本上运行

没有“传递本地路径”的优化。直接以sudo podman build调用会跳过所有这些步骤,原地访问构建上下文——这就是它能工作的原因。

重现问题

  # Mount /var/tmp as noexec
  export TMPDIR=/tmp/test-noexec
  mkdir -p "$TMPDIR"
  sudo mount --bind -o rw,noexec,nosuid,nodev,bind "$TMPDIR" /var/tmp

  # Create build context
  mkdir -p /tmp/ctx/utils
  printf '#!/bin/bash\necho "ok"\n' > /tmp/ctx/utils/helper.sh
  chmod 755 /tmp/ctx/utils/helper.sh
  cat > /tmp/ctx/Dockerfile <<'DF'
  FROM registry.access.redhat.com/ubi9/ubi-minimal:latest
  USER 0
  RUN --mount=type=bind,source=utils/helper.sh,target=/helper.sh,ro /helper.sh
  DF

  # Direct build: PASS
  sudo podman build --no-cache -t test-direct /tmp/ctx

  # Remote build: FAIL — Permission denied
  sudo systemctl start podman.socket
  sudo CONTAINER_HOST=unix:///run/podman/podman.sock \
    podman --remote build --no-cache -t test-remote /tmp/ctx

  # Cleanup
  sudo umount /var/tmp

为什么你的CI会遇到这个

在你的GitHub Actions设置中,某些东西把 /var/tmp挂载为noexec——请检查类似这样的行:

  sudo mount --bind -o rw,noexec,nosuid,nodev,bind $TMPDIR /var/tmp

这通常是为了把Podman的临时I/O重定向到一个不同的卷。noexec标志随后会被守护进程在此处提取的任何文件继承,RUN --mount=type=bind也会把它传播到容器中。

另外:--security-opt label=disable 在Ubuntu上并没有帮助

这只是禁用SELinux标签分离。On Ubuntu(使用AppArmor,而非SELinux)基本上是无效的。如果AppArmor也在起作用,你需要单独处理 --security-opt apparmor=unconfined

解决办法

  1. 为Podman服务设置TMPDIR(相当简洁)——将守护进程的staging目录重定向到非noexec的文件系统:
# /etc/systemd/system/podman.service.d/tmpdir.conf
[Service]
Environment="TMPDIR=/home/runner/.local/share/containers/tmpdir"

然后执行systemctl daemon-reload && systemctl restart podman.socket。

  1. 使用bash替代直接执行(我的方案)——因为bash读取并解释脚本,而不是执行(exec)它,所以绕过了noexec:
RUN --mount=type=bind,source=utils/dnf-helper.sh,target=/utils/dnf-helper.sh,ro \
    bash /utils/dnf-helper.sh install perl
  1. /var/tmp 挂载中移除noexec(不推荐)——如果你能控制挂载,只需去掉该标志。
  2. 使用COPY代替bind mount(有点麻烦)——完全避免该问题,但会多一层镜像层次:
COPY utils/dnf-helper.sh /tmp/dnf-helper.sh
RUN /tmp/dnf-helper.sh install perl && rm /tmp/dnf-helper.sh

上游问题

已作为containers/podman#28993向外提交(https://github.com/containers/podman/issues/28993)。与containers/podman#20839(https://github.com/containers/podman/issues/20839)相关,涉及TMPDIR在多种远程操作中未被正确尊重的问题。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章