Skip to content

Commit b27a939

Browse files
committed
ceph: canonicalize server path in place
syzbot reported that 4fbc0c7 ("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: 4fbc0c7 ("ceph: remove the extra slashes in the server path") Reported-by: [email protected] Signed-off-by: Ilya Dryomov <[email protected]> Reviewed-by: Jeff Layton <[email protected]>
1 parent 8e4473b commit b27a939

File tree

2 files changed

+29
-94
lines changed

2 files changed

+29
-94
lines changed

fs/ceph/super.c

Lines changed: 28 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,26 @@ struct ceph_parse_opts_ctx {
202202
struct ceph_mount_options *opts;
203203
};
204204

205+
/*
206+
* Remove adjacent slashes and then the trailing slash, unless it is
207+
* the only remaining character.
208+
*
209+
* E.g. "//dir1////dir2///" --> "/dir1/dir2", "///" --> "/".
210+
*/
211+
static void canonicalize_path(char *path)
212+
{
213+
int i, j = 0;
214+
215+
for (i = 0; path[i] != '\0'; i++) {
216+
if (path[i] != '/' || j < 1 || path[j - 1] != '/')
217+
path[j++] = path[i];
218+
}
219+
220+
if (j > 1 && path[j - 1] == '/')
221+
j--;
222+
path[j] = '\0';
223+
}
224+
205225
/*
206226
* Parse the source parameter. Distinguish the server list from the path.
207227
*
@@ -224,15 +244,16 @@ static int ceph_parse_source(struct fs_parameter *param, struct fs_context *fc)
224244

225245
dev_name_end = strchr(dev_name, '/');
226246
if (dev_name_end) {
227-
kfree(fsopt->server_path);
228-
229247
/*
230248
* The server_path will include the whole chars from userland
231249
* including the leading '/'.
232250
*/
251+
kfree(fsopt->server_path);
233252
fsopt->server_path = kstrdup(dev_name_end, GFP_KERNEL);
234253
if (!fsopt->server_path)
235254
return -ENOMEM;
255+
256+
canonicalize_path(fsopt->server_path);
236257
} else {
237258
dev_name_end = dev_name + strlen(dev_name);
238259
}
@@ -456,81 +477,13 @@ static int strcmp_null(const char *s1, const char *s2)
456477
return strcmp(s1, s2);
457478
}
458479

459-
/**
460-
* path_remove_extra_slash - Remove the extra slashes in the server path
461-
* @server_path: the server path and could be NULL
462-
*
463-
* Return NULL if the path is NULL or only consists of "/", or a string
464-
* without any extra slashes including the leading slash(es) and the
465-
* slash(es) at the end of the server path, such as:
466-
* "//dir1////dir2///" --> "dir1/dir2"
467-
*/
468-
static char *path_remove_extra_slash(const char *server_path)
469-
{
470-
const char *path = server_path;
471-
const char *cur, *end;
472-
char *buf, *p;
473-
int len;
474-
475-
/* if the server path is omitted */
476-
if (!path)
477-
return NULL;
478-
479-
/* remove all the leading slashes */
480-
while (*path == '/')
481-
path++;
482-
483-
/* if the server path only consists of slashes */
484-
if (*path == '\0')
485-
return NULL;
486-
487-
len = strlen(path);
488-
489-
buf = kmalloc(len + 1, GFP_KERNEL);
490-
if (!buf)
491-
return ERR_PTR(-ENOMEM);
492-
493-
end = path + len;
494-
p = buf;
495-
do {
496-
cur = strchr(path, '/');
497-
if (!cur)
498-
cur = end;
499-
500-
len = cur - path;
501-
502-
/* including one '/' */
503-
if (cur != end)
504-
len += 1;
505-
506-
memcpy(p, path, len);
507-
p += len;
508-
509-
while (cur <= end && *cur == '/')
510-
cur++;
511-
path = cur;
512-
} while (path < end);
513-
514-
*p = '\0';
515-
516-
/*
517-
* remove the last slash if there has and just to make sure that
518-
* we will get something like "dir1/dir2"
519-
*/
520-
if (*(--p) == '/')
521-
*p = '\0';
522-
523-
return buf;
524-
}
525-
526480
static int compare_mount_options(struct ceph_mount_options *new_fsopt,
527481
struct ceph_options *new_opt,
528482
struct ceph_fs_client *fsc)
529483
{
530484
struct ceph_mount_options *fsopt1 = new_fsopt;
531485
struct ceph_mount_options *fsopt2 = fsc->mount_options;
532486
int ofs = offsetof(struct ceph_mount_options, snapdir_name);
533-
char *p1, *p2;
534487
int ret;
535488

536489
ret = memcmp(fsopt1, fsopt2, ofs);
@@ -540,21 +493,12 @@ static int compare_mount_options(struct ceph_mount_options *new_fsopt,
540493
ret = strcmp_null(fsopt1->snapdir_name, fsopt2->snapdir_name);
541494
if (ret)
542495
return ret;
496+
543497
ret = strcmp_null(fsopt1->mds_namespace, fsopt2->mds_namespace);
544498
if (ret)
545499
return ret;
546500

547-
p1 = path_remove_extra_slash(fsopt1->server_path);
548-
if (IS_ERR(p1))
549-
return PTR_ERR(p1);
550-
p2 = path_remove_extra_slash(fsopt2->server_path);
551-
if (IS_ERR(p2)) {
552-
kfree(p1);
553-
return PTR_ERR(p2);
554-
}
555-
ret = strcmp_null(p1, p2);
556-
kfree(p1);
557-
kfree(p2);
501+
ret = strcmp_null(fsopt1->server_path, fsopt2->server_path);
558502
if (ret)
559503
return ret;
560504

@@ -957,7 +901,9 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc,
957901
mutex_lock(&fsc->client->mount_mutex);
958902

959903
if (!fsc->sb->s_root) {
960-
const char *path, *p;
904+
const char *path = fsc->mount_options->server_path ?
905+
fsc->mount_options->server_path + 1 : "";
906+
961907
err = __ceph_open_session(fsc->client, started);
962908
if (err < 0)
963909
goto out;
@@ -969,22 +915,11 @@ static struct dentry *ceph_real_mount(struct ceph_fs_client *fsc,
969915
goto out;
970916
}
971917

972-
p = path_remove_extra_slash(fsc->mount_options->server_path);
973-
if (IS_ERR(p)) {
974-
err = PTR_ERR(p);
975-
goto out;
976-
}
977-
/* if the server path is omitted or just consists of '/' */
978-
if (!p)
979-
path = "";
980-
else
981-
path = p;
982918
dout("mount opening path '%s'\n", path);
983919

984920
ceph_fs_debugfs_init(fsc);
985921

986922
root = open_root_dentry(fsc, path, started);
987-
kfree(p);
988923
if (IS_ERR(root)) {
989924
err = PTR_ERR(root);
990925
goto out;

fs/ceph/super.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ struct ceph_mount_options {
9191

9292
char *snapdir_name; /* default ".snap" */
9393
char *mds_namespace; /* default NULL */
94-
char *server_path; /* default "/" */
94+
char *server_path; /* default NULL (means "/") */
9595
char *fscache_uniq; /* default NULL */
9696
};
9797

0 commit comments

Comments
 (0)