[apparmor] [PATCH v2] tests: Add pivot_root tests

Tyler Hicks tyhicks at canonical.com
Mon Apr 14 22:20:28 UTC 2014


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 <tyhicks at canonical.com>
---

* Changes from v1:
  - Added the license information to the new pivot_root.c file

 tests/regression/apparmor/Makefile      |   2 +
 tests/regression/apparmor/mkprofile.pl  |  16 ++++
 tests/regression/apparmor/pivot_root.c  | 140 +++++++++++++++++++++++++++
 tests/regression/apparmor/pivot_root.sh | 164 ++++++++++++++++++++++++++++++++
 4 files changed, 322 insertions(+)
 create mode 100644 tests/regression/apparmor/pivot_root.c
 create mode 100755 tests/regression/apparmor/pivot_root.sh

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..1b6ac94
--- /dev/null
+++ b/tests/regression/apparmor/pivot_root.c
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 Canonical, Ltd.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, contact Canonical Ltd.
+ */
+
+#define _GNU_SOURCE
+
+#include <alloca.h>
+#include <errno.h>
+#include <sched.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/apparmor.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+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 <PUT_OLD> <NEW_ROOT> <PROFILE>\n\n"
+			"  <PUT_OLD>\t\tThe put_old param of pivot_root()\n"
+			"  <NEW_ROOT>\t\tThe new_root param of pivot_root()\n"
+			"  <PROFILE>\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 <PROFILE> 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..4e57845
--- /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/<PID>/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/<PID>/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"
-- 
1.9.1




More information about the AppArmor mailing list