tests: Create socketpair test for checking labeling on fds

Bug: https://bugs.launchpad.net/bugs/1235478

This is a test to check the label on file descriptors returned from
socketpair().

In its simple form, it simply calls socketpair() and checks the
labels on both fds.

In its complex form, it has the ability to do the simple test, then set
up an exec transition using aa_change_onexec(), and re-exec itself to
check the labeling after the file descriptors have been passed across an
exec transition.

The complex form is meant to test revalidation at exec. AppArmor
currently keeps the original labeling in place across the exec
transition.

Note that this test does not currently test read/write access to the
file descriptors. It only checks the label, as returned by
aa_getpeercon(2).

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
Tyler Hicks 2014-05-27 09:19:15 +02:00
parent 0cf50140a5
commit 29469c6e2a
3 changed files with 292 additions and 0 deletions

View file

@ -97,6 +97,7 @@ SRC=access.c \
rename.c \
readdir.c \
rw.c \
socketpair.c \
symlink.c \
syscall_mknod.c \
swap.c \
@ -174,6 +175,7 @@ TESTS=access \
rename \
readdir \
rw \
socketpair \
swap \
sd_flags \
setattr \

View file

@ -0,0 +1,192 @@
/*
* 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 <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/apparmor.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#define NO_MODE "(null)"
#define ENV_FD0 "_SOCKETPAIR_FD0"
#define ENV_FD1 "_SOCKETPAIR_FD1"
static int get_socketpair(int pair[2])
{
char *fd0, *fd1;
fd0 = getenv(ENV_FD0);
fd1 = getenv(ENV_FD1);
if (fd0 && fd1) {
pair[0] = atoi(fd0);
pair[1] = atoi(fd1);
} else {
if (socketpair(AF_UNIX, SOCK_STREAM, 0, pair) < 0) {
perror("FAIL - socketpair");
return 1;
}
}
return 0;
}
static int verify_confinement_context(int fd, const char *fd_name,
const char *expected_con,
const char *expected_mode)
{
char *con, *mode;
int rc;
rc = aa_getpeercon(fd, &con, &mode);
if (rc < 0) {
fprintf(stderr, "FAIL - %s: aa_getpeercon(%d, , ): %m",
fd_name, fd);
return 1;
}
if (!mode)
mode = NO_MODE;
if (strcmp(con, expected_con)) {
fprintf(stderr,
"FAIL - %s: con \"%s\" != expected_con \"%s\"\n",
fd_name, con, expected_con);
rc = 2;
goto out;
}
if (strcmp(mode, expected_mode)) {
fprintf(stderr,
"FAIL - %s: mode \"%s\" != expected_mode \"%s\"\n",
fd_name, mode, expected_mode);
rc = 3;
goto out;
}
rc = 0;
out:
free(con);
return rc;
}
static int reexec(int pair[2], int argc, char **argv)
{
char *new_profile;
char fd_str[32];
/* Less than 4 arguments means that no <CHANGE_ONEXEC> args exist */
if (argc < 4)
return 0;
/**
* Save off the first <CHANGE_ONEXEC> arg and then shift all preceeding
* args by one to effectively pop off the first <CHANGE_ONEXEC>
*/
new_profile = argv[3];
argv[3] = argv[2];
argv[2] = argv[1];
argv[1] = argv[0];
argv++;
if (aa_change_onexec(new_profile) < 0) {
perror("FAIL - aa_change_onexec");
return 1;
}
snprintf(fd_str, sizeof(fd_str), "%d", pair[0]);
if (setenv(ENV_FD0, fd_str, 1) < 0) {
perror("FAIL - setenv");
return 2;
}
snprintf(fd_str, sizeof(fd_str), "%d", pair[1]);
if (setenv(ENV_FD1, fd_str, 1) < 0) {
perror("FAIL - setenv");
return 3;
}
execv(argv[0], argv);
perror("FAIL - execv");
return 4;
}
int main(int argc, char **argv)
{
char *expected_con, *expected_mode;
int pair[2], rc;
if (argc < 3) {
fprintf(stderr,
"FAIL - usage: %s <CON> <MODE> [<CHANGE_ONEXEC> ...]\n\n"
" <CON>\t\tThe expected confinement context\n"
" <MODE>\tThe expected confinement mode\n"
" <CHANGE_ONEXEC>\tThe profile to change to on exec\n\n"
"This program gets a socket pair and then verifies \n"
"the confinement context and mode of each file \n"
"descriptor. If there is no expected mode string, \n"
"<MODE> should be \"%s\".\n\n"
"Multiple <CHANGE_ONEXEC> profiles can be specified \n"
"and the test will run normally for the first pair, \n"
"then call aa_change_onexec() to rexec itself under \n"
"the next <CHANGE_ONEXEC> and verify the passed in \n"
"socket pairs still have the correct labeling.\n" ,
argv[0], NO_MODE);
exit(1);
}
/**
* If ENV_FD0 and ENV_FD1 are set, they'll point to fds that were
* passed in. If they're not set, call socketpair().
*/
if (get_socketpair(pair))
exit(2);
expected_con = argv[1];
expected_mode = argv[2];
if (verify_confinement_context(pair[0], "pair[0]",
expected_con, expected_mode)) {
rc = 3;
goto out;
}
if (verify_confinement_context(pair[1], "pair[1]",
expected_con, expected_mode)) {
rc = 4;
goto out;
}
if (reexec(pair, argc, argv)) {
rc = 5;
goto out;
}
printf("PASS\n");
rc = 0;
out:
close(pair[0]);
close(pair[1]);
exit(rc);
}

View file

@ -0,0 +1,98 @@
#! /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 socketpair
#=DESCRIPTION
# This test verifies that the fds returned from the socketpair syscall are
# correctly labeled
#=END
pwd=`dirname $0`
pwd=`cd $pwd ; /bin/pwd`
bin=$pwd
. $bin/prologue.inc
do_test()
{
local desc="SOCKETPAIR ($1)"
shift
runchecktest "$desc" "$@"
}
exec="/proc/*/attr/exec:w"
np1="new_profile_1"
np2="new_profile_2"
# Ensure everything works as expected when unconfined
do_test "unconfined" pass "unconfined" "(null)"
# Test the test
do_test "unconfined bad con" fail "uncon" "(null)"
do_test "unconfined bad mode" fail "unconfined" "(null)XXX"
# Ensure correct labeling under confinement
genprofile
do_test "confined" pass "$test" "enforce"
# Test the test
do_test "confined bad con" fail "/bad${test}" "enforce"
do_test "confined bad mode" fail "$test" "inforce"
# Ensure correct mode when using the complain flag
genprofile flag:complain
do_test "complain" pass "$test" "complain"
# Test the test
genprofile flag:complain
do_test "complain bad mode" fail "$test" "enforce"
# Ensure correct mode when using the audit flag
genprofile flag:audit
do_test "complain" pass "$test" "enforce"
# Ensure correct labeling after passing fd pair across exec
genprofile $exec 'change_profile->':$np1 -- image=$np1 addimage:$test
do_test "confined exec transition" pass "$test" "enforce" "$np1"
# Ensure correct labeling after passing fd pair across a no-transition exec
# NOTE: The test still calls aa_change_onexec(), so change_profile -> $test
# is still needed
genprofile $exec 'change_profile->':$test
do_test "confined exec no transition" pass "$test" "enforce" "$test"
# Ensure correct complain mode after passing fd pair across exec
genprofile flag:complain $exec 'change_profile->':$np1 -- \
image=$np1 addimage:$test
do_test "confined exec transition from complain" pass "$test" "complain" "$np1"
# Ensure correct enforce mode after passing fd pair across exec
genprofile $exec 'change_profile->':$np1 -- \
image=$np1 addimage:$test flag:complain
do_test "confined exec transition to complain" pass "$test" "enforce" "$np1"
# Ensure correct labeling after passing fd pair across 2 execs
gp_args="$exec change_profile->:$np1 -- \
image=$np1 addimage:$test $exec change_profile->:$np2 -- \
image=$np2 addimage:$test"
genprofile $gp_args
do_test "confined 2 exec transitions" pass "$test" "enforce" "$np1" "$np2"
# Test the test
do_test "confined 2 exec transitions bad con" fail "$test" "enforce" "$np1" "$np1"
do_test "confined 2 exec transitions bad mode" fail "$test" "complain" "$np1" "$np2"
# Ensure correct labeling after passing fd pair across exec to unconfined
genprofile $exec 'change_profile->':unconfined
do_test "confined exec transition to unconfined" pass "$test" "enforce" "unconfined"
# Ensure correct labeling after passing fd pair across exec from unconfined
genprofile image=$np1 addimage:$test
do_test "unconfined exec transition ton confined" pass "unconfined" "(null)" "$np1"