commit a253257cd298892da43e15201d83f9a02c9b58b5
Author: Alexander Larsson <alexl@redhat.com>
Date:   Tue Jun 18 10:20:36 2024 +0200
Subject: Add --bind-fd and --ro-bind-fd to let you bind a O_PATH fd.
References: CVE-2024-42472
References: bsc#1229157
Upstream: Backport from upstream
    
    This is useful for example if you for some reason don't have the real
    path. It is also a way to make bind-mounts race-free (i.e. to have the
    mount actually be the thing you wanted to be mounted, avoiding issues
    where some other process replaces the target in parallel with the bwrap
    launch.
    
    Unfortunately due to some technical details we can't actually directly
    mount the dirfd, as they come from different user namespace which is not
    permitted, but at least we can delay resolving the fd to a path as much as
    possible, and then validate after mount that we actually mounted the right
    thing.
    
    Signed-off-by: Alexander Larsson <alexl@redhat.com>

--- bubblewrap-0.8.0/bubblewrap.c
+++ bubblewrap-0.8.0_new/bubblewrap.c
@@ -339,6 +339,8 @@
            "    --dev-bind-try SRC DEST      Equal to --dev-bind but ignores non-existent SRC\n"
            "    --ro-bind SRC DEST           Bind mount the host path SRC readonly on DEST\n"
            "    --ro-bind-try SRC DEST       Equal to --ro-bind but ignores non-existent SRC\n"
+           "    --bind-fd FD DEST            Bind open directory or path fd on DEST\n"
+           "    --ro-bind-fd FD DEST         Bind open directory or path fd read-only on DEST\n"
            "    --remount-ro DEST            Remount DEST as readonly; does not recursively remount\n"
            "    --exec-label LABEL           Exec label for the sandbox\n"
            "    --file-label LABEL           File label for temporary sandbox content\n"
@@ -1237,6 +1239,30 @@
                          (op->type == SETUP_RO_BIND_MOUNT ? BIND_READONLY : 0) |
                          (op->type == SETUP_DEV_BIND_MOUNT ? BIND_DEVICES : 0),
                          0, 0, source, dest);
+
+          if (op->fd >= 0)
+            {
+              struct stat fd_st, mount_st;
+
+              /* When using bind-fd, there is a race condition between resolving the fd as a magic symlink
+               * and mounting it, where someone could replace what is at the symlink target. Ideally
+               * we would not even resolve the symlink and directly bind-mount from the fd, but unfortunately
+               * we can't do that, because its not permitted to bind mount a fd from another user namespace.
+               * So, we resolve, mount and then compare fstat+stat to detect the race. */
+
+              if (fstat(op->fd, &fd_st) != 0)
+                die_with_error("Can't stat fd %d", op->fd);
+              if (lstat(dest, &mount_st) != 0)
+                die_with_error("Can't stat mount at %s", dest);
+
+              if (fd_st.st_ino != mount_st.st_ino ||
+                  fd_st.st_dev != mount_st.st_dev)
+                die_with_error("Race condition binding dirfd");
+
+              close(op->fd);
+              op->fd = -1;
+            }
+
           break;
 
         case SETUP_REMOUNT_RO_NO_RECURSIVE:
@@ -1852,6 +1878,30 @@
 
           argv += 2;
           argc -= 2;
+        }
+      else if (strcmp (arg, "--bind-fd") == 0 ||
+               strcmp (arg, "--ro-bind-fd") == 0)
+        {
+          int src_fd;
+          char *endptr;
+
+          if (argc < 3)
+            die ("--bind-fd takes two arguments");
+
+          src_fd = strtol (argv[1], &endptr, 10);
+          if (argv[1][0] == 0 || endptr[0] != 0 || src_fd < 0)
+            die ("Invalid fd: %s", argv[1]);
+
+          if (strcmp(arg, "--ro-bind-fd") == 0)
+            op = setup_op_new (SETUP_RO_BIND_MOUNT);
+          else
+            op = setup_op_new (SETUP_BIND_MOUNT);
+          op->source = xasprintf ("/proc/self/fd/%d", src_fd);
+          op->fd = src_fd;
+          op->dest = argv[2];
+
+          argv += 2;
+          argc -= 2;
         }
       else if (strcmp (arg, "--proc") == 0)
         {

--- bubblewrap-0.8.0/tests/test-run.sh
+++ bubblewrap-0.8.0_new/tests/test-run.sh
@@ -523,4 +523,10 @@
 assert_files_equal stdout reference
 echo "ok - environment manipulation"
 
+echo "foobar" > file-data
+$RUN --proc /proc --dev /dev --bind / / --bind-fd 100 /tmp cat /tmp/file-data 100< . > stdout
+assert_file_has_content stdout foobar
+
+ok "bind-fd"
+
 echo "ok - End of test"
