Luis Henriques 8d1ba8
From: Ilya Dryomov <idryomov@gmail.com>
Luis Henriques 8d1ba8
Date: Mon, 10 Feb 2020 22:51:08 +0100
Luis Henriques 8d1ba8
Subject: ceph: canonicalize server path in place
Luis Henriques 8d1ba8
Git-commit: b27a939e8376a3f1ed09b9c33ef44d20f18ec3d0
Luis Henriques 8d1ba8
Patch-mainline: v5.6-rc2
Luis Henriques 8d1ba8
References: bsc#1168443
Luis Henriques 8d1ba8
Luis Henriques 8d1ba8
syzbot reported that 4fbc0c711b24 ("ceph: remove the extra slashes in
Luis Henriques 8d1ba8
the server path") had caused a regression where an allocation could be
Luis Henriques 8d1ba8
done under a spinlock -- compare_mount_options() is called by sget_fc()
Luis Henriques 8d1ba8
with sb_lock held.
Luis Henriques 8d1ba8
Luis Henriques 8d1ba8
We don't really need the supplied server path, so canonicalize it
Luis Henriques 8d1ba8
in place and compare it directly.  To make this work, the leading
Luis Henriques 8d1ba8
slash is kept around and the logic in ceph_real_mount() to skip it
Luis Henriques 8d1ba8
is restored.  CEPH_MSG_CLIENT_SESSION now reports the same (i.e.
Luis Henriques 8d1ba8
canonicalized) path, with the leading slash of course.
Luis Henriques 8d1ba8
Luis Henriques 8d1ba8
Fixes: 4fbc0c711b24 ("ceph: remove the extra slashes in the server path")
Luis Henriques 8d1ba8
Reported-by: syzbot+98704a51af8e3d9425a9@syzkaller.appspotmail.com
Luis Henriques 8d1ba8
Signed-off-by: Ilya Dryomov <idryomov@gmail.com>
Luis Henriques 8d1ba8
Reviewed-by: Jeff Layton <jlayton@kernel.org>
Luis Henriques 8d1ba8
Acked-by: Luis Henriques <lhenriques@suse.com>
Luis Henriques 8d1ba8
---
Luis Henriques 8d1ba8
 fs/ceph/super.c |  118 ++++++++++++--------------------------------------------
Luis Henriques 8d1ba8
 fs/ceph/super.h |    2 
Luis Henriques 8d1ba8
 2 files changed, 28 insertions(+), 92 deletions(-)
Luis Henriques 8d1ba8
Luis Henriques 8d1ba8
--- a/fs/ceph/super.c
Luis Henriques 8d1ba8
+++ b/fs/ceph/super.c
Luis Henriques 8d1ba8
@@ -124,6 +124,26 @@ static int ceph_sync_fs(struct super_blo
Luis Henriques 8d1ba8
 }
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
 /*
Luis Henriques 8d1ba8
+ * Remove adjacent slashes and then the trailing slash, unless it is
Luis Henriques 8d1ba8
+ * the only remaining character.
Luis Henriques 8d1ba8
+ *
Luis Henriques 8d1ba8
+ * E.g. "//dir1////dir2///" --> "/dir1/dir2", "///" --> "/".
Luis Henriques 8d1ba8
+ */
Luis Henriques 8d1ba8
+static void canonicalize_path(char *path)
Luis Henriques 8d1ba8
+{
Luis Henriques 8d1ba8
+	int i, j = 0;
Luis Henriques 8d1ba8
+
Luis Henriques 8d1ba8
+	for (i = 0; path[i] != '\0'; i++) {
Luis Henriques 8d1ba8
+		if (path[i] != '/' || j < 1 || path[j - 1] != '/')
Luis Henriques 8d1ba8
+			path[j++] = path[i];
Luis Henriques 8d1ba8
+	}
Luis Henriques 8d1ba8
+
Luis Henriques 8d1ba8
+	if (j > 1 && path[j - 1] == '/')
Luis Henriques 8d1ba8
+		j--;
Luis Henriques 8d1ba8
+	path[j] = '\0';
Luis Henriques 8d1ba8
+}
Luis Henriques 8d1ba8
+
Luis Henriques 8d1ba8
+/*
Luis Henriques 8d1ba8
  * mount options
Luis Henriques 8d1ba8
  */
Luis Henriques 8d1ba8
 enum {
Luis Henriques 8d1ba8
@@ -389,73 +409,6 @@ static int strcmp_null(const char *s1, c
Luis Henriques 8d1ba8
 	return strcmp(s1, s2);
Luis Henriques 8d1ba8
 }
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
-/**
Luis Henriques 8d1ba8
- * path_remove_extra_slash - Remove the extra slashes in the server path
Luis Henriques 8d1ba8
- * @server_path: the server path and could be NULL
Luis Henriques 8d1ba8
- *
Luis Henriques 8d1ba8
- * Return NULL if the path is NULL or only consists of "/", or a string
Luis Henriques 8d1ba8
- * without any extra slashes including the leading slash(es) and the
Luis Henriques 8d1ba8
- * slash(es) at the end of the server path, such as:
Luis Henriques 8d1ba8
- * "//dir1////dir2///" --> "dir1/dir2"
Luis Henriques 8d1ba8
- */
Luis Henriques 8d1ba8
-static char *path_remove_extra_slash(const char *server_path)
Luis Henriques 8d1ba8
-{
Luis Henriques 8d1ba8
-	const char *path = server_path;
Luis Henriques 8d1ba8
-	const char *cur, *end;
Luis Henriques 8d1ba8
-	char *buf, *p;
Luis Henriques 8d1ba8
-	int len;
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	/* if the server path is omitted */
Luis Henriques 8d1ba8
-	if (!path)
Luis Henriques 8d1ba8
-		return NULL;
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	/* remove all the leading slashes */
Luis Henriques 8d1ba8
-	while (*path == '/')
Luis Henriques 8d1ba8
-		path++;
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	/* if the server path only consists of slashes */
Luis Henriques 8d1ba8
-	if (*path == '\0')
Luis Henriques 8d1ba8
-		return NULL;
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	len = strlen(path);
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	buf = kmalloc(len + 1, GFP_KERNEL);
Luis Henriques 8d1ba8
-	if (!buf)
Luis Henriques 8d1ba8
-		return ERR_PTR(-ENOMEM);
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	end = path + len;
Luis Henriques 8d1ba8
-	p = buf;
Luis Henriques 8d1ba8
-	do {
Luis Henriques 8d1ba8
-		cur = strchr(path, '/');
Luis Henriques 8d1ba8
-		if (!cur)
Luis Henriques 8d1ba8
-			cur = end;
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-		len = cur - path;
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-		/* including one '/' */
Luis Henriques 8d1ba8
-		if (cur != end)
Luis Henriques 8d1ba8
-			len += 1;
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-		memcpy(p, path, len);
Luis Henriques 8d1ba8
-		p += len;
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-		while (cur <= end && *cur == '/')
Luis Henriques 8d1ba8
-			cur++;
Luis Henriques 8d1ba8
-		path = cur;
Luis Henriques 8d1ba8
-	} while (path < end);
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	*p = '\0';
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	/*
Luis Henriques 8d1ba8
-	 * remove the last slash if there has and just to make sure that
Luis Henriques 8d1ba8
-	 * we will get something like "dir1/dir2"
Luis Henriques 8d1ba8
-	 */
Luis Henriques 8d1ba8
-	if (*(--p) == '/')
Luis Henriques 8d1ba8
-		*p = '\0';
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
-	return buf;
Luis Henriques 8d1ba8
-}
Luis Henriques 8d1ba8
-
Luis Henriques 8d1ba8
 static int compare_mount_options(struct ceph_mount_options *new_fsopt,
Luis Henriques 8d1ba8
 				 struct ceph_options *new_opt,
Luis Henriques 8d1ba8
 				 struct ceph_fs_client *fsc)
Luis Henriques 8d1ba8
@@ -463,7 +416,6 @@ static int compare_mount_options(struct
Luis Henriques 8d1ba8
 	struct ceph_mount_options *fsopt1 = new_fsopt;
Luis Henriques 8d1ba8
 	struct ceph_mount_options *fsopt2 = fsc->mount_options;
Luis Henriques 8d1ba8
 	int ofs = offsetof(struct ceph_mount_options, snapdir_name);
Luis Henriques 8d1ba8
-	char *p1, *p2;
Luis Henriques 8d1ba8
 	int ret;
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
 	ret = memcmp(fsopt1, fsopt2, ofs);
Luis Henriques 8d1ba8
@@ -473,21 +425,12 @@ static int compare_mount_options(struct
Luis Henriques 8d1ba8
 	ret = strcmp_null(fsopt1->snapdir_name, fsopt2->snapdir_name);
Luis Henriques 8d1ba8
 	if (ret)
Luis Henriques 8d1ba8
 		return ret;
Luis Henriques 8d1ba8
+
Luis Henriques 8d1ba8
 	ret = strcmp_null(fsopt1->mds_namespace, fsopt2->mds_namespace);
Luis Henriques 8d1ba8
 	if (ret)
Luis Henriques 8d1ba8
 		return ret;
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
-	p1 = path_remove_extra_slash(fsopt1->server_path);
Luis Henriques 8d1ba8
-	if (IS_ERR(p1))
Luis Henriques 8d1ba8
-		return PTR_ERR(p1);
Luis Henriques 8d1ba8
-	p2 = path_remove_extra_slash(fsopt2->server_path);
Luis Henriques 8d1ba8
-	if (IS_ERR(p2)) {
Luis Henriques 8d1ba8
-		kfree(p1);
Luis Henriques 8d1ba8
-		return PTR_ERR(p2);
Luis Henriques 8d1ba8
-	}
Luis Henriques 8d1ba8
-	ret = strcmp_null(p1, p2);
Luis Henriques 8d1ba8
-	kfree(p1);
Luis Henriques 8d1ba8
-	kfree(p2);
Luis Henriques 8d1ba8
+	ret = strcmp_null(fsopt1->server_path, fsopt2->server_path);
Luis Henriques 8d1ba8
 	if (ret)
Luis Henriques 8d1ba8
 		return ret;
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
@@ -555,6 +498,8 @@ static int parse_mount_options(struct ce
Luis Henriques 8d1ba8
 			err = -ENOMEM;
Luis Henriques 8d1ba8
 			goto out;
Luis Henriques 8d1ba8
 		}
Luis Henriques 8d1ba8
+
Luis Henriques 8d1ba8
+		canonicalize_path(fsopt->server_path);
Luis Henriques 8d1ba8
 	} else {
Luis Henriques 8d1ba8
 		dev_name_end = dev_name + strlen(dev_name);
Luis Henriques 8d1ba8
 	}
Luis Henriques 8d1ba8
@@ -981,7 +926,9 @@ static struct dentry *ceph_real_mount(st
Luis Henriques 8d1ba8
 	mutex_lock(&fsc->client->mount_mutex);
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
 	if (!fsc->sb->s_root) {
Luis Henriques 8d1ba8
-		const char *path, *p;
Luis Henriques 8d1ba8
+		const char *path = fsc->mount_options->server_path ?
Luis Henriques 8d1ba8
+				     fsc->mount_options->server_path + 1 : "";
Luis Henriques 8d1ba8
+
Luis Henriques 8d1ba8
 		err = __ceph_open_session(fsc->client, started);
Luis Henriques 8d1ba8
 		if (err < 0)
Luis Henriques 8d1ba8
 			goto out;
Luis Henriques 8d1ba8
@@ -993,16 +940,6 @@ static struct dentry *ceph_real_mount(st
Luis Henriques 8d1ba8
 				goto out;
Luis Henriques 8d1ba8
 		}
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
-		p = path_remove_extra_slash(fsc->mount_options->server_path);
Luis Henriques 8d1ba8
-		if (IS_ERR(p)) {
Luis Henriques 8d1ba8
-			err = PTR_ERR(p);
Luis Henriques 8d1ba8
-			goto out;
Luis Henriques 8d1ba8
-		}
Luis Henriques 8d1ba8
-		/* if the server path is omitted or just consists of '/' */
Luis Henriques 8d1ba8
-		if (!p)
Luis Henriques 8d1ba8
-			path = "";
Luis Henriques 8d1ba8
-		else
Luis Henriques 8d1ba8
-			path = p;
Luis Henriques 8d1ba8
 		dout("mount opening path '%s'\n", path);
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
 		err = ceph_fs_debugfs_init(fsc);
Luis Henriques 8d1ba8
@@ -1010,7 +947,6 @@ static struct dentry *ceph_real_mount(st
Luis Henriques 8d1ba8
 			goto out;
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
 		root = open_root_dentry(fsc, path, started);
Luis Henriques 8d1ba8
-		kfree(p);
Luis Henriques 8d1ba8
 		if (IS_ERR(root)) {
Luis Henriques 8d1ba8
 			err = PTR_ERR(root);
Luis Henriques 8d1ba8
 			goto out;
Luis Henriques 8d1ba8
--- a/fs/ceph/super.h
Luis Henriques 8d1ba8
+++ b/fs/ceph/super.h
Luis Henriques 8d1ba8
@@ -85,7 +85,7 @@ struct ceph_mount_options {
Luis Henriques 8d1ba8
 
Luis Henriques 8d1ba8
 	char *snapdir_name;   /* default ".snap" */
Luis Henriques 8d1ba8
 	char *mds_namespace;  /* default NULL */
Luis Henriques 8d1ba8
-	char *server_path;    /* default  "/" */
Luis Henriques 8d1ba8
+	char *server_path;    /* default NULL (means "/") */
Luis Henriques 8d1ba8
 	char *fscache_uniq;   /* default NULL */
Luis Henriques 8d1ba8
 };
Luis Henriques 8d1ba8