From 307399fcebf1cc85970ec8d5eea652ec8d5390a1 Mon Sep 17 00:00:00 2001 From: xxyzz Date: Mon, 31 Aug 2026 15:19:28 +0800 Subject: [PATCH] Use new mount API for bind mount Use `open_tree()` and `move_mount()` if they are available otherwise fallback to `mount()`, GH issue #755 Signed-off-by: xxyzz --- bind-mount.c | 114 ++++++++++++++++++++++++++++++++++++++---- bind-mount.h | 2 + bubblewrap.c | 4 ++ tests/test-sandbox.py | 10 ++++ utils.c | 24 +++++++++ utils.h | 12 +++++ 6 files changed, 156 insertions(+), 10 deletions(-) diff --git a/bind-mount.c b/bind-mount.c index b1687bbe..c082df86 100644 --- a/bind-mount.c +++ b/bind-mount.c @@ -29,6 +29,11 @@ #else # define USE_MOUNT_SETATTR_FALLBACK 0 #endif +#if ASSUMED_KERNEL < BWRAP_KERNEL_VERSION (5, 2, 0) +# define USE_BIND_MOUNT_FALLBACK 1 +#else +# define USE_BIND_MOUNT_FALLBACK 0 +#endif static bind_mount_result mount_setattr_setup (const char *resolved_dest, bind_option_t options, @@ -39,6 +44,7 @@ static bind_mount_result mount_setattr_fallback (const char *resolved_dest, char **failing_path); bool opt_force_mount_setattr_fallback = false; +bool opt_force_bind_mount_fallback = false; #if USE_MOUNT_SETATTR_FALLBACK static char * @@ -420,16 +426,100 @@ bind_mount (const char *src, return bind_mount_fd (src_fd, dest_fd, options, failing_path); } +static bind_mount_result +bind_mount_fallback (const char *src_path, + const char *dest_path, + bind_option_t options, + char **failing_path) +{ +#if !USE_BIND_MOUNT_FALLBACK + debug ("No bind mount fallback"); + (void) src_path; + (void) dest_path; + (void) options; + (void) failing_path; + errno = ENOSYS; + return BIND_MOUNT_ERROR_MOUNT; +#else + debug ("Bind mount \"%s\" to \"%s\"", src_path, dest_path); + bool recursive = (options & BIND_RECURSIVE) != 0; + if (mount (src_path, dest_path, NULL, + MS_SILENT | MS_BIND | (recursive ? MS_REC : 0), NULL) != 0) + { + if (failing_path != NULL) + *failing_path = xstrdup (dest_path); + return BIND_MOUNT_ERROR_MOUNT; + } + + debug ("-> Success"); + return BIND_MOUNT_SUCCESS; +#endif +} + +static bind_mount_result +new_bind_mount (int src_fd, + const char *src_path, + int dest_fd, + const char *dest_path, + bind_option_t options, + char **failing_path) +{ + static bool new_mount_supported = true; + bool recursive = (options & BIND_RECURSIVE) != 0; + + if (new_mount_supported && !opt_force_bind_mount_fallback) + { + debug ("Remounting \"%s\" on \"%s\" using open_tree + move_mount", + src_path, dest_path); + unsigned int open_tree_flags = OPEN_TREE_CLONE | AT_EMPTY_PATH | OPEN_TREE_CLOEXEC; + if (recursive) + open_tree_flags |= AT_RECURSIVE; + cleanup_fd int tree_fd = open_tree_wrapper (src_fd, "", open_tree_flags); + if (tree_fd == -1) + { + if (errno != ENOSYS) + { + if (failing_path != NULL) + *failing_path = xstrdup (src_path); + return BIND_MOUNT_ERROR_OPEN_TREE; + } + } + else if (move_mount_wrapper (tree_fd, "", dest_fd, "", + MOVE_MOUNT_F_EMPTY_PATH | + MOVE_MOUNT_T_EMPTY_PATH) == 0) + { + debug ("-> Success"); + return BIND_MOUNT_SUCCESS; + } + else if (errno != ENOSYS) + { + if (failing_path != NULL) { + if (errno == EINVAL) + *failing_path = xstrdup (src_path); + else + *failing_path = xstrdup (dest_path); + } + return BIND_MOUNT_ERROR_MOVE_MOUNT; + } + + debug ("-> Falling back"); + } + + new_mount_supported = false; + return bind_mount_fallback (src_path, dest_path, options, failing_path); +} + bind_mount_result bind_mount_fd (int src_fd, int dest_fd, bind_option_t options, char **failing_path) { - bool recursive = (options & BIND_RECURSIVE) != 0; cleanup_free char *resolved_dest = NULL; + cleanup_free char *src_proc = NULL; cleanup_free char *dest_proc = NULL; + src_proc = fd_to_proc_path (src_fd); dest_proc = fd_to_proc_path (dest_fd); /* If we are in a case-insensitive filesystem, mountinfo might contain a @@ -453,15 +543,9 @@ bind_mount_fd (int src_fd, if (src_fd != -1) { - cleanup_free char *src_proc = fd_to_proc_path (src_fd); - - if (mount (src_proc, dest_proc, NULL, MS_SILENT | MS_BIND | (recursive ? MS_REC : 0), NULL) != 0) - { - if (failing_path != NULL) - *failing_path = steal_pointer (&resolved_dest); - - return BIND_MOUNT_ERROR_MOUNT; - } + bind_mount_result mount_result = new_bind_mount (src_fd, src_proc, dest_fd, resolved_dest, options, failing_path); + if (mount_result != BIND_MOUNT_SUCCESS) + return mount_result; } return mount_setattr_setup (resolved_dest, options, failing_path); @@ -608,6 +692,14 @@ bind_mount_result_to_string (bind_mount_result res, string = xasprintf ("mount_setattr() failed at \"%s\"", failing_path); break; + case BIND_MOUNT_ERROR_OPEN_TREE: + string = xasprintf ("open_tree() failed at \"%s\"", failing_path); + break; + + case BIND_MOUNT_ERROR_MOVE_MOUNT: + string = xasprintf ("move_mount() failed at \"%s\"", failing_path); + break; + case BIND_MOUNT_SUCCESS: string = xstrdup ("Success"); break; @@ -654,6 +746,8 @@ die_with_bind_result (bind_mount_result res, case BIND_MOUNT_ERROR_MOUNT: case BIND_MOUNT_ERROR_REMOUNT_DEST: case BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT: + case BIND_MOUNT_ERROR_OPEN_TREE: + case BIND_MOUNT_ERROR_MOVE_MOUNT: fprintf (stderr, ": %s", mount_strerror (saved_errno)); break; diff --git a/bind-mount.h b/bind-mount.h index 71f54c39..7d7a5aa4 100644 --- a/bind-mount.h +++ b/bind-mount.h @@ -38,6 +38,8 @@ typedef enum BIND_MOUNT_ERROR_REMOUNT_SUBMOUNT, BIND_MOUNT_ERROR_OPEN_FD, BIND_MOUNT_ERROR_MOUNT_SETATTR, + BIND_MOUNT_ERROR_OPEN_TREE, + BIND_MOUNT_ERROR_MOVE_MOUNT, } bind_mount_result; bind_mount_result bind_mount (const char *src, diff --git a/bubblewrap.c b/bubblewrap.c index 87da2307..2ddc3781 100644 --- a/bubblewrap.c +++ b/bubblewrap.c @@ -2740,6 +2740,10 @@ parse_args_recurse (int *argcp, #endif exit (0); } + else if (strcmp (val, "force-bind-mount-fallback") == 0) + { + opt_force_bind_mount_fallback = true; + } else { die ("Unknown --debug-opt value: %s", val); diff --git a/tests/test-sandbox.py b/tests/test-sandbox.py index d33f1ad3..ef3bb76a 100644 --- a/tests/test-sandbox.py +++ b/tests/test-sandbox.py @@ -351,6 +351,16 @@ def _test_ro_bind_root_is_recursive(self): f'{mountpoint} is not read-only', ) + @in_sandbox('--debug-opt=force-bind-mount-fallback') + def _test_ro_bind_root_is_recursive_bind_mount_fallback(self): + self._test_ro_bind_root_is_recursive() + + def test_ro_bind_root_is_recursive_bind_mount_fallback(self): + if get_assumed_kernel() >= (5, 2, 0): + self.skipTest('mount() fallback not compiled') + else: + self._test_ro_bind_root_is_recursive_bind_mount_fallback() + # ------ tmpfs ------ @in_sandbox('--tmpfs', '/tmp/t') diff --git a/utils.c b/utils.c index ac5c446d..0241cd06 100644 --- a/utils.c +++ b/utils.c @@ -1133,3 +1133,27 @@ mount_setattr_wrapper (int dirfd, const char *path, unsigned int flags, return -1; #endif } + +int +open_tree_wrapper (int dirfd, const char *path, unsigned int flags) +{ +#ifdef __NR_open_tree + return syscall (__NR_open_tree, dirfd, path, flags); +#else + errno = ENOSYS; + return -1; +#endif +} + +int +move_mount_wrapper (int from_dirfd, const char *from_path, int to_dirfd, + const char *to_path, unsigned int flags) +{ +#ifdef __NR_move_mount + return syscall (__NR_move_mount, from_dirfd, from_path, to_dirfd, + to_path, flags); +#else + errno = ENOSYS; + return -1; +#endif +} diff --git a/utils.h b/utils.h index 0872223a..aa62b1ca 100644 --- a/utils.h +++ b/utils.h @@ -160,6 +160,7 @@ int label_create_file (const char *file_label); extern bool opt_force_openat_fallback; extern bool opt_force_mount_setattr_fallback; +extern bool opt_force_bind_mount_fallback; int safe_openat (int dirfd, const char *rootfs, @@ -318,5 +319,16 @@ struct mount_attr #define AT_RECURSIVE 0x8000 #endif +#ifndef OPEN_TREE_CLONE +#define OPEN_TREE_CLONE (1 << 0) +#define OPEN_TREE_CLOEXEC O_CLOEXEC +#define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 +#define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 +#endif + int mount_setattr_wrapper (int dirfd, const char *path, unsigned int flags, struct mount_attr *attr, size_t size); +int open_tree_wrapper (int dirfd, const char *path, unsigned int flags); +int move_mount_wrapper (int from_dirfd, const char *from_path, + int to_dirfd, const char *to_path, + unsigned int flags);