mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-31 14:25:52 +00:00
Compare commits
84 Commits
apparmor-3
...
v2.9.2
Author | SHA1 | Date | |
---|---|---|---|
|
b37bd8a1aa | ||
|
ead71a306a | ||
|
aa45be1c10 | ||
|
c1c5192532 | ||
|
2b9260f27a | ||
|
4063647a5f | ||
|
b4048cf3de | ||
|
8a475341e8 | ||
|
5ca6986b43 | ||
|
4b58cf3bc4 | ||
|
a373b4ee93 | ||
|
8d5569f20b | ||
|
5390777e45 | ||
|
79240e7ddd | ||
|
494daee246 | ||
|
194cbfa94c | ||
|
9452e1e2af | ||
|
1556f782e3 | ||
|
7d1ff607fe | ||
|
242ece320a | ||
|
dc1d8e5253 | ||
|
0ac23ee34a | ||
|
5bc15cda41 | ||
|
9ebb1913bd | ||
|
720f6624e6 | ||
|
387de4458f | ||
|
38a69f5ebc | ||
|
7d84c61b6c | ||
|
f836ebd42b | ||
|
52b6aeb04c | ||
|
475a9bc691 | ||
|
0f7bf53afb | ||
|
8dcd54e365 | ||
|
097eb4258f | ||
|
9bc15eb6b8 | ||
|
9d6f7f53cb | ||
|
c1ae887576 | ||
|
0ec6ce96d2 | ||
|
8c19eb5521 | ||
|
21a41deabe | ||
|
576e8fe33b | ||
|
3c928c04e1 | ||
|
37b872b155 | ||
|
5ab8b7a483 | ||
|
b813f4ba53 | ||
|
05ab11fec4 | ||
|
2d7ba0871f | ||
|
c98b26069a | ||
|
70dc81c4fd | ||
|
1b68baf7a3 | ||
|
6af7faa2b7 | ||
|
a1529a16bd | ||
|
321a2c1dcb | ||
|
735ef5d32b | ||
|
9428498d90 | ||
|
3ea1e541c7 | ||
|
29b0634f34 | ||
|
586222c94e | ||
|
232b51504c | ||
|
df099620dd | ||
|
22d647ecb1 | ||
|
07b0886796 | ||
|
9da31bf281 | ||
|
c5ff27a91b | ||
|
cf4afcb860 | ||
|
75a186fa9f | ||
|
05bef291d7 | ||
|
76f71f7d84 | ||
|
34f2c1c6ea | ||
|
67dae2f1cf | ||
|
bbaaa00249 | ||
|
9ed8789918 | ||
|
f45628d749 | ||
|
602decfbfc | ||
|
9aa1efd744 | ||
|
c51a68eaaf | ||
|
49b739b184 | ||
|
53d071adf5 | ||
|
70cda06789 | ||
|
e8ffc1c4e8 | ||
|
09c93be47c | ||
|
ac8d886645 | ||
|
ec1dda24d0 | ||
|
e7e9053598 |
2
Makefile
2
Makefile
@@ -14,7 +14,7 @@ DIRS=parser \
|
||||
|
||||
#REPO_URL?=lp:apparmor
|
||||
# --per-file-timestamps is failing over SSH, https://bugs.launchpad.net/bzr/+bug/1257078
|
||||
REPO_URL?=https://code.launchpad.net/~apparmor-dev/apparmor/master
|
||||
REPO_URL?=https://code.launchpad.net/~apparmor-dev/apparmor/2.9
|
||||
# alternate possibilities to export from
|
||||
#REPO_URL=.
|
||||
#REPO_URL="bzr+ssh://bazaar.launchpad.net/~sbeattie/+junk/apparmor-dev/"
|
||||
|
@@ -1 +1 @@
|
||||
2.9.1
|
||||
2.9.2
|
||||
|
@@ -148,11 +148,14 @@ ostream &af_rule::dump_peer(ostream &os)
|
||||
|
||||
ostream &af_rule::dump(ostream &os)
|
||||
{
|
||||
os << dump_prefix(os);
|
||||
dump_prefix(os);
|
||||
os << af_name;
|
||||
os << dump_local(os);
|
||||
if (has_peer_conds())
|
||||
os << " peer=(" << dump_peer(os) << ")";
|
||||
dump_local(os);
|
||||
if (has_peer_conds()) {
|
||||
os << " peer=(";
|
||||
dump_peer(os);
|
||||
os << ")";
|
||||
}
|
||||
os << ",\n";
|
||||
|
||||
return os;
|
||||
|
@@ -61,7 +61,7 @@ B<SUBPROFILE> = [ I<COMMENT> ... ] ( I<PROGRAMHAT> | 'profile ' I<PROGRAMCHILD>
|
||||
B<CAPABILITY> = (lowercase capability name without 'CAP_' prefix; see
|
||||
capabilities(7))
|
||||
|
||||
B<NETWORK RULE> = 'network' [ [ I<DOMAIN> ] [ I<TYPE> ] [ I<PROTOCOL> ] ] ','
|
||||
B<NETWORK RULE> = 'network' [ [ I<DOMAIN> [ I<TYPE> | I<PROTOCOL> ] ] | [ I<PROTOCOL> ] ] ','
|
||||
|
||||
B<DOMAIN> = ( 'inet' | 'ax25' | 'ipx' | 'appletalk' | 'netrom' | 'bridge' | 'atmpvc' | 'x25' | 'inet6' | 'rose' | 'netbeui' | 'security' | 'key' | 'packet' | 'ash' | 'econet' | 'atmsvc' | 'sna' | 'irda' | 'pppox' | 'wanpipe' | 'bluetooth' | 'netlink' ) ','
|
||||
|
||||
@@ -1192,10 +1192,6 @@ files, and the X socket.
|
||||
|
||||
=back
|
||||
|
||||
The abstractions stored in F</etc/apparmor.d/program-chunks/> are
|
||||
intended for use by specific program suites, and are not generally
|
||||
useful.
|
||||
|
||||
Some of the abstractions rely on variables that are set in files in the
|
||||
F</etc/apparmor.d/tunables/> directory. These variables are currently
|
||||
B<@{HOME}> and B<@{HOMEDIRS}>. Variables cannot be set in profile scope;
|
||||
|
@@ -149,7 +149,7 @@ ostream &dbus_rule::dump(ostream &os)
|
||||
if (interface)
|
||||
os << " interface=\"" << interface << "\"";
|
||||
if (member)
|
||||
os << " member=\"" << member << os << "\"";
|
||||
os << " member=\"" << member << "\"";
|
||||
|
||||
if (!(mode & AA_DBUS_BIND) && (peer_label || name)) {
|
||||
os << " peer=( ";
|
||||
|
20
parser/lib.c
20
parser/lib.c
@@ -62,9 +62,9 @@
|
||||
int dirat_for_each(DIR *dir, const char *name, void *data,
|
||||
int (* cb)(DIR *, const char *, struct stat *, void *))
|
||||
{
|
||||
struct dirent *dirent = NULL, *ent;
|
||||
struct dirent *dirent = NULL;
|
||||
DIR *d = NULL;
|
||||
int error = 0;
|
||||
int error;
|
||||
|
||||
if (!cb || (!dir && !name)) {
|
||||
errno = EINVAL;
|
||||
@@ -102,11 +102,19 @@ int dirat_for_each(DIR *dir, const char *name, void *data,
|
||||
d = dir;
|
||||
}
|
||||
|
||||
for (error = readdir_r(d, dirent, &ent);
|
||||
error == 0 && ent != NULL;
|
||||
error = readdir_r(d, dirent, &ent)) {
|
||||
for (;;) {
|
||||
struct dirent *ent;
|
||||
struct stat my_stat;
|
||||
|
||||
error = readdir_r(d, dirent, &ent);
|
||||
if (error) {
|
||||
PDEBUG("readdir_r failed");
|
||||
errno = error; /* readdir_r directly returns an errno */
|
||||
goto fail;
|
||||
} else if (!ent) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (strcmp(ent->d_name, ".") == 0 ||
|
||||
strcmp(ent->d_name, "..") == 0)
|
||||
continue;
|
||||
@@ -126,7 +134,7 @@ int dirat_for_each(DIR *dir, const char *name, void *data,
|
||||
closedir(d);
|
||||
free(dirent);
|
||||
|
||||
return error;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
error = errno;
|
||||
|
@@ -1335,19 +1335,16 @@ int accept_perms(NodeSet *state, perms_t &perms)
|
||||
}
|
||||
|
||||
perms.allow |= exact_match_allow & ~(ALL_AA_EXEC_TYPE);
|
||||
|
||||
if (exact_match_allow & AA_USER_EXEC_TYPE) {
|
||||
perms.audit |= exact_audit & ~(ALL_AA_EXEC_TYPE);
|
||||
|
||||
if (exact_match_allow & AA_USER_EXEC) {
|
||||
perms.allow = (exact_match_allow & AA_USER_EXEC_TYPE) |
|
||||
(perms.allow & ~AA_USER_EXEC_TYPE);
|
||||
perms.audit = (exact_audit & AA_USER_EXEC_TYPE) |
|
||||
(perms.audit & ~AA_USER_EXEC_TYPE);
|
||||
perms.exact = AA_USER_EXEC_TYPE;
|
||||
}
|
||||
if (exact_match_allow & AA_OTHER_EXEC_TYPE) {
|
||||
if (exact_match_allow & AA_OTHER_EXEC) {
|
||||
perms.allow = (exact_match_allow & AA_OTHER_EXEC_TYPE) |
|
||||
(perms.allow & ~AA_OTHER_EXEC_TYPE);
|
||||
perms.audit = (exact_audit & AA_OTHER_EXEC_TYPE) |
|
||||
(perms.audit & ~AA_OTHER_EXEC_TYPE);
|
||||
perms.exact |= AA_OTHER_EXEC_TYPE;
|
||||
}
|
||||
if (AA_USER_EXEC & perms.deny)
|
||||
|
@@ -321,31 +321,19 @@ struct aa_network_entry *network_entry(const char *family, const char *type,
|
||||
|
||||
#define ALL_TYPES 0x43e
|
||||
|
||||
/* another case of C++ not supporting non-trivial designated initializers */
|
||||
#undef AA_GEN_NET_ENT
|
||||
#define AA_GEN_NET_ENT(name, AF) name, /* [AF] = name, */
|
||||
|
||||
static const char *network_families[] = {
|
||||
#include "af_names.h"
|
||||
};
|
||||
|
||||
int net_find_af_val(const char *af)
|
||||
{
|
||||
int i;
|
||||
for (i = 0; network_families[i]; i++) {
|
||||
if (strcmp(network_families[i], af) == 0)
|
||||
return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
const char *net_find_af_name(unsigned int af)
|
||||
{
|
||||
size_t i;
|
||||
|
||||
if (af < 0 || af > get_af_max())
|
||||
return NULL;
|
||||
|
||||
return network_families[af];
|
||||
for (i = 0; i < sizeof(network_mappings) / sizeof(*network_mappings); i++) {
|
||||
if (network_mappings[i].family == af)
|
||||
return network_mappings[i].family_name;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void __debug_network(unsigned int *array, const char *name)
|
||||
@@ -375,7 +363,7 @@ void __debug_network(unsigned int *array, const char *name)
|
||||
|
||||
for (i = 0; i < af_max; i++) {
|
||||
if (array[i]) {
|
||||
const char *fam = network_families[i];
|
||||
const char *fam = net_find_af_name(i);
|
||||
if (fam)
|
||||
printf("%s ", fam);
|
||||
else
|
||||
|
@@ -125,7 +125,6 @@ struct network {
|
||||
|
||||
int net_find_type_val(const char *type);
|
||||
const char *net_find_type_name(int type);
|
||||
int net_find_af_val(const char *af);
|
||||
const char *net_find_af_name(unsigned int af);
|
||||
const struct network_tuple *net_find_mapping(const struct network_tuple *map,
|
||||
const char *family,
|
||||
|
@@ -172,7 +172,7 @@ extern int preprocess_only;
|
||||
|
||||
|
||||
#ifdef DEBUG
|
||||
#define PDEBUG(fmt, args...) printf("parser: " fmt, ## args)
|
||||
#define PDEBUG(fmt, args...) fprintf(stderr, "parser: " fmt, ## args)
|
||||
#else
|
||||
#define PDEBUG(fmt, args...) /* Do nothing */
|
||||
#endif
|
||||
|
@@ -587,7 +587,9 @@ static int features_dir_cb(DIR *dir, const char *name, struct stat *st,
|
||||
if (S_ISREG(st->st_mode)) {
|
||||
int len, file;
|
||||
int remaining = fst->size - (fst->pos - *fst->buffer);
|
||||
if (!(file = openat(dirfd(dir), name, O_RDONLY))) {
|
||||
|
||||
file = openat(dirfd(dir), name, O_RDONLY);
|
||||
if (file == -1) {
|
||||
PDEBUG("Could not open '%s'", name);
|
||||
return -1;
|
||||
}
|
||||
|
@@ -43,7 +43,7 @@
|
||||
/* #define DEBUG */
|
||||
#ifdef DEBUG
|
||||
#undef PDEBUG
|
||||
#define PDEBUG(fmt, args...) printf("Lexer: " fmt, ## args)
|
||||
#define PDEBUG(fmt, args...) fprintf(stderr, "Lexer: " fmt, ## args)
|
||||
#else
|
||||
#undef PDEBUG
|
||||
#define PDEBUG(fmt, args...) /* Do nothing */
|
||||
@@ -534,7 +534,7 @@ static int parse_X_sub_mode(const char *X, const char *str_mode, int *result, in
|
||||
int mode = 0;
|
||||
const char *p;
|
||||
|
||||
PDEBUG("Parsing X mode: %s\n", X, str_mode);
|
||||
PDEBUG("Parsing %s mode: %s\n", X, str_mode);
|
||||
|
||||
if (!str_mode)
|
||||
return 0;
|
||||
|
@@ -34,8 +34,10 @@
|
||||
|
||||
/* #define DEBUG */
|
||||
#ifdef DEBUG
|
||||
#define PDEBUG(fmt, args...) printf("Lexer: " fmt, ## args)
|
||||
#undef PDEBUG
|
||||
#define PDEBUG(fmt, args...) fprintf(stderr, "Lexer: " fmt, ## args)
|
||||
#else
|
||||
#undef PDEBUG
|
||||
#define PDEBUG(fmt, args...) /* Do nothing */
|
||||
#endif
|
||||
#define NPDEBUG(fmt, args...) /* Do nothing */
|
||||
|
@@ -491,9 +491,14 @@ static int process_dfa_entry(aare_rules *dfarules, struct cod_entry *entry)
|
||||
* out by a deny rule, as both pieces of the link pair must
|
||||
* match. audit info for the link is carried on the second
|
||||
* entry of the pair
|
||||
*
|
||||
* So if a deny rule only record it if there are permissions other
|
||||
* than link in the entry.
|
||||
* TODO: split link and change_profile entries earlier
|
||||
*/
|
||||
if (entry->deny && (entry->mode & AA_LINK_BITS)) {
|
||||
if (!dfarules->add_rule(tbuf.c_str(), entry->deny,
|
||||
if (entry->deny) {
|
||||
if ((entry->mode & ~(AA_LINK_BITS | AA_CHANGE_PROFILE)) &&
|
||||
!dfarules->add_rule(tbuf.c_str(), entry->deny,
|
||||
entry->mode & ~AA_LINK_BITS,
|
||||
entry->audit & ~AA_LINK_BITS, dfaflags))
|
||||
return FALSE;
|
||||
|
@@ -9,6 +9,8 @@ PROVE_ARG=-f
|
||||
ifeq ($(VERBOSE),1)
|
||||
PROVE_ARG+=-v
|
||||
PYTEST_ARG = -v
|
||||
else
|
||||
VERBOSE=
|
||||
endif
|
||||
|
||||
all: tests
|
||||
|
@@ -22,37 +22,51 @@
|
||||
|
||||
set -o pipefail
|
||||
|
||||
APPARMOR_PARSER="${APPARMOR_PARSER:-../apparmor_parser}"
|
||||
_SCRIPTDIR=$(dirname "${BASH_SOURCE[0]}" )
|
||||
|
||||
APPARMOR_PARSER="${APPARMOR_PARSER:-${_SCRIPTDIR}/../apparmor_parser}"
|
||||
fails=0
|
||||
errors=0
|
||||
verbose="${VERBOSE:-}"
|
||||
|
||||
hash_binary_policy()
|
||||
{
|
||||
printf %s "$1" | ${APPARMOR_PARSER} -qS 2>/dev/null| md5sum | cut -d ' ' -f 1
|
||||
printf %s "$1" | ${APPARMOR_PARSER} --features-file ${_SCRIPTDIR}/features_files/features.all -qS 2>/dev/null| md5sum | cut -d ' ' -f 1
|
||||
return $?
|
||||
}
|
||||
|
||||
# verify_binary_equality - compares the binary policy of multiple profiles
|
||||
# $1: A short description of the test
|
||||
# $2: The known-good profile
|
||||
# $3..$n: The profiles to compare against $2
|
||||
# verify_binary - compares the binary policy of multiple profiles
|
||||
# $1: Test type (equality or inequality)
|
||||
# $2: A short description of the test
|
||||
# $3: The known-good profile
|
||||
# $4..$n: The profiles to compare against $3
|
||||
#
|
||||
# Upon failure/error, prints out the test description and profiles that failed
|
||||
# and increments $fails or $errors for each failure and error, respectively
|
||||
verify_binary_equality()
|
||||
verify_binary()
|
||||
{
|
||||
local desc=$1
|
||||
local good_profile=$2
|
||||
local t=$1
|
||||
local desc=$2
|
||||
local good_profile=$3
|
||||
local good_hash
|
||||
local ret=0
|
||||
|
||||
shift
|
||||
shift
|
||||
shift
|
||||
|
||||
printf "Binary equality %s" "$desc"
|
||||
if [ "$t" != "equality" ] && [ "$t" != "inequality" ]
|
||||
then
|
||||
printf "\nERROR: Unknown test mode:\n%s\n\n" "$t" 1>&2
|
||||
((errors++))
|
||||
return $((ret + 1))
|
||||
fi
|
||||
|
||||
if [ -n "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
||||
good_hash=$(hash_binary_policy "$good_profile")
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
||||
printf "\nERROR: Error hashing the following \"known-good\" profile:\n%s\n\n" \
|
||||
"$good_profile" 1>&2
|
||||
((errors++))
|
||||
@@ -64,28 +78,54 @@ verify_binary_equality()
|
||||
hash=$(hash_binary_policy "$profile")
|
||||
if [ $? -ne 0 ]
|
||||
then
|
||||
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
||||
printf "\nERROR: Error hashing the following profile:\n%s\n\n" \
|
||||
"$profile" 1>&2
|
||||
((errors++))
|
||||
((ret++))
|
||||
elif [ "$hash" != "$good_hash" ]
|
||||
elif [ "$t" == "equality" ] && [ "$hash" != "$good_hash" ]
|
||||
then
|
||||
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
||||
printf "\nFAIL: Hash values do not match\n" 2>&1
|
||||
printf "known-good (%s) != profile-under-test (%s) for the following profile:\n%s\n\n" \
|
||||
"$good_hash" "$hash" "$profile" 1>&2
|
||||
((fails++))
|
||||
((ret++))
|
||||
elif [ "$t" == "inequality" ] && [ "$hash" == "$good_hash" ]
|
||||
then
|
||||
if [ -z "$verbose" ] ; then printf "Binary %s %s" "$t" "$desc" ; fi
|
||||
printf "\nFAIL: Hash values match\n" 2>&1
|
||||
printf "known-good (%s) == profile-under-test (%s) for the following profile:\n%s\n\n" \
|
||||
"$good_hash" "$hash" "$profile" 1>&2
|
||||
((fails++))
|
||||
((ret++))
|
||||
fi
|
||||
done
|
||||
|
||||
if [ $ret -eq 0 ]
|
||||
then
|
||||
printf " ok\n"
|
||||
fi
|
||||
if [ -z "$verbose" ] ; then
|
||||
printf "."
|
||||
else
|
||||
printf " ok\n"
|
||||
|
||||
fi
|
||||
fi
|
||||
return $ret
|
||||
}
|
||||
|
||||
verify_binary_equality()
|
||||
{
|
||||
verify_binary "equality" "$@"
|
||||
}
|
||||
|
||||
verify_binary_inequality()
|
||||
{
|
||||
verify_binary "inequality" "$@"
|
||||
}
|
||||
|
||||
printf "Equality Tests:\n"
|
||||
|
||||
verify_binary_equality "dbus send" \
|
||||
"/t { dbus send, }" \
|
||||
"/t { dbus write, }" \
|
||||
@@ -225,11 +265,205 @@ verify_binary_equality "dbus minimization found in dbus abstractions" \
|
||||
peer=(name=org.freedesktop.DBus),
|
||||
dbus send bus=session, }"
|
||||
|
||||
# Rules compatible with audit, deny, and audit deny
|
||||
# note: change_profile does not support audit/allow/deny atm
|
||||
for rule in "capability" "capability mac_admin" \
|
||||
"network" "network tcp" "network inet6 tcp"\
|
||||
"mount" "mount /a" "mount /a -> /b" "mount options in (ro) /a -> b" \
|
||||
"remount" "remount /a" \
|
||||
"umount" "umount /a" \
|
||||
"pivot_root" "pivot_root /a" "pivot_root oldroot=/" \
|
||||
"pivot_root oldroot=/ /a" "pivot_root oldroot=/ /a -> foo" \
|
||||
"ptrace" "ptrace trace" "ptrace (readby,tracedby) peer=unconfined" \
|
||||
"signal" "signal (send,receive)" "signal peer=unconfined" \
|
||||
"signal receive set=(kill)" \
|
||||
"dbus" "dbus send" "dbus bus=system" "dbus bind name=foo" \
|
||||
"dbus peer=(label=foo)" "dbus eavesdrop" \
|
||||
"unix" "unix (create, listen, accept)" "unix addr=@*" "unix addr=none" \
|
||||
"unix peer=(label=foo)" \
|
||||
"/f r" "/f w" "/f rwmlk" "/** r" "/**/ w" \
|
||||
"file /f r" "file /f w" "file /f rwmlk" \
|
||||
"link /a -> /b" "link subset /a -> /b" \
|
||||
"l /a -> /b" "l subset /a -> /b" \
|
||||
"file l /a -> /b" "l subset /a -> /b"
|
||||
do
|
||||
verify_binary_equality "allow modifier for \"${rule}\"" \
|
||||
"/t { ${rule}, }" \
|
||||
"/t { allow ${rule}, }"
|
||||
|
||||
verify_binary_equality "audit allow modifier for \"${rule}\"" \
|
||||
"/t { audit ${rule}, }" \
|
||||
"/t { audit allow ${rule}, }"
|
||||
|
||||
verify_binary_inequality "audit, deny, and audit deny modifiers for \"${rule}\"" \
|
||||
"/t { ${rule}, }" \
|
||||
"/t { audit ${rule}, }" \
|
||||
"/t { audit allow ${rule}, }" \
|
||||
"/t { deny ${rule}, }" \
|
||||
"/t { audit deny ${rule}, }"
|
||||
|
||||
verify_binary_inequality "audit vs deny and audit deny modifiers for \"${rule}\"" \
|
||||
"/t { audit ${rule}, }" \
|
||||
"/t { deny ${rule}, }" \
|
||||
"/t { audit deny ${rule}, }"
|
||||
|
||||
verify_binary_inequality "deny and audit deny modifiers for \"${rule}\"" \
|
||||
"/t { deny ${rule}, }" \
|
||||
"/t { audit deny ${rule}, }"
|
||||
done
|
||||
|
||||
# Rules that need special treatment for the deny modifier
|
||||
for rule in "/f ux" "/f Ux" "/f px" "/f Px" "/f cx" "/f Cx" "/f ix" \
|
||||
"/f pux" "/f Pux" "/f pix" "/f Pix" \
|
||||
"/f cux" "/f Cux" "/f cix" "/f Cix" \
|
||||
"/* ux" "/* Ux" "/* px" "/* Px" "/* cx" "/* Cx" "/* ix" \
|
||||
"/* pux" "/* Pux" "/* pix" "/* Pix" \
|
||||
"/* cux" "/* Cux" "/* cix" "/* Cix" \
|
||||
"/f px -> b " "/f Px -> b" "/f cx -> b" "/f Cx -> b" \
|
||||
"/f pux -> b" "/f Pux -> b" "/f pix -> b" "/f Pix -> b" \
|
||||
"/f cux -> b" "/f Cux -> b" "/f cix -> b" "/f Cix -> b" \
|
||||
"/* px -> b" "/* Px -> b" "/* cx -> b" "/* Cx -> b" \
|
||||
"/* pux -> b" "/* Pux -> b" "/* pix -> b" "/* Pix -> b" \
|
||||
"/* cux -> b" "/* Cux -> b" "/* cix -> b" "/* Cix -> b" \
|
||||
"file /f ux" "file /f Ux" "file /f px" "file /f Px" \
|
||||
"file /f cx" "file /f Cx" "file /f ix" \
|
||||
"file /f pux" "file /f Pux" "file /f pix" "file /f Pix" \
|
||||
"/f cux" "/f Cux" "/f cix" "/f Cix" \
|
||||
"file /* ux" "file /* Ux" "file /* px" "file /* Px" \
|
||||
"file /* cx" "file /* Cx" "file /* ix" \
|
||||
"file /* pux" "file /* Pux" "file /* pix" "file /* Pix" \
|
||||
"file /* cux" "file /* Cux" "file /* cix" "file /* Cix" \
|
||||
"file /f px -> b " "file /f Px -> b" "file /f cx -> b" "file /f Cx -> b" \
|
||||
"file /f pux -> b" "file /f Pux -> b" "file /f pix -> b" "file /f Pix -> b" \
|
||||
"file /f cux -> b" "file /f Cux -> b" "file /f cix -> b" "file /f Cix -> b" \
|
||||
"file /* px -> b" "file /* Px -> b" "file /* cx -> b" "file /* Cx -> b" \
|
||||
"file /* pux -> b" "file /* Pux -> b" "file /* pix -> b" "file /* Pix -> b" \
|
||||
"file /* cux -> b" "file /* Cux -> b" "file /* cix -> b" "file /* Cix -> b"
|
||||
|
||||
do
|
||||
verify_binary_equality "allow modifier for \"${rule}\"" \
|
||||
"/t { ${rule}, }" \
|
||||
"/t { allow ${rule}, }"
|
||||
|
||||
verify_binary_equality "audit allow modifier for \"${rule}\"" \
|
||||
"/t { audit ${rule}, }" \
|
||||
"/t { audit allow ${rule}, }"
|
||||
|
||||
# skip rules that don't end with x perm
|
||||
if [ -n "${rule##*x}" ] ; then continue ; fi
|
||||
|
||||
verify_binary_inequality "deny, audit deny modifier for \"${rule}\"" \
|
||||
"/t { ${rule}, }" \
|
||||
"/t { audit ${rule}, }" \
|
||||
"/t { audit allow ${rule}, }" \
|
||||
"/t { deny ${rule% *} x, }" \
|
||||
"/t { audit deny ${rule% *} x, }"
|
||||
|
||||
verify_binary_inequality "audit vs deny and audit deny modifiers for \"${rule}\"" \
|
||||
"/t { audit ${rule}, }" \
|
||||
"/t { deny ${rule% *} x, }" \
|
||||
"/t { audit deny ${rule% *} x, }"
|
||||
|
||||
done
|
||||
|
||||
# verify deny and audit deny differ for x perms
|
||||
for prefix in "/f" "/*" "file /f" "file /*" ; do
|
||||
verify_binary_inequality "deny and audit deny x modifiers for \"${prefix}\"" \
|
||||
"/t { deny ${prefix} x, }" \
|
||||
"/t { audit deny ${prefix} x, }"
|
||||
done
|
||||
|
||||
#Test equality of leading and trailing file permissions
|
||||
for audit in "" "audit" ; do
|
||||
for allow in "" "allow" "deny" ; do
|
||||
for owner in "" "owner" ; do
|
||||
for f in "" "file" ; do
|
||||
prefix="$audit $allow $owner $f"
|
||||
for perm in "r" "w" "a" "l" "k" "m" "rw" "ra" \
|
||||
"rl" "rk" "rm" "wl" "wk" "wm" \
|
||||
"rwl" "rwk" "rwm" "ral" "rak" \
|
||||
"ram" "rlk" "rlm" "rkm" "wlk" \
|
||||
"wlm" "wkm" "alk" "alm" "akm" \
|
||||
"lkm" "rwlk" "rwlm" "rwkm" \
|
||||
"ralk" "ralm" "wlkm" "alkm" \
|
||||
"rwlkm" "ralkm" ; do
|
||||
verify_binary_equality "leading and trailing perms for \"${perm}\"" \
|
||||
"/t { ${prefix} /f ${perm}, }" \
|
||||
"/t { ${prefix} ${perm} /f, }"
|
||||
done
|
||||
if [ "$allow" == "deny" ] ; then continue ; fi
|
||||
for perm in "ux" "Ux" "px" "Px" "cx" "Cx" \
|
||||
"ix" "pux" "Pux" "pix" "Pix" \
|
||||
"cux" "Cux" "cix" "Cix"
|
||||
do
|
||||
verify_binary_equality "leading and trailing perms for \"${perm}\"" \
|
||||
"/t { ${prefix} /f ${perm}, }" \
|
||||
"/t { ${prefix} ${perm} /f, }"
|
||||
done
|
||||
for perm in "px" "Px" "cx" "Cx" \
|
||||
"pux" "Pux" "pix" "Pix" \
|
||||
"cux" "Cux" "cix" "Cix"
|
||||
do
|
||||
verify_binary_equality "leading and trailing perms for x-transition \"${perm}\"" \
|
||||
"/t { ${prefix} /f ${perm} -> b, }" \
|
||||
"/t { ${prefix} ${perm} /f -> b, }"
|
||||
done
|
||||
done
|
||||
done
|
||||
done
|
||||
done
|
||||
|
||||
#Test rule overlap for x most specific match
|
||||
for perm1 in "ux" "Ux" "px" "Px" "cx" "Cx" "ix" "pux" "Pux" \
|
||||
"pix" "Pix" "cux" "Cux" "cix" "Cix" "px -> b" \
|
||||
"Px -> b" "cx -> b" "Cx -> b" "pux -> b" "Pux ->b" \
|
||||
"pix -> b" "Pix -> b" "cux -> b" "Cux -> b" \
|
||||
"cix -> b" "Cix -> b"
|
||||
do
|
||||
for perm2 in "ux" "Ux" "px" "Px" "cx" "Cx" "ix" "pux" "Pux" \
|
||||
"pix" "Pix" "cux" "Cux" "cix" "Cix" "px -> b" \
|
||||
"Px -> b" "cx -> b" "Cx -> b" "pux -> b" "Pux ->b" \
|
||||
"pix -> b" "Pix -> b" "cux -> b" "Cux -> b" \
|
||||
"cix -> b" "Cix -> b"
|
||||
do
|
||||
if [ "$perm1" == "$perm2" ] ; then
|
||||
verify_binary_equality "Exec perm \"${perm1}\" - most specific match: same as glob" \
|
||||
"/t { /* ${perm1}, /f ${perm2}, }" \
|
||||
"/t { /* ${perm1}, }"
|
||||
else
|
||||
verify_binary_inequality "Exec \"${perm1}\" vs \"${perm2}\" - most specific match: different from glob" \
|
||||
"/t { /* ${perm1}, /f ${perm2}, }" \
|
||||
"/t { /* ${perm1}, }"
|
||||
fi
|
||||
done
|
||||
verify_binary_inequality "Exec \"${perm1}\" vs deny x - most specific match: different from glob" \
|
||||
"/t { /* ${perm1}, audit deny /f x, }" \
|
||||
"/t { /* ${perm1}, }"
|
||||
|
||||
done
|
||||
|
||||
#Test deny carves out permission
|
||||
verify_binary_inequality "Deny removes r perm" \
|
||||
"/t { /foo/[abc] r, audit deny /foo/b r, }" \
|
||||
"/t { /foo/[abc] r, }"
|
||||
|
||||
verify_binary_equality "Deny removes r perm" \
|
||||
"/t { /foo/[abc] r, audit deny /foo/b r, }" \
|
||||
"/t { /foo/[ac] r, }"
|
||||
|
||||
#this one may not be true in the future depending on if the compiled profile
|
||||
#is explicitly including deny permissions for dynamic composition
|
||||
verify_binary_equality "Deny of ungranted perm" \
|
||||
"/t { /foo/[abc] r, audit deny /foo/b w, }" \
|
||||
"/t { /foo/[abc] r, }"
|
||||
|
||||
|
||||
if [ $fails -ne 0 -o $errors -ne 0 ]
|
||||
then
|
||||
printf "ERRORS: %d\nFAILS: %d\n" $errors $fails 2>&1
|
||||
exit $(($fails + $errors))
|
||||
fi
|
||||
|
||||
[ -z "${verbose}" ] && printf "\n"
|
||||
printf "PASS\n"
|
||||
exit 0
|
||||
|
9
parser/tst/simple_tests/file/ok_audit_deny_link.sd
Normal file
9
parser/tst/simple_tests/file/ok_audit_deny_link.sd
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
#=DESCRIPTION simple link access test
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
|
||||
profile test {
|
||||
audit deny link /alpha/beta -> /tmp/**,
|
||||
}
|
||||
|
9
parser/tst/simple_tests/file/ok_deny_link.sd
Normal file
9
parser/tst/simple_tests/file/ok_deny_link.sd
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
#=DESCRIPTION simple link access test
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
|
||||
profile test {
|
||||
deny link /alpha/beta -> /tmp/**,
|
||||
}
|
||||
|
@@ -42,7 +42,7 @@ class AAParserValgrindTests(testlib.AATestTemplate):
|
||||
self.maxDiff = None
|
||||
|
||||
def _runtest(self, testname, config):
|
||||
parser_args = ['-Q', '-I', config.testdir]
|
||||
parser_args = ['-Q', '-I', config.testdir, '-M', './features_files/features.all']
|
||||
failure_rc = [VALGRIND_ERROR_CODE, testlib.TIMEOUT_ERROR_CODE]
|
||||
command = [config.valgrind]
|
||||
command.extend(VALGRIND_ARGS)
|
||||
|
@@ -20,6 +20,7 @@
|
||||
owner /{,var/}run/gdm{,3}/*/database r,
|
||||
owner /{,var/}run/lightdm/authority/[0-9]* r,
|
||||
owner /{,var/}run/lightdm/*/xauthority r,
|
||||
owner /{,var/}run/user/*/gdm/Xauthority r,
|
||||
|
||||
# the unix socket to use to connect to the display
|
||||
/tmp/.X11-unix/* w,
|
||||
|
@@ -8,4 +8,6 @@
|
||||
/usr/lib/aspell/ r,
|
||||
/usr/lib/aspell/* r,
|
||||
/usr/lib/aspell/*.so m,
|
||||
/usr/share/aspell/ r,
|
||||
/usr/share/aspell/* r,
|
||||
/var/lib/aspell/* r,
|
||||
|
@@ -32,6 +32,7 @@
|
||||
/usr/share/zoneinfo/ r,
|
||||
/usr/share/zoneinfo/** r,
|
||||
/usr/share/X11/locale/** r,
|
||||
/{,var/}run/systemd/journal/dev-log w,
|
||||
|
||||
/usr/lib{,32,64}/locale/** mr,
|
||||
/usr/lib{,32,64}/gconv/*.so mr,
|
||||
|
17
profiles/apparmor.d/abstractions/mir
Normal file
17
profiles/apparmor.d/abstractions/mir
Normal file
@@ -0,0 +1,17 @@
|
||||
# vim:syntax=apparmor
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2015 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.
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
# mir libraries sometimes do not have a lib prefix
|
||||
# see LP: #1422521
|
||||
/usr/lib/@{multiarch}/mir/*.so* mr,
|
||||
/usr/lib/@{multiarch}/mir/**/*.so* mr,
|
||||
|
||||
# unprivileged mir socket for clients
|
@@ -1,6 +1,7 @@
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2002-2005 Novell/SUSE
|
||||
# Copyright (C) 2015 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
|
||||
@@ -14,11 +15,21 @@
|
||||
capability setgid,
|
||||
capability sys_chroot,
|
||||
|
||||
# postfix's master can send us signals
|
||||
signal receive peer=/usr/lib/postfix/master,
|
||||
|
||||
unix (send, receive) peer=(label=/usr/lib/postfix/master),
|
||||
|
||||
/etc/mailname r,
|
||||
/etc/postfix/*.cf r,
|
||||
/etc/postfix/*.db r,
|
||||
@{PROC}/net/if_inet6 r,
|
||||
/usr/lib/postfix/*.so mr,
|
||||
/usr/lib64/sasl2/* mr,
|
||||
/usr/lib64/sasl2/ r,
|
||||
/usr/lib/sasl2/* mr,
|
||||
/usr/lib/sasl2/ r,
|
||||
/usr/lib{,32,64}/sasl2/* mr,
|
||||
/usr/lib{,32,64}/sasl2/ r,
|
||||
/usr/lib/@{multiarch}/sasl2/* mr,
|
||||
/usr/lib/@{multiarch}/sasl2/ r,
|
||||
|
||||
/var/spool/postfix/etc/* r,
|
||||
/var/spool/postfix/lib/lib*.so* mr,
|
||||
/var/spool/postfix/lib/@{multiarch}/lib*.so* mr,
|
||||
|
@@ -12,6 +12,10 @@
|
||||
/etc/ssl/ r,
|
||||
/etc/ssl/certs/ r,
|
||||
/etc/ssl/certs/* r,
|
||||
/etc/pki/trust/ r,
|
||||
/etc/pki/trust/* r,
|
||||
/etc/pki/trust/anchors/ r,
|
||||
/etc/pki/trust/anchors/** r,
|
||||
/usr/share/ca-certificates/ r,
|
||||
/usr/share/ca-certificates/** r,
|
||||
/usr/share/ssl/certs/ca-bundle.crt r,
|
||||
|
@@ -10,6 +10,7 @@
|
||||
/usr/bin/balsa Cx -> sanitized_helper,
|
||||
/usr/bin/claws-mail Cx -> sanitized_helper,
|
||||
/usr/bin/evolution Cx -> sanitized_helper,
|
||||
/usr/bin/geary Cx -> sanitized_helper,
|
||||
/usr/bin/gnome-gmail Cx -> sanitized_helper,
|
||||
/usr/lib/GNUstep/Applications/GNUMail.app/GNUMail Cx -> sanitized_helper,
|
||||
/usr/bin/kmail Cx -> sanitized_helper,
|
||||
|
@@ -59,6 +59,9 @@ profile sanitized_helper {
|
||||
# permissions for /usr/share, but for now just do this. (LP: #972367)
|
||||
/usr/share/software-center/* Pixr,
|
||||
|
||||
# Allow exec of texlive font build scripts (LP: #1010909)
|
||||
/usr/share/texlive/texmf{,-dist}/web2c/{,**/}* Pixr,
|
||||
|
||||
# While the chromium and chrome sandboxes are setuid root, they only link
|
||||
# in limited libraries so glibc's secure execution should be enough to not
|
||||
# require the santized_helper (ie, LD_PRELOAD will only use standard system
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/mysql>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/openssl>
|
||||
#include <abstractions/wutmp>
|
||||
#include <abstractions/dovecot-common>
|
||||
|
||||
|
@@ -26,6 +26,7 @@
|
||||
|
||||
@{HOME} r, # ???
|
||||
/usr/lib/dovecot/imap mr,
|
||||
/{,var/}run/dovecot/auth-master rw,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
#include <local/usr.lib.dovecot.imap>
|
||||
|
@@ -24,6 +24,7 @@
|
||||
network inet6 stream,
|
||||
|
||||
/usr/lib/dovecot/imap-login mr,
|
||||
/{,var/}run/dovecot/anvil rw,
|
||||
/{,var/}run/dovecot/login/ r,
|
||||
/{,var/}run/dovecot/login/* rw,
|
||||
|
||||
|
@@ -45,6 +45,8 @@
|
||||
|
||||
/var/lib/misc/dnsmasq.leases rw, # Required only for DHCP server usage
|
||||
|
||||
/bin/bash ix, # Required to execute --dhcp-script argument
|
||||
|
||||
# access to iface mtu needed for Router Advertisement messages in IPv6
|
||||
# Neighbor Discovery protocol (RFC 2461)
|
||||
@{PROC}/sys/net/ipv6/conf/*/mtu r,
|
||||
@@ -64,9 +66,13 @@
|
||||
/{,var/}run/libvirt/network/*.pid rw,
|
||||
|
||||
# libvirt lease helper
|
||||
/usr/lib/libvirt/libvirt_leaseshelper ix,
|
||||
/usr/lib{,64}/libvirt/libvirt_leaseshelper ix,
|
||||
/{,var/}run/leaseshelper.pid rwk,
|
||||
|
||||
# lxc-net pid and lease files
|
||||
/{,var/}run/lxc/dnsmasq.pid rw,
|
||||
/var/lib/misc/dnsmasq.*.leases rw,
|
||||
|
||||
# NetworkManager integration
|
||||
/{,var/}run/nm-dns-dnsmasq.conf r,
|
||||
/{,var/}run/sendsigs.omit.d/*dnsmasq.pid w,
|
||||
|
@@ -15,6 +15,7 @@
|
||||
/usr/sbin/dovecot {
|
||||
#include <abstractions/authentication>
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/dovecot-common>
|
||||
#include <abstractions/mysql>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/ssl_certs>
|
||||
@@ -25,7 +26,6 @@
|
||||
capability fsetid,
|
||||
capability kill,
|
||||
capability net_bind_service,
|
||||
capability setgid,
|
||||
capability setuid,
|
||||
capability sys_chroot,
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
/etc/lsb-release r,
|
||||
/etc/SuSE-release r,
|
||||
@{PROC}/@{pid}/mounts r,
|
||||
@{PROC}/filesystems r,
|
||||
/usr/bin/doveconf rix,
|
||||
/usr/lib/dovecot/anvil Px,
|
||||
/usr/lib/dovecot/auth Px,
|
||||
|
@@ -1,6 +1,9 @@
|
||||
# Last Modified: Mon Dec 1 22:23:12 2014
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2002-2005 Novell/SUSE
|
||||
# Copyright (C) 2014 Christian Boltz
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -8,12 +11,12 @@
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
# vim:syntax=apparmor
|
||||
# Last Modified: Wed Aug 17 14:28:07 2005
|
||||
|
||||
#include <tunables/global>
|
||||
|
||||
/usr/sbin/mysqld {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/mysql>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/user-tmp>
|
||||
|
||||
@@ -21,8 +24,22 @@
|
||||
capability setgid,
|
||||
capability setuid,
|
||||
|
||||
/etc/hosts.allow r,
|
||||
/etc/hosts.deny r,
|
||||
/etc/my.cnf r,
|
||||
/etc/my.cnf.d/ r,
|
||||
/etc/my.cnf.d/*.cnf r,
|
||||
/root/.my.cnf r,
|
||||
/usr/lib{,32,64}/**.so mr,
|
||||
/usr/sbin/mysqld r,
|
||||
/usr/share/mariadb/*/errmsg.sys r,
|
||||
/usr/share/mysql-community-server/*/errmsg.sys r,
|
||||
/usr/share/mysql/** r,
|
||||
/var/lib/mysql/** lrw,
|
||||
/var/lib/mysql/ r,
|
||||
/var/lib/mysql/** rwl,
|
||||
/var/log/mysql/mysqld-upgrade-run.log w,
|
||||
/var/log/mysql/mysqld.log w,
|
||||
/var/log/mysql/mysqld.log-20* w,
|
||||
/{,var/}run/mysql/mysqld.pid w,
|
||||
|
||||
}
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
#define SD_ID_MAGIC 0x8c235e38
|
||||
|
||||
inline int do_open (char * file)
|
||||
static inline int do_open (char * file)
|
||||
{
|
||||
int fd, rc;
|
||||
char buf[128];
|
||||
|
@@ -25,6 +25,7 @@ put_old=${new_root}put_old/
|
||||
bad=$tmpdir/BAD/
|
||||
proc=$new_root/proc
|
||||
fstype="ext2"
|
||||
root_was_shared="no"
|
||||
|
||||
pivot_root_cleanup() {
|
||||
mountpoint -q "$proc"
|
||||
@@ -36,9 +37,32 @@ pivot_root_cleanup() {
|
||||
if [ $? -eq 0 ] ; then
|
||||
umount "$new_root"
|
||||
fi
|
||||
|
||||
if [ "${root_was_shared}" = "yes" ] ; then
|
||||
[ -n "$VERBOSE" ] && echo 'notice: re-mounting / as shared'
|
||||
mount --make-shared /
|
||||
fi
|
||||
}
|
||||
do_onexit="pivot_root_cleanup"
|
||||
|
||||
# systemd mounts / and everything under it MS_SHARED. This breaks
|
||||
# pivot_root entirely, so attempt to detect it, and remount /
|
||||
# MS_PRIVATE temporarily.
|
||||
FINDMNT=/bin/findmnt
|
||||
if [ -x "${FINDMNT}" ] && ${FINDMNT} -no PROPAGATION / > /dev/null 2>&1 ; then
|
||||
if [ "$(${FINDMNT} -no PROPAGATION /)" == "shared" ] ; then
|
||||
root_was_shared="yes"
|
||||
fi
|
||||
elif [ "$(ps hp1 -ocomm)" = "systemd" ] ; then
|
||||
# no findmnt or findmnt doesn't know the PROPAGATION column,
|
||||
# but init is systemd so assume rootfs is shared
|
||||
root_was_shared="yes"
|
||||
fi
|
||||
if [ "${root_was_shared}" = "yes" ] ; then
|
||||
[ -n "$VERBOSE" ] && echo 'notice: re-mounting / as private'
|
||||
mount --make-private /
|
||||
fi
|
||||
|
||||
# Create disk image since pivot_root doesn't allow old root and new root to be
|
||||
# on the same filesystem
|
||||
dd if=/dev/zero of="$disk_img" bs=1024 count=512 2> /dev/null
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#! /usr/bin/env python
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2011-2013 Canonical Ltd.
|
||||
# Copyright (C) 2011-2015 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
|
||||
@@ -68,16 +68,38 @@ if __name__ == "__main__":
|
||||
apparmor.easyprof.print_basefilenames(easyp.get_templates())
|
||||
sys.exit(0)
|
||||
elif options.template and options.show_template:
|
||||
files = [os.path.join(easyp.dirs['templates'], options.template)]
|
||||
apparmor.easyprof.print_files(files)
|
||||
sys_t = os.path.join(easyp.dirs['templates'], options.template)
|
||||
inc_t = None
|
||||
if options.include_templates_dir:
|
||||
inc_t = os.path.join(easyp.dirs['templates_include'],
|
||||
options.template)
|
||||
|
||||
if os.path.exists(sys_t):
|
||||
apparmor.easyprof.print_files([sys_t])
|
||||
elif os.path.exists(inc_t):
|
||||
apparmor.easyprof.print_files([inc_t])
|
||||
else:
|
||||
error("Could not find '%s'" % options.template)
|
||||
sys.exit(0)
|
||||
elif options.list_policy_groups:
|
||||
apparmor.easyprof.print_basefilenames(easyp.get_policy_groups())
|
||||
sys.exit(0)
|
||||
elif options.policy_groups and options.show_policy_group:
|
||||
files = []
|
||||
for g in options.policy_groups.split(','):
|
||||
files = [os.path.join(easyp.dirs['policygroups'], g)]
|
||||
apparmor.easyprof.print_files(files)
|
||||
sys_g = os.path.join(easyp.dirs['policygroups'], g)
|
||||
inc_g = None
|
||||
if options.include_policy_groups_dir:
|
||||
inc_g = os.path.join(easyp.dirs['policygroups_include'], g)
|
||||
|
||||
if os.path.exists(sys_g):
|
||||
files.append(sys_g)
|
||||
elif os.path.exists(inc_g):
|
||||
files.append(inc_g)
|
||||
else:
|
||||
error("Could not find '%s'" % g)
|
||||
|
||||
apparmor.easyprof.print_files(files)
|
||||
sys.exit(0)
|
||||
elif binary == None and not options.profile_name and \
|
||||
not options.manifest:
|
||||
|
@@ -97,7 +97,7 @@ the specified template uses this value. May be specified multiple times.
|
||||
|
||||
List available templates.
|
||||
|
||||
=item --show-template=TEMPLATE
|
||||
=item --show-template
|
||||
|
||||
Display template specified with --template.
|
||||
|
||||
@@ -105,18 +105,30 @@ Display template specified with --template.
|
||||
|
||||
Use PATH instead of system templates directory.
|
||||
|
||||
=item --include-templates-dir=PATH
|
||||
|
||||
Include PATH when searching for templates in addition to the system templates
|
||||
directory (or the one specified with --templates-dir). System templates will
|
||||
match before those in PATH.
|
||||
|
||||
=item --list-policy-groups
|
||||
|
||||
List available policy groups.
|
||||
|
||||
=item --show-policy-group
|
||||
|
||||
Display policy groups specified with --policy.
|
||||
Display policy groups specified with --policy-groups.
|
||||
|
||||
=item --policy-groups-dir=PATH
|
||||
|
||||
Use PATH instead of system policy-groups directory.
|
||||
|
||||
=item --include-policy-groups-dir=PATH
|
||||
|
||||
Include PATH when searching for policy groups in addition to the system
|
||||
policy-groups directory (or the one specified with --policy-groups-dir). System
|
||||
policy-groups will match before those in PATH.
|
||||
|
||||
=item --policy-version=VERSION
|
||||
|
||||
Must be used with --policy-vendor and is used to specify the version of policy
|
||||
|
@@ -134,10 +134,10 @@ def filter_processes(processes, status):
|
||||
|
||||
def find_apparmorfs():
|
||||
'''Finds AppArmor mount point'''
|
||||
for p in open("/proc/mounts").readlines():
|
||||
if p.split()[2] == "securityfs" and \
|
||||
os.path.exists(os.path.join(p.split()[1], "apparmor")):
|
||||
return os.path.join(p.split()[1], "apparmor")
|
||||
for p in open("/proc/mounts","rb").readlines():
|
||||
if p.split()[2].decode() == "securityfs" and \
|
||||
os.path.exists(os.path.join(p.split()[1].decode(), "apparmor")):
|
||||
return os.path.join(p.split()[1].decode(), "apparmor")
|
||||
return False
|
||||
|
||||
def errormsg(message):
|
||||
|
@@ -63,8 +63,9 @@ for pid in sorted(pids):
|
||||
if os.path.exists("/proc/%s/attr/current"%pid):
|
||||
with aa.open_file_read("/proc/%s/attr/current"%pid) as current:
|
||||
for line in current:
|
||||
if line.startswith("/") or line.startswith("null"):
|
||||
attr = line.strip()
|
||||
line = line.strip()
|
||||
if line.endswith(' (complain)', 1) or line.endswith(' (enforce)', 1): # enforce at least one char as profile name
|
||||
attr = line
|
||||
|
||||
cmdline = apparmor.common.cmd(["cat", "/proc/%s/cmdline"%pid])[1]
|
||||
pname = cmdline.split("\0")[0]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2014 Christian Boltz <apparmor@cboltz.de>
|
||||
# Copyright (C) 2014-2015 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -13,7 +13,7 @@
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
# No old version logs, only 2.6 + supported
|
||||
from __future__ import with_statement
|
||||
from __future__ import division, with_statement
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
@@ -31,7 +31,7 @@ import apparmor.severity
|
||||
|
||||
from copy import deepcopy
|
||||
|
||||
from apparmor.common import (AppArmorException, open_file_read, valid_path, hasher,
|
||||
from apparmor.common import (AppArmorException, AppArmorBug, open_file_read, valid_path, hasher,
|
||||
open_file_write, convert_regexp, DebugLogger)
|
||||
|
||||
import apparmor.ui as aaui
|
||||
@@ -48,7 +48,8 @@ from apparmor.regex import (RE_PROFILE_START, RE_PROFILE_END, RE_PROFILE_CAP, RE
|
||||
RE_NETWORK_FAMILY_TYPE, RE_NETWORK_FAMILY, RE_PROFILE_CHANGE_HAT,
|
||||
RE_PROFILE_HAT_DEF, RE_PROFILE_DBUS, RE_PROFILE_MOUNT,
|
||||
RE_PROFILE_SIGNAL, RE_PROFILE_PTRACE, RE_PROFILE_PIVOT_ROOT,
|
||||
RE_PROFILE_UNIX, RE_RULE_HAS_COMMA, RE_HAS_COMMENT_SPLIT )
|
||||
RE_PROFILE_UNIX, RE_RULE_HAS_COMMA, RE_HAS_COMMENT_SPLIT,
|
||||
strip_quotes, parse_profile_start_line )
|
||||
|
||||
import apparmor.rules as aarules
|
||||
|
||||
@@ -603,9 +604,9 @@ def get_profile_flags(filename, program):
|
||||
with open_file_read(filename) as f_in:
|
||||
for line in f_in:
|
||||
if RE_PROFILE_START.search(line):
|
||||
matches = RE_PROFILE_START.search(line).groups()
|
||||
profile = matches[1] or matches[3]
|
||||
flags = matches[6]
|
||||
matches = parse_profile_start_line(line, filename)
|
||||
profile = matches['profile']
|
||||
flags = matches['flags']
|
||||
if profile == program or program is None:
|
||||
return flags
|
||||
|
||||
@@ -638,49 +639,52 @@ def change_profile_flags(filename, program, flag, set_flag):
|
||||
|
||||
def set_profile_flags(prof_filename, program, newflags):
|
||||
"""Reads the old profile file and updates the flags accordingly"""
|
||||
regex_bin_flag = re.compile('^(\s*)("?(/.+?)"??|(profile\s+"?(.+?)"??))\s+((flags=)?\((.*)\)\s+)?\{\s*(#.*)?$')
|
||||
# TODO: use RE_PROFILE_START (only difference: doesn't have a match group for the leading space)
|
||||
# TODO: also use the global regex for matching the hat
|
||||
# TODO: count the number of matching lines (separated by profile and hat?) and return it
|
||||
# so that code calling this function can make sure to only report success if there was a match
|
||||
regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$')
|
||||
if os.path.isfile(prof_filename):
|
||||
with open_file_read(prof_filename) as f_in:
|
||||
temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir)
|
||||
shutil.copymode(prof_filename, temp_file.name)
|
||||
with open_file_write(temp_file.name) as f_out:
|
||||
for line in f_in:
|
||||
comment = ''
|
||||
if '#' in line:
|
||||
comment = '#' + line.split('#', 1)[1].rstrip()
|
||||
match = regex_bin_flag.search(line)
|
||||
if not line.strip() or line.strip().startswith('#'):
|
||||
pass
|
||||
elif match:
|
||||
matches = match.groups()
|
||||
space = matches[0]
|
||||
profile = matches[1] # profile name including quotes and "profile" keyword
|
||||
if matches[2]:
|
||||
binary = matches[2]
|
||||
else:
|
||||
binary = matches[4]
|
||||
flag = matches[6] or 'flags='
|
||||
flags = matches[7]
|
||||
if binary == program or program is None:
|
||||
if newflags:
|
||||
line = '%s%s %s(%s) {%s\n' % (space, profile, flag, newflags, comment)
|
||||
else:
|
||||
line = '%s%s {%s\n' % (space, profile, comment)
|
||||
else:
|
||||
match = regex_hat_flag.search(line)
|
||||
if match:
|
||||
hat, flags = match.groups()[:2]
|
||||
if newflags:
|
||||
line = '%s flags=(%s) {%s\n' % (hat, newflags, comment)
|
||||
else:
|
||||
line = '%s {%s\n' % (hat, comment)
|
||||
f_out.write(line)
|
||||
os.rename(temp_file.name, prof_filename)
|
||||
# TODO: use RE_PROFILE_HAT_DEF for matching the hat (regex_hat_flag is totally broken!)
|
||||
#regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$')
|
||||
|
||||
found = False
|
||||
|
||||
if newflags and newflags.strip() == '':
|
||||
raise AppArmorBug('New flags for %s contain only whitespace' % prof_filename)
|
||||
|
||||
with open_file_read(prof_filename) as f_in:
|
||||
temp_file = tempfile.NamedTemporaryFile('w', prefix=prof_filename, suffix='~', delete=False, dir=profile_dir)
|
||||
shutil.copymode(prof_filename, temp_file.name)
|
||||
with open_file_write(temp_file.name) as f_out:
|
||||
for line in f_in:
|
||||
if RE_PROFILE_START.search(line):
|
||||
matches = parse_profile_start_line(line, prof_filename)
|
||||
space = matches['leadingspace'] or ''
|
||||
profile = matches['profile']
|
||||
|
||||
if profile == program or program is None:
|
||||
found = True
|
||||
header_data = {
|
||||
'attachment': matches['attachment'] or '',
|
||||
'flags': newflags,
|
||||
'profile_keyword': matches['profile_keyword'],
|
||||
'header_comment': matches['comment'] or '',
|
||||
}
|
||||
line = write_header(header_data, len(space)/2, profile, False, True)
|
||||
line = '%s\n' % line[0]
|
||||
#else:
|
||||
# match = regex_hat_flag.search(line)
|
||||
# if match:
|
||||
# hat, flags = match.groups()[:2]
|
||||
# if newflags:
|
||||
# line = '%s flags=(%s) {%s\n' % (hat, newflags, comment)
|
||||
# else:
|
||||
# line = '%s {%s\n' % (hat, comment)
|
||||
f_out.write(line)
|
||||
os.rename(temp_file.name, prof_filename)
|
||||
|
||||
if not found:
|
||||
if program is None:
|
||||
raise AppArmorBug("%(file)s doesn't contain a valid profile (syntax error?)" % {'file': prof_filename})
|
||||
else:
|
||||
raise AppArmorBug("%(file)s doesn't contain a valid profile for %(profile)s (syntax error?)" % {'file': prof_filename, 'profile': program})
|
||||
|
||||
def profile_exists(program):
|
||||
"""Returns True if profile exists, False otherwise"""
|
||||
@@ -2546,17 +2550,25 @@ def validate_profile_mode(mode, allow, nt_name=None):
|
||||
else:
|
||||
return False
|
||||
|
||||
# rpm backup files, dotfiles, emacs backup files should not be processed
|
||||
# The skippable files type needs be synced with apparmor initscript
|
||||
|
||||
def is_skippable_file(path):
|
||||
"""Returns True if filename matches something to be skipped"""
|
||||
if (re.search('(^|/)\.[^/]*$', path) or re.search('\.rpm(save|new)$', path)
|
||||
or re.search('\.dpkg-(old|new)$', path) or re.search('\.swp$', path)
|
||||
or path[-1] == '~' or path == 'README'):
|
||||
"""Returns True if filename matches something to be skipped (rpm or dpkg backup files, hidden files etc.)
|
||||
The list of skippable files needs to be synced with apparmor initscript and libapparmor _aa_is_blacklisted()
|
||||
path: filename (with or without directory)"""
|
||||
|
||||
basename = os.path.basename(path)
|
||||
|
||||
if not basename or basename[0] == '.' or basename == 'README':
|
||||
return True
|
||||
|
||||
skippable_suffix = ('.dpkg-new', '.dpkg-old', '.dpkg-dist', '.dpkg-bak', '.rpmnew', '.rpmsave', '.orig', '.rej', '~')
|
||||
if basename.endswith(skippable_suffix):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def is_skippable_dir(path):
|
||||
if re.search('(disable|cache|force-complain|lxc)', path):
|
||||
if re.search('^(.*/)?(disable|cache|force-complain|lxc)/?$', path):
|
||||
return True
|
||||
return False
|
||||
|
||||
@@ -2620,6 +2632,41 @@ def attach_profile_data(profiles, profile_data):
|
||||
for p in profile_data.keys():
|
||||
profiles[p] = deepcopy(profile_data[p])
|
||||
|
||||
|
||||
def parse_profile_start(line, file, lineno, profile, hat):
|
||||
matches = parse_profile_start_line(line, file)
|
||||
|
||||
if profile: # we are inside a profile, so we expect a child profile
|
||||
if not matches['profile_keyword']:
|
||||
raise AppArmorException(_('%(profile)s profile in %(file)s contains syntax errors in line %(line)s: missing "profile" keyword.') % {
|
||||
'profile': profile, 'file': file, 'line': lineno + 1 })
|
||||
if profile != hat:
|
||||
# nesting limit reached - a child profile can't contain another child profile
|
||||
raise AppArmorException(_('%(profile)s profile in %(file)s contains syntax errors in line %(line)s: a child profile inside another child profile is not allowed.') % {
|
||||
'profile': profile, 'file': file, 'line': lineno + 1 })
|
||||
|
||||
hat = matches['profile']
|
||||
in_contained_hat = True
|
||||
pps_set_profile = True
|
||||
pps_set_hat_external = False
|
||||
|
||||
else: # stand-alone profile
|
||||
profile = matches['profile']
|
||||
if len(profile.split('//')) >= 2:
|
||||
profile, hat = profile.split('//')[:2]
|
||||
pps_set_hat_external = True
|
||||
else:
|
||||
hat = profile
|
||||
pps_set_hat_external = False
|
||||
|
||||
in_contained_hat = False
|
||||
pps_set_profile = False
|
||||
|
||||
attachment = matches['attachment']
|
||||
flags = matches['flags']
|
||||
|
||||
return (profile, hat, attachment, flags, in_contained_hat, pps_set_profile, pps_set_hat_external)
|
||||
|
||||
def parse_profile_data(data, file, do_include):
|
||||
profile_data = hasher()
|
||||
profile = None
|
||||
@@ -2643,41 +2690,17 @@ def parse_profile_data(data, file, do_include):
|
||||
lastline = None
|
||||
# Starting line of a profile
|
||||
if RE_PROFILE_START.search(line):
|
||||
matches = RE_PROFILE_START.search(line).groups()
|
||||
|
||||
if profile:
|
||||
#print(profile, hat)
|
||||
if profile != hat or not matches[3]:
|
||||
raise AppArmorException(_('%(profile)s profile in %(file)s contains syntax errors in line: %(line)s.') % { 'profile': profile, 'file': file, 'line': lineno + 1 })
|
||||
# Keep track of the start of a profile
|
||||
if profile and profile == hat and matches[3]:
|
||||
# local profile
|
||||
hat = matches[3]
|
||||
in_contained_hat = True
|
||||
(profile, hat, attachment, flags, in_contained_hat, pps_set_profile, pps_set_hat_external) = parse_profile_start(line, file, lineno, profile, hat)
|
||||
if attachment:
|
||||
profile_data[profile][hat]['attachment'] = attachment
|
||||
if pps_set_profile:
|
||||
profile_data[profile][hat]['profile'] = True
|
||||
else:
|
||||
if matches[1]:
|
||||
profile = matches[1]
|
||||
else:
|
||||
profile = matches[3]
|
||||
#print(profile)
|
||||
if len(profile.split('//')) >= 2:
|
||||
profile, hat = profile.split('//')[:2]
|
||||
else:
|
||||
hat = None
|
||||
in_contained_hat = False
|
||||
if hat:
|
||||
profile_data[profile][hat]['external'] = True
|
||||
else:
|
||||
hat = profile
|
||||
if pps_set_hat_external:
|
||||
profile_data[profile][hat]['external'] = True
|
||||
|
||||
# Profile stored
|
||||
existing_profiles[profile] = file
|
||||
|
||||
flags = matches[6]
|
||||
|
||||
profile = strip_quotes(profile)
|
||||
if hat:
|
||||
hat = strip_quotes(hat)
|
||||
# save profile name and filename
|
||||
profile_data[profile][hat]['name'] = profile
|
||||
profile_data[profile][hat]['filename'] = file
|
||||
@@ -2878,7 +2901,7 @@ def parse_profile_data(data, file, do_include):
|
||||
|
||||
path = strip_quotes(matches[4].strip())
|
||||
mode = matches[5]
|
||||
nt_name = matches[6]
|
||||
nt_name = matches[7]
|
||||
if nt_name:
|
||||
nt_name = nt_name.strip()
|
||||
|
||||
@@ -3236,13 +3259,12 @@ def parse_unix_rule(line):
|
||||
|
||||
def separate_vars(vs):
|
||||
"""Returns a list of all the values for a variable"""
|
||||
data = []
|
||||
data = set()
|
||||
|
||||
#data = [i.strip('"') for i in vs.split()]
|
||||
RE_VARS = re.compile('\s*((\".+?\")|([^\"]\S+))\s*(.*)$')
|
||||
while RE_VARS.search(vs):
|
||||
matches = RE_VARS.search(vs).groups()
|
||||
data.append(strip_quotes(matches[0]))
|
||||
data.add(strip_quotes(matches[0]))
|
||||
vs = matches[3]
|
||||
|
||||
return data
|
||||
@@ -3254,29 +3276,25 @@ def is_active_profile(pname):
|
||||
return False
|
||||
|
||||
def store_list_var(var, list_var, value, var_operation, filename):
|
||||
"""Store(add new variable or add values to variable) the variables encountered in the given list_var"""
|
||||
"""Store(add new variable or add values to variable) the variables encountered in the given list_var
|
||||
- the 'var' parameter will be modified
|
||||
- 'list_var' is the variable name, for example '@{foo}'
|
||||
"""
|
||||
vlist = separate_vars(value)
|
||||
if var_operation == '=':
|
||||
if not var.get(list_var, False):
|
||||
var[list_var] = set(vlist)
|
||||
else:
|
||||
#print('Ignored: New definition for variable for:',list_var,'=', value, 'operation was:',var_operation,'old value=', var[list_var])
|
||||
raise AppArmorException(_('Redefining existing variable %(variable)s: %(value)s in %(file)s') % { 'variable': list_var, 'value': value, 'file': filename })
|
||||
elif var_operation == '+=':
|
||||
if var.get(list_var, False):
|
||||
var[list_var] = set(var[list_var] + vlist)
|
||||
var[list_var] |= vlist
|
||||
else:
|
||||
raise AppArmorException(_('Values added to a non-existing variable %(variable)s: %(value)s in %(file)s') % { 'variable': list_var, 'value': value, 'file': filename })
|
||||
else:
|
||||
raise AppArmorException(_('Unknown variable operation %(operation)s for variable %(variable)s in %(file)s') % { 'operation': var_operation, 'variable': list_var, 'file': filename })
|
||||
|
||||
|
||||
def strip_quotes(data):
|
||||
if data[0] + data[-1] == '""':
|
||||
return data[1:-1]
|
||||
else:
|
||||
return data
|
||||
|
||||
def quote_if_needed(data):
|
||||
# quote data if it contains whitespace
|
||||
if ' ' in data:
|
||||
@@ -3291,17 +3309,27 @@ def escape(escape):
|
||||
return escape
|
||||
|
||||
def write_header(prof_data, depth, name, embedded_hat, write_flags):
|
||||
pre = ' ' * depth
|
||||
pre = ' ' * int(depth * 2)
|
||||
data = []
|
||||
unquoted_name = name
|
||||
name = quote_if_needed(name)
|
||||
|
||||
if (not embedded_hat and re.search('^[^/]|^"[^/]', name)) or (embedded_hat and re.search('^[^^]', name)):
|
||||
name = 'profile %s' % name
|
||||
attachment = ''
|
||||
if prof_data['attachment']:
|
||||
attachment = ' %s' % quote_if_needed(prof_data['attachment'])
|
||||
|
||||
comment = ''
|
||||
if prof_data['header_comment']:
|
||||
comment = ' %s' % prof_data['header_comment']
|
||||
|
||||
if (not embedded_hat and re.search('^[^/]', unquoted_name)) or (embedded_hat and re.search('^[^^]', unquoted_name)) or prof_data['attachment'] or prof_data['profile_keyword']:
|
||||
name = 'profile %s%s' % (name, attachment)
|
||||
|
||||
flags = ''
|
||||
if write_flags and prof_data['flags']:
|
||||
data.append('%s%s flags=(%s) {' % (pre, name, prof_data['flags']))
|
||||
else:
|
||||
data.append('%s%s {' % (pre, name))
|
||||
flags = ' flags=(%s)' % prof_data['flags']
|
||||
|
||||
data.append('%s%s%s {%s' % (pre, name, flags, comment))
|
||||
|
||||
return data
|
||||
|
||||
@@ -3408,14 +3436,18 @@ def write_net_rules(prof_data, depth, allow):
|
||||
data.append('%s%snetwork,' % (pre, audit))
|
||||
else:
|
||||
for fam in sorted(prof_data[allow]['netdomain']['rule'].keys()):
|
||||
audit = ''
|
||||
if prof_data[allow]['netdomain']['rule'][fam] is True:
|
||||
if prof_data[allow]['netdomain']['audit'][fam]:
|
||||
audit = 'audit'
|
||||
data.append('%s%s%snetwork %s' % (pre, audit, allowstr, fam))
|
||||
audit = 'audit '
|
||||
if fam == 'all':
|
||||
data.append('%s%s%snetwork,' % (pre, audit, allowstr))
|
||||
else:
|
||||
data.append('%s%s%snetwork %s,' % (pre, audit, allowstr, fam))
|
||||
else:
|
||||
for typ in sorted(prof_data[allow]['netdomain']['rule'][fam].keys()):
|
||||
if prof_data[allow]['netdomain']['audit'][fam].get(typ, False):
|
||||
audit = 'audit'
|
||||
audit = 'audit '
|
||||
data.append('%s%s%snetwork %s %s,' % (pre, audit, allowstr, fam, typ))
|
||||
if prof_data[allow].get('netdomain', False):
|
||||
data.append('')
|
||||
@@ -3733,6 +3765,14 @@ def serialize_profile(profile_data, name, options):
|
||||
|
||||
return string + '\n'
|
||||
|
||||
def serialize_parse_profile_start(line, file, lineno, profile, hat, prof_data_profile, prof_data_external, correct):
|
||||
(profile, hat, attachment, flags, in_contained_hat, pps_set_profile, pps_set_hat_external) = parse_profile_start(line, file, lineno, profile, hat)
|
||||
|
||||
if hat and profile != hat and '%s//%s'%(profile, hat) in line and not prof_data_external:
|
||||
correct = False
|
||||
|
||||
return (profile, hat, attachment, flags, in_contained_hat, correct)
|
||||
|
||||
def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
data = []
|
||||
string = ''
|
||||
@@ -3835,31 +3875,9 @@ def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
line = line.rstrip('\n')
|
||||
#data.append(' ')#data.append('read: '+line)
|
||||
if RE_PROFILE_START.search(line):
|
||||
matches = RE_PROFILE_START.search(line).groups()
|
||||
if profile and profile == hat and matches[3]:
|
||||
hat = matches[3]
|
||||
in_contained_hat = True
|
||||
if write_prof_data[profile][hat]['profile']:
|
||||
pass
|
||||
else:
|
||||
if matches[1]:
|
||||
profile = matches[1]
|
||||
else:
|
||||
profile = matches[3]
|
||||
if len(profile.split('//')) >= 2:
|
||||
profile, hat = profile.split('//')[:2]
|
||||
else:
|
||||
hat = None
|
||||
in_contained_hat = False
|
||||
if hat and not write_prof_data[profile][hat]['external']:
|
||||
correct = False
|
||||
else:
|
||||
hat = profile
|
||||
|
||||
flags = matches[6]
|
||||
profile = strip_quotes(profile)
|
||||
if hat:
|
||||
hat = strip_quotes(hat)
|
||||
(profile, hat, attachment, flags, in_contained_hat, correct) = serialize_parse_profile_start(
|
||||
line, prof_filename, None, profile, hat, write_prof_data[profile][hat]['profile'], write_prof_data[profile][hat]['external'], correct)
|
||||
|
||||
if not write_prof_data[hat]['name'] == profile:
|
||||
correct = False
|
||||
@@ -4085,7 +4103,11 @@ def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
var_operation = matches[1]
|
||||
value = strip_quotes(matches[2])
|
||||
var_set = hasher()
|
||||
if profile:
|
||||
if var_operation == '+=':
|
||||
correct = False # adding proper support for "add to variable" needs big changes
|
||||
# (like storing a variable's "history" - where it was initially defined and what got added where)
|
||||
# so just skip any comparison and assume a non-match
|
||||
elif profile:
|
||||
store_list_var(var_set, list_var, value, var_operation, prof_filename)
|
||||
if not var_set[list_var] == write_prof_data['lvar'].get(list_var, False):
|
||||
correct = False
|
||||
@@ -4147,7 +4169,7 @@ def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
|
||||
path = strip_quotes(matches[4].strip())
|
||||
mode = matches[5]
|
||||
nt_name = matches[6]
|
||||
nt_name = matches[7]
|
||||
if nt_name:
|
||||
nt_name = nt_name.strip()
|
||||
|
||||
@@ -4157,14 +4179,17 @@ def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
else:
|
||||
tmpmode = str_to_mode(mode)
|
||||
|
||||
if not write_prof_data[hat][allow]['path'][path].get('mode', set()) & tmpmode:
|
||||
if not write_prof_data[hat][allow]['path'].get(path):
|
||||
correct = False
|
||||
else:
|
||||
if not write_prof_data[hat][allow]['path'][path].get('mode', set()) & tmpmode:
|
||||
correct = False
|
||||
|
||||
if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name:
|
||||
correct = False
|
||||
if nt_name and not write_prof_data[hat][allow]['path'][path].get('to', False) == nt_name:
|
||||
correct = False
|
||||
|
||||
if audit and not write_prof_data[hat][allow]['path'][path].get('audit', set()) & tmpmode:
|
||||
correct = False
|
||||
if audit and not write_prof_data[hat][allow]['path'][path].get('audit', set()) & tmpmode:
|
||||
correct = False
|
||||
|
||||
if correct:
|
||||
if not segments['path'] and True in segments.values():
|
||||
@@ -4435,6 +4460,8 @@ def load_include(incname):
|
||||
#If the include is a directory means include all subfiles
|
||||
elif os.path.isdir(profile_dir + '/' + incfile):
|
||||
load_includeslist += list(map(lambda x: incfile + '/' + x, os.listdir(profile_dir + '/' + incfile)))
|
||||
else:
|
||||
raise AppArmorException("Include file %s not found" % (profile_dir + '/' + incfile) )
|
||||
|
||||
return 0
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2011-2013 Canonical Ltd.
|
||||
# Copyright (C) 2011-2015 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
|
||||
@@ -312,15 +312,26 @@ class AppArmorEasyProfile:
|
||||
# the templates directory to the parent of the template so we don't
|
||||
# have to require --template-dir with absolute paths.
|
||||
self.dirs['templates'] = os.path.abspath(os.path.dirname(opt.template))
|
||||
|
||||
if opt.include_templates_dir and \
|
||||
os.path.isdir(opt.include_templates_dir):
|
||||
self.dirs['templates_include'] = os.path.abspath(opt.include_templates_dir)
|
||||
|
||||
if opt.policy_groups_dir and os.path.isdir(opt.policy_groups_dir):
|
||||
self.dirs['policygroups'] = os.path.abspath(opt.policy_groups_dir)
|
||||
|
||||
if opt.include_policy_groups_dir and \
|
||||
os.path.isdir(opt.include_policy_groups_dir):
|
||||
self.dirs['policygroups_include'] = os.path.abspath(opt.include_policy_groups_dir)
|
||||
|
||||
self.policy_version = None
|
||||
self.policy_vendor = None
|
||||
if (opt.policy_version and not opt.policy_vendor) or \
|
||||
(opt.policy_vendor and not opt.policy_version):
|
||||
raise AppArmorException("Must specify both policy version and vendor")
|
||||
|
||||
# If specified --policy-version and --policy-vendor, use
|
||||
# templates_dir/policy_vendor/policy_version
|
||||
if opt.policy_version and opt.policy_vendor:
|
||||
self.policy_vendor = opt.policy_vendor
|
||||
self.policy_version = str(opt.policy_version)
|
||||
@@ -361,11 +372,22 @@ class AppArmorEasyProfile:
|
||||
for f in get_directory_contents(self.dirs['templates']):
|
||||
if os.path.isfile(f):
|
||||
self.templates.append(f)
|
||||
|
||||
if 'templates_include' in self.dirs:
|
||||
for f in get_directory_contents(self.dirs['templates_include']):
|
||||
if os.path.isfile(f) and f not in self.templates:
|
||||
self.templates.append(f)
|
||||
|
||||
self.policy_groups = []
|
||||
for f in get_directory_contents(self.dirs['policygroups']):
|
||||
if os.path.isfile(f):
|
||||
self.policy_groups.append(f)
|
||||
|
||||
if 'policygroups_include' in self.dirs:
|
||||
for f in get_directory_contents(self.dirs['policygroups_include']):
|
||||
if os.path.isfile(f) and f not in self.policy_groups:
|
||||
self.policy_groups.append(f)
|
||||
|
||||
def _get_defaults(self):
|
||||
'''Read in defaults from configuration'''
|
||||
if not os.path.exists(self.conffile):
|
||||
@@ -411,13 +433,25 @@ class AppArmorEasyProfile:
|
||||
elif template.startswith('/') and not allow_abs_path:
|
||||
raise AppArmorException("Cannot use an absolute path template '%s'" % template)
|
||||
|
||||
# If have an abs path, just use it
|
||||
if template.startswith('/'):
|
||||
if not os.path.exists(template):
|
||||
raise AppArmorException('%s does not exist' % (template))
|
||||
self.template = template
|
||||
else:
|
||||
self.template = os.path.join(self.dirs['templates'], template)
|
||||
return
|
||||
|
||||
if not os.path.exists(self.template):
|
||||
raise AppArmorException('%s does not exist' % (self.template))
|
||||
# Find the template since we don't have an abs path
|
||||
sys_t = os.path.join(self.dirs['templates'], template)
|
||||
inc_t = None
|
||||
if 'templates_include' in self.dirs:
|
||||
inc_t = os.path.join(self.dirs['templates_include'], template)
|
||||
|
||||
if os.path.exists(sys_t):
|
||||
self.template = sys_t
|
||||
elif inc_t is not None and os.path.exists(inc_t):
|
||||
self.template = inc_t
|
||||
else:
|
||||
raise AppArmorException('%s does not exist' % (template))
|
||||
|
||||
def get_templates(self):
|
||||
'''Get list of all available templates by filename'''
|
||||
@@ -427,7 +461,16 @@ class AppArmorEasyProfile:
|
||||
'''Get contents of specific policygroup'''
|
||||
p = policygroup
|
||||
if not p.startswith('/'):
|
||||
p = os.path.join(self.dirs['policygroups'], p)
|
||||
sys_p = os.path.join(self.dirs['policygroups'], p)
|
||||
inc_p = None
|
||||
if 'policygroups_include' in self.dirs:
|
||||
inc_p = os.path.join(self.dirs['policygroups_include'], p)
|
||||
|
||||
if os.path.exists(sys_p):
|
||||
p = sys_p
|
||||
elif inc_p is not None and os.path.exists(inc_p):
|
||||
p = inc_p
|
||||
|
||||
if self.policy_groups == None or not p in self.policy_groups:
|
||||
raise AppArmorException("Policy group '%s' does not exist" % p)
|
||||
return open(p).read()
|
||||
@@ -437,11 +480,25 @@ class AppArmorEasyProfile:
|
||||
self.policy_groups = []
|
||||
if policygroups != None:
|
||||
for p in policygroups.split(','):
|
||||
if not p.startswith('/'):
|
||||
p = os.path.join(self.dirs['policygroups'], p)
|
||||
if not os.path.exists(p):
|
||||
# If have abs path, just use it
|
||||
if p.startswith('/'):
|
||||
if not os.path.exists(p):
|
||||
raise AppArmorException('%s does not exist' % (p))
|
||||
self.policy_groups.append(p)
|
||||
continue
|
||||
|
||||
# Find the policy group since we don't have and abs path
|
||||
sys_p = os.path.join(self.dirs['policygroups'], p)
|
||||
inc_p = None
|
||||
if 'policygroups_include' in self.dirs:
|
||||
inc_p = os.path.join(self.dirs['policygroups_include'], p)
|
||||
|
||||
if os.path.exists(sys_p):
|
||||
self.policy_groups.append(sys_p)
|
||||
elif inc_p is not None and os.path.exists(inc_p):
|
||||
self.policy_groups.append(inc_p)
|
||||
else:
|
||||
raise AppArmorException('%s does not exist' % (p))
|
||||
self.policy_groups.append(p)
|
||||
|
||||
def get_policy_groups(self):
|
||||
'''Get list of all policy groups by filename'''
|
||||
@@ -777,6 +834,10 @@ def add_parser_policy_args(parser):
|
||||
dest="templates_dir",
|
||||
help="Use non-default templates directory",
|
||||
metavar="DIR")
|
||||
parser.add_option("--include-templates-dir",
|
||||
dest="include_templates_dir",
|
||||
help="Also search DIR for templates",
|
||||
metavar="DIR")
|
||||
parser.add_option("-p", "--policy-groups",
|
||||
action="callback",
|
||||
callback=check_for_manifest_arg,
|
||||
@@ -787,6 +848,10 @@ def add_parser_policy_args(parser):
|
||||
dest="policy_groups_dir",
|
||||
help="Use non-default policy-groups directory",
|
||||
metavar="DIR")
|
||||
parser.add_option("--include-policy-groups-dir",
|
||||
dest="include_policy_groups_dir",
|
||||
help="Also search DIR for policy groups",
|
||||
metavar="DIR")
|
||||
parser.add_option("--policy-version",
|
||||
action="callback",
|
||||
callback=check_for_manifest_arg,
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2015 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -25,7 +26,7 @@ from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
class ReadLog:
|
||||
RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=')
|
||||
RE_LOG_v2_6_syslog = re.compile('kernel:\s+(\[[\d\.\s]+\]\s+)?(audit:\s+)?type=\d+\s+audit\([\d\.\:]+\):\s+apparmor=')
|
||||
RE_LOG_v2_6_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=')
|
||||
# Used by netdomain to identify the operation types
|
||||
# New socket names
|
||||
@@ -111,34 +112,15 @@ class ReadLog:
|
||||
ev['pid'] = event.pid
|
||||
ev['task'] = event.task
|
||||
ev['info'] = event.info
|
||||
dmask = event.denied_mask
|
||||
rmask = event.requested_mask
|
||||
ev['error_code'] = event.error_code
|
||||
ev['denied_mask'] = event.denied_mask
|
||||
ev['request_mask'] = event.requested_mask
|
||||
ev['magic_token'] = event.magic_token
|
||||
if ev['operation'] and self.op_type(ev['operation']) == 'net':
|
||||
ev['family'] = event.net_family
|
||||
ev['protocol'] = event.net_protocol
|
||||
ev['sock_type'] = event.net_sock_type
|
||||
LibAppArmor.free_record(event)
|
||||
# Map c (create) to a and d (delete) to w, logprof doesn't support c and d
|
||||
if rmask:
|
||||
rmask = rmask.replace('c', 'a')
|
||||
rmask = rmask.replace('d', 'w')
|
||||
if not validate_log_mode(hide_log_mode(rmask)):
|
||||
raise AppArmorException(_('Log contains unknown mode %s') % rmask)
|
||||
if dmask:
|
||||
dmask = dmask.replace('c', 'a')
|
||||
dmask = dmask.replace('d', 'w')
|
||||
if not validate_log_mode(hide_log_mode(dmask)):
|
||||
raise AppArmorException(_('Log contains unknown mode %s') % dmask)
|
||||
#print('parse_event:', ev['profile'], dmask, ev['name2'])
|
||||
mask, name = log_str_to_mode(ev['profile'], dmask, ev['name2'])
|
||||
|
||||
ev['denied_mask'] = mask
|
||||
ev['name2'] = name
|
||||
|
||||
mask, name = log_str_to_mode(ev['profile'], rmask, ev['name2'])
|
||||
ev['request_mask'] = mask
|
||||
ev['name2'] = name
|
||||
|
||||
if not ev['time']:
|
||||
ev['time'] = int(time.time())
|
||||
@@ -162,6 +144,11 @@ class ReadLog:
|
||||
except KeyError:
|
||||
ev['aamode'] = None
|
||||
|
||||
# "translate" disconnected paths to errors, which means the event will be ignored.
|
||||
# XXX Ideally we should propose to add the attach_disconnected flag to the profile
|
||||
if ev['error_code'] == 13 and ev['info'] == 'Failed name lookup - disconnected path':
|
||||
ev['aamode'] = 'ERROR'
|
||||
|
||||
if ev['aamode']:
|
||||
#debug_logger.debug(ev)
|
||||
return ev
|
||||
@@ -248,6 +235,10 @@ class ReadLog:
|
||||
if profile != 'null-complain-profile' and not self.profile_exists(profile):
|
||||
return None
|
||||
if e['operation'] == 'exec':
|
||||
# convert rmask and dmask to mode arrays
|
||||
e['denied_mask'], e['name2'] = log_str_to_mode(e['profile'], e['denied_mask'], e['name2'])
|
||||
e['request_mask'], e['name2'] = log_str_to_mode(e['profile'], e['request_mask'], e['name2'])
|
||||
|
||||
if e.get('info', False) and e['info'] == 'mandatory profile missing':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'exec',
|
||||
[profile, hat, aamode, 'PERMITTING', e['denied_mask'], e['name'], e['name2']])
|
||||
@@ -257,21 +248,29 @@ class ReadLog:
|
||||
else:
|
||||
self.debug_logger.debug('add_event_to_tree: dropped exec event in %s' % e['profile'])
|
||||
|
||||
elif 'file_' in e['operation']:
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
elif e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'rename_src',
|
||||
'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link']:
|
||||
#print(e['operation'], e['name'])
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
elif e['operation'] == 'capable':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'capability',
|
||||
[profile, hat, prog, aamode, e['name'], ''])
|
||||
elif e['operation'] == 'setattr' or 'xattr' in e['operation']:
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
elif 'inode_' in e['operation']:
|
||||
elif ( e['operation'].startswith('file_') or e['operation'].startswith('inode_') or
|
||||
e['operation'] in ['open', 'truncate', 'mkdir', 'mknod', 'chmod', 'rename_src',
|
||||
'rename_dest', 'unlink', 'rmdir', 'symlink_create', 'link',
|
||||
'sysctl', 'getattr', 'setattr', 'xattr'] ):
|
||||
|
||||
# Map c (create) to a and d (delete) to w (logging is more detailed than the profile language)
|
||||
rmask = e['request_mask']
|
||||
rmask = rmask.replace('c', 'a')
|
||||
rmask = rmask.replace('d', 'w')
|
||||
if not validate_log_mode(hide_log_mode(rmask)):
|
||||
raise AppArmorException(_('Log contains unknown mode %s') % rmask)
|
||||
|
||||
dmask = e['denied_mask']
|
||||
dmask = dmask.replace('c', 'a')
|
||||
dmask = dmask.replace('d', 'w')
|
||||
if not validate_log_mode(hide_log_mode(dmask)):
|
||||
raise AppArmorException(_('Log contains unknown mode %s') % dmask)
|
||||
|
||||
# convert rmask and dmask to mode arrays
|
||||
e['denied_mask'], e['name2'] = log_str_to_mode(e['profile'], dmask, e['name2'])
|
||||
e['request_mask'], e['name2'] = log_str_to_mode(e['profile'], rmask, e['name2'])
|
||||
|
||||
# check if this is an exec event
|
||||
is_domain_change = False
|
||||
if e['operation'] == 'inode_permission' and (e['denied_mask'] & AA_MAY_EXEC) and aamode == 'PERMITTING':
|
||||
following = self.peek_at_next_log_entry()
|
||||
@@ -288,9 +287,9 @@ class ReadLog:
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
|
||||
elif e['operation'] == 'sysctl':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'path',
|
||||
[profile, hat, prog, aamode, e['denied_mask'], e['name'], ''])
|
||||
elif e['operation'] == 'capable':
|
||||
self.add_to_tree(e['pid'], e['parent'], 'capability',
|
||||
[profile, hat, prog, aamode, e['name'], ''])
|
||||
|
||||
elif e['operation'] == 'clone':
|
||||
parent, child = e['pid'], e['task']
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2014 Christian Boltz <apparmor@cboltz.de>
|
||||
# Copyright (C) 2014-2015 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -14,6 +14,11 @@
|
||||
# ----------------------------------------------------------------------
|
||||
|
||||
import re
|
||||
from apparmor.common import AppArmorBug, AppArmorException
|
||||
|
||||
# setup module translations
|
||||
from apparmor.translations import init_translation
|
||||
_ = init_translation()
|
||||
|
||||
## Profile parsing Regex
|
||||
RE_AUDIT_DENY = '^\s*(?P<audit>audit\s+)?(?P<allow>allow\s+|deny\s+)?' # line start, optionally: leading whitespace, <audit> and <allow>/deny
|
||||
@@ -21,7 +26,6 @@ RE_OWNER = '(?P<owner>owner\s+)?' # optionally: <owner>
|
||||
RE_EOL = '\s*(?P<comment>#.*?)?\s*$' # optional whitespace, optional <comment>, optional whitespace, end of the line
|
||||
RE_COMMA_EOL = '\s*,' + RE_EOL # optional whitespace, comma + RE_EOL
|
||||
|
||||
RE_PROFILE_START = re.compile('^\s*("?(/.+?)"??|(profile\s+"?(.+?)"??))\s+((flags=)?\((.+)\)\s+)?\{' + RE_EOL)
|
||||
RE_PROFILE_END = re.compile('^\s*\}' + RE_EOL)
|
||||
RE_PROFILE_CAP = re.compile(RE_AUDIT_DENY + 'capability(?P<capability>(\s+\S+)+)?' + RE_COMMA_EOL)
|
||||
RE_PROFILE_LINK = re.compile(RE_AUDIT_DENY + 'link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)' + RE_COMMA_EOL)
|
||||
@@ -55,3 +59,51 @@ RE_RULE_HAS_COMMA = re.compile('^' + __re_no_or_quoted_hash +
|
||||
RE_HAS_COMMENT_SPLIT = re.compile('^(?P<not_comment>' + __re_no_or_quoted_hash + ')' + # store in 'not_comment' group
|
||||
'(?P<comment>#.*)$') # match trailing comment and store in 'comment' group
|
||||
|
||||
|
||||
|
||||
RE_PROFILE_START = re.compile(
|
||||
'^(?P<leadingspace>\s*)' +
|
||||
'(' +
|
||||
'(?P<plainprofile>(/\S+|"[^"]+"))' + # just a path
|
||||
'|' + # or
|
||||
'(' + 'profile' + '\s+(?P<namedprofile>(\S+|"[^"]+"))' + '(\s+(?P<attachment>(/\S+|"/[^"]+")))?' + ')' + # 'profile', profile name, optionally attachment
|
||||
')' +
|
||||
'\s+((flags=)?\((?P<flags>.+)\)\s+)?\{' +
|
||||
RE_EOL)
|
||||
|
||||
def parse_profile_start_line(line, filename):
|
||||
matches = RE_PROFILE_START.search(line)
|
||||
|
||||
if not matches:
|
||||
raise AppArmorBug('The given line from file %(filename)s is not the start of a profile: %(line)s' % { 'filename': filename, 'line': line } )
|
||||
|
||||
result = {}
|
||||
|
||||
for section in [ 'leadingspace', 'plainprofile', 'namedprofile', 'attachment', 'flags', 'comment']:
|
||||
if matches.group(section):
|
||||
result[section] = matches.group(section)
|
||||
|
||||
# sections with optional quotes
|
||||
if section in ['plainprofile', 'namedprofile', 'attachment']:
|
||||
result[section] = strip_quotes(result[section])
|
||||
else:
|
||||
result[section] = None
|
||||
|
||||
if result['flags'] and result['flags'].strip() == '':
|
||||
raise AppArmorException(_('Invalid syntax in %(filename)s: Empty set of flags in line %(line)s.' % { 'filename': filename, 'line': line } ))
|
||||
|
||||
if result['plainprofile']:
|
||||
result['profile'] = result['plainprofile']
|
||||
result['profile_keyword'] = False
|
||||
else:
|
||||
result['profile'] = result['namedprofile']
|
||||
result['profile_keyword'] = True
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def strip_quotes(data):
|
||||
if data[0] + data[-1] == '""':
|
||||
return data[1:-1]
|
||||
else:
|
||||
return data
|
||||
|
@@ -32,9 +32,6 @@ class aa_tools:
|
||||
|
||||
if tool_name in ['audit']:
|
||||
self.remove = args.remove
|
||||
elif tool_name == 'disable':
|
||||
self.disabledir = apparmor.profile_dir + '/disable'
|
||||
self.check_disable_dir()
|
||||
elif tool_name == 'autodep':
|
||||
self.force = args.force
|
||||
self.aa_mountpoint = apparmor.check_for_apparmor()
|
||||
@@ -50,10 +47,6 @@ class aa_tools:
|
||||
if not user_perm(apparmor.profile_dir):
|
||||
raise apparmor.AppArmorException("Cannot write to profile directory: %s" % (apparmor.profile_dir))
|
||||
|
||||
def check_disable_dir(self):
|
||||
if not os.path.isdir(self.disabledir):
|
||||
raise apparmor.AppArmorException("Can't find AppArmor disable directory %s" % self.disabledir)
|
||||
|
||||
def get_next_to_profile(self):
|
||||
'''Iterator function to walk the list of arguments passed'''
|
||||
|
||||
@@ -89,12 +82,13 @@ class aa_tools:
|
||||
yield (program, profile)
|
||||
|
||||
def act(self):
|
||||
# used by aa-cleanprof
|
||||
apparmor.read_profiles()
|
||||
|
||||
for (program, profile) in self.get_next_to_profile():
|
||||
if program is None:
|
||||
program = profile
|
||||
|
||||
apparmor.read_profiles()
|
||||
|
||||
if not program or not(os.path.exists(program) or apparmor.profile_exists(program)):
|
||||
if program and not program.startswith('/'):
|
||||
program = aaui.UI_GetString(_('The given program cannot be found, please try with the fully qualified path name of the program: '), '')
|
||||
@@ -116,10 +110,7 @@ class aa_tools:
|
||||
# One simply does not walk in here!
|
||||
raise apparmor.AppArmorException('Unknown tool: %s' % self.name)
|
||||
|
||||
cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-R', filename])
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
self.reload_profile(profile)
|
||||
|
||||
else:
|
||||
if '/' not in program:
|
||||
@@ -129,6 +120,8 @@ class aa_tools:
|
||||
sys.exit(1)
|
||||
|
||||
def cmd_disable(self):
|
||||
apparmor.read_profiles()
|
||||
|
||||
for (program, profile) in self.get_next_to_profile():
|
||||
|
||||
output_name = profile if program is None else program
|
||||
@@ -140,17 +133,13 @@ class aa_tools:
|
||||
aaui.UI_Info(_('Disabling %s.') % output_name)
|
||||
self.disable_profile(profile)
|
||||
|
||||
# FIXME: this should be a profile_remove function/method
|
||||
# FIXME: should ensure profile is loaded before unloading
|
||||
cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-R', profile])
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
self.unload_profile(profile)
|
||||
|
||||
def cmd_enforce(self):
|
||||
apparmor.read_profiles()
|
||||
|
||||
for (program, profile) in self.get_next_to_profile():
|
||||
|
||||
apparmor.read_profiles()
|
||||
output_name = profile if program is None else program
|
||||
|
||||
if not os.path.isfile(profile) or apparmor.is_skippable_file(profile):
|
||||
@@ -159,16 +148,13 @@ class aa_tools:
|
||||
|
||||
apparmor.set_enforce(profile, program)
|
||||
|
||||
# FIXME: this should be a profile_reload function/method
|
||||
cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-r', profile])
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
self.reload_profile(profile)
|
||||
|
||||
def cmd_complain(self):
|
||||
apparmor.read_profiles()
|
||||
|
||||
for (program, profile) in self.get_next_to_profile():
|
||||
|
||||
apparmor.read_profiles()
|
||||
output_name = profile if program is None else program
|
||||
|
||||
if not os.path.isfile(profile) or apparmor.is_skippable_file(profile):
|
||||
@@ -177,16 +163,13 @@ class aa_tools:
|
||||
|
||||
apparmor.set_complain(profile, program)
|
||||
|
||||
# FIXME: this should be a profile_reload function/method
|
||||
cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-r', profile])
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
self.reload_profile(profile)
|
||||
|
||||
def cmd_audit(self):
|
||||
apparmor.read_profiles()
|
||||
|
||||
for (program, profile) in self.get_next_to_profile():
|
||||
|
||||
apparmor.read_profiles()
|
||||
output_name = profile if program is None else program
|
||||
|
||||
if not os.path.isfile(profile) or apparmor.is_skippable_file(profile):
|
||||
@@ -200,20 +183,16 @@ class aa_tools:
|
||||
aaui.UI_Info(_('Removing audit mode from %s.') % output_name)
|
||||
apparmor.change_profile_flags(profile, program, 'audit', not self.remove)
|
||||
|
||||
# FIXME: this should be a profile_reload function/method
|
||||
cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '-r', profile])
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
self.reload_profile(profile)
|
||||
|
||||
def cmd_autodep(self):
|
||||
apparmor.read_profiles()
|
||||
|
||||
for (program, profile) in self.get_next_to_profile():
|
||||
if not program:
|
||||
aaui.UI_Info(_('Please pass an application to generate a profile for, not a profile itself - skipping %s.') % profile)
|
||||
continue
|
||||
|
||||
apparmor.read_profiles()
|
||||
|
||||
apparmor.check_qualifiers(program)
|
||||
|
||||
if os.path.exists(apparmor.get_profile_filename(program)) and not self.force:
|
||||
@@ -263,3 +242,16 @@ class aa_tools:
|
||||
|
||||
def disable_profile(self, filename):
|
||||
apparmor.create_symlink('disable', filename)
|
||||
|
||||
def unload_profile(self, profile):
|
||||
# FIXME: should ensure profile is loaded before unloading
|
||||
cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '--base', apparmor.profile_dir, '-R', profile])
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
|
||||
def reload_profile(self, profile):
|
||||
cmd_info = cmd([apparmor.parser, '-I%s' % apparmor.profile_dir, '--base', apparmor.profile_dir, '-r', profile])
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
|
@@ -86,29 +86,6 @@ class Test(unittest.TestCase):
|
||||
for path in globs.keys():
|
||||
self.assertEqual(apparmor.aa.glob_path_withext(path), globs[path], 'Unexpected glob generated for path: %s'%path)
|
||||
|
||||
def test_parse_event(self):
|
||||
parser = apparmor.logparser.ReadLog('', '', '', '', '')
|
||||
event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30'
|
||||
parsed_event = parser.parse_event(event)
|
||||
self.assertEqual(parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg', 'Incorrectly parsed/decoded name')
|
||||
self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo', 'Incorrectly parsed/decode profile name')
|
||||
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
|
||||
self.assertEqual(parsed_event['request_mask'], set(['w', 'a', '::w', '::a']))
|
||||
#print(parsed_event)
|
||||
|
||||
#event = 'type=AVC msg=audit(1322614912.304:857): apparmor="ALLOWED" operation="getattr" parent=16001 profile=74657374207370616365 name=74657374207370616365 pid=17011 comm="bash" requested_mask="r" denied_mask="r" fsuid=0 ouid=0'
|
||||
#parsed_event = apparmor.aa.parse_event(event)
|
||||
#print(parsed_event)
|
||||
|
||||
event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="rw" denied_mask="rw" fsuid=0 ouid=1000'
|
||||
parsed_event = parser.parse_event(event)
|
||||
self.assertEqual(parsed_event['name'], '/home/foo/.bash_history', 'Incorrectly parsed/decoded name')
|
||||
self.assertEqual(parsed_event['profile'], 'foo bar', 'Incorrectly parsed/decode profile name')
|
||||
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
|
||||
self.assertEqual(parsed_event['request_mask'], set(['r', 'w', 'a','::r' , '::w', '::a']))
|
||||
#print(parsed_event)
|
||||
|
||||
|
||||
def test_modes_to_string(self):
|
||||
|
||||
for string in self.MODE_TEST.keys():
|
||||
|
@@ -1,5 +1,6 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2015 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -12,8 +13,10 @@
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import unittest
|
||||
import inspect
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
import apparmor.common
|
||||
import apparmor.config
|
||||
@@ -34,6 +37,10 @@ class Test(unittest.TestCase):
|
||||
# print("Please press the Y button on the keyboard.")
|
||||
# self.assertEqual(apparmor.common.readkey().lower(), 'y', 'Error reading key from shell!')
|
||||
|
||||
|
||||
class AATest(unittest.TestCase):
|
||||
tests = []
|
||||
|
||||
class AAParseTest(unittest.TestCase):
|
||||
parse_function = None
|
||||
|
||||
@@ -44,6 +51,32 @@ class AAParseTest(unittest.TestCase):
|
||||
'parse object %s returned "%s", expected "%s"' \
|
||||
%(self.parse_function.__doc__, parsed.serialize(), rule))
|
||||
|
||||
def setup_all_loops(module_name):
|
||||
'''call setup_tests_loop() for each class in module_name'''
|
||||
for name, obj in inspect.getmembers(sys.modules[module_name]):
|
||||
if inspect.isclass(obj):
|
||||
if issubclass(obj, unittest.TestCase):
|
||||
setup_tests_loop(obj)
|
||||
|
||||
def setup_tests_loop(test_class):
|
||||
'''Create tests in test_class using test_class.tests and self._run_test()
|
||||
|
||||
test_class.tests should be tuples of (test_data, expected_results)
|
||||
test_data and expected_results can be of any type as long as test_class._run_test()
|
||||
know how to handle them.
|
||||
|
||||
A typical definition for _run_test() is:
|
||||
def test_class._run_test(self, test_data, expected)
|
||||
'''
|
||||
|
||||
for (i, (test_data, expected)) in enumerate(test_class.tests):
|
||||
def stub_test(self, test_data=test_data, expected=expected):
|
||||
self._run_test(test_data, expected)
|
||||
|
||||
stub_test.__doc__ = "test '%s'" % (test_data)
|
||||
setattr(test_class, 'test_%d' % (i), stub_test)
|
||||
|
||||
|
||||
def setup_regex_tests(test_class):
|
||||
'''Create tests in test_class using test_class.tests and AAParseTest._test_parse_rule()
|
||||
|
||||
@@ -63,6 +96,13 @@ def write_file(directory, file, contents):
|
||||
f.write(contents)
|
||||
return path
|
||||
|
||||
def read_file(path):
|
||||
'''read and return file contents'''
|
||||
with open(path, 'r') as f:
|
||||
return f.read()
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.test_RegexParser']
|
||||
unittest.main()
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#! /usr/bin/env python
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2011-2013 Canonical Ltd.
|
||||
# Copyright (C) 2011-2015 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
|
||||
@@ -112,7 +112,8 @@ class T(unittest.TestCase):
|
||||
|
||||
# Copy everything into place
|
||||
for d in ['easyprof/policygroups', 'easyprof/templates']:
|
||||
shutil.copytree(os.path.join(topdir, d), os.path.join(self.tmpdir, os.path.basename(d)))
|
||||
shutil.copytree(os.path.join(topdir, d),
|
||||
os.path.join(self.tmpdir, os.path.basename(d)))
|
||||
|
||||
# Create a test template
|
||||
self.test_template = "test-template"
|
||||
@@ -167,6 +168,17 @@ TEMPLATES_DIR="%s/templates"
|
||||
|
||||
(self.options, self.args) = easyprof.parse_args(self.full_args + [self.binary])
|
||||
|
||||
# Now create some differently prefixed files in the include-dir
|
||||
self.test_include_dir = os.path.join(self.tmpdir, 'include-dir')
|
||||
os.mkdir(self.test_include_dir)
|
||||
os.mkdir(os.path.join(self.test_include_dir, "templates"))
|
||||
os.mkdir(os.path.join(self.test_include_dir, "policygroups"))
|
||||
for d in ['policygroups', 'templates']:
|
||||
for f in easyprof.get_directory_contents(os.path.join(
|
||||
self.tmpdir, d)):
|
||||
shutil.copy(f, os.path.join(self.test_include_dir, d,
|
||||
"inc_%s" % os.path.basename(f)))
|
||||
|
||||
def tearDown(self):
|
||||
'''Teardown for tests'''
|
||||
if os.path.exists(self.tmpdir):
|
||||
@@ -505,6 +517,53 @@ POLICYGROUPS_DIR="%s/templates"
|
||||
self.assertTrue(os.path.exists(path), "Could not find '%s'" % path)
|
||||
open(path).read()
|
||||
|
||||
def test_templates_list_include(self):
|
||||
'''Test templates (list with --include-templates-dir)'''
|
||||
args = self.full_args
|
||||
args.append('--list-templates')
|
||||
(self.options, self.args) = easyprof.parse_args(args)
|
||||
|
||||
easyp = easyprof.AppArmorEasyProfile(None, self.options)
|
||||
orig_templates = easyp.get_templates()
|
||||
|
||||
args = self.full_args
|
||||
args.append('--list-templates')
|
||||
args.append('--include-templates-dir=%s' %
|
||||
os.path.join(self.test_include_dir, 'templates'))
|
||||
(self.options, self.args) = easyprof.parse_args(args)
|
||||
|
||||
easyp = easyprof.AppArmorEasyProfile(None, self.options)
|
||||
inc_templates = easyp.get_templates()
|
||||
self.assertTrue(len(inc_templates) == len(orig_templates) * 2,
|
||||
"templates missing: %s" % inc_templates)
|
||||
|
||||
for i in inc_templates:
|
||||
self.assertTrue(os.path.exists(i), "Could not find '%s'" % i)
|
||||
|
||||
def test_templates_show_include(self):
|
||||
'''Test templates (show with --include-templates-dir)'''
|
||||
files = []
|
||||
for f in glob.glob("%s/templates/*" % self.test_include_dir):
|
||||
files.append(f)
|
||||
|
||||
for f in files:
|
||||
args = self.full_args
|
||||
args += ['--show-template',
|
||||
'--template', f,
|
||||
'--include-templates-dir=%s' %
|
||||
os.path.join(self.test_include_dir, 'templates')]
|
||||
(self.options, self.args) = easyprof.parse_args(args)
|
||||
easyp = easyprof.AppArmorEasyProfile(None, self.options)
|
||||
|
||||
path = os.path.join(easyp.dirs['templates_include'], f)
|
||||
self.assertTrue(os.path.exists(path), "Could not find '%s'" % path)
|
||||
open(path).read()
|
||||
|
||||
bn = os.path.basename(f)
|
||||
# setup() copies everything in the include prefixed with inc_
|
||||
self.assertTrue(bn.startswith('inc_'),
|
||||
"'%s' does not start with 'inc_'" % bn)
|
||||
|
||||
#
|
||||
# Policygroups tests
|
||||
#
|
||||
@@ -515,7 +574,7 @@ POLICYGROUPS_DIR="%s/templates"
|
||||
(self.options, self.args) = easyprof.parse_args(args)
|
||||
|
||||
easyp = easyprof.AppArmorEasyProfile(None, self.options)
|
||||
for i in easyp.get_templates():
|
||||
for i in easyp.get_policy_groups():
|
||||
self.assertTrue(os.path.exists(i), "Could not find '%s'" % i)
|
||||
|
||||
def test_policygroups_show(self):
|
||||
@@ -526,7 +585,8 @@ POLICYGROUPS_DIR="%s/templates"
|
||||
|
||||
for f in files:
|
||||
args = self.full_args
|
||||
args += ['--show-template', '--template', f]
|
||||
args += ['--show-policy-group',
|
||||
'--policy-groups', os.path.basename(f)]
|
||||
(self.options, self.args) = easyprof.parse_args(args)
|
||||
easyp = easyprof.AppArmorEasyProfile(None, self.options)
|
||||
|
||||
@@ -534,6 +594,53 @@ POLICYGROUPS_DIR="%s/templates"
|
||||
self.assertTrue(os.path.exists(path), "Could not find '%s'" % path)
|
||||
open(path).read()
|
||||
|
||||
def test_policygroups_list_include(self):
|
||||
'''Test policygroups (list with --include-policy-groups-dir)'''
|
||||
args = self.full_args
|
||||
args.append('--list-policy-groups')
|
||||
(self.options, self.args) = easyprof.parse_args(args)
|
||||
|
||||
easyp = easyprof.AppArmorEasyProfile(None, self.options)
|
||||
orig_policy_groups = easyp.get_policy_groups()
|
||||
|
||||
args = self.full_args
|
||||
args.append('--list-policy-groups')
|
||||
args.append('--include-policy-groups-dir=%s' %
|
||||
os.path.join(self.test_include_dir, 'policygroups'))
|
||||
(self.options, self.args) = easyprof.parse_args(args)
|
||||
|
||||
easyp = easyprof.AppArmorEasyProfile(None, self.options)
|
||||
inc_policy_groups = easyp.get_policy_groups()
|
||||
self.assertTrue(len(inc_policy_groups) == len(orig_policy_groups) * 2,
|
||||
"policy_groups missing: %s" % inc_policy_groups)
|
||||
|
||||
for i in inc_policy_groups:
|
||||
self.assertTrue(os.path.exists(i), "Could not find '%s'" % i)
|
||||
|
||||
def test_policygroups_show_include(self):
|
||||
'''Test policygroups (show with --include-policy-groups-dir)'''
|
||||
files = []
|
||||
for f in glob.glob("%s/policygroups/*" % self.test_include_dir):
|
||||
files.append(f)
|
||||
|
||||
for f in files:
|
||||
args = self.full_args
|
||||
args += ['--show-policy-group',
|
||||
'--policy-groups', os.path.basename(f),
|
||||
'--include-policy-groups-dir=%s' %
|
||||
os.path.join(self.test_include_dir, 'policygroups')]
|
||||
(self.options, self.args) = easyprof.parse_args(args)
|
||||
easyp = easyprof.AppArmorEasyProfile(None, self.options)
|
||||
|
||||
path = os.path.join(easyp.dirs['policygroups_include'], f)
|
||||
self.assertTrue(os.path.exists(path), "Could not find '%s'" % path)
|
||||
open(path).read()
|
||||
|
||||
bn = os.path.basename(f)
|
||||
# setup() copies everything in the include prefixed with inc_
|
||||
self.assertTrue(bn.startswith('inc_'),
|
||||
"'%s' does not start with 'inc_'" % bn)
|
||||
|
||||
#
|
||||
# Manifest file argument tests
|
||||
#
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#! /usr/bin/env python
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2014 Christian Boltz
|
||||
# Copyright (C) 2014-2015 Christian Boltz
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of version 2 of the GNU General Public
|
||||
@@ -10,14 +10,25 @@
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops
|
||||
import os
|
||||
import shutil
|
||||
import tempfile
|
||||
from common_test import write_file
|
||||
from common_test import read_file, write_file
|
||||
|
||||
from apparmor.aa import check_for_apparmor
|
||||
from apparmor.aa import check_for_apparmor, get_profile_flags, set_profile_flags, is_skippable_file, is_skippable_dir, parse_profile_start, separate_vars, store_list_var, write_header, serialize_parse_profile_start
|
||||
from apparmor.common import AppArmorException, AppArmorBug
|
||||
|
||||
class AaTest_check_for_apparmor(unittest.TestCase):
|
||||
class AaTestWithTempdir(AATest):
|
||||
def setUp(self):
|
||||
self.tmpdir = tempfile.mkdtemp(prefix='aa-py-')
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.exists(self.tmpdir):
|
||||
shutil.rmtree(self.tmpdir)
|
||||
|
||||
|
||||
class AaTest_check_for_apparmor(AaTestWithTempdir):
|
||||
FILESYSTEMS_WITH_SECURITYFS = 'nodev\tdevtmpfs\nnodev\tsecurityfs\nnodev\tsockfs\n\text3\n\text2\n\text4'
|
||||
FILESYSTEMS_WITHOUT_SECURITYFS = 'nodev\tdevtmpfs\nnodev\tsockfs\n\text3\n\text2\n\text4'
|
||||
|
||||
@@ -28,13 +39,6 @@ class AaTest_check_for_apparmor(unittest.TestCase):
|
||||
MOUNTS_WITHOUT_SECURITYFS = ( 'proc /proc proc rw,relatime 0 0\n'
|
||||
'/dev/sda1 / ext3 rw,noatime,data=ordered 0 0' )
|
||||
|
||||
def setUp(self):
|
||||
self.tmpdir = tempfile.mkdtemp(prefix='aa-py-')
|
||||
|
||||
def tearDown(self):
|
||||
if os.path.exists(self.tmpdir):
|
||||
shutil.rmtree(self.tmpdir)
|
||||
|
||||
def test_check_for_apparmor_None_1(self):
|
||||
filesystems = write_file(self.tmpdir, 'filesystems', self.FILESYSTEMS_WITHOUT_SECURITYFS)
|
||||
mounts = write_file(self.tmpdir, 'mounts', self.MOUNTS_WITH_SECURITYFS)
|
||||
@@ -70,6 +74,467 @@ class AaTest_check_for_apparmor(unittest.TestCase):
|
||||
mounts = write_file(self.tmpdir, 'mounts', self.MOUNTS_WITH_SECURITYFS % self.tmpdir)
|
||||
self.assertEqual('%s/security/apparmor' % self.tmpdir, check_for_apparmor(filesystems, mounts))
|
||||
|
||||
class AaTest_get_profile_flags(AaTestWithTempdir):
|
||||
def _test_get_flags(self, profile_header, expected_flags):
|
||||
file = write_file(self.tmpdir, 'profile', '%s {\n}\n' % profile_header)
|
||||
flags = get_profile_flags(file, '/foo')
|
||||
self.assertEqual(flags, expected_flags)
|
||||
|
||||
def test_get_flags_01(self):
|
||||
self._test_get_flags('/foo', None)
|
||||
def test_get_flags_02(self):
|
||||
self._test_get_flags('/foo ( complain )', ' complain ')
|
||||
def test_get_flags_04(self):
|
||||
self._test_get_flags('/foo (complain)', 'complain')
|
||||
def test_get_flags_05(self):
|
||||
self._test_get_flags('/foo flags=(complain)', 'complain')
|
||||
def test_get_flags_06(self):
|
||||
self._test_get_flags('/foo flags=(complain, audit)', 'complain, audit')
|
||||
|
||||
def test_get_flags_invalid_01(self):
|
||||
with self.assertRaises(AppArmorException):
|
||||
self._test_get_flags('/foo ()', None)
|
||||
def test_get_flags_invalid_02(self):
|
||||
with self.assertRaises(AppArmorException):
|
||||
self._test_get_flags('/foo flags=()', None)
|
||||
def test_get_flags_invalid_03(self):
|
||||
with self.assertRaises(AppArmorException):
|
||||
self._test_get_flags('/foo ( )', ' ')
|
||||
|
||||
def test_get_flags_other_profile(self):
|
||||
with self.assertRaises(AppArmorException):
|
||||
self._test_get_flags('/no-such-profile flags=(complain)', 'complain')
|
||||
|
||||
class AaTest_set_profile_flags(AaTestWithTempdir):
|
||||
def _test_set_flags(self, profile, old_flags, new_flags, whitespace='', comment='', more_rules='',
|
||||
expected_flags='@-@-@', check_new_flags=True, profile_name='/foo'):
|
||||
if old_flags:
|
||||
old_flags = ' %s' % old_flags
|
||||
|
||||
if expected_flags == '@-@-@':
|
||||
expected_flags = new_flags
|
||||
|
||||
if expected_flags:
|
||||
expected_flags = ' flags=(%s)' % (expected_flags)
|
||||
else:
|
||||
expected_flags = ''
|
||||
|
||||
if comment:
|
||||
comment = ' %s' % comment
|
||||
|
||||
dummy_profile_content = ' #include <abstractions/base>\n capability chown,\n /bar r,'
|
||||
prof_template = '%s%s%s {%s\n%s\n%s\n}\n'
|
||||
old_prof = prof_template % (whitespace, profile, old_flags, comment, more_rules, dummy_profile_content)
|
||||
new_prof = prof_template % (whitespace, profile, expected_flags, comment, more_rules, dummy_profile_content)
|
||||
|
||||
self.file = write_file(self.tmpdir, 'profile', old_prof)
|
||||
set_profile_flags(self.file, profile_name, new_flags)
|
||||
if check_new_flags:
|
||||
real_new_prof = read_file(self.file)
|
||||
self.assertEqual(new_prof, real_new_prof)
|
||||
|
||||
# tests that actually don't change the flags
|
||||
def test_set_flags_nochange_01(self):
|
||||
self._test_set_flags('/foo', '', '')
|
||||
def test_set_flags_nochange_02(self):
|
||||
self._test_set_flags('/foo', '( complain )', ' complain ', whitespace=' ')
|
||||
def test_set_flags_nochange_03(self):
|
||||
self._test_set_flags('/foo', '(complain)', 'complain')
|
||||
def test_set_flags_nochange_04(self):
|
||||
self._test_set_flags('/foo', 'flags=(complain)', 'complain')
|
||||
def test_set_flags_nochange_05(self):
|
||||
self._test_set_flags('/foo', 'flags=(complain, audit)', 'complain, audit', whitespace=' ')
|
||||
def test_set_flags_nochange_06(self):
|
||||
self._test_set_flags('/foo', 'flags=(complain, audit)', 'complain, audit', whitespace=' ', comment='# a comment')
|
||||
def test_set_flags_nochange_07(self):
|
||||
self._test_set_flags('/foo', 'flags=(complain, audit)', 'complain, audit', whitespace=' ', more_rules=' # a comment\n#another comment')
|
||||
def test_set_flags_nochange_08(self):
|
||||
self._test_set_flags('profile /foo', 'flags=(complain)', 'complain')
|
||||
def test_set_flags_nochange_09(self):
|
||||
self._test_set_flags('profile xy /foo', 'flags=(complain)', 'complain', profile_name='xy')
|
||||
def test_set_flags_nochange_10(self):
|
||||
self._test_set_flags('profile "/foo bar"', 'flags=(complain)', 'complain', profile_name='/foo bar')
|
||||
def test_set_flags_nochange_11(self):
|
||||
self._test_set_flags('/foo', '(complain)', 'complain', profile_name=None)
|
||||
#def test_set_flags_nochange_12(self):
|
||||
# XXX changes the flags for the child profile (which happens to have the same profile name) to 'complain'
|
||||
# self._test_set_flags('/foo', 'flags=(complain)', 'complain', more_rules=' profile /foo {\n}')
|
||||
|
||||
# tests that change the flags
|
||||
def test_set_flags_01(self):
|
||||
self._test_set_flags('/foo', '', 'audit')
|
||||
def test_set_flags_02(self):
|
||||
self._test_set_flags('/foo', '( complain )', 'audit ', whitespace=' ')
|
||||
def test_set_flags_04(self):
|
||||
self._test_set_flags('/foo', '(complain)', 'audit')
|
||||
def test_set_flags_05(self):
|
||||
self._test_set_flags('/foo', 'flags=(complain)', 'audit')
|
||||
def test_set_flags_06(self):
|
||||
self._test_set_flags('/foo', 'flags=(complain, audit)', None, whitespace=' ')
|
||||
def test_set_flags_07(self):
|
||||
self._test_set_flags('/foo', 'flags=(complain, audit)', '', expected_flags=None)
|
||||
def test_set_flags_08(self):
|
||||
self._test_set_flags('/foo', '( complain )', 'audit ', whitespace=' ', profile_name=None)
|
||||
def test_set_flags_09(self):
|
||||
self._test_set_flags('profile /foo', 'flags=(complain)', 'audit')
|
||||
def test_set_flags_10(self):
|
||||
self._test_set_flags('profile xy /foo', 'flags=(complain)', 'audit', profile_name='xy')
|
||||
def test_set_flags_11(self):
|
||||
self._test_set_flags('profile "/foo bar"', 'flags=(complain)', 'audit', profile_name='/foo bar')
|
||||
def test_set_flags_12(self):
|
||||
self._test_set_flags('profile xy "/foo bar"', 'flags=(complain)', 'audit', profile_name='xy')
|
||||
|
||||
|
||||
# XXX regex_hat_flag in set_profile_flags() is totally broken - it matches for ' ' and ' X ', but doesn't match for ' ^foo {'
|
||||
# oh, it matches on a line like 'dbus' and changes it to 'dbus flags=(...)' if there's no leading whitespace (and no comma)
|
||||
#def test_set_flags_hat_01(self):
|
||||
# self._test_set_flags(' ^hat', '', 'audit')
|
||||
|
||||
|
||||
def test_set_flags_invalid_01(self):
|
||||
with self.assertRaises(AppArmorBug):
|
||||
self._test_set_flags('/foo', '()', None, check_new_flags=False)
|
||||
def test_set_flags_invalid_02(self):
|
||||
with self.assertRaises(AppArmorBug):
|
||||
self._test_set_flags('/foo', 'flags=()', None, check_new_flags=False)
|
||||
def test_set_flags_invalid_03(self):
|
||||
with self.assertRaises(AppArmorException):
|
||||
self._test_set_flags('/foo', '( )', '', check_new_flags=False)
|
||||
def test_set_flags_invalid_04(self):
|
||||
with self.assertRaises(AppArmorBug):
|
||||
self._test_set_flags('/foo', 'flags=(complain, audit)', ' ', check_new_flags=False) # whitespace-only newflags
|
||||
|
||||
def test_set_flags_other_profile(self):
|
||||
# test behaviour if the file doesn't contain the specified /foo profile
|
||||
orig_prof = '/no-such-profile flags=(complain) {\n}'
|
||||
self.file = write_file(self.tmpdir, 'profile', orig_prof)
|
||||
|
||||
with self.assertRaises(AppArmorBug):
|
||||
set_profile_flags(self.file, '/foo', 'audit')
|
||||
|
||||
# the file should not be changed
|
||||
real_new_prof = read_file(self.file)
|
||||
self.assertEqual(orig_prof, real_new_prof)
|
||||
|
||||
def test_set_flags_no_profile_found(self):
|
||||
# test behaviour if the file doesn't contain any profile
|
||||
orig_prof = '# /comment flags=(complain) {\n# }'
|
||||
self.file = write_file(self.tmpdir, 'profile', orig_prof)
|
||||
|
||||
with self.assertRaises(AppArmorBug):
|
||||
set_profile_flags(self.file, None, 'audit')
|
||||
|
||||
# the file should not be changed
|
||||
real_new_prof = read_file(self.file)
|
||||
self.assertEqual(orig_prof, real_new_prof)
|
||||
|
||||
def test_set_flags_file_not_found(self):
|
||||
with self.assertRaises(IOError):
|
||||
set_profile_flags('%s/file-not-found' % self.tmpdir, '/foo', 'audit')
|
||||
|
||||
|
||||
class AaTest_is_skippable_file(AATest):
|
||||
def test_not_skippable_01(self):
|
||||
self.assertFalse(is_skippable_file('bin.ping'))
|
||||
def test_not_skippable_02(self):
|
||||
self.assertFalse(is_skippable_file('usr.lib.dovecot.anvil'))
|
||||
def test_not_skippable_03(self):
|
||||
self.assertFalse(is_skippable_file('bin.~ping'))
|
||||
def test_not_skippable_04(self):
|
||||
self.assertFalse(is_skippable_file('bin.rpmsave.ping'))
|
||||
def test_not_skippable_05(self):
|
||||
# normally is_skippable_file should be called without directory, but it shouldn't hurt too much
|
||||
self.assertFalse(is_skippable_file('/etc/apparmor.d/bin.ping'))
|
||||
def test_not_skippable_06(self):
|
||||
self.assertFalse(is_skippable_file('bin.pingrej'))
|
||||
|
||||
def test_skippable_01(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping.dpkg-new'))
|
||||
def test_skippable_02(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping.dpkg-old'))
|
||||
def test_skippable_03(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping..dpkg-dist'))
|
||||
def test_skippable_04(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping..dpkg-bak'))
|
||||
def test_skippable_05(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping.rpmnew'))
|
||||
def test_skippable_06(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping.rpmsave'))
|
||||
def test_skippable_07(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping.orig'))
|
||||
def test_skippable_08(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping.rej'))
|
||||
def test_skippable_09(self):
|
||||
self.assertTrue(is_skippable_file('bin.ping~'))
|
||||
def test_skippable_10(self):
|
||||
self.assertTrue(is_skippable_file('.bin.ping'))
|
||||
def test_skippable_11(self):
|
||||
self.assertTrue(is_skippable_file('')) # empty filename
|
||||
def test_skippable_12(self):
|
||||
self.assertTrue(is_skippable_file('/etc/apparmor.d/')) # directory without filename
|
||||
def test_skippable_13(self):
|
||||
self.assertTrue(is_skippable_file('README'))
|
||||
|
||||
|
||||
class AaTest_is_skippable_dir(AATest):
|
||||
tests = [
|
||||
('disable', True),
|
||||
('cache', True),
|
||||
('lxc', True),
|
||||
('force-complain', True),
|
||||
('/etc/apparmor.d/cache', True),
|
||||
('/etc/apparmor.d/lxc/', True),
|
||||
|
||||
('dont_disable', False),
|
||||
('/etc/apparmor.d/cache_foo', False),
|
||||
('abstractions', False),
|
||||
('apache2.d', False),
|
||||
('/etc/apparmor.d/apache2.d', False),
|
||||
('local', False),
|
||||
('/etc/apparmor.d/local/', False),
|
||||
('tunables', False),
|
||||
('/etc/apparmor.d/tunables', False),
|
||||
('/etc/apparmor.d/tunables/multiarch.d', False),
|
||||
('/etc/apparmor.d/tunables/xdg-user-dirs.d', False),
|
||||
('/etc/apparmor.d/tunables/home.d', False),
|
||||
('/etc/apparmor.d/abstractions', False),
|
||||
('/etc/apparmor.d/abstractions/ubuntu-browsers.d', False),
|
||||
('/etc/apparmor.d/abstractions/apparmor_api', False),
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
self.assertEqual(is_skippable_dir(params), expected)
|
||||
|
||||
class AaTest_parse_profile_start(AATest):
|
||||
def _parse(self, line, profile, hat):
|
||||
return parse_profile_start(line, 'somefile', 1, profile, hat)
|
||||
# (profile, hat, flags, in_contained_hat, pps_set_profile, pps_set_hat_external)
|
||||
|
||||
def test_parse_profile_start_01(self):
|
||||
result = self._parse('/foo {', None, None)
|
||||
expected = ('/foo', '/foo', None, None, False, False, False)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_parse_profile_start_02(self):
|
||||
result = self._parse('/foo (complain) {', None, None)
|
||||
expected = ('/foo', '/foo', None, 'complain', False, False, False)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_parse_profile_start_03(self):
|
||||
result = self._parse('profile foo /foo {', None, None) # named profile
|
||||
expected = ('foo', 'foo', '/foo', None, False, False, False)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_parse_profile_start_04(self):
|
||||
result = self._parse('profile /foo {', '/bar', '/bar') # child profile
|
||||
expected = ('/bar', '/foo', None, None, True, True, False)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_parse_profile_start_05(self):
|
||||
result = self._parse('/foo//bar {', None, None) # external hat
|
||||
expected = ('/foo', 'bar', None, None, False, False, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_parse_profile_start_06(self):
|
||||
result = self._parse('profile "/foo" (complain) {', None, None)
|
||||
expected = ('/foo', '/foo', None, 'complain', False, False, False)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
|
||||
def test_parse_profile_start_invalid_01(self):
|
||||
with self.assertRaises(AppArmorException):
|
||||
self._parse('/foo {', '/bar', '/bar') # child profile without profile keyword
|
||||
|
||||
def test_parse_profile_start_invalid_02(self):
|
||||
with self.assertRaises(AppArmorBug):
|
||||
self._parse('xy', '/bar', '/bar') # not a profile start
|
||||
|
||||
class AaTest_separate_vars(AATest):
|
||||
tests = [
|
||||
('' , set() ),
|
||||
(' ' , set() ),
|
||||
(' foo bar' , {'foo', 'bar' }),
|
||||
('foo " ' , {'foo' }), # XXX " is ignored
|
||||
(' " foo ' , {' "', 'foo' }), # XXX really?
|
||||
(' foo bar ' , {'foo', 'bar' }),
|
||||
(' foo bar # comment' , {'foo', 'bar', 'comment' }), # XXX should comments be stripped?
|
||||
('foo' , {'foo' }),
|
||||
('"foo" "bar baz"' , {'foo', 'bar baz' }),
|
||||
('foo "bar baz" xy' , {'foo', 'bar baz', 'xy' }),
|
||||
('foo "bar baz ' , {'foo', 'bar', 'baz' }), # half-quoted
|
||||
(' " foo" bar' , {' foo', 'bar' }),
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
result = separate_vars(params)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
|
||||
class AaTest_store_list_var(AATest):
|
||||
tests = [
|
||||
# old var value operation expected (False for exception)
|
||||
([ {} , 'foo' , '=' ], {'foo'} ), # set
|
||||
([ {} , 'foo bar' , '=' ], {'foo', 'bar'} ), # set multi
|
||||
([ {'@{var}': {'foo'}} , 'bar' , '=' ], False ), # redefine var
|
||||
([ {} , 'bar' , '+=' ], False ), # add to undefined var
|
||||
([ {'@{var}': {'foo'}} , 'bar' , '+=' ], {'foo', 'bar'} ), # add
|
||||
([ {'@{var}': {'foo'}} , 'bar baz' , '+=' ], {'foo', 'bar', 'baz'} ), # add multi
|
||||
([ {'@{var}': {'foo', 'xy'}} , 'bar baz' , '+=' ], {'foo', 'xy', 'bar', 'baz'} ), # add multi to multi
|
||||
([ {} , 'foo' , '-=' ], False ), # unknown operation
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
var = params[0]
|
||||
value = params[1]
|
||||
operation = params[2]
|
||||
|
||||
if not expected:
|
||||
with self.assertRaises(AppArmorException):
|
||||
store_list_var(var, '@{var}', value, operation, 'somefile')
|
||||
return
|
||||
|
||||
# dumy value that must not be changed
|
||||
var['@{foo}'] = {'one', 'two'}
|
||||
|
||||
exp_var = {
|
||||
'@{foo}': {'one', 'two'},
|
||||
'@{var}': expected,
|
||||
}
|
||||
|
||||
store_list_var(var, '@{var}', value, operation, 'somefile')
|
||||
|
||||
self.assertEqual(var.keys(), exp_var.keys())
|
||||
|
||||
for key in exp_var:
|
||||
self.assertEqual(var[key], exp_var[key])
|
||||
|
||||
|
||||
class AaTest_write_header(AATest):
|
||||
tests = [
|
||||
# name embedded_hat write_flags depth flags attachment prof.keyw. comment expected
|
||||
(['/foo', False, True, 1, 'complain', None, None, None ], ' /foo flags=(complain) {'),
|
||||
(['/foo', True, True, 1, 'complain', None, None, None ], ' profile /foo flags=(complain) {'),
|
||||
(['/foo sp', False, False, 2, 'complain', None, None, None ], ' "/foo sp" {'),
|
||||
(['/foo' ,False, False, 2, 'complain', None, None, None ], ' /foo {'),
|
||||
(['/foo', True, False, 2, 'complain', None, None, None ], ' profile /foo {'),
|
||||
(['/foo', False, True, 0, None, None, None, None ], '/foo {'),
|
||||
(['/foo', True, True, 0, None, None, None, None ], 'profile /foo {'),
|
||||
(['/foo', False, False, 0, None, None, None, None ], '/foo {'),
|
||||
(['/foo', True, False, 0, None, None, None, None ], 'profile /foo {'),
|
||||
(['bar', False, True, 1, 'complain', None, None, None ], ' profile bar flags=(complain) {'),
|
||||
(['bar', False, True, 1, 'complain', '/foo', None, None ], ' profile bar /foo flags=(complain) {'),
|
||||
(['bar', True, True, 1, 'complain', '/foo', None, None ], ' profile bar /foo flags=(complain) {'),
|
||||
(['bar baz', False, True, 1, None, '/foo', None, None ], ' profile "bar baz" /foo {'),
|
||||
(['bar', True, True, 1, None, '/foo', None, None ], ' profile bar /foo {'),
|
||||
(['bar baz', False, True, 1, 'complain', '/foo sp', None, None ], ' profile "bar baz" "/foo sp" flags=(complain) {'),
|
||||
(['^foo', False, True, 1, 'complain', None, None, None ], ' profile ^foo flags=(complain) {'),
|
||||
(['^foo', True, True, 1, 'complain', None, None, None ], ' ^foo flags=(complain) {'),
|
||||
(['^foo', True, True, 1.5, 'complain', None, None, None ], ' ^foo flags=(complain) {'),
|
||||
(['^foo', True, True, 1.3, 'complain', None, None, None ], ' ^foo flags=(complain) {'),
|
||||
(['/foo', False, True, 1, 'complain', None, 'profile', None ], ' profile /foo flags=(complain) {'),
|
||||
(['/foo', True, True, 1, 'complain', None, 'profile', None ], ' profile /foo flags=(complain) {'),
|
||||
(['/foo', False, True, 1, 'complain', None, None, '# x' ], ' /foo flags=(complain) { # x'),
|
||||
(['/foo', True, True, 1, None, None, None, '# x' ], ' profile /foo { # x'),
|
||||
(['/foo', False, True, 1, None, None, 'profile', '# x' ], ' profile /foo { # x'),
|
||||
(['/foo', True, True, 1, 'complain', None, 'profile', '# x' ], ' profile /foo flags=(complain) { # x'),
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
name = params[0]
|
||||
embedded_hat = params[1]
|
||||
write_flags = params[2]
|
||||
depth = params[3]
|
||||
prof_data = { 'flags': params[4], 'attachment': params[5], 'profile_keyword': params[6], 'header_comment': params[7] }
|
||||
|
||||
result = write_header(prof_data, depth, name, embedded_hat, write_flags)
|
||||
self.assertEqual(result, [expected])
|
||||
|
||||
class AaTest_serialize_parse_profile_start(AATest):
|
||||
def _parse(self, line, profile, hat, prof_data_profile, prof_data_external):
|
||||
# 'correct' is always True in the code that uses serialize_parse_profile_start() (set some lines above the function call)
|
||||
return serialize_parse_profile_start(line, 'somefile', 1, profile, hat, prof_data_profile, prof_data_external, True)
|
||||
|
||||
def test_serialize_parse_profile_start_01(self):
|
||||
result = self._parse('/foo {', None, None, False, False)
|
||||
expected = ('/foo', '/foo', None, None, False, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_02(self):
|
||||
result = self._parse('/foo (complain) {', None, None, False, False)
|
||||
expected = ('/foo', '/foo', None, 'complain', False, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_03(self):
|
||||
result = self._parse('profile foo /foo {', None, None, False, False) # named profile
|
||||
expected = ('foo', 'foo', '/foo', None, False, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_04(self):
|
||||
result = self._parse('profile /foo {', '/bar', '/bar', False, False) # child profile
|
||||
expected = ('/bar', '/foo', None, None, True, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_05(self):
|
||||
result = self._parse('/foo//bar {', None, None, False, False) # external hat
|
||||
expected = ('/foo', 'bar', None, None, False, False) # note correct == False here
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_06(self):
|
||||
result = self._parse('profile "/foo" (complain) {', None, None, False, False)
|
||||
expected = ('/foo', '/foo', None, 'complain', False, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_07(self):
|
||||
result = self._parse('/foo {', None, None, True, False)
|
||||
expected = ('/foo', '/foo', None, None, False, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_08(self):
|
||||
result = self._parse('/foo {', None, None, False, True)
|
||||
expected = ('/foo', '/foo', None, None, False, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_09(self):
|
||||
result = self._parse('/foo {', None, None, True, True)
|
||||
expected = ('/foo', '/foo', None, None, False, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_10(self):
|
||||
result = self._parse('profile /foo {', '/bar', '/bar', True, False) # child profile
|
||||
expected = ('/bar', '/foo', None, None, True, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_11(self):
|
||||
result = self._parse('profile /foo {', '/bar', '/bar', False, True) # child profile
|
||||
expected = ('/bar', '/foo', None, None, True, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
def test_serialize_parse_profile_start_12(self):
|
||||
result = self._parse('profile /foo {', '/bar', '/bar', True, True) # child profile
|
||||
expected = ('/bar', '/foo', None, None, True, True)
|
||||
self.assertEqual(result, expected)
|
||||
|
||||
class AaTestInvalid_serialize_parse_profile_start(AATest):
|
||||
tests = [
|
||||
# line profile hat p_d_profile p_d_external expected
|
||||
(['/foo {', '/bar', '/bar', False, False ], AppArmorException), # child profile without 'profile' keyword
|
||||
(['profile /foo {', '/bar', '/xy', False, False ], AppArmorException), # already inside a child profile - nesting level reached
|
||||
(['/ext//hat {', '/bar', '/bar', True, True ], AppArmorException), # external hat inside a profile
|
||||
(['/ext//hat {', '/bar', '/bar', True, False ], AppArmorException), # external hat inside a profile
|
||||
(['xy', '/bar', '/bar', False, False ], AppArmorBug ), # not a profile start
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
line = params[0]
|
||||
profile = params[1]
|
||||
hat = params[2]
|
||||
prof_data_profile = params[3]
|
||||
prof_data_external = params[4]
|
||||
|
||||
with self.assertRaises(expected):
|
||||
# 'correct' is always True in the code that uses serialize_parse_profile_start() (set some lines above the function call)
|
||||
serialize_parse_profile_start(line, 'somefile', 1, profile, hat, prof_data_profile, prof_data_external, True)
|
||||
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
||||
|
45
utils/test/test-example.py
Normal file
45
utils/test/test-example.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#! /usr/bin/env python
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2015 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops
|
||||
|
||||
class TestFoo(AATest):
|
||||
tests = [
|
||||
(0, 0 ),
|
||||
(42, 42),
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
self.assertEqual(params, expected)
|
||||
|
||||
class TestBar(AATest):
|
||||
tests = [
|
||||
('a', 'foo'),
|
||||
('b', 'bar'),
|
||||
('c', 'baz'),
|
||||
]
|
||||
|
||||
def _run_test(self, params, expected):
|
||||
self.assertNotEqual(params, expected)
|
||||
|
||||
def testAdditionalBarTest(self):
|
||||
self.assertEqual(1, 1)
|
||||
|
||||
class TestBaz(AATest):
|
||||
def test_Baz_only_one_test(self):
|
||||
self.assertEqual("baz", "baz")
|
||||
|
||||
|
||||
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
unittest.main(verbosity=2)
|
99
utils/test/test-logparser.py
Normal file
99
utils/test/test-logparser.py
Normal file
@@ -0,0 +1,99 @@
|
||||
# ----------------------------------------------------------------------
|
||||
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
|
||||
# Copyright (C) 2015 Christian Boltz <apparmor@cboltz.de>
|
||||
#
|
||||
# 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 as 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.
|
||||
#
|
||||
# ----------------------------------------------------------------------
|
||||
import unittest
|
||||
|
||||
from apparmor.logparser import ReadLog
|
||||
|
||||
class TestParseEvent(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.parser = ReadLog('', '', '', '', '')
|
||||
|
||||
def test_parse_event_audit_1(self):
|
||||
event = 'type=AVC msg=audit(1345027352.096:499): apparmor="ALLOWED" operation="rename_dest" parent=6974 profile="/usr/sbin/httpd2-prefork//vhost_foo" name=2F686F6D652F7777772F666F6F2E6261722E696E2F68747470646F63732F61707061726D6F722F696D616765732F746573742F696D61676520312E6A7067 pid=20143 comm="httpd2-prefork" requested_mask="wc" denied_mask="wc" fsuid=30 ouid=30'
|
||||
parsed_event = self.parser.parse_event(event)
|
||||
self.assertEqual(parsed_event['name'], '/home/www/foo.bar.in/httpdocs/apparmor/images/test/image 1.jpg')
|
||||
self.assertEqual(parsed_event['profile'], '/usr/sbin/httpd2-prefork//vhost_foo')
|
||||
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
|
||||
self.assertEqual(parsed_event['request_mask'], 'wc')
|
||||
|
||||
self.assertIsNotNone(ReadLog.RE_LOG_v2_6_audit.search(event))
|
||||
self.assertIsNone(ReadLog.RE_LOG_v2_6_syslog.search(event))
|
||||
|
||||
def test_parse_event_audit_2(self):
|
||||
event = 'type=AVC msg=audit(1322614918.292:4376): apparmor="ALLOWED" operation="file_perm" parent=16001 profile=666F6F20626172 name="/home/foo/.bash_history" pid=17011 comm="bash" requested_mask="rw" denied_mask="rw" fsuid=0 ouid=1000'
|
||||
parsed_event = self.parser.parse_event(event)
|
||||
self.assertEqual(parsed_event['name'], '/home/foo/.bash_history')
|
||||
self.assertEqual(parsed_event['profile'], 'foo bar')
|
||||
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
|
||||
self.assertEqual(parsed_event['request_mask'], 'rw')
|
||||
|
||||
self.assertIsNotNone(ReadLog.RE_LOG_v2_6_audit.search(event))
|
||||
self.assertIsNone(ReadLog.RE_LOG_v2_6_syslog.search(event))
|
||||
|
||||
def test_parse_event_syslog_1(self):
|
||||
# from https://bugs.launchpad.net/apparmor/+bug/1399027
|
||||
event = '2014-06-09T20:37:28.975070+02:00 geeko kernel: [21028.143765] type=1400 audit(1402339048.973:1421): apparmor="ALLOWED" operation="open" profile="/home/cb/linuxtag/apparmor/scripts/hello" name="/dev/tty" pid=14335 comm="hello" requested_mask="rw" denied_mask="rw" fsuid=1000 ouid=0'
|
||||
parsed_event = self.parser.parse_event(event)
|
||||
self.assertEqual(parsed_event['name'], '/dev/tty')
|
||||
self.assertEqual(parsed_event['profile'], '/home/cb/linuxtag/apparmor/scripts/hello')
|
||||
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
|
||||
self.assertEqual(parsed_event['request_mask'], 'rw')
|
||||
|
||||
self.assertIsNone(ReadLog.RE_LOG_v2_6_audit.search(event))
|
||||
self.assertIsNotNone(ReadLog.RE_LOG_v2_6_syslog.search(event))
|
||||
|
||||
def test_parse_event_syslog_2(self):
|
||||
# from https://bugs.launchpad.net/apparmor/+bug/1399027
|
||||
event = 'Dec 7 13:18:59 rosa kernel: audit: type=1400 audit(1417954745.397:82): apparmor="ALLOWED" operation="open" profile="/home/simi/bin/aa-test" name="/usr/bin/" pid=3231 comm="ls" requested_mask="r" denied_mask="r" fsuid=1000 ouid=0'
|
||||
parsed_event = self.parser.parse_event(event)
|
||||
self.assertEqual(parsed_event['name'], '/usr/bin/')
|
||||
self.assertEqual(parsed_event['profile'], '/home/simi/bin/aa-test')
|
||||
self.assertEqual(parsed_event['aamode'], 'PERMITTING')
|
||||
self.assertEqual(parsed_event['request_mask'], 'r')
|
||||
|
||||
self.assertIsNone(ReadLog.RE_LOG_v2_6_audit.search(event))
|
||||
self.assertIsNotNone(ReadLog.RE_LOG_v2_6_syslog.search(event))
|
||||
|
||||
def test_parse_disconnected_path(self):
|
||||
# from https://bugzilla.opensuse.org/show_bug.cgi?id=918787
|
||||
event = 'type=AVC msg=audit(1424425690.883:716630): apparmor="ALLOWED" operation="file_mmap" info="Failed name lookup - disconnected path" error=-13 profile="/sbin/klogd" name="var/run/nscd/passwd" pid=25333 comm="id" requested_mask="r" denied_mask="r" fsuid=1002 ouid=0'
|
||||
parsed_event = self.parser.parse_event(event)
|
||||
|
||||
self.assertEqual(parsed_event, {
|
||||
'aamode': 'ERROR', # aamode for disconnected paths overridden aamode in parse_event()
|
||||
'active_hat': None,
|
||||
'attr': None,
|
||||
'denied_mask': 'r',
|
||||
'error_code': 13,
|
||||
'info': 'Failed name lookup - disconnected path',
|
||||
'magic_token': 0,
|
||||
'name': 'var/run/nscd/passwd',
|
||||
'name2': None,
|
||||
'operation': 'file_mmap',
|
||||
'parent': 0,
|
||||
'pid': 25333,
|
||||
'profile': '/sbin/klogd',
|
||||
'request_mask': 'r',
|
||||
'resource': 'Failed name lookup - disconnected path',
|
||||
'task': 0,
|
||||
'time': 1424425690
|
||||
})
|
||||
|
||||
self.assertIsNotNone(ReadLog.RE_LOG_v2_6_audit.search(event))
|
||||
self.assertIsNone(ReadLog.RE_LOG_v2_6_syslog.search(event))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
@@ -11,9 +11,41 @@
|
||||
|
||||
import apparmor.aa as aa
|
||||
import unittest
|
||||
from common_test import AATest, setup_all_loops
|
||||
from apparmor.common import AppArmorBug
|
||||
|
||||
from apparmor.regex import strip_quotes, parse_profile_start_line, RE_PROFILE_START
|
||||
|
||||
|
||||
class AARegexHasComma(unittest.TestCase):
|
||||
class AARegexTest(AATest):
|
||||
def _run_test(self, params, expected):
|
||||
return _regex_test(self, params, expected)
|
||||
|
||||
class AANamedRegexTest(AATest):
|
||||
def _run_test(self, line, expected):
|
||||
'''Run a line through self.regex.search() and verify the result
|
||||
|
||||
Keyword arguments:
|
||||
line -- the line to search
|
||||
expected -- False if the search isn't expected to match or, if the search
|
||||
is expected to match, a tuple of expected match groups.
|
||||
'''
|
||||
matches = self.regex.search(line)
|
||||
if not expected:
|
||||
self.assertFalse(matches)
|
||||
return
|
||||
|
||||
self.assertTrue(matches)
|
||||
|
||||
for exp in expected:
|
||||
match = matches.group(exp)
|
||||
if match:
|
||||
match = match
|
||||
self.assertEqual(match, expected[exp], 'Group %s mismatch in rule %s' % (exp,line))
|
||||
|
||||
|
||||
|
||||
class AARegexHasComma(AATest):
|
||||
'''Tests for apparmor.aa.RE_RULE_HAS_COMMA'''
|
||||
|
||||
def _check(self, line, expected=True):
|
||||
@@ -93,7 +125,7 @@ def setup_has_comma_testcases():
|
||||
setattr(AARegexHasComma, 'test_comma_%d' % (i), stub_test_comma)
|
||||
setattr(AARegexHasComma, 'test_no_comma_%d' % (i), stub_test_no_comma)
|
||||
|
||||
class AARegexSplitComment(unittest.TestCase):
|
||||
class AARegexSplitComment(AATest):
|
||||
'''Tests for RE_HAS_COMMENT_SPLIT'''
|
||||
|
||||
def _check(self, line, expected, comment=None, not_comment=None):
|
||||
@@ -141,7 +173,7 @@ def setup_split_comment_testcases():
|
||||
setattr(AARegexSplitComment, 'test_split_comment_%d' % (i), stub_test)
|
||||
|
||||
|
||||
def regex_test(self, line, expected):
|
||||
def _regex_test(self, line, expected):
|
||||
'''Run a line through self.regex.search() and verify the result
|
||||
|
||||
Keyword arguments:
|
||||
@@ -165,23 +197,10 @@ def regex_test(self, line, expected):
|
||||
self.assertEqual(group, expected[i], 'Group %d mismatch in rule %s' % (i,line))
|
||||
|
||||
|
||||
def setup_regex_tests(test_class):
|
||||
'''Create tests in test_class using test_class.tests and regex_tests()
|
||||
|
||||
test_class.tests should be tuples of (line, expected_results) where
|
||||
expected_results is False if test_class.regex.search(line) should not
|
||||
match. If the search should match, expected_results should be a tuple of
|
||||
the expected groups, with all of the strings stripped.
|
||||
'''
|
||||
for (i, (line, expected)) in enumerate(test_class.tests):
|
||||
def stub_test(self, line=line, expected=expected):
|
||||
regex_test(self, line, expected)
|
||||
|
||||
stub_test.__doc__ = "test '%s'" % (line)
|
||||
setattr(test_class, 'test_%d' % (i), stub_test)
|
||||
|
||||
|
||||
class AARegexCapability(unittest.TestCase):
|
||||
|
||||
class AARegexCapability(AARegexTest):
|
||||
'''Tests for RE_PROFILE_CAP'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -196,7 +215,7 @@ class AARegexCapability(unittest.TestCase):
|
||||
]
|
||||
|
||||
|
||||
class AARegexPath(unittest.TestCase):
|
||||
class AARegexPath(AARegexTest):
|
||||
'''Tests for RE_PROFILE_PATH_ENTRY'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -215,7 +234,7 @@ class AARegexPath(unittest.TestCase):
|
||||
]
|
||||
|
||||
|
||||
class AARegexBareFile(unittest.TestCase):
|
||||
class AARegexBareFile(AARegexTest):
|
||||
'''Tests for RE_PROFILE_BARE_FILE_ENTRY'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -233,7 +252,7 @@ class AARegexBareFile(unittest.TestCase):
|
||||
]
|
||||
|
||||
|
||||
class AARegexDbus(unittest.TestCase):
|
||||
class AARegexDbus(AARegexTest):
|
||||
'''Tests for RE_PROFILE_DBUS'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -249,7 +268,7 @@ class AARegexDbus(unittest.TestCase):
|
||||
(' audit dbusdriver,', False),
|
||||
]
|
||||
|
||||
class AARegexMount(unittest.TestCase):
|
||||
class AARegexMount(AARegexTest):
|
||||
'''Tests for RE_PROFILE_MOUNT'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -273,7 +292,7 @@ class AARegexMount(unittest.TestCase):
|
||||
|
||||
|
||||
|
||||
class AARegexSignal(unittest.TestCase):
|
||||
class AARegexSignal(AARegexTest):
|
||||
'''Tests for RE_PROFILE_SIGNAL'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -299,7 +318,7 @@ class AARegexSignal(unittest.TestCase):
|
||||
]
|
||||
|
||||
|
||||
class AARegexPtrace(unittest.TestCase):
|
||||
class AARegexPtrace(AARegexTest):
|
||||
'''Tests for RE_PROFILE_PTRACE'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -321,7 +340,7 @@ class AARegexPtrace(unittest.TestCase):
|
||||
]
|
||||
|
||||
|
||||
class AARegexPivotRoot(unittest.TestCase):
|
||||
class AARegexPivotRoot(AARegexTest):
|
||||
'''Tests for RE_PROFILE_PIVOT_ROOT'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -348,7 +367,7 @@ class AARegexPivotRoot(unittest.TestCase):
|
||||
('pivot_rootbeer /new, # comment', False),
|
||||
]
|
||||
|
||||
class AARegexUnix(unittest.TestCase):
|
||||
class AARegexUnix(AARegexTest):
|
||||
'''Tests for RE_PROFILE_UNIX'''
|
||||
|
||||
def setUp(self):
|
||||
@@ -373,22 +392,102 @@ class AARegexUnix(unittest.TestCase):
|
||||
('deny unixlike,', False),
|
||||
]
|
||||
|
||||
if __name__ == '__main__':
|
||||
verbosity = 2
|
||||
class AANamedRegexProfileStart_2(AANamedRegexTest):
|
||||
'''Tests for RE_PROFILE_START'''
|
||||
|
||||
def setUp(self):
|
||||
self.regex = RE_PROFILE_START
|
||||
|
||||
tests = [
|
||||
('/bin/foo ', False), # no '{'
|
||||
('/bin/foo /bin/bar', False), # missing 'profile' keyword
|
||||
('profile {', False), # no attachment
|
||||
(' profile foo bar /foo {', False), # missing quotes around "foo bar"
|
||||
|
||||
(' /foo {', { 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }),
|
||||
(' "/foo" {', { 'plainprofile': '"/foo"', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }),
|
||||
(' profile /foo {', { 'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None }),
|
||||
(' profile "/foo" {', { 'plainprofile': None, 'namedprofile': '"/foo"', 'attachment': None, 'flags': None, 'comment': None }),
|
||||
(' profile foo /foo {', { 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None }),
|
||||
(' profile foo /foo (audit) {', { 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': 'audit', 'comment': None }),
|
||||
(' profile "foo" "/foo" {', { 'plainprofile': None, 'namedprofile': '"foo"', 'attachment': '"/foo"', 'flags': None, 'comment': None }),
|
||||
(' profile "foo bar" /foo {', { 'plainprofile': None, 'namedprofile': '"foo bar"', 'attachment': '/foo', 'flags': None, 'comment': None }),
|
||||
(' /foo (complain) {', { 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None }),
|
||||
(' /foo flags=(complain) {', { 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None }),
|
||||
(' /foo (complain) { # x', { 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': '# x'}),
|
||||
|
||||
(' /foo {', { 'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': ' ' }),
|
||||
('/foo {', { 'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': '' }),
|
||||
(' profile foo {', { 'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': ' ' }),
|
||||
('profile foo {', { 'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': '' }),
|
||||
]
|
||||
|
||||
|
||||
class Test_parse_profile_start_line(AATest):
|
||||
tests = [
|
||||
(' /foo {', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }),
|
||||
(' "/foo" {', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': None, 'comment': None }),
|
||||
(' profile /foo {', { 'profile': '/foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None }),
|
||||
(' profile "/foo" {', { 'profile': '/foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': '/foo', 'attachment': None, 'flags': None, 'comment': None }),
|
||||
(' profile foo /foo {', { 'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None }),
|
||||
(' profile foo /foo (audit) {', { 'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': 'audit', 'comment': None }),
|
||||
(' profile "foo" "/foo" {', { 'profile': 'foo', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo', 'attachment': '/foo', 'flags': None, 'comment': None }),
|
||||
(' profile "foo bar" /foo {', { 'profile': 'foo bar', 'profile_keyword': True, 'plainprofile': None, 'namedprofile': 'foo bar','attachment': '/foo', 'flags': None, 'comment': None }),
|
||||
(' /foo (complain) {', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None }),
|
||||
(' /foo flags=(complain) {', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': None }),
|
||||
(' /foo (complain) { # x', { 'profile': '/foo', 'profile_keyword': False, 'plainprofile': '/foo', 'namedprofile': None, 'attachment': None, 'flags': 'complain', 'comment': '# x'}),
|
||||
|
||||
(' /foo {', { 'profile': '/foo', 'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': ' ' }),
|
||||
('/foo {', { 'profile': '/foo', 'plainprofile': '/foo', 'namedprofile': None, 'leadingspace': None }),
|
||||
(' profile foo {', { 'profile': 'foo', 'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': ' ' }),
|
||||
('profile foo {', { 'profile': 'foo', 'plainprofile': None, 'namedprofile': 'foo', 'leadingspace': None }),
|
||||
]
|
||||
|
||||
def _run_test(self, line, expected):
|
||||
matches = parse_profile_start_line(line, 'somefile')
|
||||
|
||||
self.assertTrue(matches)
|
||||
|
||||
for exp in expected:
|
||||
self.assertEqual(matches[exp], expected[exp], 'Group %s mismatch in rule %s' % (exp,line))
|
||||
|
||||
class TestInvalid_parse_profile_start_line(AATest):
|
||||
tests = [
|
||||
('/bin/foo ', False), # no '{'
|
||||
('/bin/foo /bin/bar', False), # missing 'profile' keyword
|
||||
('profile {', False), # no attachment
|
||||
(' profile foo bar /foo {', False), # missing quotes around "foo bar"
|
||||
]
|
||||
|
||||
def _run_test(self, line, expected):
|
||||
with self.assertRaises(AppArmorBug):
|
||||
parse_profile_start_line(line, 'somefile')
|
||||
|
||||
|
||||
class TestStripQuotes(AATest):
|
||||
def test_strip_quotes_01(self):
|
||||
self.assertEqual('foo', strip_quotes('foo'))
|
||||
def test_strip_quotes_02(self):
|
||||
self.assertEqual('foo', strip_quotes('"foo"'))
|
||||
def test_strip_quotes_03(self):
|
||||
self.assertEqual('"foo', strip_quotes('"foo'))
|
||||
def test_strip_quotes_04(self):
|
||||
self.assertEqual('foo"', strip_quotes('foo"'))
|
||||
def test_strip_quotes_05(self):
|
||||
self.assertEqual('', strip_quotes('""'))
|
||||
def test_strip_quotes_06(self):
|
||||
self.assertEqual('foo"bar', strip_quotes('foo"bar'))
|
||||
def test_strip_quotes_07(self):
|
||||
self.assertEqual('foo"bar', strip_quotes('"foo"bar"'))
|
||||
def test_strip_quotes_08(self):
|
||||
self.assertEqual('"""foo"bar"""', strip_quotes('""""foo"bar""""'))
|
||||
|
||||
|
||||
|
||||
setup_all_loops(__name__)
|
||||
if __name__ == '__main__':
|
||||
# these two are not converted to a tests[] loop yet
|
||||
setup_has_comma_testcases()
|
||||
setup_split_comment_testcases()
|
||||
|
||||
test_suite = unittest.TestSuite()
|
||||
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexHasComma))
|
||||
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(AARegexSplitComment))
|
||||
|
||||
for tests in (AARegexCapability, AARegexPath, AARegexBareFile,
|
||||
AARegexDbus, AARegexMount, AARegexUnix,
|
||||
AARegexSignal, AARegexPtrace, AARegexPivotRoot):
|
||||
setup_regex_tests(tests)
|
||||
test_suite.addTest(unittest.TestLoader().loadTestsFromTestCase(tests))
|
||||
|
||||
result = unittest.TextTestRunner(verbosity=verbosity).run(test_suite)
|
||||
if not result.wasSuccessful():
|
||||
exit(1)
|
||||
unittest.main(verbosity=2)
|
||||
|
Reference in New Issue
Block a user