2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-31 22:35:35 +00:00

Merge Merge expand mount tests

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1006
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
(cherry picked from commit e6e5e7981f)
Signed-off-by: Jon Tourville <jon.tourville@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1033
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Jon Tourville <jon.tourville@canonical.com>
This commit is contained in:
Jon Tourville
2023-05-18 12:53:37 +00:00
4 changed files with 385 additions and 14 deletions

View File

@@ -307,6 +307,9 @@ unix_socket_client: unix_socket_client.c unix_socket_common.o
unix_socket: unix_socket.c unix_socket_common.o unix_socket_client
${CC} ${CFLAGS} ${LDFLAGS} $(filter-out unix_socket_client, $^) -o $@ ${LDLIBS}
mount: mount.c
${CC} ${CFLAGS} -std=gnu99 ${LDFLAGS} $^ -o $@ ${LDLIBS}
tests: all
@if [ `whoami` = "root" ] ;\
then \

View File

@@ -407,8 +407,8 @@ sub gen_from_args() {
if ($rule =~ /^qual=([^:]*):(.*)/) {
# Strip qualifiers from rule to pass as separate argument
$qualifier = "$1 ";
$qualifier =~ s/,/ /g;
$rule = $2;
$qualifier =~ s/,/ /g;
}
#($fn, @rules) = split (/:/, $rule);

View File

@@ -14,27 +14,163 @@
#include <sys/stat.h>
#include <sys/mount.h>
#include <string.h>
#include <stdlib.h>
struct mnt_keyword_table {
const char *keyword;
unsigned long set;
unsigned long clear;
};
static struct mnt_keyword_table mnt_opts_table[] = {
{ "rw", 0, MS_RDONLY }, /* read-write */
{ "ro", MS_RDONLY, 0 }, /* read-only */
{ "exec", 0, MS_NOEXEC }, /* permit execution of binaries */
{ "noexec", MS_NOEXEC, 0 }, /* don't execute binaries */
{ "suid", 0, MS_NOSUID }, /* honor suid executables */
{ "nosuid", MS_NOSUID, 0 }, /* don't honor suid executables */
{ "dev", 0, MS_NODEV }, /* interpret device files */
{ "nodev", MS_NODEV, 0 }, /* don't interpret devices */
{ "async", 0, MS_SYNCHRONOUS }, /* asynchronous I/O */
{ "sync", MS_SYNCHRONOUS, 0 }, /* synchronous I/O */
{ "loud", 0, MS_SILENT }, /* print out messages. */
{ "silent", MS_SILENT, 0 }, /* be quiet */
{ "nomand", 0, MS_MANDLOCK }, /* forbid mandatory locks on this FS */
{ "mand", MS_MANDLOCK, 0 }, /* allow mandatory locks on this FS */
{ "atime", 0, MS_NOATIME }, /* update access time */
{ "noatime", MS_NOATIME, 0 }, /* do not update access time */
{ "noiversion", 0, MS_I_VERSION }, /* don't update inode I_version time */
{ "iversion", MS_I_VERSION, 0 }, /* update inode I_version time */
{ "diratime", 0, MS_NODIRATIME }, /* update dir access times */
{ "nodiratime", MS_NODIRATIME, 0 }, /* do not update dir access times */
{ "nostrictatime", 0, MS_STRICTATIME }, /* kernel default atime */
{ "strictatime", MS_STRICTATIME, 0 }, /* strict atime semantics */
/* MS_LAZYTIME added in 4.0 kernel */
#ifdef MS_LAZYTIME
{ "nolazytime", 0, MS_LAZYTIME },
{ "lazytime", MS_LAZYTIME, 0 }, /* update {a,m,c}time on the in-memory inode only */
#endif
{ "acl", MS_POSIXACL, 0 },
{ "noacl", 0, MS_POSIXACL },
{ "norelatime", 0, MS_RELATIME },
{ "relatime", MS_RELATIME, 0 },
{ "dirsync", MS_DIRSYNC, 0 }, /* synchronous directory modifications */
{ "nodirsync", 0, MS_DIRSYNC },
/* MS_NOSYMFOLLOW added in 5.10 kernel */
#ifdef MS_NOSYMFOLLOW
{ "nosymfollow", MS_NOSYMFOLLOW, 0 },
{ "symfollow", 0, MS_NOSYMFOLLOW },
#endif
{ "bind", MS_BIND, 0 }, /* remount part of the tree elsewhere */
{ "rbind", MS_BIND | MS_REC, 0 }, /* idem, plus mounted subtrees */
{ "unbindable", MS_UNBINDABLE, 0 }, /* unbindable */
{ "runbindable", MS_UNBINDABLE | MS_REC, 0 },
{ "private", MS_PRIVATE, 0 }, /* private */
{ "rprivate", MS_PRIVATE | MS_REC, 0 },
{ "slave", MS_SLAVE, 0 }, /* slave */
{ "rslave", MS_SLAVE | MS_REC, 0 },
{ "shared", MS_SHARED, 0 }, /* shared */
{ "rshared", MS_SHARED | MS_REC, 0 },
{ "move", MS_MOVE, 0 },
{ "remount", MS_REMOUNT, 0 },
};
const unsigned int mnt_opts_table_size =
sizeof(mnt_opts_table) / sizeof(struct mnt_keyword_table);
unsigned long get_mnt_opt_bit(char *key)
{
for (unsigned int i = 0; i < mnt_opts_table_size; i++) {
if (strcmp(mnt_opts_table[i].keyword, key) == 0) {
return mnt_opts_table[i].set;
}
}
fprintf(stderr, "FAIL: invalid option\n");
exit(1);
}
static void usage(char *prog_name)
{
fprintf(stderr, "Usage: %s mount|umount <source> <target> [options]\n", prog_name);
fprintf(stderr, "Options are:\n");
fprintf(stderr, "-o flags sent to the mount syscall\n");
fprintf(stderr, "-d data sent to the mount syscall\n");
exit(1);
}
int main(int argc, char *argv[])
{
if (argc != 4) {
fprintf(stderr, "usage: %s [mount|umount] loopdev mountpoint\n",
argv[0]);
return 1;
char *options = NULL;
char *data = NULL;
int index;
int c;
char *op, *source, *target, *token;
unsigned long flags = 0;
while ((c = getopt (argc, argv, "o:d:h")) != -1) {
switch (c)
{
case 'o':
options = optarg;
break;
case 'd':
data = optarg;
break;
case 'h':
usage(argv[0]);
break;
default:
break;
}
}
if (strcmp(argv[1], "mount") == 0) {
if (mount(argv[2], argv[3], "ext2", 0xc0ed0000 | MS_NODEV, NULL ) == -1) {
index = optind;
if (argc - optind < 3) {
fprintf(stderr, "FAIL: missing positional arguments\n");
usage(argv[0]);
}
op = argv[index++];
source = argv[index++];
target = argv[index++];
if (options) {
token = strtok(options, ",");
while (token) {
flags |= get_mnt_opt_bit(token);
token = strtok(NULL, ",");
}
}
if (strcmp(op, "mount") == 0) {
if (mount(source, target, "ext2", flags, data) == -1) {
fprintf(stderr, "FAIL: mount %s on %s failed - %s\n",
argv[2], argv[3],
strerror(errno));
source, target, strerror(errno));
return errno;
}
} else if (strcmp(argv[1], "umount") == 0) {
if (umount(argv[3]) == -1) {
} else if (strcmp(op, "umount") == 0) {
if (umount(target) == -1) {
fprintf(stderr, "FAIL: umount %s failed - %s\n",
argv[3],
strerror(errno));
target, strerror(errno));
return errno;
}
} else {

View File

@@ -28,9 +28,11 @@ bin=$pwd
mount_file=$tmpdir/mountfile
mount_point=$tmpdir/mountpoint
mount_point2=$tmpdir/mountpoint2
mount_bad=$tmpdir/mountbad
loop_device="unset"
fstype="ext2"
root_was_shared="no"
setup_mnt() {
/bin/mount -n -t${fstype} ${loop_device} ${mount_point}
@@ -41,6 +43,10 @@ remove_mnt() {
if [ $? -eq 0 ] ; then
/bin/umount -t${fstype} ${mount_point}
fi
mountpoint -q "${mount_point2}"
if [ $? -eq 0 ] ; then
/bin/umount -t${fstype} ${mount_point2}
fi
mountpoint -q "${mount_bad}"
if [ $? -eq 0 ] ; then
/bin/umount -t${fstype} ${mount_bad}
@@ -53,12 +59,16 @@ mount_cleanup() {
then
/sbin/losetup -d ${loop_device} &> /dev/null
fi
if [ "${root_was_shared}" = "yes" ] ; then
mount --make-shared /
fi
}
do_onexit="mount_cleanup"
dd if=/dev/zero of=${mount_file} bs=1024 count=512 2> /dev/null
/sbin/mkfs -t${fstype} -F ${mount_file} > /dev/null 2> /dev/null
/bin/mkdir ${mount_point}
/bin/mkdir ${mount_point2}
/bin/mkdir ${mount_bad}
# in a modular udev world, the devices won't exist until the loopback
@@ -71,6 +81,190 @@ fi
loop_device=$(losetup -f) || fatalerror 'Unable to find a free loop device'
/sbin/losetup "$loop_device" ${mount_file} > /dev/null 2> /dev/null
# systemd mounts / and everything under it MS_SHARED which does
# not work with "move", so attempt to detect it, and remount /
# MS_PRIVATE temporarily. snippet from pivot_root.sh
FINDMNT=/bin/findmnt
if [ -x "${FINDMNT}" ] && ${FINDMNT} -no PROPAGATION / > /dev/null 2>&1 ; then
if [ "$(${FINDMNT} -no PROPAGATION /)" == "shared" ] ; then
root_was_shared="yes"
fi
elif [ "$(ps hp1 -ocomm)" = "systemd" ] ; then
# no findmnt or findmnt doesn't know the PROPAGATION column,
# but init is systemd so assume rootfs is shared
root_was_shared="yes"
fi
if [ "${root_was_shared}" = "yes" ] ; then
mount --make-private /
fi
options=(
# default and non-default options
"rw,ro"
"exec,noexec"
"suid,nosuid"
"dev,nodev"
"async,sync"
"loud,silent"
"nomand,mand"
"atime,noatime"
"noiversion,iversion"
"diratime,nodiratime"
"nostrictatime,strictatime"
"norelatime,relatime"
"nodirsync,dirsync"
"noacl,acl"
)
# Options added in newer kernels
new_options=(
"nolazytime,lazytime"
"symfollow,nosymfollow"
)
prop_options=(
"unbindable"
"runbindable"
"private"
"rprivate"
"slave"
"rslave"
"shared"
"rshared"
)
combinations=()
setup_all_combinations() {
n=${#options[@]}
for (( i = 1; i < (1 << n); i++ )); do
list=()
for (( j = 0; j < n; j++ )); do
if (( (1 << j) & i )); then
current_options="${options[j]}"
nondefault=${current_options#*,}
list+=("$nondefault")
fi
done
combination=$(IFS=,; printf "%s" "${list[*]}")
combinations+=($combination)
done
}
run_all_combinations_test() {
for combination in "${combinations[@]}"; do
if [ "$(parser_supports "mount options=($combination),")" = "true" ] ; then
genprofile cap:sys_admin "mount:options=($combination)"
runchecktest "MOUNT (confined cap mount combination pass test $combination)" pass mount ${loop_device} ${mount_point} -o $combination
remove_mnt
genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=($combination)"
runchecktest "MOUNT (confined cap mount combination deny test $combination)" fail mount ${loop_device} ${mount_point} -o $combination
remove_mnt
fi
genprofile cap:sys_admin "mount:options=(rw)"
runchecktest "MOUNT (confined cap mount combination fail test $combination)" fail mount ${loop_device} ${mount_point} -o $combination
remove_mnt
done
}
test_nonfs_options() {
if [ "$(parser_supports "mount options=($1),")" != "true" ] ; then
return
fi
genprofile cap:sys_admin "mount:options=($1)"
runchecktest "MOUNT (confined cap mount $1)" pass mount ${loop_device} ${mount_point} -o $1
remove_mnt
genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=($1)"
runchecktest "MOUNT (confined cap mount deny $1)" fail mount ${loop_device} ${mount_point} -o $1
remove_mnt
genprofile cap:sys_admin "mount:options=($1)"
runchecktest "MOUNT (confined cap mount bad option $2)" fail mount ${loop_device} ${mount_point} -o $2
remove_mnt
}
test_dir_options() {
if [ "$(parser_supports "mount options=($1),")" != "true" ] ; then
return
fi
genprofile cap:sys_admin "mount:ALL"
runchecktest "MOUNT (confined cap mount dir setup $1)" pass mount ${loop_device} ${mount_point}
genprofile cap:sys_admin "mount:options=($1)"
runchecktest "MOUNT (confined cap mount dir $1)" pass mount ${mount_point} ${mount_point2} -o $1
remove_mnt
genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=($1)"
runchecktest "MOUNT (confined cap mount dir setup 2 $1)" pass mount ${loop_device} ${mount_point}
runchecktest "MOUNT (confined cap mount dir deny $1)" fail mount ${mount_point} ${mount_point2} -o $1
remove_mnt
}
test_propagation_options() {
if [ "$(parser_supports "mount options=($1),")" != "true" ] ; then
return
fi
genprofile cap:sys_admin "mount:ALL"
runchecktest "MOUNT (confined cap mount propagation setup $1)" pass mount ${loop_device} ${mount_point}
genprofile cap:sys_admin "mount:options=($1)"
runchecktest "MOUNT (confined cap mount propagation $1)" pass mount none ${mount_point} -o $1
remove_mnt
genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=($1)"
runchecktest "MOUNT (confined cap mount propagation deny setup 2 $1)" pass mount ${loop_device} ${mount_point}
runchecktest "MOUNT (confined cap mount propagation deny $1)" fail mount none ${mount_point} -o $1
remove_mnt
}
test_remount() {
# setup by mounting first
genprofile cap:sys_admin "mount:ALL"
runchecktest "MOUNT (confined cap mount remount setup)" pass mount ${loop_device} ${mount_point}
genprofile cap:sys_admin "mount:options=(remount)"
runchecktest "MOUNT (confined cap mount remount option)" pass mount ${loop_device} ${mount_point} -o remount
genprofile cap:sys_admin "remount:ALL"
runchecktest "MOUNT (confined cap mount remount)" pass mount ${loop_device} ${mount_point} -o remount
genprofile cap:sys_admin "mount:ALL" "qual=deny:mount:options=(remount)"
runchecktest "MOUNT (confined cap mount remount deny option)" fail mount ${loop_device} ${mount_point} -o remount
genprofile cap:sys_admin "qual=deny:remount:ALL"
runchecktest "MOUNT (confined cap mount remount deny)" fail mount ${loop_device} ${mount_point} -o remount
# TODO: add test for remount options
remove_mnt
}
test_options() {
for i in "${options[@]}"; do
default="${i%,*}"
nondefault="${i#*,}"
test_nonfs_options $default $nondefault
test_nonfs_options $nondefault $default
done
for i in "bind" "rbind" "move"; do
test_dir_options $i
done
for i in "${prop_options[@]}"; do
test_propagation_options $i
done
test_remount
# the following combinations tests take a long time to complete
# setup_all_combinations
# run_all_combinations_test
}
# TEST 1. Make sure can mount and umount unconfined
runchecktest "MOUNT (unconfined)" pass mount ${loop_device} ${mount_point}
@@ -80,6 +274,43 @@ setup_mnt
runchecktest "UMOUNT (unconfined)" pass umount ${loop_device} ${mount_point}
remove_mnt
# Check mount options that may not be available on this kernel
for i in "${new_options[@]}"; do
default="${i%,*}"
if "$bin/mount" mount ${loop_device} ${mount_point} -o $default > /dev/null 2>&1; then
remove_mnt
options+=($i)
else
echo " not supported by kernel - skipping mount options=($i),"
fi
done
for i in "${options[@]}"; do
default="${i%,*}"
nondefault="${i#*,}"
runchecktest "MOUNT (unconfined mount $default)" pass mount ${loop_device} ${mount_point} -o $default
remove_mnt
runchecktest "MOUNT (unconfined mount $nondefault)" pass mount ${loop_device} ${mount_point} -o $nondefault
remove_mnt
done
for i in "bind" "rbind" "move"; do
runchecktest "MOUNT (unconfined mount setup $i)" pass mount ${loop_device} ${mount_point}
runchecktest "MOUNT (unconfined mount $i)" pass mount ${mount_point} ${mount_point2} -o $i
remove_mnt
done
for i in "${prop_options[@]}"; do
runchecktest "MOUNT (unconfined mount dir setup $i)" pass mount ${loop_device} ${mount_point}
runchecktest "MOUNT (unconfined mount dir $i)" pass mount none ${mount_point} -o $i
remove_mnt
done
runchecktest "MOUNT (unconfined mount remount setup)" pass mount ${loop_device} ${mount_point}
runchecktest "MOUNT (unconfined mount remount)" pass mount ${loop_device} ${mount_point} -o remount
remove_mnt
# TEST A2. confine MOUNT no perms
genprofile
runchecktest "MOUNT (confined no perm)" fail mount ${loop_device} ${mount_point}
@@ -157,6 +388,7 @@ else
runchecktest "UMOUNT (confined cap umount:ALL)" pass umount ${loop_device} ${mount_point}
remove_mnt
test_options
fi
#need tests for move mount, remount, bind mount, chroot
#need tests for chroot