mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-23 02:27:12 +00:00
Add support for profiles with xattrs matching
Add userland support for matching based on extended file attributes. This leverages DFA based matching already in the kernel: https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=8e51f908 https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/commit/?id=73f488cd Matching is exposed via flags on the profile: /usr/bin/* xattrs=(user.foo=bar user.bar=foo) { # ... } xattr values are appended to the existing xmatch via a null transition. $ echo '/usr/bin/* xattrs=(user.foo=foo user.bar=bar) {}' | \ ./parser/apparmor_parser -QT -D expr-tree DFA: Expression Tree /usr/bin/[^\0000/]([^\0000/])*(\0000bar)?(\0000foo)?< 0x1> DFA: Expression Tree (\a|(\n|(\0002|\t)))< 0x4> Tested manually on a 4.19 kernel via QEMU+KVM. TODO: * ~~Add regression tests~~ (EDIT: done) * ~~EDIT: add support in the tools~~ (EDIT: done) Questions for reviewers: * ~~parser/libapparmor: regex construction probably needs cleaning up~~ (EDIT: done) * ~~parser/parser_regex.c: confused what xmatch length is for~~ (EDIT: done) /cc @mjg59 PR: https://gitlab.com/apparmor/apparmor/merge_requests/270 Signed-off-by: John Johansen <john.johansen@canonical.com>
This commit is contained in:
commit
cfe20d2b63
@ -30,7 +30,7 @@ SYSTEMD_UNIT_DIR=${DESTDIR}/usr/lib/systemd/system
|
|||||||
CONFDIR=/etc/apparmor
|
CONFDIR=/etc/apparmor
|
||||||
INSTALL_CONFDIR=${DESTDIR}${CONFDIR}
|
INSTALL_CONFDIR=${DESTDIR}${CONFDIR}
|
||||||
LOCALEDIR=/usr/share/locale
|
LOCALEDIR=/usr/share/locale
|
||||||
MANPAGES=apparmor.d.5 apparmor.7 apparmor_parser.8 aa-teardown.8
|
MANPAGES=apparmor.d.5 apparmor.7 apparmor_parser.8 aa-teardown.8 apparmor_xattrs.7
|
||||||
|
|
||||||
YACC := bison
|
YACC := bison
|
||||||
YFLAGS := -d
|
YFLAGS := -d
|
||||||
|
@ -66,7 +66,7 @@ B<COMMENT> = '#' I<TEXT> [ '\r' ] '\n'
|
|||||||
|
|
||||||
B<TEXT> = any characters
|
B<TEXT> = any characters
|
||||||
|
|
||||||
B<PROFILE> = ( I<PROFILE HEAD> ) [ I<ATTACHMENT SPECIFICATION> ] [ I<PROFILE FLAG CONDS> ] '{' ( I<RULES> )* '}'
|
B<PROFILE> = ( I<PROFILE HEAD> ) [ I<ATTACHMENT SPECIFICATION> ] [ I<PROFILE XATTR CONDS> ] [ I<PROFILE FLAG CONDS> ] '{' ( I<RULES> )* '}'
|
||||||
|
|
||||||
B<PROFILE HEAD> = [ 'profile' ] I<FILEGLOB> | 'profile' I<PROFILE NAME>
|
B<PROFILE HEAD> = [ 'profile' ] I<FILEGLOB> | 'profile' I<PROFILE NAME>
|
||||||
|
|
||||||
@ -78,6 +78,12 @@ B<UNQUOTED PROFILE NAME> = (must start with alphanumeric character (after variab
|
|||||||
|
|
||||||
B<ATTACHMENT SPECIFICATION> = I<FILEGLOB>
|
B<ATTACHMENT SPECIFICATION> = I<FILEGLOB>
|
||||||
|
|
||||||
|
B<PROFILE XATTR CONDS> = [ 'xattrs=' ] '(' comma or white space separated list of I<PROFILE XATTR> ')'
|
||||||
|
|
||||||
|
B<PROFILE XATTR> = extended attribute name '=' I<XATTR VALUE FILEGLOB>
|
||||||
|
|
||||||
|
B<XATTR VALUE FILEGLOB> = I<FILEGLOB>
|
||||||
|
|
||||||
B<PROFILE FLAG CONDS> = [ 'flags=' ] '(' comma or white space separated list of I<PROFILE FLAGS> ')'
|
B<PROFILE FLAG CONDS> = [ 'flags=' ] '(' comma or white space separated list of I<PROFILE FLAGS> ')'
|
||||||
|
|
||||||
B<PROFILE FLAGS> = 'complain' | 'audit' | 'enforce' | 'mediate_deleted' | 'attach_disconnected' | 'chroot_relative'
|
B<PROFILE FLAGS> = 'complain' | 'audit' | 'enforce' | 'mediate_deleted' | 'attach_disconnected' | 'chroot_relative'
|
||||||
@ -1371,6 +1377,18 @@ Directories anywhere underneath F</tmp>.
|
|||||||
|
|
||||||
=back
|
=back
|
||||||
|
|
||||||
|
=head2 Extended Attributes
|
||||||
|
|
||||||
|
AppArmor profiles have the ability to target files based on their xattr(7)
|
||||||
|
values in addition to their path. For example, the following profile matches
|
||||||
|
files in /usr/bin with the attribute "security.apparmor" and value "trusted":
|
||||||
|
|
||||||
|
/usr/bin/* xattrs(security.apparmor="trusted") {
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
|
||||||
|
See apparmor_xattrs(7) for further details.
|
||||||
|
|
||||||
=head2 Rule Qualifiers
|
=head2 Rule Qualifiers
|
||||||
|
|
||||||
There are several rule qualifiers that can be applied to permission rules.
|
There are several rule qualifiers that can be applied to permission rules.
|
||||||
@ -1609,7 +1627,7 @@ negative values match when specifying one or the other. Eg, 'rw' matches when
|
|||||||
|
|
||||||
=head1 SEE ALSO
|
=head1 SEE ALSO
|
||||||
|
|
||||||
apparmor(7), apparmor_parser(8), aa-complain(1),
|
apparmor(7), apparmor_parser(8), apprmor_xattrs(7), aa-complain(1),
|
||||||
aa-enforce(1), aa_change_hat(2), mod_apparmor(5), and
|
aa-enforce(1), aa_change_hat(2), mod_apparmor(5), and
|
||||||
L<https://wiki.apparmor.net>.
|
L<https://wiki.apparmor.net>.
|
||||||
|
|
||||||
|
108
parser/apparmor_xattrs.pod
Normal file
108
parser/apparmor_xattrs.pod
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# ----------------------------------------------------------------------
|
||||||
|
# Copyright (c) 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
|
||||||
|
# 2008, 2009
|
||||||
|
# NOVELL (All rights reserved)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2010
|
||||||
|
# Canonical Ltd. (All rights reserved)
|
||||||
|
#
|
||||||
|
# Copyright (c) 2013
|
||||||
|
# Christian Boltz (All rights reserved)
|
||||||
|
#
|
||||||
|
# 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 Novell, Inc.
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
AppArmor profile xattr(7) matching
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
AppArmor profiles can conditionally match files based on the presence and value
|
||||||
|
of extended attributes in addition to file path. The following profile applies
|
||||||
|
to any file under "/usr/bin" where the "security.apparmor" extended attribute
|
||||||
|
has the value "trusted":
|
||||||
|
|
||||||
|
profile trusted /usr/bin/* xattrs=(security.apparmor="trusted") {
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
|
||||||
|
Note that "security.apparmor" and "trusted" are arbitrary, and profiles can
|
||||||
|
match based on the value of any attribute.
|
||||||
|
|
||||||
|
The xattrs value may also contain a path regex:
|
||||||
|
|
||||||
|
profile trusted /usr/bin/* xattrs=(user.trust="tier/*") {
|
||||||
|
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
|
||||||
|
The getfattr(1) and setfattr(1) tools can be used to view and manage xattr
|
||||||
|
values:
|
||||||
|
|
||||||
|
$ setfattr -n 'security.apparmor' -v 'trusted' /usr/bin/example-tool
|
||||||
|
$ getfattr --absolute-names -d -m - /usr/bin/example-tool
|
||||||
|
# file: usr/bin/example-tool
|
||||||
|
security.apparmor="trusted"
|
||||||
|
|
||||||
|
The priority of each profile is determined by the length of the path, then the
|
||||||
|
number of xattrs specified. A more specific path is preferred over xattr
|
||||||
|
matches:
|
||||||
|
|
||||||
|
# Highest priority, longest path.
|
||||||
|
profile example1 /usr/bin/example-tool {
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
|
||||||
|
# Lower priority than the longer path, but higher priority than a rule
|
||||||
|
# with fewer xattr matches.
|
||||||
|
profile example2 /usr/** xattrs=(
|
||||||
|
security.apparmor="trusted"
|
||||||
|
user.domain="**"
|
||||||
|
) {
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
|
||||||
|
# Lowest priority. Same path length as the second profile, but has
|
||||||
|
# fewer xattr matches.
|
||||||
|
profile example2 /usr/** {
|
||||||
|
# ...
|
||||||
|
}
|
||||||
|
|
||||||
|
xattr matching requires the following kernel feature:
|
||||||
|
|
||||||
|
/sys/kernel/security/apparmor/features/domain/attach_conditions/xattr
|
||||||
|
|
||||||
|
=head1 KNOWN ISSUES
|
||||||
|
|
||||||
|
AppArmor profiles currently can't reliably match extended attributes with
|
||||||
|
binary values such as security.evm and security.ima. In the future AppArmor may
|
||||||
|
gain the ability to match based on the presence of certain attributes while
|
||||||
|
ignoring their values.
|
||||||
|
|
||||||
|
=head1 SEE ALSO
|
||||||
|
|
||||||
|
apparmor(8),
|
||||||
|
apparmor_parser(8),
|
||||||
|
apparmor.d(5),
|
||||||
|
xattr(7),
|
||||||
|
aa-autodep(1), clean(1),
|
||||||
|
auditd(8),
|
||||||
|
getfattr(1),
|
||||||
|
setfattr(1),
|
||||||
|
and L<https://wiki.apparmor.net>.
|
||||||
|
|
||||||
|
=cut
|
@ -124,6 +124,44 @@ bool aare_rules::add_rule_vec(int deny, uint32_t perms, uint32_t audit,
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* append_rule is like add_rule, but appends the rule to any existing rules
|
||||||
|
* with a null transition. The appended rule matches with the same permissions
|
||||||
|
* as the rule it's appended to.
|
||||||
|
*
|
||||||
|
* This is used by xattrs matching where, after matching the path, the DFA is
|
||||||
|
* advanced by a null character for each xattr.
|
||||||
|
*/
|
||||||
|
bool aare_rules::append_rule(const char *rule, dfaflags_t flags)
|
||||||
|
{
|
||||||
|
Node *tree = NULL;
|
||||||
|
if (regex_parse(&tree, rule))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (flags & DFA_DUMP_RULE_EXPR) {
|
||||||
|
cerr << "rule: ";
|
||||||
|
cerr << rule;
|
||||||
|
cerr << " -> ";
|
||||||
|
tree->dump(cerr);
|
||||||
|
cerr << "\n\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* For each matching state, we want to create an optional path
|
||||||
|
* separated by a null character.
|
||||||
|
*
|
||||||
|
* When matching xattrs, the DFA must end up in an accepting state for
|
||||||
|
* the path, then each value of the xattrs. Using an optional node
|
||||||
|
* lets each rule end up in an accepting state.
|
||||||
|
*/
|
||||||
|
tree = new OptionalNode(new CatNode(new CharNode(0), tree));
|
||||||
|
PermExprMap::iterator it;
|
||||||
|
for (it = expr_map.begin(); it != expr_map.end(); it++) {
|
||||||
|
expr_map[it->first] = new CatNode(it->second, tree);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/* create a dfa from the ruleset
|
/* create a dfa from the ruleset
|
||||||
* returns: buffer contain dfa tables, @size set to the size of the tables
|
* returns: buffer contain dfa tables, @size set to the size of the tables
|
||||||
* else NULL on failure, @min_match_len set to the shortest string
|
* else NULL on failure, @min_match_len set to the shortest string
|
||||||
|
@ -104,6 +104,7 @@ class aare_rules {
|
|||||||
uint32_t audit, dfaflags_t flags);
|
uint32_t audit, dfaflags_t flags);
|
||||||
bool add_rule_vec(int deny, uint32_t perms, uint32_t audit, int count,
|
bool add_rule_vec(int deny, uint32_t perms, uint32_t audit, int count,
|
||||||
const char **rulev, dfaflags_t flags);
|
const char **rulev, dfaflags_t flags);
|
||||||
|
bool append_rule(const char *rule, dfaflags_t flags);
|
||||||
void *create_dfa(size_t *size, int *min_match_len, dfaflags_t flags);
|
void *create_dfa(size_t *size, int *min_match_len, dfaflags_t flags);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -534,6 +534,9 @@ static void count_tree_nodes(Node *t, struct node_counts *counts)
|
|||||||
} else if (dynamic_cast<StarNode *>(t)) {
|
} else if (dynamic_cast<StarNode *>(t)) {
|
||||||
counts->star++;
|
counts->star++;
|
||||||
count_tree_nodes(t->child[0], counts);
|
count_tree_nodes(t->child[0], counts);
|
||||||
|
} else if (dynamic_cast<OptionalNode *>(t)) {
|
||||||
|
counts->optional++;
|
||||||
|
count_tree_nodes(t->child[0], counts);
|
||||||
} else if (dynamic_cast<CharNode *>(t)) {
|
} else if (dynamic_cast<CharNode *>(t)) {
|
||||||
counts->charnode++;
|
counts->charnode++;
|
||||||
} else if (dynamic_cast<AnyCharNode *>(t)) {
|
} else if (dynamic_cast<AnyCharNode *>(t)) {
|
||||||
@ -559,7 +562,7 @@ Node *simplify_tree(Node *t, dfaflags_t flags)
|
|||||||
int i;
|
int i;
|
||||||
|
|
||||||
if (flags & DFA_DUMP_TREE_STATS) {
|
if (flags & DFA_DUMP_TREE_STATS) {
|
||||||
struct node_counts counts = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
struct node_counts counts = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
count_tree_nodes(t, &counts);
|
count_tree_nodes(t, &counts);
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"expr tree: c %d, [] %d, [^] %d, | %d, + %d, * %d, . %d, cat %d\n",
|
"expr tree: c %d, [] %d, [^] %d, | %d, + %d, * %d, . %d, cat %d\n",
|
||||||
@ -595,7 +598,7 @@ Node *simplify_tree(Node *t, dfaflags_t flags)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (flags & DFA_DUMP_TREE_STATS) {
|
if (flags & DFA_DUMP_TREE_STATS) {
|
||||||
struct node_counts counts = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
struct node_counts counts = { 0, 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||||
count_tree_nodes(t, &counts);
|
count_tree_nodes(t, &counts);
|
||||||
fprintf(stderr,
|
fprintf(stderr,
|
||||||
"simplified expr tree: c %d, [] %d, [^] %d, | %d, + %d, * %d, . %d, cat %d\n",
|
"simplified expr tree: c %d, [] %d, [^] %d, | %d, + %d, * %d, . %d, cat %d\n",
|
||||||
|
@ -482,6 +482,26 @@ public:
|
|||||||
bool contains_null() { return child[0]->contains_null(); }
|
bool contains_null() { return child[0]->contains_null(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/* Match a node zero or one times. */
|
||||||
|
class OptionalNode: public OneChildNode {
|
||||||
|
public:
|
||||||
|
OptionalNode(Node *left): OneChildNode(left) { nullable = true; }
|
||||||
|
void compute_firstpos() { firstpos = child[0]->firstpos; }
|
||||||
|
void compute_lastpos() { lastpos = child[0]->lastpos; }
|
||||||
|
int eq(Node *other)
|
||||||
|
{
|
||||||
|
if (dynamic_cast<OptionalNode *>(other))
|
||||||
|
return child[0]->eq(other->child[0]);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
ostream &dump(ostream &os)
|
||||||
|
{
|
||||||
|
os << '(';
|
||||||
|
child[0]->dump(os);
|
||||||
|
return os << ")?";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/* Match a node one or more times. (This is a unary operator.) */
|
/* Match a node one or more times. (This is a unary operator.) */
|
||||||
class PlusNode: public OneChildNode {
|
class PlusNode: public OneChildNode {
|
||||||
public:
|
public:
|
||||||
@ -713,6 +733,7 @@ struct node_counts {
|
|||||||
int alt;
|
int alt;
|
||||||
int plus;
|
int plus;
|
||||||
int star;
|
int star;
|
||||||
|
int optional;
|
||||||
int any;
|
int any;
|
||||||
int cat;
|
int cat;
|
||||||
};
|
};
|
||||||
|
@ -371,6 +371,28 @@ void sd_serialize_xtable(std::ostringstream &buf, char **table)
|
|||||||
sd_write_structend(buf);
|
sd_write_structend(buf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void sd_serialize_xattrs(std::ostringstream &buf, struct cond_entry_list xattrs)
|
||||||
|
{
|
||||||
|
int count;
|
||||||
|
struct cond_entry *entry;
|
||||||
|
|
||||||
|
if (!(xattrs.list))
|
||||||
|
return;
|
||||||
|
|
||||||
|
count = 0;
|
||||||
|
for (entry = xattrs.list; entry; entry = entry->next) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
sd_write_struct(buf, "xattrs");
|
||||||
|
sd_write_array(buf, NULL, count);
|
||||||
|
for (entry = xattrs.list; entry; entry = entry->next) {
|
||||||
|
sd_write_string(buf, entry->name, NULL);
|
||||||
|
}
|
||||||
|
sd_write_arrayend(buf);
|
||||||
|
sd_write_structend(buf);
|
||||||
|
}
|
||||||
|
|
||||||
void sd_serialize_profile(std::ostringstream &buf, Profile *profile,
|
void sd_serialize_profile(std::ostringstream &buf, Profile *profile,
|
||||||
int flattened)
|
int flattened)
|
||||||
{
|
{
|
||||||
@ -432,6 +454,8 @@ void sd_serialize_profile(std::ostringstream &buf, Profile *profile,
|
|||||||
sd_write_uint32(buf, 0);
|
sd_write_uint32(buf, 0);
|
||||||
sd_write_structend(buf);
|
sd_write_structend(buf);
|
||||||
|
|
||||||
|
sd_serialize_xattrs(buf, profile->xattrs);
|
||||||
|
|
||||||
sd_serialize_rlimits(buf, &profile->rlimits);
|
sd_serialize_rlimits(buf, &profile->rlimits);
|
||||||
|
|
||||||
if (profile->net.allow && kernel_supports_network) {
|
if (profile->net.allow && kernel_supports_network) {
|
||||||
|
@ -308,7 +308,7 @@ GT >
|
|||||||
}
|
}
|
||||||
|
|
||||||
<INITIAL,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE>{
|
<INITIAL,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE>{
|
||||||
peer/{WS}*={WS}*\( {
|
(peer|xattrs)/{WS}*={WS}*\( {
|
||||||
/* we match to the = in the lexer so that we can switch scanner
|
/* we match to the = in the lexer so that we can switch scanner
|
||||||
* state. By the time the parser see the = it may be too late
|
* state. By the time the parser see the = it may be too late
|
||||||
* as bison may have requested the next token from the scanner
|
* as bison may have requested the next token from the scanner
|
||||||
|
@ -432,12 +432,29 @@ static const char *local_name(const char *name)
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* get_xattr_value returns the value of an xattr expression, performing NULL
|
||||||
|
* checks along the way. The method returns NULL if the xattr match doesn't
|
||||||
|
* have an xattrs (though this case currently isn't permitted by the parser).
|
||||||
|
*/
|
||||||
|
char *get_xattr_value(struct cond_entry *entry)
|
||||||
|
{
|
||||||
|
if (!entry->eq)
|
||||||
|
return NULL;
|
||||||
|
if (!entry->vals)
|
||||||
|
return NULL;
|
||||||
|
return entry->vals->value;
|
||||||
|
}
|
||||||
|
|
||||||
static int process_profile_name_xmatch(Profile *prof)
|
static int process_profile_name_xmatch(Profile *prof)
|
||||||
{
|
{
|
||||||
std::string tbuf;
|
std::string tbuf;
|
||||||
pattern_t ptype;
|
pattern_t ptype;
|
||||||
const char *name;
|
const char *name;
|
||||||
|
|
||||||
|
struct cond_entry *entry;
|
||||||
|
const char *xattr_value;
|
||||||
|
|
||||||
/* don't filter_slashes for profile names */
|
/* don't filter_slashes for profile names */
|
||||||
if (prof->attachment)
|
if (prof->attachment)
|
||||||
name = prof->attachment;
|
name = prof->attachment;
|
||||||
@ -451,7 +468,7 @@ static int process_profile_name_xmatch(Profile *prof)
|
|||||||
if (ptype == ePatternInvalid) {
|
if (ptype == ePatternInvalid) {
|
||||||
PERROR(_("%s: Invalid profile name '%s' - bad regular expression\n"), progname, name);
|
PERROR(_("%s: Invalid profile name '%s' - bad regular expression\n"), progname, name);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
} else if (ptype == ePatternBasic && !(prof->altnames || prof->attachment)) {
|
} else if (ptype == ePatternBasic && !(prof->altnames || prof->attachment || prof->xattrs.list)) {
|
||||||
/* no regex so do not set xmatch */
|
/* no regex so do not set xmatch */
|
||||||
prof->xmatch = NULL;
|
prof->xmatch = NULL;
|
||||||
prof->xmatch_len = 0;
|
prof->xmatch_len = 0;
|
||||||
@ -479,6 +496,28 @@ static int process_profile_name_xmatch(Profile *prof)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (prof->xattrs.list) {
|
||||||
|
for (entry = prof->xattrs.list; entry; entry = entry->next) {
|
||||||
|
xattr_value = get_xattr_value(entry);
|
||||||
|
if (!xattr_value)
|
||||||
|
xattr_value = "**"; // Default to allowing any value.
|
||||||
|
/* len is measured because it's required to
|
||||||
|
* convert the regex to pcre, but doesn't impact
|
||||||
|
* xmatch_len. The kernel uses the number of
|
||||||
|
* xattrs matched to prioritized in addition to
|
||||||
|
* xmatch_len.
|
||||||
|
*/
|
||||||
|
int len;
|
||||||
|
tbuf.clear();
|
||||||
|
convert_aaregex_to_pcre(xattr_value, 0,
|
||||||
|
glob_default, tbuf,
|
||||||
|
&len);
|
||||||
|
if (!rules->append_rule(tbuf.c_str(), dfaflags)) {
|
||||||
|
delete rules;
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
prof->xmatch = rules->create_dfa(&prof->xmatch_size, &prof->xmatch_len, dfaflags);
|
prof->xmatch = rules->create_dfa(&prof->xmatch_size, &prof->xmatch_len, dfaflags);
|
||||||
delete rules;
|
delete rules;
|
||||||
if (!prof->xmatch)
|
if (!prof->xmatch)
|
||||||
|
@ -306,9 +306,9 @@ opt_id: { /* nothing */ $$ = NULL; }
|
|||||||
opt_id_or_var: { /* nothing */ $$ = NULL; }
|
opt_id_or_var: { /* nothing */ $$ = NULL; }
|
||||||
| id_or_var { $$ = $1; }
|
| id_or_var { $$ = $1; }
|
||||||
|
|
||||||
profile_base: TOK_ID opt_id_or_var flags TOK_OPEN rules TOK_CLOSE
|
profile_base: TOK_ID opt_id_or_var opt_cond_list flags TOK_OPEN rules TOK_CLOSE
|
||||||
{
|
{
|
||||||
Profile *prof = $5;
|
Profile *prof = $6;
|
||||||
bool self_stack = false;
|
bool self_stack = false;
|
||||||
|
|
||||||
if (!prof) {
|
if (!prof) {
|
||||||
@ -342,7 +342,13 @@ profile_base: TOK_ID opt_id_or_var flags TOK_OPEN rules TOK_CLOSE
|
|||||||
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."));
|
||||||
prof->flags = $3;
|
if ($3.name) {
|
||||||
|
if (strcmp($3.name, "xattrs") != 0)
|
||||||
|
yyerror(_("profile id: invalid conditional group %s=()"), $3.name);
|
||||||
|
free ($3.name);
|
||||||
|
prof->xattrs = $3;
|
||||||
|
}
|
||||||
|
prof->flags = $4;
|
||||||
if (force_complain && kernel_abi_version == 5)
|
if (force_complain && kernel_abi_version == 5)
|
||||||
/* newer abis encode force complain as part of the
|
/* newer abis encode force complain as part of the
|
||||||
* header
|
* header
|
||||||
@ -393,6 +399,12 @@ hat: hat_start profile_base
|
|||||||
Profile *prof = $2;
|
Profile *prof = $2;
|
||||||
if ($2)
|
if ($2)
|
||||||
PDEBUG("Matched: hat %s { ... }\n", prof->name);
|
PDEBUG("Matched: hat %s { ... }\n", prof->name);
|
||||||
|
/*
|
||||||
|
* It isn't clear what a xattrs match on a hat profile
|
||||||
|
* should do, disallow it for now.
|
||||||
|
*/
|
||||||
|
if ($2->xattrs.list)
|
||||||
|
yyerror("hat profiles can't use xattrs matches");
|
||||||
|
|
||||||
prof->flags.hat = 1;
|
prof->flags.hat = 1;
|
||||||
$$ = prof;
|
$$ = prof;
|
||||||
|
@ -120,6 +120,8 @@ public:
|
|||||||
size_t xmatch_size;
|
size_t xmatch_size;
|
||||||
int xmatch_len;
|
int xmatch_len;
|
||||||
|
|
||||||
|
struct cond_entry_list xattrs;
|
||||||
|
|
||||||
/* char *sub_name; */ /* subdomain name or NULL */
|
/* char *sub_name; */ /* subdomain name or NULL */
|
||||||
/* int default_deny; */ /* TRUE or FALSE */
|
/* int default_deny; */ /* TRUE or FALSE */
|
||||||
int local;
|
int local;
|
||||||
@ -151,6 +153,8 @@ public:
|
|||||||
xmatch_size = 0;
|
xmatch_size = 0;
|
||||||
xmatch_len = 0;
|
xmatch_len = 0;
|
||||||
|
|
||||||
|
xattrs.list = NULL;
|
||||||
|
|
||||||
local = local_mode = local_audit = 0;
|
local = local_mode = local_audit = 0;
|
||||||
|
|
||||||
parent = NULL;
|
parent = NULL;
|
||||||
|
7
parser/tst/simple_tests/xattrs/bad_01.sd
Normal file
7
parser/tst/simple_tests/xattrs/bad_01.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description wrong conditional group
|
||||||
|
#=EXRESULT FAIL
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test peer=(myvalue=foo) {
|
||||||
|
/foo r,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/bad_02.sd
Normal file
7
parser/tst/simple_tests/xattrs/bad_02.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description no xattrs value
|
||||||
|
#=EXRESULT FAIL
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test xattrs=(myvalue) {
|
||||||
|
/foo r,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/bad_03.sd
Normal file
7
parser/tst/simple_tests/xattrs/bad_03.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description flags before xattrs
|
||||||
|
#=EXRESULT FAIL
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test flags=(complain) xattrs=(myvalue=foo) {
|
||||||
|
/foo r,
|
||||||
|
}
|
10
parser/tst/simple_tests/xattrs/hats_01.sd
Normal file
10
parser/tst/simple_tests/xattrs/hats_01.sd
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#
|
||||||
|
#=Description hat profile with xattrs
|
||||||
|
#=EXRESULT FAIL
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test {
|
||||||
|
^hat xattrs=(myvalue=foo) {
|
||||||
|
/foo r,
|
||||||
|
}
|
||||||
|
/foo w,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/ok_01.sd
Normal file
7
parser/tst/simple_tests/xattrs/ok_01.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description basic xattr value
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test xattrs=(myvalue=foo) {
|
||||||
|
/foo r,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/ok_02.sd
Normal file
7
parser/tst/simple_tests/xattrs/ok_02.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description xattrs with quoted value
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test xattrs=(myvalue="foo.bar") {
|
||||||
|
/foo r,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/ok_03.sd
Normal file
7
parser/tst/simple_tests/xattrs/ok_03.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description match any value of an xattr
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test xattrs=(myvalue="*") {
|
||||||
|
/foo r,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/ok_04.sd
Normal file
7
parser/tst/simple_tests/xattrs/ok_04.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description key with '.' character
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test xattrs=(hello.world=foo) {
|
||||||
|
/foo r,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/ok_05.sd
Normal file
7
parser/tst/simple_tests/xattrs/ok_05.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description multiple xattrs
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test xattrs=(hello.world=foo goodbye.word=bar) {
|
||||||
|
/foo r,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/ok_06.sd
Normal file
7
parser/tst/simple_tests/xattrs/ok_06.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description xattrs then flags
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test xattrs=(myvalue=foo) flags=(audit, mediate_deleted) {
|
||||||
|
/foo r,
|
||||||
|
}
|
8
parser/tst/simple_tests/xattrs/ok_07.sd
Normal file
8
parser/tst/simple_tests/xattrs/ok_07.sd
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#
|
||||||
|
#=Description named profile
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
|
||||||
|
profile xattrs-test /usr/bin/hi xattrs=(user.foo=* user.bar=*) {
|
||||||
|
/foo r,
|
||||||
|
}
|
8
parser/tst/simple_tests/xattrs/ok_08.sd
Normal file
8
parser/tst/simple_tests/xattrs/ok_08.sd
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
#
|
||||||
|
#=Description named profile without path
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
|
||||||
|
profile xattrs-test xattrs=(user.foo="bar") {
|
||||||
|
/foo r,
|
||||||
|
}
|
7
parser/tst/simple_tests/xattrs/ok_09.sd
Normal file
7
parser/tst/simple_tests/xattrs/ok_09.sd
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
#
|
||||||
|
#=Description profile with xattrs then flags
|
||||||
|
#=EXRESULT PASS
|
||||||
|
#
|
||||||
|
/usr/bin/xattrs-test xattrs=(myvalue=foo) flags=(complain) {
|
||||||
|
/foo r,
|
||||||
|
}
|
@ -139,7 +139,8 @@ SRC=access.c \
|
|||||||
unix_socket.c \
|
unix_socket.c \
|
||||||
unix_socket_client.c \
|
unix_socket_client.c \
|
||||||
unlink.c \
|
unlink.c \
|
||||||
xattrs.c
|
xattrs.c \
|
||||||
|
xattrs_profile.c
|
||||||
|
|
||||||
#only do the ioperm/iopl tests for x86 derived architectures
|
#only do the ioperm/iopl tests for x86 derived architectures
|
||||||
ifneq (,$(findstring $(shell uname -i),i386 i486 i586 i686 x86 x86_64))
|
ifneq (,$(findstring $(shell uname -i),i386 i486 i586 i686 x86 x86_64))
|
||||||
@ -239,6 +240,7 @@ TESTS=aa_exec \
|
|||||||
unix_socket_unnamed \
|
unix_socket_unnamed \
|
||||||
unlink\
|
unlink\
|
||||||
xattrs\
|
xattrs\
|
||||||
|
xattrs_profile\
|
||||||
longpath
|
longpath
|
||||||
|
|
||||||
#only do dbus if proper libs are installl
|
#only do dbus if proper libs are installl
|
||||||
|
@ -20,6 +20,8 @@ my $usestdin = '';
|
|||||||
my %output_rules;
|
my %output_rules;
|
||||||
my $hat = "__no_hat";
|
my $hat = "__no_hat";
|
||||||
my %flags;
|
my %flags;
|
||||||
|
my %xattrs;
|
||||||
|
my $path = '';
|
||||||
|
|
||||||
GetOptions(
|
GetOptions(
|
||||||
'escape|E' => \$escape,
|
'escape|E' => \$escape,
|
||||||
@ -374,6 +376,26 @@ sub gen_addimage($) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub gen_xattr($) {
|
||||||
|
my $rule = shift;
|
||||||
|
my @rules = split (/:/, $rule);
|
||||||
|
if (@rules != 3) {
|
||||||
|
(!$nowarn) && print STDERR "Warning: invalid xattr description '$rule', ignored\n";
|
||||||
|
} else {
|
||||||
|
$xattrs{$rules[1]} = $rules[2];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sub gen_path($) {
|
||||||
|
my $rule = shift;
|
||||||
|
my @rules = split (/:/, $rule);
|
||||||
|
if (@rules != 2) {
|
||||||
|
(!$nowarn) && print STDERR "Warning: invalid path description '$rule', ignored\n";
|
||||||
|
} else {
|
||||||
|
$path = $rules[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
sub emit_flags($) {
|
sub emit_flags($) {
|
||||||
my $hat = shift;
|
my $hat = shift;
|
||||||
|
|
||||||
@ -429,6 +451,10 @@ sub gen_from_args() {
|
|||||||
} elsif ($rule =~ /^addimage:/) {
|
} elsif ($rule =~ /^addimage:/) {
|
||||||
gen_addimage($rule);
|
gen_addimage($rule);
|
||||||
$addimage = 1;
|
$addimage = 1;
|
||||||
|
} elsif ($rule =~ /^xattr:/) {
|
||||||
|
gen_xattr($rule);
|
||||||
|
} elsif ($rule =~ /^path:/) {
|
||||||
|
gen_path($rule);
|
||||||
} else {
|
} else {
|
||||||
gen_file($rule);
|
gen_file($rule);
|
||||||
}
|
}
|
||||||
@ -438,9 +464,25 @@ sub gen_from_args() {
|
|||||||
|
|
||||||
print STDOUT "# Profile autogenerated by $__VERSION__\n";
|
print STDOUT "# Profile autogenerated by $__VERSION__\n";
|
||||||
if (not substr($bin, 0, 1) eq "/") {
|
if (not substr($bin, 0, 1) eq "/") {
|
||||||
print STDOUT "profile "
|
print STDOUT "profile "
|
||||||
}
|
}
|
||||||
print STDOUT "$bin ";
|
print STDOUT "$bin ";
|
||||||
|
if (not $path eq "") {
|
||||||
|
print STDOUT "$path "
|
||||||
|
}
|
||||||
|
if (%xattrs) {
|
||||||
|
print STDOUT "xattrs=(";
|
||||||
|
my $firstloop = 1;
|
||||||
|
foreach my $xattr (keys %xattrs) {
|
||||||
|
if ($firstloop) {
|
||||||
|
$firstloop = 0;
|
||||||
|
} else {
|
||||||
|
print STDOUT " ";
|
||||||
|
}
|
||||||
|
print STDOUT "$xattr=$xattrs{$xattr}";
|
||||||
|
}
|
||||||
|
print STDOUT ") ";
|
||||||
|
}
|
||||||
emit_flags('__no_hat');
|
emit_flags('__no_hat');
|
||||||
print STDOUT "{\n";
|
print STDOUT "{\n";
|
||||||
foreach my $outrule (@{$output_rules{'__no_hat'}}) {
|
foreach my $outrule (@{$output_rules{'__no_hat'}}) {
|
||||||
|
63
tests/regression/apparmor/xattrs_profile.c
Normal file
63
tests/regression/apparmor/xattrs_profile.c
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
#include <sys/types.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018 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.
|
||||||
|
*/
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
/*
|
||||||
|
* NAME xattr_profile
|
||||||
|
* DESCRIPTION this test asserts that it's running under a specific apparmor
|
||||||
|
* profile
|
||||||
|
*/
|
||||||
|
int main(int argc, char *argv[])
|
||||||
|
{
|
||||||
|
FILE *fd;
|
||||||
|
ssize_t n;
|
||||||
|
size_t len = 0;
|
||||||
|
char *line;
|
||||||
|
char *token;
|
||||||
|
const char *path = "/proc/self/attr/current";
|
||||||
|
|
||||||
|
if (argc != 2) {
|
||||||
|
fprintf(stderr, "usage: %s apparmor-profile\n", argv[0]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd = fopen(path, "r");
|
||||||
|
if (fd == NULL) {
|
||||||
|
fprintf(stderr, "failed to open %s: %s", path, strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((n = getline(&line, &len, fd)) == -1) {
|
||||||
|
fprintf(stderr, "failed to read %s: %s", path, strerror(errno));
|
||||||
|
fclose(fd);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
fclose(fd);
|
||||||
|
if ((token = strsep(&line, "\n")) != NULL) {
|
||||||
|
line = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get name of profile without "(complain)" or similar suffix
|
||||||
|
if ((token = strsep(&line, " ")) != NULL) {
|
||||||
|
line = token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(line, argv[1])) {
|
||||||
|
printf("FAILED: run as profile %s, expected %s\n",
|
||||||
|
line, argv[1]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("PASS\n");
|
||||||
|
return 0;
|
||||||
|
}
|
159
tests/regression/apparmor/xattrs_profile.sh
Executable file
159
tests/regression/apparmor/xattrs_profile.sh
Executable file
@ -0,0 +1,159 @@
|
|||||||
|
#! /bin/bash
|
||||||
|
# Copyright (C) 2018 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 xattrs_profile
|
||||||
|
#=DESCRIPTION
|
||||||
|
# This test verifies that profiles using xattr matching match correctly.
|
||||||
|
#=END
|
||||||
|
|
||||||
|
pwd=`dirname $0`
|
||||||
|
pwd=`cd $pwd ; /bin/pwd`
|
||||||
|
|
||||||
|
bin=$pwd
|
||||||
|
|
||||||
|
. $bin/prologue.inc
|
||||||
|
|
||||||
|
file="$bin/xattrs_profile"
|
||||||
|
|
||||||
|
requires_kernel_features domain/attach_conditions/xattr
|
||||||
|
|
||||||
|
|
||||||
|
# Clean up existing xattrs
|
||||||
|
clean_xattr()
|
||||||
|
{
|
||||||
|
setfattr --remove=user.foo $file 2> /dev/null || true
|
||||||
|
setfattr --remove=user.bar $file 2> /dev/null || true
|
||||||
|
setfattr --remove=user.spam $file 2> /dev/null || true
|
||||||
|
}
|
||||||
|
|
||||||
|
set_xattr()
|
||||||
|
{
|
||||||
|
setfattr --name="$1" --value="$2" $file
|
||||||
|
}
|
||||||
|
|
||||||
|
clean_xattr
|
||||||
|
|
||||||
|
# Test basic basic xattr
|
||||||
|
|
||||||
|
genprofile "image=profile_1" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$file" \
|
||||||
|
"/proc/*/attr/current:r" \
|
||||||
|
"xattr:user.foo:hello" \
|
||||||
|
"xattr:user.bar:bye" \
|
||||||
|
--nowarn
|
||||||
|
|
||||||
|
runchecktest "Path with no xattrs" pass unconfined
|
||||||
|
set_xattr "user.foo" "hello"
|
||||||
|
runchecktest "Path only matching one xattr" pass unconfined
|
||||||
|
set_xattr "user.bar" "hello"
|
||||||
|
runchecktest "Path not matching xattr value" pass unconfined
|
||||||
|
set_xattr "user.bar" "bye"
|
||||||
|
runchecktest "Path matching xattrs value" pass profile_1
|
||||||
|
set_xattr "user.spam" "hello"
|
||||||
|
runchecktest "Path matching xattrs value with additional xattr" pass profile_1
|
||||||
|
|
||||||
|
clean_xattr
|
||||||
|
|
||||||
|
# Test basic xattrs with wildcards
|
||||||
|
|
||||||
|
genprofile "image=profile_1" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$bin/xattrs_profile" \
|
||||||
|
"/proc/*/attr/current:r" \
|
||||||
|
"xattr:user.foo:hello/*" \
|
||||||
|
"xattr:user.bar:*"
|
||||||
|
|
||||||
|
runchecktest "Path with no xattrs" pass unconfined
|
||||||
|
set_xattr "user.foo" "hello"
|
||||||
|
runchecktest "Path not matching xattr regexs" pass unconfined
|
||||||
|
set_xattr "user.bar" "hello"
|
||||||
|
runchecktest "Path matching one xattr regex" pass unconfined
|
||||||
|
set_xattr "user.foo" "hello/foo"
|
||||||
|
runchecktest "Path matching xattrs regex" pass profile_1
|
||||||
|
set_xattr "user.spam" "bye"
|
||||||
|
runchecktest "Path matching xattrs regex with additional xattr" pass profile_1
|
||||||
|
|
||||||
|
clean_xattr
|
||||||
|
|
||||||
|
# Test that longer paths have higher priority than xattrs
|
||||||
|
|
||||||
|
genprofile "image=profile_1" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$bin/*" \
|
||||||
|
"/proc/*/attr/current:r" \
|
||||||
|
"xattr:user.foo:hello" \
|
||||||
|
-- \
|
||||||
|
"image=profile_2" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$bin/xattrs_profile" \
|
||||||
|
"/proc/*/attr/current:r"
|
||||||
|
|
||||||
|
runchecktest "Path with no xattrs" pass profile_2
|
||||||
|
set_xattr "user.foo" "hello"
|
||||||
|
runchecktest "Path more specific than xattr profile" pass profile_2
|
||||||
|
|
||||||
|
clean_xattr
|
||||||
|
|
||||||
|
# Test that longer paths with xattrs have higher priority than shorter paths
|
||||||
|
|
||||||
|
genprofile "image=profile_1" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$file" \
|
||||||
|
"/proc/*/attr/current:r" \
|
||||||
|
"xattr:user.foo:hello" \
|
||||||
|
-- \
|
||||||
|
"image=profile_2" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$bin/xattrs_*" \
|
||||||
|
"/proc/*/attr/current:r"
|
||||||
|
|
||||||
|
runchecktest "Path with no xattrs" pass profile_2
|
||||||
|
set_xattr "user.foo" "hello"
|
||||||
|
runchecktest "Path with xattrs longer" pass profile_1
|
||||||
|
|
||||||
|
clean_xattr
|
||||||
|
|
||||||
|
# Test that xattrs break path length ties
|
||||||
|
|
||||||
|
genprofile "image=profile_1" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$file" \
|
||||||
|
"/proc/*/attr/current:r" \
|
||||||
|
"xattr:user.foo:hello" \
|
||||||
|
-- \
|
||||||
|
"image=profile_2" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$file" \
|
||||||
|
"/proc/*/attr/current:r"
|
||||||
|
|
||||||
|
runchecktest "Path with no xattrs" pass profile_2
|
||||||
|
set_xattr "user.foo" "hello"
|
||||||
|
runchecktest "Profiles with xattrs and same path length" pass profile_1
|
||||||
|
|
||||||
|
clean_xattr
|
||||||
|
|
||||||
|
# xattr matching doesn't work if the xattr value has a null character. This
|
||||||
|
# impacts matching security.ima and security.evm values.
|
||||||
|
#
|
||||||
|
# A kernel patch has been proposed to fix this:
|
||||||
|
# https://lists.ubuntu.com/archives/apparmor/2018-December/011882.html
|
||||||
|
|
||||||
|
genprofile "image=profile_1" \
|
||||||
|
"addimage:$file" \
|
||||||
|
"path:$file" \
|
||||||
|
"/proc/*/attr/current:r" \
|
||||||
|
"xattr:user.foo:**" \
|
||||||
|
|
||||||
|
runchecktest "Path with no xattrs" pass unconfined
|
||||||
|
set_xattr "user.foo" "ab"
|
||||||
|
runchecktest "matches value" pass profile_1
|
||||||
|
set_xattr "user.foo" "0x610062" # "a\0b"
|
||||||
|
runchecktest "xattr values with null characters don't work" pass unconfined
|
||||||
|
|
||||||
|
clean_xattr
|
@ -45,7 +45,7 @@ RE_PROFILE_CONDITIONAL_VARIABLE = re.compile('^\s*if\s+(not\s+)?defined\s+(@\{?\
|
|||||||
RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$')
|
RE_PROFILE_CONDITIONAL_BOOLEAN = re.compile('^\s*if\s+(not\s+)?defined\s+(\$\{?\w+\}?)\s*\{\s*(#.*)?$')
|
||||||
RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + 'network(?P<details>\s+.*)?' + RE_COMMA_EOL)
|
RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + 'network(?P<details>\s+.*)?' + RE_COMMA_EOL)
|
||||||
RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)' + RE_COMMA_EOL)
|
RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)' + RE_COMMA_EOL)
|
||||||
RE_PROFILE_HAT_DEF = re.compile('^(?P<leadingspace>\s*)(?P<hat_keyword>\^|hat\s+)(?P<hat>\"??[^)]+?\"??)' + RE_XATTRS + RE_FLAGS + '\s*\{' + RE_EOL)
|
RE_PROFILE_HAT_DEF = re.compile('^(?P<leadingspace>\s*)(?P<hat_keyword>\^|hat\s+)(?P<hat>\"??[^)]+?\"??)' + RE_FLAGS + '\s*\{' + RE_EOL)
|
||||||
RE_PROFILE_DBUS = re.compile(RE_AUDIT_DENY + '(dbus\s*,|dbus(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
RE_PROFILE_DBUS = re.compile(RE_AUDIT_DENY + '(dbus\s*,|dbus(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
||||||
RE_PROFILE_MOUNT = re.compile(RE_AUDIT_DENY + '((mount|remount|umount|unmount)(\s+[^#]*)?\s*,)' + RE_EOL)
|
RE_PROFILE_MOUNT = re.compile(RE_AUDIT_DENY + '((mount|remount|umount|unmount)(\s+[^#]*)?\s*,)' + RE_EOL)
|
||||||
RE_PROFILE_SIGNAL = re.compile(RE_AUDIT_DENY + '(signal\s*,|signal(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
RE_PROFILE_SIGNAL = re.compile(RE_AUDIT_DENY + '(signal\s*,|signal(?P<details>\s+[^#]*)\s*,)' + RE_EOL)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user