From d105fafabbd01d39dd98d4e7d24e41da5cb2680e Mon Sep 17 00:00:00 2001 From: Bill Zissimooulos Date: Sat, 12 May 2018 18:51:44 -0700 Subject: rename: perform user mode dir loop check when not done in kernel Linux performs the dir loop check (rename(a, a/b/c) or rename(a/b/c, a), etc.) in kernel. Unfortunately other systems do not perform this check (e.g. FreeBSD). This results in a deadlock in get_path2, because libfuse did not expect to handle such cases. We add a check_dir_loop function that performs the dir loop check in user mode and enable it on systems that need it. --- lib/fuse.c | 62 ++++++++++++++- test/test_syscalls.c | 216 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 275 insertions(+), 3 deletions(-) diff --git a/lib/fuse.c b/lib/fuse.c index 2fb942c..7072fb0 100644 --- a/lib/fuse.c +++ b/lib/fuse.c @@ -1248,6 +1248,49 @@ static int get_path_wrlock(struct fuse *f, fuse_ino_t nodeid, const char *name, return get_path_common(f, nodeid, name, path, wnode); } +#if defined(__FreeBSD__) +#define CHECK_DIR_LOOP true +#else +#define CHECK_DIR_LOOP false +#endif + +static int check_dir_loop(struct fuse *f, + fuse_ino_t nodeid1, const char *name1, + fuse_ino_t nodeid2, const char *name2) +{ + struct node *node, *node1, *node2; + fuse_ino_t id1, id2; + + node1 = lookup_node(f, nodeid1, name1); + id1 = node1 ? node1->nodeid : nodeid1; + + node2 = lookup_node(f, nodeid2, name2); + id2 = node2 ? node2->nodeid : nodeid2; + + for (node = get_node(f, id2); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + if (node->name == NULL || node->parent == NULL) + break; + + if (node->nodeid != id2 && node->nodeid == id1) + return -EINVAL; + } + + if (node2) + { + for (node = get_node(f, id1); node->nodeid != FUSE_ROOT_ID; + node = node->parent) { + if (node->name == NULL || node->parent == NULL) + break; + + if (node->nodeid != id1 && node->nodeid == id2) + return -ENOTEMPTY; + } + } + + return 0; +} + static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, fuse_ino_t nodeid2, const char *name2, char **path1, char **path2, @@ -1272,11 +1315,20 @@ static int try_get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, fuse_ino_t nodeid2, const char *name2, char **path1, char **path2, - struct node **wnode1, struct node **wnode2) + struct node **wnode1, struct node **wnode2, + bool dir_loop) { int err; pthread_mutex_lock(&f->lock); + + if (dir_loop) + { + err = check_dir_loop(f, nodeid1, name1, nodeid2, name2); + if (err) + goto out_unlock; + } + err = try_get_path2(f, nodeid1, name1, nodeid2, name2, path1, path2, wnode1, wnode2); if (err == -EAGAIN) { @@ -1297,6 +1349,8 @@ static int get_path2(struct fuse *f, fuse_ino_t nodeid1, const char *name1, debug_path(f, "DEQUEUE PATH1", nodeid1, name1, !!wnode1); debug_path(f, " PATH2", nodeid2, name2, !!wnode2); } + +out_unlock: pthread_mutex_unlock(&f->lock); return err; @@ -2965,7 +3019,8 @@ static void fuse_lib_rename(fuse_req_t req, fuse_ino_t olddir, int err; err = get_path2(f, olddir, oldname, newdir, newname, - &oldpath, &newpath, &wnode1, &wnode2); + &oldpath, &newpath, &wnode1, &wnode2, + CHECK_DIR_LOOP); if (!err) { struct fuse_intr_data d; err = 0; @@ -3001,7 +3056,8 @@ static void fuse_lib_link(fuse_req_t req, fuse_ino_t ino, fuse_ino_t newparent, int err; err = get_path2(f, ino, NULL, newparent, newname, - &oldpath, &newpath, NULL, NULL); + &oldpath, &newpath, NULL, NULL, + false); if (!err) { struct fuse_intr_data d; diff --git a/test/test_syscalls.c b/test/test_syscalls.c index 392a210..38a37a1 100644 --- a/test/test_syscalls.c +++ b/test/test_syscalls.c @@ -1282,6 +1282,221 @@ static int test_rename_dir(void) return 0; } +static int test_rename_dir_loop(void) +{ +#define PATH(p) (snprintf(path, sizeof path, "%s/%s", testdir, p), path) +#define PATH2(p) (snprintf(path2, sizeof path2, "%s/%s", testdir, p), path2) + + char path[1024], path2[1024]; + int err = 0; + int res; + + start_test("rename dir loop"); + + res = create_dir(testdir, testdir_files); + if (res == -1) + return -1; + + res = mkdir(PATH("a"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = rename(PATH("a"), PATH2("a")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/b"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = mkdir(PATH("a/b/c"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b/c")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a"), PATH2("a/b/c/a")); + if (res == 0 || errno != EINVAL) { + PERROR("rename"); + goto fail; + } + + errno = 0; + res = rename(PATH("a/b/c"), PATH2("a")); + if (res == 0 || errno != ENOTEMPTY) { + PERROR("rename"); + goto fail; + } + + res = open(PATH("a/foo"), O_CREAT, 0644); + if (res == -1) { + PERROR("open"); + goto fail; + } + close(res); + + res = rename(PATH("a/foo"), PATH2("a/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/foo"), PATH2("a/b/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/b/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/foo"), PATH2("a/b/c/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/b/c/bar"), PATH2("a/foo")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = open(PATH("a/bar"), O_CREAT, 0644); + if (res == -1) { + PERROR("open"); + goto fail; + } + close(res); + + res = rename(PATH("a/foo"), PATH2("a/bar")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + unlink(PATH("a/bar")); + + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/d"), PATH2("a/b")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/d"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = rename(PATH("a/d"), PATH2("a/b")); + if (res == -1) { + PERROR("rename"); + goto fail; + } + + res = mkdir(PATH("a/d"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + res = mkdir(PATH("a/d/e"), 0755); + if (res == -1) { + PERROR("mkdir"); + goto fail; + } + + errno = 0; + res = rename(PATH("a/b"), PATH2("a/d")); + if (res == 0 || errno != ENOTEMPTY) { + PERROR("rename"); + goto fail; + } + + rmdir(PATH("a/d/e")); + rmdir(PATH("a/d")); + + rmdir(PATH("a/b/c")); + rmdir(PATH("a/b")); + rmdir(PATH("a")); + + err += cleanup_dir(testdir, testdir_files, 0); + res = rmdir(testdir); + if (res == -1) { + PERROR("rmdir"); + goto fail; + } + res = check_nonexist(testdir); + if (res == -1) + return -1; + if (err) + return -1; + + success(); + return 0; + +fail: + unlink(PATH("a/bar")); + + rmdir(PATH("a/d/e")); + rmdir(PATH("a/d")); + + rmdir(PATH("a/b/c")); + rmdir(PATH("a/b")); + rmdir(PATH("a")); + + cleanup_dir(testdir, testdir_files, 1); + rmdir(testdir); + + return -1; + +#undef PATH2 +#undef PATH +} + #ifndef __FreeBSD__ static int test_mkfifo(void) { @@ -1461,6 +1676,7 @@ int main(int argc, char *argv[]) err += test_mkdir(); err += test_rename_file(); err += test_rename_dir(); + err += test_rename_dir_loop(); err += test_utime(); err += test_truncate(0); err += test_truncate(testdatalen / 2); -- cgit v1.2.3