commit 80008dec997f00dd072d5b5dea24711b3851cd26 Author: Tyler Hicks Date: Wed Apr 9 12:19:31 2014 -0500 tests: Add pivot_root tests This test attempts to clone itself in a new mount namespace, pivot root into a new filesystem (ext2 disk image mounted over loopback), and then verify that a profile transition, if one was specified in the pivot_root rule, has properly occurred. Signed-off-by: Tyler Hicks diff --git a/tests/regression/apparmor/Makefile b/tests/regression/apparmor/Makefile index 3ab22d1..948ad85 100644 --- a/tests/regression/apparmor/Makefile +++ b/tests/regression/apparmor/Makefile @@ -92,6 +92,7 @@ SRC=access.c \ open.c \ openat.c \ pipe.c \ + pivot_root.c \ ptrace.c \ ptrace_helper.c \ pwrite.c \ @@ -160,6 +161,7 @@ TESTS=access \ open \ openat \ pipe \ + pivot_root \ ptrace \ pwrite \ query_label \ diff --git a/tests/regression/apparmor/mkprofile.pl b/tests/regression/apparmor/mkprofile.pl index e3f1598..1773f74 100755 --- a/tests/regression/apparmor/mkprofile.pl +++ b/tests/regression/apparmor/mkprofile.pl @@ -246,6 +246,20 @@ sub gen_umount($) { } } +sub gen_pivot_root($) { + my $rule = shift; + my @rules = split (/:/, $rule); + if (@rules == 2) { + if ($rules[1] =~ /^ALL$/) { + push (@{$output_rules{$hat}}, " pivot_root,\n"); + } else { + push (@{$output_rules{$hat}}, " pivot_root $rules[1],\n"); + } + } else { + (!$nowarn) && print STDERR "Warning: invalid pivot_root description '$rule', ignored\n"; + } +} + sub gen_file($) { my $rule = shift; my @rules = split (/:/, $rule); @@ -338,6 +352,8 @@ sub gen_from_args() { gen_remount($rule); } elsif ($rule =~ /^umount:/) { gen_umount($rule); + } elsif ($rule =~ /^pivot_root:/) { + gen_pivot_root($rule); } elsif ($rule =~ /^flag:/) { gen_flag($rule); } elsif ($rule =~ /^hat:/) { diff --git a/tests/regression/apparmor/pivot_root.c b/tests/regression/apparmor/pivot_root.c new file mode 100644 index 0000000..e5008b1 --- /dev/null +++ b/tests/regression/apparmor/pivot_root.c @@ -0,0 +1,124 @@ +#define _GNU_SOURCE + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct clone_arg { + const char *put_old; + const char *new_root; + const char *expected_con; +}; + +static int _pivot_root(const char *new_root, const char *put_old) +{ +#ifdef __NR_pivot_root + return syscall(__NR_pivot_root, new_root, put_old); +#else + errno = ENOSYS; + return -1; +#endif +} + +static int pivot_and_verify_con(void *arg) +{ + const char *put_old = ((struct clone_arg *)arg)->put_old; + const char *new_root = ((struct clone_arg *)arg)->new_root; + const char *expected_con = ((struct clone_arg *)arg)->expected_con; + char *con; + int rc; + + rc = chdir(new_root); + if (rc < 0) { + perror("FAIL - chdir"); + exit(100); + } + + rc = _pivot_root(new_root, put_old); + if (rc < 0) { + perror("FAIL - pivot_root"); + exit(101); + } + + rc = aa_getcon(&con, NULL); + if (rc < 0) { + perror("FAIL - aa_getcon"); + exit(102); + } + + if (strcmp(expected_con, con)) { + fprintf(stderr, "FAIL - expected_con (%s) != con (%s)\n", + expected_con, con); + exit(103); + } + + free(con); + exit(0); +} + +static pid_t _clone(int (*fn)(void *), void *arg) +{ + size_t stack_size = sysconf(_SC_PAGESIZE); + void *stack = alloca(stack_size); + +#ifdef __ia64__ + return __clone2(pivot_and_verify_con, stack, stack_size, + CLONE_NEWNS | SIGCHLD, arg); +#else + return clone(pivot_and_verify_con, stack + stack_size, + CLONE_NEWNS | SIGCHLD, arg); +#endif +} + +int main(int argc, char **argv) +{ + struct clone_arg arg; + pid_t child; + int child_status, rc; + + if (argc != 4) { + fprintf(stderr, + "FAIL - usage: %s \n\n" + " \t\tThe put_old param of pivot_root()\n" + " \t\tThe new_root param of pivot_root()\n" + " \t\tThe expected AA context after pivoting\n\n" + "This program clones itself in a new mount namespace, \n" + "does a pivot and then calls aa_getcon(). The test fails \n" + "if does not match the context returned by \n" + "aa_getcon().\n", argv[0]); + exit(1); + } + + arg.put_old = argv[1]; + arg.new_root = argv[2]; + arg.expected_con = argv[3]; + + child = _clone(pivot_and_verify_con, &arg); + if (child < 0) { + perror("FAIL - clone"); + exit(2); + } + + rc = waitpid(child, &child_status, 0); + if (rc < 0) { + perror("FAIL - waitpid"); + exit(3); + } else if (!WIFEXITED(child_status)) { + fprintf(stderr, "FAIL - child didn't exit\n"); + exit(4); + } else if (WEXITSTATUS(child_status)) { + /* The child has already printed a FAIL message */ + exit(WEXITSTATUS(child_status)); + } + + printf("PASS\n"); + exit (0); +} diff --git a/tests/regression/apparmor/pivot_root.sh b/tests/regression/apparmor/pivot_root.sh new file mode 100755 index 0000000..1b9e8a5 --- /dev/null +++ b/tests/regression/apparmor/pivot_root.sh @@ -0,0 +1,164 @@ +#! /bin/bash +# Copyright (C) 2014 Canonical, Ltd. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation, version 2 of the +# License. + +#=NAME mount +#=DESCRIPTION +# This test verifies that the pivot_root syscall is indeed restricted for +# confined processes. +#=END + +pwd=`dirname $0` +pwd=`cd $pwd ; /bin/pwd` + +bin=$pwd + +. $bin/prologue.inc + +disk_img=$tmpdir/disk_img +new_root=$tmpdir/new_root +put_old=$new_root/put_old +bad=$tmpdir/BAD +proc=$new_root/proc +fstype="ext2" + +pivot_root_cleanup() { + mountpoint -q "$proc" + if [ $? -eq 0 ] ; then + umount "$proc" + fi + + mountpoint -q "$new_root" + if [ $? -eq 0 ] ; then + umount "$new_root" + fi +} +do_onexit="pivot_root_cleanup" + +# Create disk image since pivot_root doesn't allow old root and new root to be +# on the same filesystem +dd if=/dev/zero of="$disk_img" bs=1024 count=512 2> /dev/null +/sbin/mkfs -t "$fstype" -F "$disk_img" > /dev/null 2> /dev/null +/bin/mkdir "$new_root" +/bin/mount -o loop -t "$fstype" "$disk_img" "$new_root" + +# Must mount proc because the pivot_root test program calls aa_getcon() after +# pivot_root() and aa_getcon() reads /proc//attr/current +mkdir "$proc" +mount -t proc proc "$proc" + +# Will be used for pivot_root()'s put_old parameter +mkdir "$put_old" + +do_test() +{ + local desc="PIVOT_ROOT ($1)" + shift + + runchecktest "$desc" "$@" +} + +# Needed for aa_getcon() +cur="/proc/*/attr/current:r" + +# Needed for clone(CLONE_NEWNS) and pivot_root() +cap=capability:sys_admin + +# A profile name that'll be used to test AA's transitions during pivot_root() +new_prof=/sbin/init + + +# Ensure everything works as expected when unconfined +do_test "unconfined" pass "$put_old" "$new_root" unconfined + +# Ensure the test binary is accurately doing post pivot_root profile verification +do_test "unconfined, bad context" fail "$put_old" "$new_root" "$bad" + +# Ensure failure when no perms are granted +genprofile +do_test "no perms" fail "$put_old" "$new_root" "$test" + +if [ "$(have_features mount)" != "true" ] ; then + # pivot_root mediation isn't supported by this kernel, so verify that + # capability sys_admin is sufficient and skip the remaining tests + genprofile $cur $cap + do_test "cap" pass "$put_old" "$new_root" "$test" + + exit +fi + +# Ensure failure when no pivot_root perms are granted +genprofile $cur $cap +do_test "cap only" fail "$put_old" "$new_root" "$test" + +# Ensure failure when everything except capability sys_admin is granted +genprofile $cur "pivot_root:ALL" +do_test "bare rule, no cap" fail "$put_old" "$new_root" "$test" + +# Give sufficient perms with full pivot_root access +genprofile $cur $cap "pivot_root:ALL" +do_test "bare rule" pass "$put_old" "$new_root" "$test" + +# Give sufficient perms and specify new_root +genprofile $cur $cap "pivot_root:$new_root" +do_test "new_root" pass "$put_old" "$new_root" "$test" + +# Ensure failure when new_root is bad +genprofile $cur $cap "pivot_root:$bad" +do_test "bad new_root" fail "$put_old" "$new_root" "$test" + +# Give sufficient perms and specify put_old +genprofile $cur $cap "pivot_root:oldroot=$put_old" +do_test "put_old" pass "$put_old" "$new_root" "$test" + +# Ensure failure when put_old is bad +genprofile $cur $cap "pivot_root:oldroot=$bad" +do_test "bad put_old" fail "$put_old" "$new_root" "$test" + +# Give sufficient perms and specify put_old and new_root +genprofile $cur $cap "pivot_root:oldroot=$put_old $new_root" +do_test "put_old, new_root" pass "$put_old" "$new_root" "$test" + +# Ensure failure when put_old is bad +genprofile $cur $cap "pivot_root:oldroot=$bad $new_root" +do_test "bad put_old, new_root" fail "$put_old" "$new_root" "$test" + +# Ensure failure when new_root is bad +genprofile $cur $cap "pivot_root:oldroot=$put_old $bad" +do_test "put_old, bad new_root" fail "$put_old" "$new_root" "$test" + +# Give sufficient perms and perform a profile transition +genprofile $cap "pivot_root:-> $new_prof" -- image=$new_prof $cur +do_test "transition" pass "$put_old" "$new_root" "$new_prof" + +# Ensure failure when the the new profile can't read /proc//attr/current +genprofile $cap "pivot_root:-> $new_prof" -- image=$new_prof +do_test "transition, no perms" fail "$put_old" "$new_root" "$new_prof" + +# Ensure failure when the new profile doesn't exist +genprofile $cap "pivot_root:-> $bad" -- image=$new_prof $cur +do_test "bad transition" fail "$put_old" "$new_root" "$new_prof" + +# Ensure the test binary is accurately doing post pivot_root profile verification +genprofile $cap "pivot_root:-> $new_prof" -- image=$new_prof $cur +do_test "bad transition comparison" fail "$put_old" "$new_root" "$test" + +# Give sufficient perms with new_root and a transition +genprofile $cap "pivot_root:$new_root -> $new_prof" -- image=$new_prof $cur +do_test "new_root, transition" pass "$put_old" "$new_root" "$new_prof" + +# Ensure failure when the new profile doesn't exist and new_root is specified +genprofile $cap "pivot_root:$new_root -> $bad" -- image=$new_prof $cur +do_test "new_root, bad transition" fail "$put_old" "$new_root" "$new_prof" + +# Give sufficient perms with new_root, put_old, and a transition +genprofile $cap "pivot_root:oldroot=$put_old $new_root -> $new_prof" -- image=$new_prof $cur +do_test "put_old, new_root, transition" pass "$put_old" "$new_root" "$new_prof" + +# Ensure failure when the new profile doesn't exist and new_root and put_old are specified +genprofile $cap "pivot_root:oldroot=$put_old $new_root -> $bad" -- image=$new_prof $cur +do_test "put_old, new_root, bad transition" fail "$put_old" "$new_root" "$new_prof"