libapparmor: Strip a trailing newline character in aa_splitcon(3)

Adjust the internal splitcon() function to strip a single trailing
newline character when the bool strip_newline argument is true.

aa_getprocattr_raw(2) needs to set strip_newline to true since the
kernel appends a newline character to the end of the AppArmor contexts
read from /proc/>PID>/attr/current.

aa_splitcon(3) also sets strip_newline to true since it is unknown
whether the context is originated from a location that appends a newline
or not.

aa_getpeercon_raw(2) does not set strip_newline to true since it is
unexpected for the kernel to append a newline to the the buffer returned
from getsockopt(2).

This patch also creates tests specifically for splitcon() and updates
the aa_splitcon(3) man page.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
This commit is contained in:
Tyler Hicks 2015-05-19 21:31:53 -05:00
parent 8fffe4c721
commit c6e395c08f
3 changed files with 168 additions and 54 deletions

View file

@ -40,6 +40,11 @@ terminated. The enforcement mode is also NUL terminated and the parenthesis
surrounding the mode are removed. If @mode is non-NULL, it will point to the
first character in the enforcement mode string on success.
The Linux kernel's /proc/<PID>/attr/current interface appends a trailing
newline character to AppArmor contexts that are read from that file. If @con
contains a single trailing newline character, it will be stripped by
aa_splitcon() prior to all other processing.
=head1 RETURN VALUE
Returns a pointer to the first character in the label string. NULL is returned
@ -50,7 +55,9 @@ on error.
Context Label Mode
----------------------------- ------------------ -------
unconfined unconfined NULL
unconfined\n unconfined NULL
/bin/ping (enforce) /bin/ping enforce
/bin/ping (enforce)\n /bin/ping enforce
/usr/sbin/rsyslogd (complain) /usr/sbin/rsyslogd complain
=head1 BUGS

View file

@ -171,19 +171,31 @@ static bool parse_unconfined(char *con, int size)
* splitcon - split the confinement context into a label and mode
* @con: the confinement context
* @size: size of the confinement context (not including the NUL terminator)
* @strip_newline: true if a trailing newline character should be stripped
* @mode: if non-NULL and a mode is present, will point to mode string in @con
* on success
*
* Modifies the @con string to split it into separate label and mode strings.
* The @mode argument is optional. If @mode is NULL, @con will still be split
* between the label and mode (if present) but @mode will not be set.
* If @strip_newline is true and @con contains a single trailing newline, it
* will be stripped on success (it will not be stripped on error). The @mode
* argument is optional. If @mode is NULL, @con will still be split between the
* label and mode (if present) but @mode will not be set.
*
* Returns: a pointer to the label string or NULL on error
*/
static char *splitcon(char *con, int size, char **mode)
static char *splitcon(char *con, int size, bool strip_newline, char **mode)
{
char *label = NULL;
char *mode_str = NULL;
char *newline = NULL;
if (size == 0)
goto out;
if (strip_newline && con[size - 1] == '\n') {
newline = &con[size - 1];
size--;
}
if (parse_unconfined(con, size)) {
label = con;
@ -203,6 +215,8 @@ static char *splitcon(char *con, int size, char **mode)
}
}
out:
if (label && strip_newline && newline)
*newline = 0; /* overwrite '\n', if requested, on success */
if (mode)
*mode = mode_str;
return label;
@ -214,15 +228,16 @@ out:
* @mode: if non-NULL and a mode is present, will point to mode string in @con
* on success
*
* Modifies the @con string to split it into separate label and mode strings.
* The @mode argument is optional. If @mode is NULL, @con will still be split
* Modifies the @con string to split it into separate label and mode strings. A
* single trailing newline character will be stripped from @con, if found. The
* @mode argument is optional. If @mode is NULL, @con will still be split
* between the label and mode (if present) but @mode will not be set.
*
* Returns: a pointer to the label string or NULL on error
*/
char *aa_splitcon(char *con, char **mode)
{
return splitcon(con, strlen(con), mode);
return splitcon(con, strlen(con), true, mode);
}
/**
@ -282,21 +297,18 @@ int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len,
errno = saved;
goto out;
} else if (size > 0 && buf[size - 1] != 0) {
char *nul;
/* check for null termination */
if (buf[size - 1] == '\n') {
nul = &buf[size - 1];
} else if (len == 0) {
errno = ERANGE;
goto out2;
} else {
nul = &buf[size];
size++;
if (buf[size - 1] != '\n') {
if (len == 0) {
errno = ERANGE;
goto out2;
} else {
buf[size] = 0;
size++;
}
}
*nul = 0;
if (splitcon(buf, nul - buf, mode) != buf) {
if (splitcon(buf, size, true, mode) != buf) {
errno = EINVAL;
goto out2;
}
@ -663,7 +675,7 @@ int aa_getpeercon_raw(int fd, char *buf, int *len, char **mode)
}
}
if (splitcon(buf, optlen - 1, mode) != buf) {
if (splitcon(buf, optlen - 1, false, mode) != buf) {
rc = -1;
errno = EINVAL;
goto out;

View file

@ -19,7 +19,7 @@
#include <stdio.h>
#include <string.h>
#include "features.c"
#include "kernel.c"
static int nullcmp_and_strcmp(const void *s1, const void *s2)
{
@ -30,6 +30,30 @@ static int nullcmp_and_strcmp(const void *s1, const void *s2)
return strcmp(s1, s2);
}
static int do_test_splitcon(char *con, int size, bool strip_nl, char **mode,
const char *expected_label,
const char *expected_mode, const char *error)
{
char *label;
int rc = 0;
label = splitcon(con, size, strip_nl, mode);
if (nullcmp_and_strcmp(label, expected_label)) {
fprintf(stderr, "FAIL: %s: label \"%s\" != \"%s\"\n",
error, label, expected_label);
rc = 1;
}
if (mode && nullcmp_and_strcmp(*mode, expected_mode)) {
fprintf(stderr, "FAIL: %s: mode \"%s\" != \"%s\"\n",
error, *mode, expected_mode);
rc = 1;
}
return rc;
}
static int do_test_aa_splitcon(char *con, char **mode,
const char *expected_label,
const char *expected_mode, const char *error)
@ -54,69 +78,136 @@ static int do_test_aa_splitcon(char *con, char **mode,
return rc;
}
#define TEST_SPLITCON(con, expected_label, expected_mode, error) \
#define TEST_SPLITCON(con, size, strip_nl, expected_label, \
expected_mode, error) \
do { \
char c1[] = con; \
char c2[] = con; \
size_t sz = size < 0 ? strlen(con) : size; \
char *mode; \
\
if (do_test_aa_splitcon(c1, &mode, expected_label, \
expected_mode, error)) { \
if (do_test_splitcon(c1, sz, strip_nl, &mode, \
expected_label, expected_mode, \
"splitcon: " error)) { \
rc = 1; \
} else if (do_test_aa_splitcon(c2, NULL, expected_label,\
NULL, \
error " (NULL mode)")) { \
} else if (do_test_splitcon(c2, sz, strip_nl, NULL, \
expected_label, NULL, \
"splitcon: " error " (NULL mode)")) { \
rc = 1; \
} \
} while (0)
#define TEST_AA_SPLITCON(con, expected_label, expected_mode, error) \
do { \
char c1[] = con; \
char c2[] = con; \
char c3[] = con "\n"; \
char *mode; \
\
if (do_test_aa_splitcon(c1, &mode, expected_label, \
expected_mode, "aa_splitcon: " error)) {\
rc = 1; \
} else if (do_test_aa_splitcon(c2, NULL, expected_label,\
NULL, \
"aa_splitcon: " error " (NULL mode)")) {\
rc = 1; \
} else if (do_test_aa_splitcon(c3, &mode, \
expected_label, expected_mode, \
"aa_splitcon: " error " (newline)")) { \
rc = 1; \
} \
} while (0)
static int test_splitcon(void)
{
int rc = 0;
/**
* NOTE: the TEST_SPLITCON() macro automatically generates
* corresponding tests with a NULL mode pointer.
*/
TEST_SPLITCON("", 0, true, NULL, NULL, "empty string test #1");
TEST_SPLITCON("", 0, false, NULL, NULL, "empty string test #2");
TEST_SPLITCON("unconfined", -1, true, "unconfined", NULL,
"unconfined #1");
TEST_SPLITCON("unconfined", -1, false, "unconfined", NULL,
"unconfined #2");
TEST_SPLITCON("unconfined\n", -1, true, "unconfined", NULL,
"unconfined #3");
TEST_SPLITCON("unconfined\n", -1, false, NULL, NULL,
"unconfined #4");
TEST_SPLITCON("label (mode)", -1, true, "label", "mode",
"basic split #1");
TEST_SPLITCON("label (mode)", -1, false, "label", "mode",
"basic split #2");
TEST_SPLITCON("label (mode)\n", -1, true, "label", "mode",
"basic split #3");
TEST_SPLITCON("label (mode)\n", -1, false, NULL, NULL,
"basic split #4");
TEST_SPLITCON("/a/b/c (enforce)", -1, true, "/a/b/c", "enforce",
"path enforce split #1");
TEST_SPLITCON("/a/b/c (enforce)", -1, false, "/a/b/c", "enforce",
"path enforce split #2");
TEST_SPLITCON("/a/b/c (enforce)\n", -1, true, "/a/b/c", "enforce",
"path enforce split #3");
TEST_SPLITCON("/a/b/c (enforce)\n", -1, false, NULL, NULL,
"path enforce split #4");
return rc;
}
static int test_aa_splitcon(void)
{
int rc = 0;
TEST_SPLITCON("label (mode)", "label", "mode", "basic split");
/**
* NOTE: the TEST_AA_SPLITCON() macro automatically generates
* corresponding tests with a NULL mode pointer and contexts with
* trailing newline characters.
*/
TEST_SPLITCON("/a/b/c (enforce)", "/a/b/c", "enforce",
"path enforce split");
TEST_AA_SPLITCON("label (mode)", "label", "mode", "basic split");
TEST_SPLITCON("/a/b/c (complain)", "/a/b/c", "complain",
"path complain split");
TEST_AA_SPLITCON("/a/b/c (enforce)", "/a/b/c", "enforce",
"path enforce split");
TEST_SPLITCON("profile_name (enforce)", "profile_name", "enforce",
"name enforce split");
TEST_AA_SPLITCON("/a/b/c (complain)", "/a/b/c", "complain",
"path complain split");
TEST_SPLITCON("profile_name (complain)", "profile_name", "complain",
"name complain split");
TEST_AA_SPLITCON("profile_name (enforce)", "profile_name", "enforce",
"name enforce split");
TEST_SPLITCON("unconfined", "unconfined", NULL, "unconfined");
TEST_AA_SPLITCON("profile_name (complain)", "profile_name", "complain",
"name complain split");
TEST_SPLITCON("(odd) (enforce)", "(odd)", "enforce",
"parenthesized label #1");
TEST_AA_SPLITCON("unconfined", "unconfined", NULL, "unconfined");
TEST_SPLITCON("(odd) (enforce) (enforce)", "(odd) (enforce)", "enforce",
"parenthesized label #2");
TEST_AA_SPLITCON("(odd) (enforce)", "(odd)", "enforce",
"parenthesized label #1");
TEST_SPLITCON("/usr/bin/😺 (enforce)", "/usr/bin/😺", "enforce",
"non-ASCII path");
TEST_AA_SPLITCON("(odd) (enforce) (enforce)", "(odd) (enforce)",
"enforce", "parenthesized label #2");
TEST_SPLITCON("👍 (enforce)", "👍", "enforce", "non-ASCII profile name");
TEST_AA_SPLITCON("/usr/bin/😺 (enforce)", "/usr/bin/😺", "enforce",
"non-ASCII path");
TEST_AA_SPLITCON("👍 (enforce)", "👍", "enforce",
"non-ASCII profile name");
/* Negative tests */
TEST_SPLITCON("", NULL, NULL, "empty string test");
TEST_AA_SPLITCON("", NULL, NULL, "empty string test");
TEST_SPLITCON("/a/b/c (complain)\n", NULL, NULL,
"path split w/ invalid trailing newline");
TEST_AA_SPLITCON("profile\t(enforce)", NULL, NULL,
"invalid tab separator");
TEST_SPLITCON("unconfined\n", NULL, NULL,
"unconfined w/ invalid trailing newline");
TEST_SPLITCON("profile\t(enforce)", NULL, NULL,
"invalid tab separator");
TEST_SPLITCON("profile(enforce)", NULL, NULL,
"invalid missing separator");
TEST_AA_SPLITCON("profile(enforce)", NULL, NULL,
"invalid missing separator");
return rc;
}
@ -125,6 +216,10 @@ int main(void)
{
int retval, rc = 0;
retval = test_splitcon();
if (retval)
rc = retval;
retval = test_aa_splitcon();
if (retval)
rc = retval;