From f889f9f434dc70c100ddd3ca8f7e51530c37829c Mon Sep 17 00:00:00 2001 From: Georgia Garcia Date: Thu, 4 Jan 2024 18:17:28 -0300 Subject: [PATCH] tests: add move_mount regression tests Signed-off-by: Georgia Garcia --- .gitignore | 1 + tests/regression/apparmor/Makefile | 1 + tests/regression/apparmor/mount.sh | 154 ++++++++++++++++ tests/regression/apparmor/move_mount.c | 244 +++++++++++++++++++++++++ 4 files changed, 400 insertions(+) create mode 100644 tests/regression/apparmor/move_mount.c diff --git a/.gitignore b/.gitignore index 97ad9ad4f..2bed5f57b 100644 --- a/.gitignore +++ b/.gitignore @@ -264,6 +264,7 @@ tests/regression/apparmor/link_subset tests/regression/apparmor/mkdir tests/regression/apparmor/mmap tests/regression/apparmor/mount +tests/regression/apparmor/move_mount tests/regression/apparmor/named_pipe tests/regression/apparmor/net_raw tests/regression/apparmor/open diff --git a/tests/regression/apparmor/Makefile b/tests/regression/apparmor/Makefile index bd00aae23..7244a45a5 100644 --- a/tests/regression/apparmor/Makefile +++ b/tests/regression/apparmor/Makefile @@ -109,6 +109,7 @@ SRC=access.c \ mmap.c \ mkdir.c \ mount.c \ + move_mount.c \ named_pipe.c \ net_raw.c \ open.c \ diff --git a/tests/regression/apparmor/mount.sh b/tests/regression/apparmor/mount.sh index eef8e57dc..f73e77531 100755 --- a/tests/regression/apparmor/mount.sh +++ b/tests/regression/apparmor/mount.sh @@ -275,6 +275,155 @@ test_options() { # run_all_combinations_test } +open_tree_test() { + desc=$1 + qualifier=$2 + additional_perms=$3 + result=$4 + + genprofile cap:sys_admin ${qualifier}mount:ALL ${additional_perms} + mount ${loop_device} ${mnt_source} + runchecktest "MOVE_MOUNT (confined${desc}: mount,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + genprofile cap:sys_admin "${qualifier}mount:-> ${mnt_target}/" ${additional_perms} + mount ${loop_device} ${mnt_source} + runchecktest "MOVE_MOUNT (confined${desc}: mount -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + genprofile cap:sys_admin "${qualifier}mount: options=(move) -> ${mnt_target}/" ${additional_perms} + mount ${loop_device} ${mnt_source} + runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + # genprofile cap:sys_admin "${qualifier}mount: detached -> ${mnt_target}/" ${additional_perms} + # mount ${loop_device} ${mnt_source} + # runchecktest "MOVE_MOUNT (confined${desc}: mount detached -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname} + # remove_mnt + + # genprofile cap:sys_admin "${qualifier}mount: options=(move) detached -> ${mnt_target}/" ${additional_perms} + # mount ${loop_device} ${mnt_source} + # runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) detached -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname} + # remove_mnt + + genprofile cap:sys_admin "${qualifier}mount: \"\" -> ${mnt_target}/" ${additional_perms} + mount ${loop_device} ${mnt_source} + runchecktest "MOVE_MOUNT (confined${desc}: mount \"\" -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + genprofile cap:sys_admin "${qualifier}mount: options=(move) \"\" -> ${mnt_target}/" ${additional_perms} + mount ${loop_device} ${mnt_source} + runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) \"\" -> ${mnt_target}/,)" ${result} open_tree ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + +} + +open_tree_tests() { + mnt_source=$1 + mnt_target=$2 + fsname=$3 + settest move_mount + # TODO: check for move_mount syscall support + # TODO: check that parser supports detached + # eg. move_mount tmpfs /tmp/move_mount_test tmpfs + + success=pass + should_fail=fail + if [ "$(kernel_features mount/move_mount)" != "true" ] ; then + # kernels that don't have move_mount should fail on with disconnected path + success=fail + # addresses kernels that are not mediating move_mount + should_fail=xfail + fi + + mount ${loop_device} ${mnt_source} + runchecktest "MOVE_MOUNT (unconfined open_tree)" pass open_tree ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + genprofile cap:sys_admin + mount ${loop_device} ${mnt_source} + runchecktest "MOVE_MOUNT (confined open_tree: no mount rule)" ${should_fail} open_tree ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + # desc qual add_perms pass/fail + open_tree_test " open_tree" "" "" pass + open_tree_test " open_tree deny" "qual=deny:" "" ${should_fail} + # now some attach_disconnected with move_mount tests + # attach_disconnected should not affect move_mount mediation + open_tree_test " open_tree att_dis" "" "flag:attach_disconnected" pass + open_tree_test " open_tree deny att_dis" "qual=deny:" "flag:attach_disconnected" ${should_fail} +} + +fsmount_test() { + desc=$1 + qualifier=$2 + additional_perms=$3 + result=$4 + + genprofile cap:sys_admin ${qualifier}mount:ALL ${additional_perms} + runchecktest "MOVE_MOUNT (confined${desc}: mount,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + genprofile cap:sys_admin "${qualifier}mount:-> ${mnt_target}/" ${additional_perms} + runchecktest "MOVE_MOUNT (confined${desc}: mount -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + genprofile cap:sys_admin "${qualifier}mount: options=(move) -> ${mnt_target}/" ${additional_perms} + runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + # genprofile cap:sys_admin "${qualifier}mount: detached -> ${mnt_target}/" ${additional_perms} + # runchecktest "MOVE_MOUNT (confined${desc}: mount detached -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname} + # remove_mnt + + # genprofile cap:sys_admin "${qualifier}mount: options=(move) detached -> ${mnt_target}/" ${additional_perms} + # runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) detached -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname} + # remove_mnt + + genprofile cap:sys_admin "${qualifier}mount: \"\" -> ${mnt_target}/" ${additional_perms} + runchecktest "MOVE_MOUNT (confined${desc}: mount \"\" -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + genprofile cap:sys_admin "${qualifier}mount: options=(move) \"\" -> ${mnt_target}/" ${additional_perms} + runchecktest "MOVE_MOUNT (confined${desc}: mount options=(move) \"\" -> ${mnt_target}/,)" ${result} fsmount ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + +} + +fsmount_tests() { + mnt_source=$1 + mnt_target=$2 + fsname=$3 + settest move_mount + # TODO: check for move_mount syscall support + # TODO: check that parser supports detached + # eg. move_mount tmpfs /tmp/move_mount_test tmpfs + + success=pass + should_fail=fail + if [ "$(kernel_features mount/move_mount)" != "true" ] ; then + # kernels that don't have move_mount should fail on with disconnected path + success=fail + # addresses kernels that are not mediating move_mount + should_fail=xfail + fi + + runchecktest "MOVE_MOUNT (unconfined fsmount)" pass fsmount ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + genprofile cap:sys_admin + runchecktest "MOVE_MOUNT (confined fsmount: no mount rule)" ${should_fail} fsmount ${mnt_source} ${mnt_target} ${fsname} + remove_mnt + + # desc qual add_perms pass/fail + fsmount_test " fsmount" "" "" pass + fsmount_test " fsmount deny" "qual=deny:" "" ${should_fail} + # now some attach_disconnected with move_mount tests + # attach_disconnected should not affect move_mount mediation + fsmount_test " fsmount att_dis" "" "flag:attach_disconnected" pass + fsmount_test " fsmount deny att_dis" "qual=deny:" "flag:attach_disconnected" ${should_fail} +} + # TEST 1. Make sure can mount and umount unconfined runchecktest "MOUNT (unconfined)" pass mount ${loop_device} ${mount_point} remove_mnt @@ -409,6 +558,11 @@ else remove_mnt test_options + + # test new mount interface + fsmount_tests tmpfs ${mount_point} tmpfs + fsmount_tests ${loop_device} ${mount_point} ${fstype} + open_tree_tests ${mount_point2} ${mount_point} ${fstype} fi #need tests for chroot diff --git a/tests/regression/apparmor/move_mount.c b/tests/regression/apparmor/move_mount.c new file mode 100644 index 000000000..7466569d0 --- /dev/null +++ b/tests/regression/apparmor/move_mount.c @@ -0,0 +1,244 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef open_tree +/* fs/namespace.c + * + * SYSCALL_DEFINE3(open_tree, int, dfd, const char __user *, filename, + * unsigned, flags) + */ +static inline int open_tree(int dirfd, const char *filename, unsigned int flags) +{ + return syscall(SYS_open_tree, dirfd, filename, flags); +} +#endif + +#ifndef move_mount +/* fs/namespace.c + * + * SYSCALL_DEFINE5(move_mount, + * int, from_dfd, const char __user *, from_pathname, + * int, to_dfd, const char __user *, to_pathname, + * unsigned int, flags) + * + * Move a mount from one place to another. In combination with + * fsopen()/fsmount() this is used to install a new mount and in combination + * with open_tree(OPEN_TREE_CLONE [| AT_RECURSIVE]) it can be used to copy + * a mount subtree. + * + * Note the flags value is a combination of MOVE_MOUNT_* flags. + * + * #define MOVE_MOUNT_F_SYMLINKS 0x00000001 // Follow symlinks on from path + * #define MOVE_MOUNT_F_AUTOMOUNTS 0x00000002 // Follow automounts on from path + * #define MOVE_MOUNT_F_EMPTY_PATH 0x00000004 // Empty from path permitted + * #define MOVE_MOUNT_T_SYMLINKS 0x00000010 // Follow symlinks on to path + * #define MOVE_MOUNT_T_AUTOMOUNTS 0x00000020//Follow automounts on to path + * #define MOVE_MOUNT_T_EMPTY_PATH 0x00000040 // Empty to path permitted + * #define MOVE_MOUNT_SET_GROUP 0x00000100 // Set sharing group instead + * #define MOVE_MOUNT_BENEATH 0x00000200 // Mount beneath top mount + * #define MOVE_MOUNT__MASK 0x00000377 + */ +static inline int move_mount(int from_dirfd, const char *from_pathname, + int to_dirfd, const char *to_pathname, + unsigned int flags) +{ + return syscall(SYS_move_mount, from_dirfd, from_pathname, + to_dirfd, to_pathname, flags); +} +#endif + +#ifndef fsmount +/* fs/namespace.c + * + * SYSCALL_DEFINE3(fsmount, int, fs_fd, unsigned int, flags, + * unsigned int, attr_flags) + * + * Create a kernel mount representation for a new, prepared superblock + * (specified by fs_fd) and attach to an open_tree-like file descriptor. + * + * #define FSMOUNT_CLOEXEC 0x00000001 + */ +static inline int fsmount(int fs_fd, unsigned int flags, + unsigned int attr_flags) +{ + return syscall(SYS_fsmount, fs_fd, flags, attr_flags); +} +#endif + +#ifndef fsconfig +/* fs/fsopen.c + * + * SYSCALL_DEFINE5(fsconfig, + * int, fd, + * unsigned int, cmd, + * const char __user *, _key, + * const void __user *, _value, + * int, aux) + * + * @fd: The filesystem context to act upon + * @cmd: The action to take + * @_key: Where appropriate, the parameter key to set + * @_value: Where appropriate, the parameter value to set + * @aux: Additional information for the value + * + * This system call is used to set parameters on a context, including + * superblock settings, data source and security labelling. + * + * Actions include triggering the creation of a superblock and the + * reconfiguration of the superblock attached to the specified context. + * + * When setting a parameter, @cmd indicates the type of value being proposed + * and @_key indicates the parameter to be altered. + * + * @_value and @aux are used to specify the value, should a value be required: + * + * (*) fsconfig_set_flag: No value is specified. The parameter must be boolean + * in nature. The key may be prefixed with "no" to invert the + * setting. @_value must be NULL and @aux must be 0. + * + * (*) fsconfig_set_string: A string value is specified. The parameter can be + * expecting boolean, integer, string or take a path. A conversion to an + * appropriate type will be attempted (which may include looking up as a + * path). @_value points to a NUL-terminated string and @aux must be 0. + * + * (*) fsconfig_set_binary: A binary blob is specified. @_value points to the + * blob and @aux indicates its size. The parameter must be expecting a + * blob. + * + * (*) fsconfig_set_path: A non-empty path is specified. The parameter must be + * expecting a path object. @_value points to a NUL-terminated string that + * is the path and @aux is a file descriptor at which to start a relative + * lookup or AT_FDCWD. + * + * (*) fsconfig_set_path_empty: As fsconfig_set_path, but with AT_EMPTY_PATH + * implied. + * + * (*) fsconfig_set_fd: An open file descriptor is specified. @_value must be + * NULL and @aux indicates the file descriptor. + */ +static inline int fsconfig(int fs_fd, unsigned int cmd, const char *key, + const void *value, int aux) +{ + return syscall(SYS_fsconfig, fs_fd, cmd, key, value, aux); +} +#endif + +#ifndef fsopen +/* fs/fsopen.c + * + * SYSCALL_DEFINE2(fsopen, const char __user *, _fs_name, unsigned int, flags) + * + * Open a filesystem by name so that it can be configured for mounting. + * + * We are allowed to specify a container in which the filesystem will be + * opened, thereby indicating which namespaces will be used (notably, which + * network namespace will be used for network filesystems). + * + * #define FSOPEN_CLOEXEC 0x00000001 + */ +static inline int fsopen(const char *fs_name, unsigned int flags) +{ + return syscall(SYS_fsopen, fs_name, flags); +} +#endif + +int do_open_tree_move_mount(const char *source, const char *target) +{ + int fd = -1, ret = 0; + + fd = open_tree(AT_FDCWD, source, OPEN_TREE_CLONE | + OPEN_TREE_CLOEXEC | AT_EMPTY_PATH); + if (fd == -1) { + perror("FAIL - open_tree"); + return -1; + } + + ret = move_mount(fd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); + if (ret == -1) + perror("FAIL - move_mount"); + + close(fd); + return ret; +} + +int do_fsmount_move_mount(const char *fsname, const char *source, const char *target) +{ + int fd = -1, mfd = -1, ret = 0; + + fd = fsopen(fsname, FSOPEN_CLOEXEC); + if (fd == -1) { + perror("FAIL - fsopen"); + return -1; + } + ret = fsconfig(fd, FSCONFIG_SET_STRING, "source", source, 0); + if (ret == -1) { + perror("FAIL - fsconfig source"); + goto fail; + } + ret = fsconfig(fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); + if (ret == -1) { + perror("FAIL - fsconfig cmd create"); + goto fail; + } + + ret = fsmount(fd, FSMOUNT_CLOEXEC, 0); + if (ret == -1) { + perror("FAIL - fsmount"); + goto fail; + } + mfd = ret; + + ret = move_mount(mfd, "", AT_FDCWD, target, MOVE_MOUNT_F_EMPTY_PATH); + if (ret == -1) { + perror("FAIL - move_mount"); + } + +fail: + if (fd != -1) + close(fd); + if (mfd != -1) + close(mfd); + return ret; +} + +void usage(const char *prog_name) +{ + fprintf(stderr, "Usage: %s \n", prog_name); + exit(1); +} + +int main(int argc, char *argv[]) +{ + const char *source, *target, *fsname, *op; + int ret = 0; + + if (argc < 5) { + fprintf(stderr, "Missing operation, or source or target mount point, or filesystem name\n"); + usage(argv[0]); + } + + op = argv[1]; + source = argv[2]; + target = argv[3]; + fsname = argv[4]; + + if (strcmp(op, "fsmount") == 0) + ret = do_fsmount_move_mount(fsname, source, target); + else if (strcmp(op, "open_tree") == 0) + ret = do_open_tree_move_mount(source, target); + else { + fprintf(stderr, "Invalid operation %s\n", op); + usage(argv[0]); + } + if (ret == 0) + printf("PASS\n"); + exit(ret); +}