linux-sandbox: Retry remount-ro on EBUSY (https://github.com/bazelbuild/bazel/pull/29433) ### Description MakeFilesystemMostlyReadOnly issues MS_BIND|MS_REMOUNT|MS_RDONLY for every entry in /proc/self/mounts. On busy mounts (notably "/") this races with concurrent symlink path lookups: pick_link() in fs/namei.c calls touch_atime(), which holds the per-mount writer counter via mnt_get_write_access() across the inode update. mnt_hold_writers() in the remount path returns -EBUSY if the counter is non-zero, causing the sandbox setup to die. The race window is short (a single update_time call), so a brief backoff-and-retry resolves it cleanly. Use the same constants runc has shipped since 2014 (5 attempts, 100 ms apart). This is my proposal for https://github.com/bazelbuild/bazel/issues/29424. ### Motivation Fixes https://github.com/bazelbuild/bazel/issues/29424. ### Build API Changes No ### Checklist - [x] I have added tests for the new use cases (if any). - [x] I have updated the documentation (if applicable). ### Release Notes RELNOTES: None Closes #29433. PiperOrigin-RevId: 922094350 Change-Id: I7ff8b1b5ad7992f164b44a6742c4f70cadb18f00
diff --git a/src/main/tools/linux-sandbox-pid1.cc b/src/main/tools/linux-sandbox-pid1.cc index f832104..b867c08 100644 --- a/src/main/tools/linux-sandbox-pid1.cc +++ b/src/main/tools/linux-sandbox-pid1.cc
@@ -40,6 +40,7 @@ #include <sys/wait.h> #include <unistd.h> +#include <ctime> #include <string> #include <unordered_set> @@ -400,7 +401,24 @@ PRINT_DEBUG("remount %s: %s", (mountFlags & MS_RDONLY) ? "ro" : "rw", ent->mnt_dir); - if (mount(nullptr, ent->mnt_dir, nullptr, mountFlags, nullptr) < 0) { + // The remount races with concurrent path lookups elsewhere on the host: + // walking through a symlink calls touch_atime(), which briefly bumps the + // per-mount writer counter (mnt_get_write_access -> mnt_inc_writers). + // MS_BIND|MS_REMOUNT|MS_RDONLY goes through mnt_hold_writers() which + // returns -EBUSY if that counter is non-zero. The window is tiny but on + // active mounts (especially "/") it can sometimes hit. Retry on EBUSY. + // This behavior mimics runc's handling of EBUSY during readonly remounts: + // https://github.com/opencontainers/runc/blob/eb7eaf19b6eec5d1143b257057899e4a7b738c81/libcontainer/rootfs_linux.go#L1305-L1309 + int rc; + for (int i = 0; i < 5; ++i) { + rc = mount(nullptr, ent->mnt_dir, nullptr, mountFlags, nullptr); + if (rc == 0 || errno != EBUSY || i == 4) break; + struct timespec delay; + delay.tv_sec = 0; + delay.tv_nsec = 100 * 1000 * 1000; // 100 milliseconds + nanosleep(&delay, nullptr); + } + if (rc < 0) { // If we get EACCES or EPERM, this might be a mount-point for which we // don't have read access. Not much we can do about this, but it also // won't do any harm, so let's go on. The same goes for EINVAL or ENOENT,