mirror of
https://gitlab.com/apparmor/apparmor.git
synced 2025-03-04 00:14:44 +01:00
tests: expand userns tests to use setns
Setns is used to associate to an existing user namespace, so the kernel security hook for user namespace creation is not called. The restriction for setns is that it should have the capability sys_admin. Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
This commit is contained in:
parent
592a0743f0
commit
c6f1981cd8
6 changed files with 274 additions and 13 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -296,6 +296,7 @@ tests/regression/apparmor/unix_socket
|
|||
tests/regression/apparmor/unix_socket_client
|
||||
tests/regression/apparmor/unlink
|
||||
tests/regression/apparmor/userns
|
||||
tests/regression/apparmor/userns_setns
|
||||
tests/regression/apparmor/uservars.inc
|
||||
tests/regression/apparmor/xattrs
|
||||
tests/regression/apparmor/xattrs_profile
|
||||
|
|
|
@ -143,6 +143,7 @@ SRC=access.c \
|
|||
unix_socket_client.c \
|
||||
unlink.c \
|
||||
userns.c \
|
||||
userns_setns.c \
|
||||
xattrs.c \
|
||||
xattrs_profile.c
|
||||
|
||||
|
@ -332,6 +333,12 @@ unix_fd_client: unix_fd_client.c unix_fd_common.o
|
|||
attach_disconnected: attach_disconnected.c unix_fd_common.o
|
||||
${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@ ${LDLIBS}
|
||||
|
||||
userns: userns.c userns.h
|
||||
${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@ ${LDLIBS}
|
||||
|
||||
userns_setns: userns_setns.c userns.h
|
||||
${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@ ${LDLIBS}
|
||||
|
||||
build-dep:
|
||||
@if [ `whoami` = "root" ] ;\
|
||||
then \
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
#include <sys/wait.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <linux/limits.h>
|
||||
#include "userns.h"
|
||||
|
||||
static void usage(char *pname)
|
||||
{
|
||||
|
@ -27,6 +31,8 @@ static void usage(char *pname)
|
|||
fprintf(stderr, "Options can be:\n");
|
||||
fprintf(stderr, " -c create user namespace using clone\n");
|
||||
fprintf(stderr, " -u create user namespace using unshare\n");
|
||||
fprintf(stderr, " -s create user namespace using setns. requires the path of binary that will create the user namespace\n");
|
||||
fprintf(stderr, " -p named pipe path. used by setns\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
|
@ -38,6 +44,91 @@ static int child(void *arg)
|
|||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
#ifndef __NR_pidfd_open
|
||||
#define __NR_pidfd_open 434 /* System call # on most architectures */
|
||||
#endif
|
||||
|
||||
static int
|
||||
pidfd_open(pid_t pid, unsigned int flags)
|
||||
{
|
||||
return syscall(__NR_pidfd_open, pid, flags);
|
||||
}
|
||||
|
||||
int userns_setns(char *client, char *pipename)
|
||||
{
|
||||
int userns, exit_status, ret;
|
||||
char *parentpipe = NULL, *childpipe = NULL;
|
||||
|
||||
if (get_pipes(pipename, &parentpipe, &childpipe) == -1) {
|
||||
fprintf(stderr, "FAIL - failed to allocate pipes\n");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (mkfifo(parentpipe, 0666) == -1)
|
||||
perror("FAIL - setns parent mkfifo");
|
||||
|
||||
/* exec the client */
|
||||
int pid = fork();
|
||||
if (pid == -1) {
|
||||
perror("FAIL - could not fork");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
} else if (!pid) {
|
||||
execl(client, client, pipename, NULL);
|
||||
printf("FAIL %d - execlp %s %s- %m\n", getuid(), client, pipename);
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (read_from_pipe(parentpipe) == -1) { // wait for child to unshare
|
||||
fprintf(stderr, "FAIL - parent could not read from pipe\n");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
userns = pidfd_open(pid, 0);
|
||||
if (userns == -1) {
|
||||
perror("FAIL - pidfd_open");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
// enter child namespace
|
||||
if (setns(userns, CLONE_NEWUSER) == -1) {
|
||||
perror("FAIL - setns");
|
||||
ret = EXIT_FAILURE;
|
||||
}
|
||||
|
||||
if (write_to_pipe(childpipe) == -1) { // let child finish
|
||||
fprintf(stderr, "FAIL - child could not write in pipe\n");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (waitpid(pid, &exit_status, 0) == -1) {
|
||||
perror("FAIL - setns waitpid");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (WIFEXITED(exit_status)) {
|
||||
if (WEXITSTATUS(exit_status) != 0) {
|
||||
fprintf(stderr, "FAIL - setns child ended with failure %d\n", exit_status);
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
ret = EXIT_SUCCESS;
|
||||
out:
|
||||
if (unlink(parentpipe) == -1)
|
||||
perror("FAIL - could not remove parentpipe");
|
||||
free(parentpipe);
|
||||
free(childpipe);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int userns_unshare()
|
||||
{
|
||||
if (unshare(CLONE_NEWUSER) == -1) {
|
||||
|
@ -60,7 +151,7 @@ int userns_clone()
|
|||
}
|
||||
|
||||
if (waitpid(child_pid, &child_exit, 0) == -1) {
|
||||
perror("FAIL - waitpid");
|
||||
perror("FAIL - clone waitpid");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
|
@ -77,16 +168,26 @@ int userns_clone()
|
|||
enum op {
|
||||
CLONE,
|
||||
UNSHARE,
|
||||
SETNS,
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int opt, ret = 0, op;
|
||||
char *client = "userns_setns";
|
||||
char *pipename = "/tmp/userns_pipe";
|
||||
|
||||
while ((opt = getopt(argc, argv, "uc")) != -1) {
|
||||
while ((opt = getopt(argc, argv, "us:cp:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'c': op = CLONE; break;
|
||||
case 'u': op = UNSHARE; break;
|
||||
case 's':
|
||||
op = SETNS;
|
||||
client = optarg;
|
||||
break;
|
||||
case 'p':
|
||||
pipename = optarg;
|
||||
break;
|
||||
default: usage(argv[0]);
|
||||
}
|
||||
}
|
||||
|
@ -95,6 +196,9 @@ int main(int argc, char *argv[])
|
|||
ret = userns_clone();
|
||||
else if (op == UNSHARE)
|
||||
ret = userns_unshare();
|
||||
else if (op == SETNS) {
|
||||
ret = userns_setns(client, pipename);
|
||||
}
|
||||
else
|
||||
fprintf(stderr, "FAIL - user namespace method not defined\n");
|
||||
|
||||
|
|
67
tests/regression/apparmor/userns.h
Normal file
67
tests/regression/apparmor/userns.h
Normal file
|
@ -0,0 +1,67 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <stdio.h>
|
||||
#include <unistd.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
int get_pipes(const char *pipename, char **parentpipe, char **childpipe)
|
||||
{
|
||||
if (asprintf(parentpipe, "%s1", pipename) == -1)
|
||||
return -1;
|
||||
if (asprintf(childpipe, "%s2", pipename) == -1)
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int read_from_pipe(char *pipename)
|
||||
{
|
||||
int fd, ret;
|
||||
char buf;
|
||||
fd_set set;
|
||||
struct timeval timeout;
|
||||
|
||||
fd = open(pipename, O_RDONLY | O_NONBLOCK);
|
||||
if (fd == -1) {
|
||||
perror("FAIL - open read pipe");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
FD_ZERO(&set);
|
||||
FD_SET(fd, &set);
|
||||
|
||||
timeout.tv_sec = 3;
|
||||
timeout.tv_usec = 0;
|
||||
|
||||
ret = select(fd + 1, &set, NULL, NULL, &timeout);
|
||||
if (ret == -1) {
|
||||
perror("FAIL - select");
|
||||
goto err;
|
||||
} else if (ret == 0) {
|
||||
fprintf(stderr, "FAIL - read timeout\n");
|
||||
goto err;
|
||||
} else {
|
||||
if (read(fd, &buf, 1) == -1) { // wait for client to unshare
|
||||
perror("FAIL - read pipe");
|
||||
close(fd);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
return EXIT_SUCCESS;
|
||||
err:
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
int write_to_pipe(char *pipename)
|
||||
{
|
||||
int fd;
|
||||
|
||||
fd = open(pipename, O_WRONLY | O_NONBLOCK);
|
||||
if (fd == -1) {
|
||||
perror("FAIL - open write pipe");
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
close(fd);
|
||||
return EXIT_SUCCESS;
|
||||
}
|
|
@ -21,6 +21,12 @@ bin=$pwd
|
|||
requires_kernel_features namespaces/mask/userns_create
|
||||
requires_parser_support "userns,"
|
||||
|
||||
userns_bin=$bin/userns
|
||||
userns_setns_bin=$bin/userns_setns
|
||||
pipe=/tmp/pipe
|
||||
parentpipe="$pipe"1
|
||||
childpipe="$pipe"2
|
||||
|
||||
apparmor_restrict_unprivileged_userns_path=/proc/sys/kernel/apparmor_restrict_unprivileged_userns
|
||||
if [ ! -e $apparmor_restrict_unprivileged_userns_path ]; then
|
||||
echo "$apparmor_restrict_unprivileged_userns_path not available. Skipping tests ..."
|
||||
|
@ -45,33 +51,48 @@ do_test()
|
|||
local desc="USERNS ($1)"
|
||||
expect_root=$2
|
||||
expect_user=$3
|
||||
generate_profile=$4
|
||||
expect_setns_root=$4
|
||||
expect_setns_user=$5
|
||||
generate_profile=$6
|
||||
|
||||
if [ ! -z "$generate_profile" ]; then
|
||||
# add profile for userns_setns_bin
|
||||
# ptrace is needed because userns_bin needs to
|
||||
# access userns_setns_bin's /proc/pid/ns/user
|
||||
generate_setns_profile="$generate_profile $userns_setns_bin:px $parentpipe:rw $childpipe:rw cap:sys_ptrace ptrace:read -- image=$userns_setns_bin userns $parentpipe:rw $childpipe:wr ptrace:readby cap:sys_admin"
|
||||
fi
|
||||
|
||||
settest userns
|
||||
$generate_profile # settest removes the profile, so load it here
|
||||
runchecktest "$desc clone - root" $expect_root -c # clone
|
||||
runchecktest "$desc unshare - root" $expect_root -u # unshare
|
||||
|
||||
$generate_setns_profile
|
||||
runchecktest "$desc setns - root" $expect_setns_root -s $userns_setns_bin -p $pipe # setns
|
||||
|
||||
settest -u "foo" userns # run tests as user foo
|
||||
$generate_profile # settest removes the profile, so load it here
|
||||
runchecktest "$desc clone - user" $expect_user -c # clone
|
||||
runchecktest "$desc unshare - user" $expect_user -u # unshare
|
||||
|
||||
$generate_setns_profile
|
||||
runchecktest "$desc setns - user" $expect_setns_user -s $userns_setns_bin -p $pipe # setns
|
||||
}
|
||||
|
||||
if [ $unprivileged_userns_clone -eq 0 ]; then
|
||||
echo "WARN: unprivileged_userns_clone is enabled. Both confined and unconfined unprivileged user namespaces are not allowed"
|
||||
|
||||
detail="unprivileged_userns_clone disabled"
|
||||
do_test "unconfined - $detail" pass fail
|
||||
do_test "unconfined - $detail" pass fail pass fail
|
||||
|
||||
generate_profile="genprofile userns cap:sys_admin"
|
||||
do_test "confined all perms $detail" pass fail "$generate_profile"
|
||||
do_test "confined all perms $detail" pass fail pass fail "$generate_profile"
|
||||
|
||||
generate_profile="genprofile cap:sys_admin"
|
||||
do_test "confined no perms $detail" fail fail "$generate_profile"
|
||||
do_test "confined no perms $detail" fail fail pass fail "$generate_profile"
|
||||
|
||||
generate_profile="genprofile userns:create cap:sys_admin"
|
||||
do_test "confined specific perms $detail" pass fail "$generate_profile"
|
||||
do_test "confined specific perms $detail" pass fail pass fail "$generate_profile"
|
||||
|
||||
exit 0
|
||||
fi
|
||||
|
@ -81,13 +102,21 @@ fi
|
|||
run_confined_tests()
|
||||
{
|
||||
generate_profile="genprofile userns"
|
||||
do_test "confined all perms $1" pass pass "$generate_profile"
|
||||
do_test "confined all perms $1" pass pass fail fail "$generate_profile"
|
||||
|
||||
generate_profile="genprofile"
|
||||
do_test "confined no perms $1" fail fail "$generate_profile"
|
||||
do_test "confined no perms $1" fail fail fail fail "$generate_profile"
|
||||
|
||||
generate_profile="genprofile userns:create"
|
||||
do_test "confined specific perms $1" pass pass "$generate_profile"
|
||||
do_test "confined specific perms $1" pass pass fail fail "$generate_profile"
|
||||
|
||||
# setns tests only pass is cap_sys_admin regardless of apparmor permissions
|
||||
# it only associates to the already created user namespace
|
||||
generate_profile="genprofile userns cap:sys_admin"
|
||||
do_test "confined specific perms $1" pass pass pass pass "$generate_profile"
|
||||
|
||||
generate_profile="genprofile cap:sys_admin"
|
||||
do_test "confined specific perms $1" fail fail pass pass "$generate_profile"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------
|
||||
|
@ -95,7 +124,7 @@ run_confined_tests()
|
|||
echo 0 > $apparmor_restrict_unprivileged_userns_path
|
||||
|
||||
detail="apparmor_restrict_unprivileged_userns disabled"
|
||||
do_test "unconfined - $detail" pass pass
|
||||
do_test "unconfined - $detail" pass pass pass pass
|
||||
|
||||
run_confined_tests "$detail"
|
||||
|
||||
|
@ -105,11 +134,11 @@ echo 1 > $apparmor_restrict_unprivileged_userns_path
|
|||
|
||||
detail="apparmor_restrict_unprivileged_userns enabled"
|
||||
# user cannot create user namespace unless cap_sys_admin
|
||||
do_test "unconfined $detail" pass fail
|
||||
do_test "unconfined $detail" pass fail pass pass
|
||||
|
||||
# it should work when running as user with cap_sys_admin
|
||||
setcap cap_sys_admin+pie $bin/userns
|
||||
do_test "unconfined cap_sys_admin $detail" pass pass
|
||||
do_test "unconfined cap_sys_admin $detail" pass pass pass pass
|
||||
# remove cap_sys_admin from binary
|
||||
setcap cap_sys_admin= $bin/userns
|
||||
|
||||
|
|
53
tests/regression/apparmor/userns_setns.c
Normal file
53
tests/regression/apparmor/userns_setns.c
Normal file
|
@ -0,0 +1,53 @@
|
|||
#define _GNU_SOURCE
|
||||
#include <sched.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "userns.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int ret;
|
||||
char *pipename = "/tmp/userns_pipe";
|
||||
char *parentpipe = NULL, *childpipe = NULL;
|
||||
|
||||
if (argc > 1)
|
||||
pipename = argv[1];
|
||||
|
||||
if (get_pipes(pipename, &parentpipe, &childpipe) == -1) {
|
||||
fprintf(stderr, "FAIL - failed to allocate pipes\n");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (mkfifo(childpipe, 0666) == -1)
|
||||
perror("FAIL - setns child mkfifo");
|
||||
|
||||
if (unshare(CLONE_NEWUSER) == -1) {
|
||||
perror("FAIL - unshare");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (write_to_pipe(parentpipe) == -1) { // let parent know user namespace is created
|
||||
fprintf(stderr, "FAIL - child could not write in pipe\n");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
if (read_from_pipe(childpipe) == -1) { // wait for parent tell child can finish
|
||||
fprintf(stderr, "FAIL - child could not read from pipe\n");
|
||||
ret = EXIT_FAILURE;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = EXIT_SUCCESS;
|
||||
out:
|
||||
if (unlink(childpipe) == -1)
|
||||
perror("FAIL - could not remove childpipe");
|
||||
free(parentpipe);
|
||||
free(childpipe);
|
||||
return ret;
|
||||
}
|
Loading…
Add table
Reference in a new issue