tests: add move_mount regression tests

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
This commit is contained in:
Georgia Garcia 2024-01-04 18:17:28 -03:00
parent 5ca2ea3621
commit f889f9f434
4 changed files with 400 additions and 0 deletions

1
.gitignore vendored
View file

@ -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

View file

@ -109,6 +109,7 @@ SRC=access.c \
mmap.c \
mkdir.c \
mount.c \
move_mount.c \
named_pipe.c \
net_raw.c \
open.c \

View file

@ -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

View file

@ -0,0 +1,244 @@
#define _GNU_SOURCE
#include <err.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/mount.h>
#include <linux/types.h>
#include <sys/syscall.h>
#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 <fsmount|open_tree> <source> <target> <fs name>\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);
}