Fix RO-remount logic for bindmounts
Using pivot-roots with bindmounts causes the kernel to keep some
mountflags of the source directory (nosuid, noexec, nodev) that have to
be specified during the RO-remount, otherwise the mount will fail with EPERM.
This was already previously covered by obtaining the source mount flags in
`setup_mount_destination`. This function failed to provide those flags if the
estination folder is already existing (mounting destination '/').
This commit moves the logic to determine the mountflags of a given
mountpoint into a dedicated function and properly handles vfs->mount
flag translation.
Test: All tests pass
Bug: crbug.com/971656
Change-Id: I7468b63e26fd43f45175ac54c952f726ff93a434
diff --git a/libminijail.c b/libminijail.c
index 6608cdd..d9b7da4 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -2800,7 +2800,7 @@
if (use_preload) {
free(oldenv_copy);
}
- die("failed to fork child");
+ pdie("failed to fork child");
}
if (child_pid) {
diff --git a/libminijail_unittest.cc b/libminijail_unittest.cc
index 9a04624..115b3ae 100644
--- a/libminijail_unittest.cc
+++ b/libminijail_unittest.cc
@@ -667,6 +667,38 @@
minijail_destroy(j);
}
+TEST_F(NamespaceTest, test_bind_ro_with_pivot_root) {
+ if (!userns_supported_) {
+ SUCCEED();
+ return;
+ }
+ struct minijail *j = minijail_new();
+ minijail_namespace_cgroups(j);
+ minijail_namespace_net(j);
+ minijail_namespace_pids(j);
+ minijail_namespace_user(j);
+ minijail_namespace_uts(j);
+ minijail_namespace_vfs(j);
+
+ minijail_use_caps(j, 0);
+ minijail_no_new_privs(j);
+ minijail_namespace_user_disable_setgroups(j);
+
+ minijail_run_as_init(j);
+ minijail_enter_pivot_root(j, "/tmp");
+
+ // /dev/shm is usually mounted nosuid,nodev.
+ minijail_bind(j, "/dev/shm", "/", false);
+
+ pid_t p = minijail_fork(j);
+ if (p) {
+ EXPECT_EQ(0, minijail_wait(j));
+ minijail_destroy(j);
+ return;
+ }
+ exit(0);
+}
+
TEST_F(NamespaceTest, test_namespaces) {
constexpr char teststr[] = "test\n";
diff --git a/system.c b/system.c
index 175e088..2e5c232 100644
--- a/system.c
+++ b/system.c
@@ -14,6 +14,7 @@
#include <stdio.h>
#include <string.h>
#include <sys/ioctl.h>
+#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/socket.h>
#include <sys/stat.h>
@@ -24,6 +25,35 @@
#include "util.h"
+/* Old libc versions might not define all constants that we need. */
+#ifndef ST_RDONLY
+#define ST_RDONLY 0x0001
+#endif
+#ifndef ST_NOSUID
+#define ST_NOSUID 0x0002
+#endif
+#ifndef ST_NODEV
+#define ST_NODEV 0x0004
+#endif
+#ifndef ST_NOEXEC
+#define ST_NOEXEC 0x0008
+#endif
+#ifndef ST_SYNCHRONOUS
+#define ST_SYNCHRONOUS 0x0010
+#endif
+#ifndef ST_MANDLOCK
+#define ST_MANDLOCK 0x0040
+#endif
+#ifndef ST_NOATIME
+#define ST_NOATIME 0x0400
+#endif
+#ifndef ST_NODIRATIME
+#define ST_NODIRATIME 0x0800
+#endif
+#ifndef ST_RELATIME
+#define ST_RELATIME 0x1000
+#endif
+
/*
* SECBIT_NO_CAP_AMBIENT_RAISE was added in kernel 4.3, so fill in the
* definition if the securebits header doesn't provide it.
@@ -296,6 +326,28 @@
}
/*
+ * get_mount_flags_for_directory: Returns the mount flags for the given
+ * directory.
+ */
+int get_mount_flags_for_directory(const char *path, unsigned long *mnt_flags)
+{
+ int rc;
+ struct statvfs stvfs_buf;
+
+ if (!mnt_flags)
+ return 0;
+
+ rc = statvfs(path, &stvfs_buf);
+ if (rc) {
+ rc = errno;
+ pwarn("failed to look up mount flags: source=%s", path);
+ return -rc;
+ }
+ *mnt_flags = vfs_flags_to_mount_flags(stvfs_buf.f_flag);
+ return 0;
+}
+
+/*
* setup_mount_destination: Ensures the mount target exists.
* Creates it if needed and possible.
*/
@@ -308,7 +360,7 @@
rc = stat(dest, &st_buf);
if (rc == 0) /* destination exists */
- return 0;
+ return get_mount_flags_for_directory(source, mnt_flags);
/*
* Try to create the destination.
@@ -342,19 +394,6 @@
(!bind && (S_ISBLK(st_buf.st_mode) ||
S_ISCHR(st_buf.st_mode)));
- /* If bind mounting, also grab the mount flags of the source. */
- if (bind && mnt_flags) {
- struct statvfs stvfs_buf;
- rc = statvfs(source, &stvfs_buf);
- if (rc) {
- rc = errno;
- pwarn(
- "failed to look up mount flags: source=%s",
- source);
- return -rc;
- }
- *mnt_flags = stvfs_buf.f_flag;
- }
} else {
/* The source is a relative path -- assume it's a pseudo fs. */
@@ -392,7 +431,35 @@
pwarn("chown(%s, %u, %u) failed", dest, uid, gid);
return -rc;
}
- return 0;
+ return get_mount_flags_for_directory(source, mnt_flags);
+}
+
+/*
+ * vfs_flags_to_mount_flags: Converts the given flags returned by statvfs to
+ * flags that can be used by mount().
+ */
+unsigned long vfs_flags_to_mount_flags(unsigned long vfs_flags)
+{
+ unsigned int i;
+ unsigned long mount_flags = 0;
+
+ static struct {
+ unsigned long mount_flag;
+ unsigned long vfs_flag;
+ } const flag_translation_table[] = {
+ {MS_NOSUID, ST_NOSUID}, {MS_NODEV, ST_NODEV},
+ {MS_NOEXEC, ST_NOEXEC}, {MS_SYNCHRONOUS, ST_SYNCHRONOUS},
+ {MS_MANDLOCK, ST_MANDLOCK}, {MS_NOATIME, ST_NOATIME},
+ {MS_NODIRATIME, ST_NODIRATIME}, {MS_RELATIME, ST_RELATIME},
+ };
+
+ for (i = 0; i < ARRAY_SIZE(flag_translation_table); i++) {
+ if (vfs_flags & flag_translation_table[i].vfs_flag) {
+ mount_flags |= flag_translation_table[i].mount_flag;
+ }
+ }
+
+ return mount_flags;
}
/*
diff --git a/system.h b/system.h
index 7369846..f332952 100644
--- a/system.h
+++ b/system.h
@@ -54,9 +54,13 @@
int mkdir_p(const char *path, mode_t mode, bool isdir);
+int get_mount_flags_for_directory(const char *path, unsigned long *mnt_flags);
+
int setup_mount_destination(const char *source, const char *dest, uid_t uid,
uid_t gid, bool bind, unsigned long *mnt_flags);
+unsigned long vfs_flags_to_mount_flags(unsigned long vfs_flags);
+
int lookup_user(const char *user, uid_t *uid, gid_t *gid);
int lookup_group(const char *group, gid_t *gid);
diff --git a/system_unittest.cc b/system_unittest.cc
index 470421d..07a343c 100644
--- a/system_unittest.cc
+++ b/system_unittest.cc
@@ -273,7 +273,7 @@
std::string proc = dir.path + "/proc";
EXPECT_EQ(0, setup_mount_destination("/proc", proc.c_str(), -1, -1, true,
&mount_flags));
- EXPECT_EQ(stvfs_buf.f_flag, mount_flags);
+ EXPECT_EQ(vfs_flags_to_mount_flags(stvfs_buf.f_flag), mount_flags);
EXPECT_EQ(0, rmdir(proc.c_str()));
// Same thing holds for children of a mount.
@@ -281,7 +281,7 @@
std::string proc_self = dir.path + "/proc_self";
EXPECT_EQ(0, setup_mount_destination("/proc/self", proc_self.c_str(), -1, -1,
true, &mount_flags));
- EXPECT_EQ(stvfs_buf.f_flag, mount_flags);
+ EXPECT_EQ(vfs_flags_to_mount_flags(stvfs_buf.f_flag), mount_flags);
EXPECT_EQ(0, rmdir(proc_self.c_str()));
}