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:
@@ -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 \
|
||||
|
@@ -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);
|
||||
|
@@ -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 {
|
||||
|
@@ -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
|
||||
|
Reference in New Issue
Block a user