mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-31 14:25:52 +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:
@@ -393,6 +393,7 @@ extern int get_rlimit(const char *name);
|
||||
extern char *process_var(const char *var);
|
||||
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);
|
||||
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);
|
||||
|
||||
/* returns -1 if value != true or false, otherwise 0 == false, 1 == true */
|
||||
|
@@ -225,8 +225,8 @@ SET_VAR_PREFIX @
|
||||
SET_VARIABLE {SET_VAR_PREFIX}(\{{VARIABLE_NAME}\}|{VARIABLE_NAME})
|
||||
BOOL_VARIABLE $(\{{VARIABLE_NAME}\}|{VARIABLE_NAME})
|
||||
|
||||
PATHNAME (\/|{SET_VARIABLE}{POST_VAR_ID}){ID}*
|
||||
QPATHNAME \"(\/|{SET_VAR_PREFIX})([^\0"]|\\\")*\"
|
||||
LABEL (\/|{SET_VARIABLE}{POST_VAR_ID}|{COLON}){ID}*
|
||||
QUOTED_LABEL \"(\/|{SET_VAR_PREFIX}|{COLON})([^\0"]|\\\")*\"
|
||||
|
||||
OPEN_PAREN \(
|
||||
CLOSE_PAREN \)
|
||||
@@ -510,7 +510,7 @@ LT_EQUAL <=
|
||||
}
|
||||
|
||||
<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);
|
||||
RETURN_TOKEN(TOK_ID);
|
||||
}
|
||||
@@ -557,7 +557,7 @@ include/{WS} {
|
||||
|
||||
{CLOSE_BRACE} { RETURN_TOKEN(TOK_CLOSE); }
|
||||
|
||||
({PATHNAME}|{QPATHNAME}) {
|
||||
({LABEL}|{QUOTED_LABEL}) {
|
||||
yylval.id = processid(yytext, yyleng);
|
||||
RETURN_TOKEN(TOK_ID);
|
||||
}
|
||||
|
@@ -569,6 +569,52 @@ int parse_X_mode(const char *X, int valid, const char *str_mode, int *mode, int
|
||||
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 *entry = NULL;
|
||||
|
@@ -318,14 +318,26 @@ profile_base: TOK_ID opt_id_or_var flags TOK_OPEN rules TOK_CLOSE
|
||||
yyerror(_("Memory allocation error."));
|
||||
}
|
||||
|
||||
parse_label(&prof->ns, &prof->name, $1);
|
||||
free($1);
|
||||
|
||||
/* Honor the --namespace-string command line option */
|
||||
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);
|
||||
if (!prof->ns)
|
||||
yyerror(_("Memory allocation error."));
|
||||
}
|
||||
|
||||
prof->name = $1;
|
||||
prof->attachment = $2;
|
||||
if ($2 && !($2[0] == '/' || strncmp($2, "@{", 2) == 0))
|
||||
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;
|
||||
if ($2)
|
||||
PDEBUG("Matched: %s://%s { ... }\n", $2, $3->name);
|
||||
else
|
||||
PDEBUG("Matched: %s { ... }\n", $3->name);
|
||||
Profile *prof = $2;
|
||||
|
||||
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'."));
|
||||
|
||||
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)
|
||||
prof->flags.hat = 1;
|
||||
$$ = prof;
|
||||
|
9
parser/tst/simple_tests/profile/profile_ns_bad5.sd
Normal file
9
parser/tst/simple_tests/profile/profile_ns_bad5.sd
Normal 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,
|
||||
}
|
13
parser/tst/simple_tests/profile/profile_ns_bad6.sd
Normal file
13
parser/tst/simple_tests/profile/profile_ns_bad6.sd
Normal 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,
|
||||
}
|
13
parser/tst/simple_tests/profile/profile_ns_bad7.sd
Normal file
13
parser/tst/simple_tests/profile/profile_ns_bad7.sd
Normal 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,
|
||||
}
|
9
parser/tst/simple_tests/profile/profile_ns_bad8.sd
Normal file
9
parser/tst/simple_tests/profile/profile_ns_bad8.sd
Normal 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,
|
||||
}
|
@@ -40,7 +40,7 @@ profile :foo:/does/not/exist2 {
|
||||
/bin/echo uxuxuxuxux,
|
||||
}
|
||||
|
||||
profile :foo:unattached {
|
||||
profile :foo:unattached2 {
|
||||
#include <includes/base>
|
||||
|
||||
/usr/X11R6/lib/lib*so* rrr,
|
||||
|
@@ -200,6 +200,7 @@ TESTS=aa_exec \
|
||||
mount \
|
||||
mult_mount \
|
||||
named_pipe \
|
||||
namespaces \
|
||||
net_raw \
|
||||
open \
|
||||
openat \
|
||||
|
76
tests/regression/apparmor/namespaces.sh
Executable file
76
tests/regression/apparmor/namespaces.sh
Executable 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
|
@@ -49,6 +49,15 @@ requires_kernel_features()
|
||||
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()
|
||||
{
|
||||
if [ ! -e "/sys/kernel/security/apparmor/.access" ]
|
||||
|
Reference in New Issue
Block a user