From feb8f56ba255d185af151438b93da7222ccff511 Mon Sep 17 00:00:00 2001 From: Boris Burkov Date: Mon, 16 Nov 2020 16:58:20 -0800 Subject: [PATCH] btrfs-progs: receive: fix btrfs_mount_root substring bug The current mount detection code in btrfs receive is not quite perfect. For example, suppose /tmp is mounted as a tmpfs. In that case, btrfs receive /tmp2 will find /tmp as the longest mount that matches a prefix of /tmp2 and blow up because it is not a btrfs filesystem, even if /tmp2 is just a directory in / mounted as btrfs. Fix this by replacing the substring check with a dirname recursion to only check the directories in the path of the dir, rather than every substring. Add a new test for this case. Signed-off-by: Boris Burkov Signed-off-by: David Sterba --- common/path-utils.c | 34 ++++++++++++++++++ common/path-utils.h | 1 + common/utils.c | 5 ++- .../045-receive-check-mount-type/test.sh | 36 +++++++++++++++++++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100755 tests/misc-tests/045-receive-check-mount-type/test.sh diff --git a/common/path-utils.c b/common/path-utils.c index e38513f6..2fb3a7da 100644 --- a/common/path-utils.c +++ b/common/path-utils.c @@ -29,6 +29,7 @@ #include #include #include +#include #include "common/path-utils.h" /* @@ -374,6 +375,39 @@ int path_is_dir(const char *path) return !!S_ISDIR(st.st_mode); } +/* + * Test if a path is recursively contained in parent. Assumes parent and path + * are null terminated absolute paths. + * + * Returns: + * 0 - path not contained in parent + * 1 - path contained in parent + * < 0 - error + * + * e.g. (/, /foo) -> 1 + * (/foo, /) -> 0 + * (/foo, /foo/bar/baz) -> 1 + */ +int path_is_in_dir(const char *parent, const char *path) +{ + char *tmp = strdup(path); + char *curr_dir = tmp; + int ret; + + while (strcmp(parent, curr_dir) != 0) { + if (strcmp(curr_dir, "/") == 0) { + ret = 0; + goto out; + } + curr_dir = dirname(curr_dir); + } + ret = 1; + +out: + free(tmp); + return ret; +} + /* * Copy a path argument from SRC to DEST and check the SRC length if it's at * most PATH_MAX and fits into DEST. DESTLEN is supposed to be exact size of diff --git a/common/path-utils.h b/common/path-utils.h index 9bb822f9..dcbec284 100644 --- a/common/path-utils.h +++ b/common/path-utils.h @@ -37,6 +37,7 @@ int path_is_reg_file(const char *path); int path_is_dir(const char *path); int is_same_loop_file(const char *a, const char *b); int path_is_reg_or_block_device(const char *filename); +int path_is_in_dir(const char *parent, const char *path); int test_issubvolname(const char *name); diff --git a/common/utils.c b/common/utils.c index 852fdd44..57e41432 100644 --- a/common/utils.c +++ b/common/utils.c @@ -1559,9 +1559,8 @@ int find_mount_root(const char *path, char **mount_root) return -errno; while ((ent = getmntent(mnttab))) { - len = strlen(ent->mnt_dir); - if (strncmp(ent->mnt_dir, path, len) == 0) { - /* match found and use the latest match */ + if (path_is_in_dir(ent->mnt_dir, path)) { + len = strlen(ent->mnt_dir); if (longest_matchlen <= len) { free(longest_match); longest_matchlen = len; diff --git a/tests/misc-tests/045-receive-check-mount-type/test.sh b/tests/misc-tests/045-receive-check-mount-type/test.sh new file mode 100755 index 00000000..4f8e6fc7 --- /dev/null +++ b/tests/misc-tests/045-receive-check-mount-type/test.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# +# Test some scenarios around the mount point we do receive onto. +# Should fail in a non-btrfs filesystem, but succeed if a non btrfs filesystem +# is the longest mounted substring of the target, but not the actual containing +# mount. +# +# This is a regression test for +# "btrfs-progs: receive: fix btrfs_mount_root substring bug" + +source "$TEST_TOP/common" + +check_prereq mkfs.btrfs +check_prereq btrfs + +setup_root_helper +prepare_test_dev + +run_check_mkfs_test_dev +run_check_mount_test_dev + +cd "$TEST_MNT" +run_check $SUDO_HELPER mkdir "foo" "foobar" +run_check $SUDO_HELPER mount -t tmpfs tmpfs "foo" +run_check $SUDO_HELPER mkdir "foo/bar" + +run_check $SUDO_HELPER "$TOP/btrfs" subvolume create "subvol" +run_check $SUDO_HELPER "$TOP/btrfs" subvolume snapshot -r "subvol" "snap" +run_check $SUDO_HELPER "$TOP/btrfs" send -f send.data "snap" +run_mustfail "no receive on tmpfs" $SUDO_HELPER "$TOP/btrfs" receive -f send.data "./foo" +run_mustfail "no receive on tmpfs" $SUDO_HELPER "$TOP/btrfs" receive -f send.data "./foo/bar" +run_check $SUDO_HELPER "$TOP/btrfs" receive -f send.data "./foobar" +run_check_umount_test_dev "foo" + +cd .. +run_check_umount_test_dev