Block symlinks in bind mount source paths.
Minijail is sometimes asked to bind mount directories owned by
less-privileged users. These less-privileged users can manufacture
a "mount-anything-anywhere" primitive by replacing bind mount paths
with symlinks.
Prevent this by checking whether the bind mount source path is a
canonical path. Because this happens at minijail_bind() time, there is
still the risk of TOCTOU issues. A follow-up patch will re-check things
closer to the mount() call. Unfortunately mount() takes paths so it is
not possible to fully eliminate this race.
Because some files that processes might want to bind mount (like files
in /sys or /dev) can be symlinks, allow users to specify a set of
prefixes exempt from these restrictions. This can also help with
rolling this restriction out (exclude some prefixes, fix the callers,
then remove the exclusions).
A follow-up to this change will add checks on the destination path,
which need to happen after the path is created. Ideally, these would
happen as close as possible to the mount() call.
Bug: 219093918
Test: New unit tests.
Change-Id: Ia747e4318ed4eabf27c64e04e0dd71723735bae2
diff --git a/libminijail.c b/libminijail.c
index 54d8a74..34db672 100644
--- a/libminijail.c
+++ b/libminijail.c
@@ -873,6 +873,42 @@
ACCESS_FS_ROUGHLY_READ | ACCESS_FS_ROUGHLY_FULL_WRITE);
}
+static bool is_valid_bind_path(const char *path)
+{
+ if (!block_symlinks_in_bindmount_paths()) {
+ return true;
+ }
+
+ /*
+ * tokenize() will modify both the |prefixes| pointer and the contents
+ * of the string, so:
+ * -Copy |BINDMOUNT_ALLOWED_PREFIXES| since it lives in .rodata.
+ * -Save the original pointer for free()ing.
+ */
+ char *prefixes = strdup(BINDMOUNT_ALLOWED_PREFIXES);
+ attribute_cleanup_str char *orig_prefixes = prefixes;
+ (void)orig_prefixes;
+
+ char *prefix = NULL;
+ bool found_prefix = false;
+ if (!is_canonical_path(path)) {
+ while ((prefix = tokenize(&prefixes, ",")) != NULL) {
+ if (path_is_parent(prefix, path)) {
+ found_prefix = true;
+ break;
+ }
+ }
+ if (!found_prefix) {
+ /*
+ * If the path does not include one of the allowed
+ * prefixes, fail.
+ */
+ warn("path '%s' is not a canonical path", path);
+ return false;
+ }
+ }
+ return true;
+}
int API minijail_mount_with_data(struct minijail *j, const char *src,
const char *dest, const char *type,
@@ -957,6 +993,13 @@
{
unsigned long flags = MS_BIND;
+ if (!is_valid_bind_path(src)) {
+ warn("src '%s' is not a valid bind mount path", src);
+ return -ELOOP;
+ }
+
+ /* |dest| might not yet exist. */
+
if (!writeable)
flags |= MS_RDONLY;