aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Frysinger <vapier@google.com>2018-01-17 15:39:48 -0500
committerMike Frysinger <vapier@google.com>2018-02-23 13:56:07 -0500
commit5fdba4ed2842acaeb2488d30f7c83bb17556eeaa (patch)
treedde02ffa9418e0d972c3b32bdc6a246d38717b8f
parent9741372f23e7895a180a535c9bfd7246bb69dd2b (diff)
downloadminijail-5fdba4ed2842acaeb2488d30f7c83bb17556eeaa.tar.gz
create parent paths of target mounts as needed
Currently if you want to bind mount a single subdir, you have to make sure to create the full parent directory chain. For example, if you want /var/lib/timezone/ but not the rest of /var, you have to do: -k none,/var,tmpfs -k none,/var/lib,tmpfs -b /var/lib/timezone/ For every additional subdir, you need to add another -k option just to do an [effective] mkdir with a tmpfs mount. The current -k/-b behavior is to run mkdir if the target doesn't already exist, but only for the final target. Lets extend it to also create any missing parent paths, so now only the base path needs to be writable: -k none,/var,tmpfs -b /var/lib/timezone/ Bug: None Test: `minijail0 --profile minimalistic-mountns -k none,/var,tmpfs -b /var/lib/timezone /bin/date` works Change-Id: I7f36bcb445ce40ed66a9403a4ee1c1fe3f9e5ea8
-rw-r--r--minijail0.15
-rw-r--r--system.c48
-rw-r--r--system.h2
-rw-r--r--system_unittest.cc52
4 files changed, 100 insertions, 7 deletions
diff --git a/minijail0.1 b/minijail0.1
index e713fed..7c535e0 100644
--- a/minijail0.1
+++ b/minijail0.1
@@ -17,7 +17,7 @@ Bind-mount \fIsrc\fR into the chroot directory at \fIdest\fR, optionally writeab
The \fIsrc\fR path must be an absolute path.
If \fIdest\fR is not specified, it will default to \fIsrc\fR.
If the destination does not exist, it will be created as a file or directory
-based on the \fIsrc\fR type.
+based on the \fIsrc\fR type (including missing parent directories).
.TP
\fB-c <caps>\fR
Restrict capabilities to \fIcaps\fR. When used in conjunction with \fB-u\fR and
@@ -81,7 +81,8 @@ fields here are filesystem specific.
If the mount is not a pseudo filesystem (e.g. proc or sysfs), \fIsrc\fR path
must be an absolute path (e.g. \fI/dev/sda1\fR and not \fIsda1\fR).
-If the destination does not exist, it will be created as a directory.
+If the destination does not exist, it will be created as a directory (including
+missing parent directories).
.TP
\fB-K\fR
Don't mark all existing mounts as MS_PRIVATE.
diff --git a/system.c b/system.c
index 74e97c2..bb1abf9 100644
--- a/system.c
+++ b/system.c
@@ -215,6 +215,39 @@ int write_pid_to_path(pid_t pid, const char *path)
}
/*
+ * Create the |path| directory and its parents (if need be) with |mode|.
+ * If not |isdir|, then |path| is actually a file, so the last component
+ * will not be created.
+ */
+int mkdir_p(const char *path, mode_t mode, bool isdir)
+{
+ char *dir = strdup(path);
+ if (!dir)
+ return -errno;
+
+ /* Starting from the root, work our way out to the end. */
+ char *p = strchr(dir + 1, '/');
+ while (p) {
+ *p = '\0';
+ if (mkdir(dir, mode) && errno != EEXIST) {
+ free(dir);
+ return -errno;
+ }
+ *p = '/';
+ p = strchr(p + 1, '/');
+ }
+
+ /*
+ * Create the last directory. We still check EEXIST here in case
+ * of trailing slashes.
+ */
+ free(dir);
+ if (isdir && mkdir(path, mode) && errno != EEXIST)
+ return -errno;
+ return 0;
+}
+
+/*
* setup_mount_destination: Ensures the mount target exists.
* Creates it if needed and possible.
*/
@@ -267,11 +300,16 @@ int setup_mount_destination(const char *source, const char *dest, uid_t uid,
domkdir = true;
}
- /* Now that we know what we want to do, do it! */
- if (domkdir) {
- if (mkdir(dest, 0700))
- return -errno;
- } else {
+ /*
+ * Now that we know what we want to do, do it!
+ * We always create the intermediate dirs and the final path with 0755
+ * perms and root/root ownership. This shouldn't be a problem because
+ * the actual mount will set those perms/ownership on the mount point
+ * which is all people should need to access it.
+ */
+ if (mkdir_p(dest, 0755, domkdir))
+ return -errno;
+ if (!domkdir) {
int fd = open(dest, O_RDWR | O_CREAT | O_CLOEXEC, 0700);
if (fd < 0)
return -errno;
diff --git a/system.h b/system.h
index 7f36ad2..b816f5f 100644
--- a/system.h
+++ b/system.h
@@ -51,6 +51,8 @@ int setup_and_dupe_pipe_end(int fds[2], size_t index, int fd);
int write_pid_to_path(pid_t pid, const char *path);
int write_proc_file(pid_t pid, const char *content, const char *basename);
+int mkdir_p(const char *path, mode_t mode, bool isdir);
+
int setup_mount_destination(const char *source, const char *dest, uid_t uid,
uid_t gid, bool bind);
diff --git a/system_unittest.cc b/system_unittest.cc
index db5fe98..c584808 100644
--- a/system_unittest.cc
+++ b/system_unittest.cc
@@ -8,6 +8,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <sys/stat.h>
#include <unistd.h>
#include <gtest/gtest.h>
@@ -125,6 +126,57 @@ TEST(write_pid_to_path, basic) {
}
// If the destination exists, there's nothing to do.
+// Also check trailing slash handling.
+TEST(mkdir_p, dest_exists) {
+ EXPECT_EQ(0, mkdir_p("/", 0, true));
+ EXPECT_EQ(0, mkdir_p("///", 0, true));
+ EXPECT_EQ(0, mkdir_p("/proc", 0, true));
+ EXPECT_EQ(0, mkdir_p("/proc/", 0, true));
+ EXPECT_EQ(0, mkdir_p("/dev", 0, true));
+ EXPECT_EQ(0, mkdir_p("/dev/", 0, true));
+}
+
+// Create a directory tree that doesn't exist.
+TEST(mkdir_p, create_tree) {
+ char *path = get_temp_path();
+ ASSERT_NE(nullptr, path);
+ unlink(path);
+
+ // Run `mkdir -p <path>/a/b/c`.
+ char *path_a, *path_a_b, *path_a_b_c;
+ ASSERT_NE(-1, asprintf(&path_a, "%s/a", path));
+ ASSERT_NE(-1, asprintf(&path_a_b, "%s/b", path_a));
+ ASSERT_NE(-1, asprintf(&path_a_b_c, "%s/c", path_a_b));
+
+ // First try creating it as a file.
+ EXPECT_EQ(0, mkdir_p(path_a_b_c, 0700, false));
+
+ // Make sure the final path doesn't exist yet.
+ struct stat st;
+ EXPECT_EQ(0, stat(path_a_b, &st));
+ EXPECT_EQ(true, S_ISDIR(st.st_mode));
+ EXPECT_EQ(-1, stat(path_a_b_c, &st));
+
+ // Then create it as a complete dir.
+ EXPECT_EQ(0, mkdir_p(path_a_b_c, 0700, true));
+
+ // Make sure the final dir actually exists.
+ EXPECT_EQ(0, stat(path_a_b_c, &st));
+ EXPECT_EQ(true, S_ISDIR(st.st_mode));
+
+ // Clean up.
+ ASSERT_EQ(0, rmdir(path_a_b_c));
+ ASSERT_EQ(0, rmdir(path_a_b));
+ ASSERT_EQ(0, rmdir(path_a));
+ ASSERT_EQ(0, rmdir(path));
+
+ free(path_a_b_c);
+ free(path_a_b);
+ free(path_a);
+ free(path);
+}
+
+// If the destination exists, there's nothing to do.
TEST(setup_mount_destination, dest_exists) {
// Pick some paths that should always exist. We pass in invalid pointers
// for other args so we crash if the dest check doesn't short circuit.