Skip to content

safe_openat fallback ignores dirfd: container device creation fails on kernels without openat2 #2154

Description

@hmhmdcy

Environment

  • Linux arm64, kernel 4.14.190 (no openat2 syscall; openat2 was added in Linux 5.6)
  • crun 1.21 (Debian); code verified identical in tags 1.22–1.28; device creation was refactored in main but the fallback below is unchanged there

Symptom

podman run fails at container start:

Error: crun: cannot resolve `null` under rootfs: No such file or directory: OCI runtime attempted to invoke a command that was not found

strace shows crun resolving <rootfs>/null (ENOENT) while creating the default device /dev/null — the correct target <rootfs>/dev/null exists. Same error has been reported in the wild with "switch to runc" as the only workaround.

Root cause

safe_openat() (src/libcrun/utils.c) first tries openat2(dirfd, path, RESOLVE_IN_ROOT). On kernels without openat2 this returns ENOSYS (also EINVAL/EPERM, e.g. when seccomp filters it), and crun falls back to safe_openat_fallback(dirfd, rootfs, path, ...):

path_in_chroot = chroot_realpath (rootfs, path, buffer);
...
ret = openat (dirfd, path_in_chroot, flags, mode);

The fallback resolves the path relative to rootfs only, ignoring dirfd; dirfd is used solely for the final openat(). That is only correct when dirfd == rootfsfd. libcrun_create_dev() calls safe_openat(devfd, rootfs, rel_dev, ...) with devfd = fd of <rootfs>/dev (a subdirectory), so the fallback resolves <rootfs>/null instead of <rootfs>/dev/null → ENOENT → container start fails. Any caller passing a non-rootfs dirfd (crun_safe_ensure_at etc.) is affected the same way.

Impact

On any kernel without openat2 (pre-5.6, or sandboxes where seccomp blocks it), crun cannot create the default devices and no container can start. A/B verified locally: unpatched crun fails with the error above; patched crun creates the container successfully.

Status in main

main has refactored device creation to mknod_and_set_attrs (mknodat/fchmodat/fchownat), which sidesteps this instance — but safe_openat_fallback itself is still dirfd-blind in main, so the bug class remains for any caller passing a subdirectory fd.

Suggested fix

Make the fallback dirfd-aware: resolve dirfd via /proc/self/fd/<dirfd>, strip the rootfs prefix, join with path, verify with chroot_realpath, then openat(dirfd, path). Patch against 1.21:

diff --git a/src/libcrun/utils.c b/src/libcrun/utils.c
index 53f3643..1145d5c 100644
--- a/src/libcrun/utils.c
+++ b/src/libcrun/utils.c
@@ -334,11 +334,33 @@ safe_openat_fallback (int dirfd, const char *rootfs, const char *path, int flags
                       int mode, libcrun_error_t *err)
 {
   const char *path_in_chroot;
+  const char *original_path = path;
   cleanup_close int fd = -1;
   char buffer[PATH_MAX];
+  char dirfd_path[PATH_MAX];
+  char joined[PATH_MAX];
   size_t rootfs_len = strlen (rootfs);
+  bool dirfd_relative = dirfd >= 0 && dirfd != AT_FDCWD && path[0] != '/';
   int ret;
 
+  if (dirfd_relative)
+    {
+      proc_fd_path_t fdpath;
+      ssize_t len;
+
+      get_proc_self_fd_path (fdpath, dirfd);
+      len = TEMP_FAILURE_RETRY (readlink (fdpath, dirfd_path, sizeof (dirfd_path) - 1));
+      if (UNLIKELY (len < 0))
+        return crun_make_error (err, errno, "readlink `%s`", fdpath);
+      dirfd_path[len] = '\0';
+
+      if (strncmp (dirfd_path, rootfs, rootfs_len) == 0 && dirfd_path[rootfs_len] == '/')
+        {
+          snprintf (joined, sizeof (joined), "%s/%s", dirfd_path + rootfs_len + 1, path);
+          path = joined;
+        }
+    }
+
   path_in_chroot = chroot_realpath (rootfs, path, buffer);
   if (path_in_chroot == NULL)
     return crun_make_error (err, errno, "cannot resolve `%s` under rootfs", path);
@@ -355,9 +377,9 @@ safe_openat_fallback (int dirfd, const char *rootfs, const char *path, int flags
       return ret;
     }
 
-  ret = openat (dirfd, path_in_chroot, flags, mode);
+  ret = openat (dirfd, dirfd_relative ? original_path : path_in_chroot, flags, mode);
   if (UNLIKELY (ret < 0))
-    return crun_make_error (err, errno, "open `%s`", path);
+    return crun_make_error (err, errno, "open `%s`", original_path);
 
   fd = ret;
 

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions