Re: [PATCH] ceph: canonicalize server path in place

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Feb 11, 2020 at 07:06:11AM -0500, Jeff Layton wrote:
> On Tue, 2020-02-11 at 10:53 +0100, Ilya Dryomov wrote:
> > syzbot reported that 4fbc0c711b24 ("ceph: remove the extra slashes in
> > the server path") had caused a regression where an allocation could be
> > done under a spinlock -- compare_mount_options() is called by sget_fc()
> > with sb_lock held.
> > 
> > We don't really need the supplied server path, so canonicalize it
> > in place and compare it directly.  To make this work, the leading
> > slash is kept around and the logic in ceph_real_mount() to skip it
> > is restored.  CEPH_MSG_CLIENT_SESSION now reports the same (i.e.
> > canonicalized) path, with the leading slash of course.
> > 
> > Fixes: 4fbc0c711b24 ("ceph: remove the extra slashes in the server path")

I think both this fix and the original patch that introduced this should
go into stable kernels, because the original issue [1] can be easily
reproducible in the LTS kernels.

[1] https://tracker.ceph.com/issues/42771

I've done a quick attempt at backporting these patches to 5.4 and attached
them to the tracker issue.  I can send them to the stable mailing-list if
you guys think the backports are OK (the new mount API conversion moved
things around and it's not a clean cherry-pick).

Cheers,
--
Luís

> > Reported-by: syzbot+98704a51af8e3d9425a9@xxxxxxxxxxxxxxxxxxxxxxxxx
> > Signed-off-by: Ilya Dryomov <idryomov@xxxxxxxxx>
> > ---
> >  fs/ceph/super.c | 121 +++++++++++-------------------------------------
> >  fs/ceph/super.h |   2 +-
> >  2 files changed, 29 insertions(+), 94 deletions(-)
> > 
> > diff --git a/fs/ceph/super.c b/fs/ceph/super.c
> > index 1d9f083b8a11..64ea34ac330b 100644
> > --- a/fs/ceph/super.c
> > +++ b/fs/ceph/super.c
> > @@ -202,6 +202,26 @@ struct ceph_parse_opts_ctx {
> >  	struct ceph_mount_options	*opts;
> >  };
> >  
> > +/*
> > + * Remove adjacent slashes and then the trailing slash, unless it is
> > + * the only remaining character.
> > + *
> > + * E.g. "//dir1////dir2///" --> "/dir1/dir2", "///" --> "/".
> > + */
> > +static void canonicalize_path(char *path)
> > +{
> > +	int i, j = 0;
> > +
> > +	for (i = 0; path[i] != '\0'; i++) {
> > +		if (path[i] != '/' || j < 1 || path[j - 1] != '/')
> > +			path[j++] = path[i];
> > +	}
> > +
> > +	if (j > 1 && path[j - 1] == '/')
> > +		j--;
> > +	path[j] = '\0';
> > +}
> > +
> >  /*
> >   * Parse the source parameter.  Distinguish the server list from the path.
> >   *
> > @@ -224,15 +244,16 @@ static int ceph_parse_source(struct fs_parameter *param, struct fs_context *fc)
> >  
> >  	dev_name_end = strchr(dev_name, '/');
> >  	if (dev_name_end) {
> > -		kfree(fsopt->server_path);
> > -
> >  		/*
> >  		 * The server_path will include the whole chars from userland
> >  		 * including the leading '/'.
> >  		 */
> > +		kfree(fsopt->server_path);
> >  		fsopt->server_path = kstrdup(dev_name_end, GFP_KERNEL);
> >  		if (!fsopt->server_path)
> >  			return -ENOMEM;
> > +
> > +		canonicalize_path(fsopt->server_path);
> >  	} else {
> >  		dev_name_end = dev_name + strlen(dev_name);
> >  	}
> > @@ -456,73 +477,6 @@ static int strcmp_null(const char *s1, const char *s2)
> >  	return strcmp(s1, s2);
> >  }
> >  
> > -/**
> > - * path_remove_extra_slash - Remove the extra slashes in the server path
> > - * @server_path: the server path and could be NULL
> > - *
> > - * Return NULL if the path is NULL or only consists of "/", or a string
> > - * without any extra slashes including the leading slash(es) and the
> > - * slash(es) at the end of the server path, such as:
> > - * "//dir1////dir2///" --> "dir1/dir2"
> > - */
> > -static char *path_remove_extra_slash(const char *server_path)
> > -{
> > -	const char *path = server_path;
> > -	const char *cur, *end;
> > -	char *buf, *p;
> > -	int len;
> > -
> > -	/* if the server path is omitted */
> > -	if (!path)
> > -		return NULL;
> > -
> > -	/* remove all the leading slashes */
> > -	while (*path == '/')
> > -		path++;
> > -
> > -	/* if the server path only consists of slashes */
> > -	if (*path == '\0')
> > -		return NULL;
> > -
> > -	len = strlen(path);
> > -
> > -	buf = kmalloc(len + 1, GFP_KERNEL);
> > -	if (!buf)
> > -		return ERR_PTR(-ENOMEM);
> > -
> > -	end = path + len;
> > -	p = buf;
> > -	do {
> > -		cur = strchr(path, '/');
> > -		if (!cur)
> > -			cur = end;
> > -
> > -		len = cur - path;
> > -
> > -		/* including one '/' */
> > -		if (cur != end)
> > -			len += 1;
> > -
> > -		memcpy(p, path, len);
> > -		p += len;
> > -
> > -		while (cur <= end && *cur == '/')
> > -			cur++;
> > -		path = cur;
> > -	} while (path < end);
> > -
> > -	*p = '\0';
> > -
> > -	/*
> > -	 * remove the last slash if there has and just to make sure that
> > -	 * we will get something like "dir1/dir2"
> > -	 */
> > -	if (*(--p) == '/')
> > -		*p = '\0';
> > -
> > -	return buf;
> > -}
> > -
> >  static int compare_mount_options(struct ceph_mount_options *new_fsopt,
> >  				 struct ceph_options *new_opt,
> >  				 struct ceph_fs_client *fsc)
> > @@ -530,7 +484,6 @@ static int compare_mount_options(struct ceph_mount_options *new_fsopt,
> >  	struct ceph_mount_options *fsopt1 = new_fsopt;
> >  	struct ceph_mount_options *fsopt2 = fsc->mount_options;
> >  	int ofs = offsetof(struct ceph_mount_options, snapdir_name);
> > -	char *p1, *p2;
> >  	int ret;
> >  
> >  	ret = memcmp(fsopt1, fsopt2, ofs);
> > @@ -540,21 +493,12 @@ static int compare_mount_options(struct ceph_mount_options *new_fsopt,
> >  	ret = strcmp_null(fsopt1->snapdir_name, fsopt2->snapdir_name);
> >  	if (ret)
> >  		return ret;
> > +
> >  	ret = strcmp_null(fsopt1->mds_namespace, fsopt2->mds_namespace);
> >  	if (ret)
> >  		return ret;
> >  
> > -	p1 = path_remove_extra_slash(fsopt1->server_path);
> > -	if (IS_ERR(p1))
> > -		return PTR_ERR(p1);
> > -	p2 = path_remove_extra_slash(fsopt2->server_path);
> > -	if (IS_ERR(p2)) {
> > -		kfree(p1);
> > -		return PTR_ERR(p2);
> > -	}
> > -	ret = strcmp_null(p1, p2);
> > -	kfree(p1);
> > -	kfree(p2);
> > +	ret = strcmp_null(fsopt1->server_path, fsopt2->server_path);
> >  	if (ret)
> >  		return ret;
> >  
> > @@ -957,7 +901,9 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc,
> >  	mutex_lock(&fsc->client->mount_mutex);
> >  
> >  	if (!fsc->sb->s_root) {
> > -		const char *path, *p;
> > +		const char *path = fsc->mount_options->server_path ?
> > +				     fsc->mount_options->server_path + 1 : "";
> > +
> >  		err = __ceph_open_session(fsc->client, started);
> >  		if (err < 0)
> >  			goto out;
> > @@ -969,22 +915,11 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc,
> >  				goto out;
> >  		}
> >  
> > -		p = path_remove_extra_slash(fsc->mount_options->server_path);
> > -		if (IS_ERR(p)) {
> > -			err = PTR_ERR(p);
> > -			goto out;
> > -		}
> > -		/* if the server path is omitted or just consists of '/' */
> > -		if (!p)
> > -			path = "";
> > -		else
> > -			path = p;
> >  		dout("mount opening path '%s'\n", path);
> >  
> >  		ceph_fs_debugfs_init(fsc);
> >  
> >  		root = open_root_dentry(fsc, path, started);
> > -		kfree(p);
> >  		if (IS_ERR(root)) {
> >  			err = PTR_ERR(root);
> >  			goto out;
> > diff --git a/fs/ceph/super.h b/fs/ceph/super.h
> > index 1e456a9011bb..037cdfb2ad4f 100644
> > --- a/fs/ceph/super.h
> > +++ b/fs/ceph/super.h
> > @@ -91,7 +91,7 @@ struct ceph_mount_options {
> >  
> >  	char *snapdir_name;   /* default ".snap" */
> >  	char *mds_namespace;  /* default NULL */
> > -	char *server_path;    /* default  "/" */
> > +	char *server_path;    /* default NULL (means "/") */
> >  	char *fscache_uniq;   /* default NULL */
> >  };
> >  
> 
> Nice work.
> 
> Reviewed-by: Jeff Layton <jlayton@xxxxxxxxxx>
> 



[Index of Archives]     [CEPH Users]     [Ceph Large]     [Ceph Dev]     [Information on CEPH]     [Linux BTRFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux