2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-08-31 22:35:35 +00:00

parser: Allow the profile keyword to be used with namespaces

https://launchpad.net/bugs/1544387

Don't split namespaces from profile names using YACC grammar. Instead,
treat the entire string as a label in the grammer. The label can then be
split into a namespace and a profile name using the new parse_label()
function.

This fixes a bug that caused the profile keyword to not be used with a
label containing a namespace in the profile declaration.

Fixing this bug uncovered a bad parser test case at
simple_tests/profile/profile_ns_ok1.sd. The test case mistakenly
included two definitions of the :foo:unattached profile despite being
marked as expected to pass. I've adjusted the name of one of the
profiles to :foo:unattached2.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
Tyler Hicks
2016-02-18 15:58:06 -06:00
parent 9b2aa90b06
commit 349b4a4ba1
12 changed files with 203 additions and 26 deletions

View File

@@ -393,6 +393,7 @@ extern int get_rlimit(const char *name);
extern char *process_var(const char *var); extern char *process_var(const char *var);
extern int parse_mode(const char *mode); extern int parse_mode(const char *mode);
extern int parse_X_mode(const char *X, int valid, const char *str_mode, int *mode, int fail); extern int parse_X_mode(const char *X, int valid, const char *str_mode, int *mode, int fail);
void parse_label(char **ns, char **name, const char *label);
extern struct cod_entry *new_entry(char *ns, char *id, int mode, char *link_id); extern struct cod_entry *new_entry(char *ns, char *id, int mode, char *link_id);
/* returns -1 if value != true or false, otherwise 0 == false, 1 == true */ /* returns -1 if value != true or false, otherwise 0 == false, 1 == true */

View File

@@ -225,8 +225,8 @@ SET_VAR_PREFIX @
SET_VARIABLE {SET_VAR_PREFIX}(\{{VARIABLE_NAME}\}|{VARIABLE_NAME}) SET_VARIABLE {SET_VAR_PREFIX}(\{{VARIABLE_NAME}\}|{VARIABLE_NAME})
BOOL_VARIABLE $(\{{VARIABLE_NAME}\}|{VARIABLE_NAME}) BOOL_VARIABLE $(\{{VARIABLE_NAME}\}|{VARIABLE_NAME})
PATHNAME (\/|{SET_VARIABLE}{POST_VAR_ID}){ID}* LABEL (\/|{SET_VARIABLE}{POST_VAR_ID}|{COLON}){ID}*
QPATHNAME \"(\/|{SET_VAR_PREFIX})([^\0"]|\\\")*\" QUOTED_LABEL \"(\/|{SET_VAR_PREFIX}|{COLON})([^\0"]|\\\")*\"
OPEN_PAREN \( OPEN_PAREN \(
CLOSE_PAREN \) CLOSE_PAREN \)
@@ -510,7 +510,7 @@ LT_EQUAL <=
} }
<MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE>{ <MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE>{
({IDS_NOEQ}|{PATHNAME}|{QUOTED_ID}) { ({IDS_NOEQ}|{LABEL}|{QUOTED_ID}) {
yylval.id = processid(yytext, yyleng); yylval.id = processid(yytext, yyleng);
RETURN_TOKEN(TOK_ID); RETURN_TOKEN(TOK_ID);
} }
@@ -557,7 +557,7 @@ include/{WS} {
{CLOSE_BRACE} { RETURN_TOKEN(TOK_CLOSE); } {CLOSE_BRACE} { RETURN_TOKEN(TOK_CLOSE); }
({PATHNAME}|{QPATHNAME}) { ({LABEL}|{QUOTED_LABEL}) {
yylval.id = processid(yytext, yyleng); yylval.id = processid(yytext, yyleng);
RETURN_TOKEN(TOK_ID); RETURN_TOKEN(TOK_ID);
} }

View File

@@ -569,6 +569,52 @@ int parse_X_mode(const char *X, int valid, const char *str_mode, int *mode, int
return 1; return 1;
} }
void parse_label(char **ns, char **name, const char *label)
{
const char *name_start = NULL;
char *_ns = NULL;
char *_name = NULL;
if (label[0] != ':') {
/* There is no namespace specified in the label */
name_start = label;
} else {
/* A leading ':' indicates that a namespace is specified */
const char *ns_start = label + 1;
const char *ns_end = strstr(ns_start, ":");
if (!ns_end)
yyerror(_("Namespace not terminated: %s\n"), label);
else if (ns_end - ns_start == 0)
yyerror(_("Empty namespace: %s\n"), label);
/**
* Handle either of the two namespace formats:
* 1) :ns:name
* 2) :ns://name
*/
name_start = ns_end + 1;
if (!strncmp(name_start, "//", 2))
name_start += 2;
_ns = strndup(ns_start, ns_end - ns_start);
if (!_ns)
yyerror(_("Memory allocation error."));
}
if (!strlen(name_start))
yyerror(_("Empty named transition profile name: %s\n"), label);
_name = strdup(name_start);
if (!_name) {
free(_ns);
yyerror(_("Memory allocation error."));
}
*ns = _ns;
*name = _name;
}
struct cod_entry *new_entry(char *ns, char *id, int mode, char *link_id) struct cod_entry *new_entry(char *ns, char *id, int mode, char *link_id)
{ {
struct cod_entry *entry = NULL; struct cod_entry *entry = NULL;

View File

@@ -318,14 +318,26 @@ profile_base: TOK_ID opt_id_or_var flags TOK_OPEN rules TOK_CLOSE
yyerror(_("Memory allocation error.")); yyerror(_("Memory allocation error."));
} }
parse_label(&prof->ns, &prof->name, $1);
free($1);
/* Honor the --namespace-string command line option */ /* Honor the --namespace-string command line option */
if (profile_ns) { if (profile_ns) {
/**
* Print warning if the profile specified a namespace
* different than the one specified with the
* --namespace-string command line option
*/
if (prof->ns && strcmp(prof->ns, profile_ns))
pwarn("%s: -n %s overriding policy specified namespace :%s:\n",
progname, profile_ns, prof->ns);
free(prof->ns);
prof->ns = strdup(profile_ns); prof->ns = strdup(profile_ns);
if (!prof->ns) if (!prof->ns)
yyerror(_("Memory allocation error.")); yyerror(_("Memory allocation error."));
} }
prof->name = $1;
prof->attachment = $2; prof->attachment = $2;
if ($2 && !($2[0] == '/' || strncmp($2, "@{", 2) == 0)) if ($2 && !($2[0] == '/' || strncmp($2, "@{", 2) == 0))
yyerror(_("Profile attachment must begin with a '/' or variable.")); yyerror(_("Profile attachment must begin with a '/' or variable."));
@@ -347,30 +359,18 @@ profile_base: TOK_ID opt_id_or_var flags TOK_OPEN rules TOK_CLOSE
}; };
profile: opt_profile_flag opt_ns profile_base profile: opt_profile_flag profile_base
{ {
Profile *prof = $3; Profile *prof = $2;
if ($2)
PDEBUG("Matched: %s://%s { ... }\n", $2, $3->name);
else
PDEBUG("Matched: %s { ... }\n", $3->name);
if ($3->name[0] != '/' && !($1 || $2)) if ($2->ns)
PDEBUG("Matched: :%s://%s { ... }\n", $2->ns, $2->name);
else
PDEBUG("Matched: %s { ... }\n", $2->name);
if ($2->name[0] != '/' && !($1 || $2->ns))
yyerror(_("Profile names must begin with a '/', namespace or keyword 'profile' or 'hat'.")); yyerror(_("Profile names must begin with a '/', namespace or keyword 'profile' or 'hat'."));
if (prof->ns) {
/**
* Print warning if the profile specified a namespace
* different than the one specified with the
* --namespace-string command line option
*/
if ($2 && strcmp(prof->ns, $2)) {
pwarn("%s: -n %s overriding policy specified namespace :%s:\n",
progname, prof->ns, $2);
}
free($2);
} else
prof->ns = $2;
if ($1 == 2) if ($1 == 2)
prof->flags.hat = 1; prof->flags.hat = 1;
$$ = prof; $$ = prof;

View File

@@ -0,0 +1,9 @@
#
#=DESCRIPTION namespace with no profile name
#=EXRESULT FAIL
# vim:syntax=apparmor
# Last Modified: Thu Feb 11 00:14:20 2016
#
:namespace: {
/does/not/exist r,
}

View File

@@ -0,0 +1,13 @@
#
#=DESCRIPTION collision same profile, same namespace with profile keyword
#=EXRESULT FAIL
# vim:syntax=apparmor
# Last Modified: Thu Feb 11 00:14:20 2016
#
profile :ns:/t {
/does/not/exist r,
}
profile :ns:/t {
/does/not/exist r,
}

View File

@@ -0,0 +1,13 @@
#
#=DESCRIPTION collision same profile, same namespace w/ and w/o profile keyword
#=EXRESULT FAIL
# vim:syntax=apparmor
# Last Modified: Thu Feb 11 00:14:20 2016
#
:ns:/t {
/does/not/exist r,
}
profile :ns:/t {
/does/not/exist r,
}

View File

@@ -0,0 +1,9 @@
#
#=DESCRIPTION no terminating ':' for ns namespace (w/ profile keyword)
#=EXRESULT FAIL
# vim:syntax=apparmor
# Last Modified: Thu Feb 11 00:14:20 2016
#
profile :ns/t {
/does/not/exist r,
}

View File

@@ -40,7 +40,7 @@ profile :foo:/does/not/exist2 {
/bin/echo uxuxuxuxux, /bin/echo uxuxuxuxux,
} }
profile :foo:unattached { profile :foo:unattached2 {
#include <includes/base> #include <includes/base>
/usr/X11R6/lib/lib*so* rrr, /usr/X11R6/lib/lib*so* rrr,

View File

@@ -200,6 +200,7 @@ TESTS=aa_exec \
mount \ mount \
mult_mount \ mult_mount \
named_pipe \ named_pipe \
namespaces \
net_raw \ net_raw \
open \ open \
openat \ openat \

View File

@@ -0,0 +1,76 @@
#! /bin/bash
# Copyright (C) 2016 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 namespaces
#=DESCRIPTION
# Verifies basic namespace functionality
#=END
pwd=`dirname $0`
pwd=`cd $pwd ; /bin/pwd`
bin=$pwd
. $bin/prologue.inc
requires_namespace_interface
# unique_ns - Print a randomly generated, unused namespace identifier to stdout
unique_ns() {
# racy way of generating a namespace name that is likely to be unique
local ns=$(mktemp --dry-run -dp /sys/kernel/security/apparmor/policy/namespaces -t test_namespaces_XXXXXX)
basename "$ns"
}
# genprofile_ns - Generate and load a profile using a randomly generated namespace
# $1: The profile name to use (without a namespace)
# $2: Non-zero if the 'profile' keyword should be prefixed to the declaration
#
# Returns the randomly generated namespace that the profile was loaded into
genprofile_ns() {
local prefix=""
local ns=$(unique_ns)
local prof=$1
if [ $2 -ne 0 ]; then
prefix="profile "
fi
# override the sys_profiles variable with a bad path so that genprofile
# doesn't perform profile load checking in the wrong policy namespace
echo "${prefix}:${ns}:${prof} {}" | sys_profiles="${sys_profiles}XXX" genprofile --stdin
echo "$ns"
}
# genprofile_ns_and_verify - Generate and load a profile into a namespace and
# verify the creation of the profile and namespace
# $1: A description of this test
# $2: Non-zero if the 'profile' keyword should be prefixed to the declaration
genprofile_ns_and_verify() {
local desc=$1
local prof="p"
local ns=$(genprofile_ns "$prof" $2)
[ -d /sys/kernel/security/apparmor/policy/namespaces/${ns} ]
local dir_created=$?
[ -d /sys/kernel/security/apparmor/policy/namespaces/${ns}/profiles/${prof}* ]
local prof_created=$?
removeprofile
if [ $dir_created -ne 0 ]; then
echo "Error: ${testname} failed. Test '${desc}' did not create the expected namespace directory in apparmorfs: policy/namespaces/${ns}"
testfailed
elif [ $prof_created -ne 0 ]; then
echo "Error: ${testname} failed. Test '${desc}' did not create the expected namespaced profile directory in apparmorfs: policy/namespaces/${ns}/profiles/${prof}"
testfailed
elif [ -n "$VERBOSE" ]; then
echo "ok: ${desc}"
fi
}
genprofile_ns_and_verify "NAMESPACES create unique ns (w/o profile keyword prefix)" 0
genprofile_ns_and_verify "NAMESPACES create unique ns (w/ profile keyword prefix)" 1

View File

@@ -49,6 +49,15 @@ requires_kernel_features()
fi fi
} }
requires_namespace_interface()
{
if [ ! -e "/sys/kernel/security/apparmor/policy/namespaces" ]
then
echo "Namespaces in apparmorfs policy interface not supported. Skipping tests ..."
exit 0
fi
}
requires_query_interface() requires_query_interface()
{ {
if [ ! -e "/sys/kernel/security/apparmor/.access" ] if [ ! -e "/sys/kernel/security/apparmor/.access" ]