mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-08-31 14:25:52 +00:00
Compare commits
178 Commits
fix-dirtes
...
v2.9.3
Author | SHA1 | Date | |
---|---|---|---|
|
ccea109dfc | ||
|
d96b06e56d | ||
|
5cb3fb29f6 | ||
|
21a61c8501 | ||
|
ddb73a9c1d | ||
|
cac4fd95c9 | ||
|
9620c54d01 | ||
|
44bf19257b | ||
|
84b045b89f | ||
|
ae9d172f7e | ||
|
580d49cbf0 | ||
|
7397ca0148 | ||
|
700162143d | ||
|
e594a321c8 | ||
|
fd38788524 | ||
|
aa11bcf5b3 | ||
|
52256d71ff | ||
|
4cb12733d3 | ||
|
6d55c72764 | ||
|
9950f71d0d | ||
|
5138e08372 | ||
|
25fab7f65c | ||
|
a404f32349 | ||
|
43b3a872f2 | ||
|
40e24e9b29 | ||
|
28a64d280c | ||
|
f20df05f2d | ||
|
f6d84c7af5 | ||
|
620d75600f | ||
|
1af6055748 | ||
|
88e5b24db2 | ||
|
3ebd441223 | ||
|
c7b6454fb0 | ||
|
a2a5dc7677 | ||
|
9c6fae0c02 | ||
|
c950c2a358 | ||
|
0a6c17de54 | ||
|
7b3a87ca8f | ||
|
260c0458a7 | ||
|
e024dd3ca9 | ||
|
17f4905b2e | ||
|
55d325d21b | ||
|
e23168bc60 | ||
|
778a92f9a3 | ||
|
a741ce1ee6 | ||
|
7fb34ede01 | ||
|
400da57849 | ||
|
cbe5cd44e7 | ||
|
eff8f2a211 | ||
|
144a16baf1 | ||
|
223322ef47 | ||
|
afe74c6faa | ||
|
6eb3d719c1 | ||
|
8011b9d9b6 | ||
|
301731ef34 | ||
|
b885d62a8a | ||
|
49e82df101 | ||
|
247d3fc22e | ||
|
480c83343b | ||
|
92b528b6db | ||
|
7876f356bf | ||
|
a70167db0e | ||
|
b7a8b6a689 | ||
|
641b25d88f | ||
|
27664acf9a | ||
|
dccd5a18cb | ||
|
6ae4a3c2f0 | ||
|
39ebf164de | ||
|
16e6d5ffd9 | ||
|
56ac5c3e5a | ||
|
bc8c770e3f | ||
|
da7719a717 | ||
|
56e7b70dd7 | ||
|
8e065f85c1 | ||
|
473bf9c164 | ||
|
7a82798f6b | ||
|
b5a7142652 | ||
|
967e50c2b3 | ||
|
c79588b1f6 | ||
|
e3e77409a3 | ||
|
76e975a0c5 | ||
|
5d9d26d1da | ||
|
946f586747 | ||
|
9ba11eb14a | ||
|
c2777bb25a | ||
|
431bce2a85 | ||
|
2c63bd020a | ||
|
6ae047d0c1 | ||
|
e20463df79 | ||
|
1d29db0cd7 | ||
|
3adde7290e | ||
|
ea72078cc4 | ||
|
43a8d7703d | ||
|
b4b6af96c9 | ||
|
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 |
15
.bzrignore
15
.bzrignore
@@ -45,16 +45,25 @@ libraries/libapparmor/ylwrap
|
||||
libraries/libapparmor/doc/Makefile
|
||||
libraries/libapparmor/doc/Makefile.in
|
||||
libraries/libapparmor/doc/*.2
|
||||
libraries/libapparmor/doc/aa_*.3
|
||||
libraries/libapparmor/include/Makefile
|
||||
libraries/libapparmor/include/sys/Makefile
|
||||
libraries/libapparmor/src/.deps
|
||||
libraries/libapparmor/src/.libs
|
||||
libraries/libapparmor/src/Makefile
|
||||
libraries/libapparmor/src/Makefile.in
|
||||
libraries/libapparmor/src/af_protos.h
|
||||
libraries/libapparmor/src/change_hat.lo
|
||||
libraries/libapparmor/src/features.lo
|
||||
libraries/libapparmor/src/grammar.lo
|
||||
libraries/libapparmor/src/kernel.lo
|
||||
libraries/libapparmor/src/kernel_interface.lo
|
||||
libraries/libapparmor/src/libaalogparse.lo
|
||||
libraries/libapparmor/src/libimmunix_warning.lo
|
||||
libraries/libapparmor/src/policy_cache.lo
|
||||
libraries/libapparmor/src/private.lo
|
||||
libraries/libapparmor/src/scanner.lo
|
||||
libraries/libapparmor/src/libapparmor.pc
|
||||
libraries/libapparmor/src/libapparmor.la
|
||||
libraries/libapparmor/src/libimmunix.la
|
||||
libraries/libapparmor/src/grammar.c
|
||||
@@ -70,12 +79,18 @@ libraries/libapparmor/swig/perl/Makefile
|
||||
libraries/libapparmor/swig/perl/Makefile.PL
|
||||
libraries/libapparmor/swig/perl/Makefile.in
|
||||
libraries/libapparmor/swig/perl/Makefile.perl
|
||||
libraries/libapparmor/swig/perl/MYMETA.json
|
||||
libraries/libapparmor/swig/perl/MYMETA.yml
|
||||
libraries/libapparmor/swig/perl/blib
|
||||
libraries/libapparmor/swig/perl/libapparmor_wrap.c
|
||||
libraries/libapparmor/swig/perl/pm_to_blib
|
||||
libraries/libapparmor/swig/python/__init__.py
|
||||
libraries/libapparmor/swig/python/build/
|
||||
libraries/libapparmor/swig/python/libapparmor_wrap.c
|
||||
libraries/libapparmor/swig/python/Makefile
|
||||
libraries/libapparmor/swig/python/Makefile.in
|
||||
libraries/libapparmor/swig/python/setup.py
|
||||
libraries/libapparmor/swig/python/test/Makefile
|
||||
libraries/libapparmor/swig/ruby/Makefile
|
||||
libraries/libapparmor/swig/ruby/Makefile.in
|
||||
libraries/libapparmor/testsuite/.deps
|
||||
|
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/"
|
||||
|
@@ -60,7 +60,7 @@ libapparmor by adding USE_SYSTEM=1 to your make command.${nl}\
|
||||
AA_LINK_FLAGS = -L$(LIBAPPARMOR_PATH)
|
||||
AA_LDLIBS = -lapparmor
|
||||
endif
|
||||
EXTRA_CFLAGS=$(CFLAGS) -fPIC -shared -Wall $(LIBAPPARMOR_INCLUDE)
|
||||
EXTRA_CFLAGS=$(CFLAGS) $(CPPFLAGS) -fPIC -shared -Wall $(LIBAPPARMOR_INCLUDE)
|
||||
LINK_FLAGS=-Xlinker -x $(AA_LINK_FLAGS)
|
||||
LIBS=-lpam $(AA_LDLIBS)
|
||||
OBJECTS=${NAME}.o get_options.o
|
||||
|
@@ -111,6 +111,7 @@ int pam_sm_open_session(pam_handle_t *pamh, int flags,
|
||||
sizeof(magic_token));
|
||||
if (retval < 0) {
|
||||
pam_syslog(pamh, LOG_ERR, "Can't read from /dev/urandom\n");
|
||||
close(fd);
|
||||
return PAM_PERM_DENIED;
|
||||
}
|
||||
} while ((magic_token == 0) || (retval != sizeof(magic_token)));
|
||||
|
@@ -198,7 +198,7 @@ list_capabilities: /usr/include/linux/capability.h
|
||||
# to mediate. We use PF_ here since that is what is required in
|
||||
# bits/socket.h, but we will rewrite these as AF_.
|
||||
|
||||
FILTER_FAMILIES=PF_UNSPEC PF_UNIX
|
||||
FILTER_FAMILIES=PF_UNIX
|
||||
|
||||
__FILTER=$(shell echo $(strip $(FILTER_FAMILIES)) | sed -e 's/ /\\\|/g')
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
2.9.1
|
||||
2.9.3
|
||||
|
@@ -40,16 +40,15 @@ An AppArmor profile applies to an executable program; if a portion of
|
||||
the program needs different access permissions than other portions,
|
||||
the program can "change profile" to a different profile. To change into a
|
||||
new profile, it can use the aa_change_profile() function to do so. It passes
|
||||
in a pointer to the I<profile> to transition to. Transitioning to another
|
||||
profile via aa_change_profile() is permanent and the process is not
|
||||
permitted to transition back to the original profile. Confined programs
|
||||
wanting to use aa_change_profile() need to have rules permitting changing
|
||||
to the named profile. See apparmor.d(8) for details.
|
||||
in a pointer to the I<profile> to transition to. Confined programs wanting to
|
||||
use aa_change_profile() need to have rules permitting changing to the named
|
||||
profile. See apparmor.d(8) for details.
|
||||
|
||||
If a program wants to return out of the current profile to the
|
||||
original profile, it should use aa_change_hat(2) instead.
|
||||
original profile, it may use aa_change_hat(2). Otherwise, the two profiles must
|
||||
have rules permitting changing between the two profiles.
|
||||
|
||||
Open file descriptors are not remediated after a call to aa_change_profile()
|
||||
Open file descriptors may not be remediated after a call to aa_change_profile()
|
||||
so the calling program must close(2) open file descriptors to ensure they
|
||||
are not available after calling aa_change_profile(). As aa_change_profile()
|
||||
is typically used just before execve(2), you may want to use open(2) or
|
||||
@@ -84,8 +83,8 @@ Insufficient kernel memory was available.
|
||||
|
||||
=item B<EPERM>
|
||||
|
||||
The calling application is not confined by apparmor, or the no_new_privs
|
||||
bit is set.
|
||||
The calling application is confined by apparmor and the no_new_privs bit is
|
||||
set.
|
||||
|
||||
=item B<EACCES>
|
||||
|
||||
|
@@ -0,0 +1 @@
|
||||
type=AVC msg=audit(1449442292.901:961): apparmor="ALLOWED" operation="change_hat" profile="/usr/sbin/httpd{,2}-prefork" pid=8527 comm="httpd-prefork" target="/usr/sbin/httpd{,2}-prefork//HANDLING_UNTRUSTED_INPUT"
|
@@ -0,0 +1,11 @@
|
||||
START
|
||||
File: testcase_changehat_01.in
|
||||
Event type: AA_RECORD_ALLOWED
|
||||
Audit ID: 1449442292.901:961
|
||||
Operation: change_hat
|
||||
Profile: /usr/sbin/httpd{,2}-prefork
|
||||
Command: httpd-prefork
|
||||
Name2: /usr/sbin/httpd{,2}-prefork//HANDLING_UNTRUSTED_INPUT
|
||||
PID: 8527
|
||||
Epoch: 1449442292
|
||||
Audit subid: 961
|
@@ -0,0 +1 @@
|
||||
Jul 25 15:02:00 redacted kernel: [ 296.524447] audit: type=1400 audit(1437850920.403:64): apparmor="ALLOWED" operation="open" profile="/usr/sbin/vsftpd" name="/home/bane/foo" pid=1811 comm="vsftpd" requested_mask="r" denied_mask="r" fsuid=1000 ouid=1000
|
@@ -0,0 +1,15 @@
|
||||
START
|
||||
File: testcase_syslog_read.in
|
||||
Event type: AA_RECORD_ALLOWED
|
||||
Audit ID: 1437850920.403:64
|
||||
Operation: open
|
||||
Mask: r
|
||||
Denied Mask: r
|
||||
fsuid: 1000
|
||||
ouid: 1000
|
||||
Profile: /usr/sbin/vsftpd
|
||||
Name: /home/bane/foo
|
||||
Command: vsftpd
|
||||
PID: 1811
|
||||
Epoch: 1437850920
|
||||
Audit subid: 64
|
@@ -56,7 +56,7 @@ CFLAGS = -g -pg -fprofile-arcs -ftest-coverage
|
||||
endif
|
||||
endif #CFLAGS
|
||||
|
||||
EXTRA_CXXFLAGS = ${CFLAGS} ${CXX_WARNINGS} -std=gnu++0x -D_GNU_SOURCE
|
||||
EXTRA_CXXFLAGS = ${CFLAGS} ${CPPFLAGS} ${CXX_WARNINGS} -std=gnu++0x -D_GNU_SOURCE
|
||||
EXTRA_CFLAGS = ${EXTRA_CXXFLAGS} ${CPP_WARNINGS}
|
||||
|
||||
#LEXLIB := -lfl
|
||||
@@ -216,7 +216,7 @@ parser_include.o: parser_include.c parser.h parser_include.h
|
||||
parser_merge.o: parser_merge.c parser.h profile.h
|
||||
$(CXX) $(EXTRA_CFLAGS) -c -o $@ $<
|
||||
|
||||
parser_regex.o: parser_regex.c parser.h profile.h libapparmor_re/apparmor_re.h $(APPARMOR_H)
|
||||
parser_regex.o: parser_regex.c parser.h profile.h libapparmor_re/apparmor_re.h libapparmor_re/aare_rules.h $(APPARMOR_H)
|
||||
$(CXX) $(EXTRA_CFLAGS) -c -o $@ $<
|
||||
|
||||
parser_symtab.o: parser_symtab.c parser.h
|
||||
|
@@ -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' ) ','
|
||||
|
||||
@@ -121,7 +121,7 @@ B<SIGNAL SET> = 'set' '=' '(' I<SIGNAL LIST> ')'
|
||||
|
||||
B<SIGNAL LIST> = Comma or space separated list of I<SIGNALS>
|
||||
|
||||
B<SIGNALS> = ( 'hup' | 'int' | 'quit' | 'ill' | 'trap' | 'abrt' | 'bus' | 'fpe' | 'kill' | 'usr1' | 'segv' | 'usr2' | 'pipe' | 'alrm' | 'term' | 'stkflt' | 'chld' | 'cont' | 'stop' | 'stp' | 'ttin' | 'ttou' | 'urg' | 'xcpu' | 'xfsz' | 'vtalrm' | 'prof' | 'winch' | 'io' | 'pwr' | 'sys' | 'emt' | 'exists' )
|
||||
B<SIGNALS> = ( 'hup' | 'int' | 'quit' | 'ill' | 'trap' | 'abrt' | 'bus' | 'fpe' | 'kill' | 'usr1' | 'segv' | 'usr2' | 'pipe' | 'alrm' | 'term' | 'stkflt' | 'chld' | 'cont' | 'stop' | 'stp' | 'ttin' | 'ttou' | 'urg' | 'xcpu' | 'xfsz' | 'vtalrm' | 'prof' | 'winch' | 'io' | 'pwr' | 'sys' | 'emt' | 'exists' | 'rtmin+0' ... 'rtmin+32' )
|
||||
|
||||
B<SIGNAL PEER> = 'peer' '=' I<AARE>
|
||||
|
||||
@@ -770,6 +770,9 @@ Example AppArmor signal rules:
|
||||
# Allow us to signal ourselves using the built-in @{profile_name} variable
|
||||
signal peer=@{profile_name},
|
||||
|
||||
# Allow two real-time signals
|
||||
signal set=(rtmin+0 rtmin+32),
|
||||
|
||||
=head2 DBus rules
|
||||
|
||||
AppArmor supports DBus mediation. The mediation is performed in conjunction
|
||||
@@ -972,8 +975,10 @@ provided AppArmor policy:
|
||||
@{HOMEDIRS}
|
||||
@{multiarch}
|
||||
@{pid}
|
||||
@{pids}
|
||||
@{PROC}
|
||||
@{securityfs}
|
||||
@{apparmorfs}
|
||||
@{sys}
|
||||
@{tid}
|
||||
@{XDG_DESKTOP_DIR}
|
||||
@@ -1192,10 +1197,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
|
||||
|
@@ -38,6 +38,8 @@
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/apparmor.h>
|
||||
#include <sys/time.h>
|
||||
#include <utime.h>
|
||||
|
||||
#include "lib.h"
|
||||
#include "parser.h"
|
||||
@@ -587,7 +589,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;
|
||||
}
|
||||
@@ -871,17 +875,18 @@ static bool valid_cached_file_version(const char *cachename)
|
||||
return true;
|
||||
}
|
||||
|
||||
/* returns true if time is more recent than mru_tstamp */
|
||||
#define mru_t_cmp(a) \
|
||||
(((a).tv_sec == (mru_tstamp).tv_sec) ? \
|
||||
(a).tv_nsec > (mru_tstamp).tv_nsec : (a).tv_sec > (mru_tstamp).tv_sec)
|
||||
#define tstamp_cmp(a, b) \
|
||||
(((a).tv_sec == (b).tv_sec) ? \
|
||||
((a).tv_nsec - (b).tv_nsec) : \
|
||||
((a).tv_sec - (b).tv_sec))
|
||||
#define tstamp_is_null(a) ((a).tv_sec == 0 && (a).tv_nsec == 0)
|
||||
|
||||
void update_mru_tstamp(FILE *file)
|
||||
{
|
||||
struct stat stat_file;
|
||||
if (fstat(fileno(file), &stat_file))
|
||||
return;
|
||||
if (mru_t_cmp(stat_file.st_mtim))
|
||||
if (tstamp_cmp(stat_file.st_mtim, mru_tstamp) > 0)
|
||||
mru_tstamp = stat_file.st_mtim;
|
||||
}
|
||||
|
||||
@@ -967,7 +972,8 @@ int process_profile(int option, const char *profilename)
|
||||
/* Load a binary cache if it exists and is newest */
|
||||
if (!skip_read_cache &&
|
||||
stat(cachename, &stat_bin) == 0 &&
|
||||
stat_bin.st_size > 0 && (mru_t_cmp(stat_bin.st_mtim)) &&
|
||||
stat_bin.st_size > 0 &&
|
||||
(tstamp_cmp(mru_tstamp, stat_bin.st_mtim) < 0) &&
|
||||
valid_cached_file_version(cachename)) {
|
||||
if (show_cache)
|
||||
PERROR("Cache hit: %s\n", cachename);
|
||||
@@ -1035,6 +1041,12 @@ out:
|
||||
}
|
||||
|
||||
if (useable_cache) {
|
||||
struct timeval t;
|
||||
/* set the mtime of the cache file to the most newest
|
||||
* mtime of policy files used to generate it
|
||||
*/
|
||||
TIMESPEC_TO_TIMEVAL(&t, &mru_tstamp);
|
||||
utimes(cachetemp, &t);
|
||||
if (rename(cachetemp, cachename) < 0) {
|
||||
pwarn("Warning failed to write cache: %s\n", cachename);
|
||||
unlink(cachetemp);
|
||||
|
@@ -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;
|
||||
@@ -759,7 +759,7 @@ static const char *capnames[] = {
|
||||
"audit_write",
|
||||
"audit_control",
|
||||
"setfcap",
|
||||
"mac_override"
|
||||
"mac_override",
|
||||
"syslog",
|
||||
};
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -94,12 +94,13 @@ aa_log_skipped_msg() {
|
||||
echo -e "$rc_skipped"
|
||||
}
|
||||
|
||||
_set_status() {
|
||||
return $1
|
||||
}
|
||||
|
||||
aa_log_end_msg() {
|
||||
v="-v"
|
||||
if [ "$1" != '0' ]; then
|
||||
rc="-v$1"
|
||||
fi
|
||||
rc_status $v
|
||||
_set_status $1
|
||||
rc_status -v
|
||||
}
|
||||
|
||||
usage() {
|
||||
|
@@ -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
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#=DESCRIPTON simple stress test nested ifs
|
||||
#=DESCRIPTION simple stress test nested ifs
|
||||
#=EXRESULT PASS
|
||||
$a1 = true
|
||||
$a2 = true
|
||||
|
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/**,
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
network unspec,
|
||||
network inet,
|
||||
network ax25,
|
||||
network ipx,
|
||||
|
9
parser/tst/simple_tests/network/network_ok_7.sd
Normal file
9
parser/tst/simple_tests/network/network_ok_7.sd
Normal file
@@ -0,0 +1,9 @@
|
||||
#
|
||||
#=DESCRIPTION basic unspec network tests
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
network unspec stream,
|
||||
network unspec dgram,
|
||||
network unspec raw,
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
#
|
||||
#=DESCRIPTION simple rtsig test
|
||||
#=EXRESULT=PASS
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
|
||||
/usr/bin/signal-test {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
#
|
||||
#=DESCRIPTION simple rtsig test
|
||||
#=EXRESULT=PASS
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
|
||||
/usr/bin/signal-test {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
#=DESCRIPTION set variable assignment using set variable as rvalue
|
||||
#=EXRESULT
|
||||
#=EXRESULT PASS
|
||||
|
||||
@{FOO}=bar baz
|
||||
@{BAR}=${FOO} blort
|
||||
|
@@ -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,12 +20,16 @@
|
||||
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,
|
||||
unix (connect, receive, send)
|
||||
type=stream
|
||||
peer=(addr="@/tmp/.X11-unix/X[0-9]*"),
|
||||
unix (connect, receive, send)
|
||||
type=stream
|
||||
peer=(addr="@/tmp/.ICE-unix/[0-9]*"),
|
||||
|
||||
/usr/include/X11/ r,
|
||||
/usr/include/X11/** r,
|
||||
|
@@ -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,
|
||||
|
@@ -26,12 +26,14 @@
|
||||
/etc/locale/** r,
|
||||
/etc/locale.alias r,
|
||||
/etc/localtime r,
|
||||
/usr/share/locale-bundle/** r,
|
||||
/usr/share/locale-langpack/** r,
|
||||
/usr/share/locale/** r,
|
||||
/usr/share/**/locale/** r,
|
||||
/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
|
@@ -38,6 +38,9 @@
|
||||
# /etc/resolvconf/run/resolv.conf
|
||||
/{,var/}run/resolvconf/resolv.conf r,
|
||||
/etc/resolvconf/run/resolv.conf r,
|
||||
# on systems using systemd's networkd, /etc/resolv.conf is a symlink to
|
||||
# /run/systemd/resolve/resolv.conf
|
||||
/{,var/}run/systemd/resolve/resolv.conf r,
|
||||
|
||||
/etc/samba/lmhosts r,
|
||||
/etc/services r,
|
||||
|
@@ -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,
|
||||
|
@@ -10,18 +10,18 @@
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
/usr/lib{,32,64}/python{2.[4-7],3.[0-4]}/**.{pyc,so} mr,
|
||||
/usr/lib{,32,64}/python{2.[4-7],3.[0-4]}/**.{egg,py,pth} r,
|
||||
/usr/lib{,32,64}/python{2.[4-7],3.[0-4]}/{site,dist}-packages/ r,
|
||||
/usr/lib{,32,64}/python3.[0-4]/lib-dynload/*.so mr,
|
||||
/usr/lib{,32,64}/python{2.[4-7],3.[0-5]}/**.{pyc,so} mr,
|
||||
/usr/lib{,32,64}/python{2.[4-7],3.[0-5]}/**.{egg,py,pth} r,
|
||||
/usr/lib{,32,64}/python{2.[4-7],3.[0-5]}/{site,dist}-packages/ r,
|
||||
/usr/lib{,32,64}/python3.[0-5]/lib-dynload/*.so mr,
|
||||
|
||||
/usr/local/lib{,32,64}/python{2.[4-7],3.[0-4]}/**.{pyc,so} mr,
|
||||
/usr/local/lib{,32,64}/python{2.[4-7],3.[0-4]}/**.{egg,py,pth} r,
|
||||
/usr/local/lib{,32,64}/python{2.[4-7],3.[0-4]}/{site,dist}-packages/ r,
|
||||
/usr/local/lib{,32,64}/python3.[0-4]/lib-dynload/*.so mr,
|
||||
/usr/local/lib{,32,64}/python{2.[4-7],3.[0-5]}/**.{pyc,so} mr,
|
||||
/usr/local/lib{,32,64}/python{2.[4-7],3.[0-5]}/**.{egg,py,pth} r,
|
||||
/usr/local/lib{,32,64}/python{2.[4-7],3.[0-5]}/{site,dist}-packages/ r,
|
||||
/usr/local/lib{,32,64}/python3.[0-5]/lib-dynload/*.so mr,
|
||||
|
||||
# Site-wide configuration
|
||||
/etc/python{2.[4-7],3.[0-4]}/** r,
|
||||
/etc/python{2.[4-7],3.[0-5]}/** r,
|
||||
|
||||
# shared python paths
|
||||
/usr/share/{pyshared,pycentral,python-support}/** r,
|
||||
@@ -34,4 +34,4 @@
|
||||
/usr/lib/wx/python/*.pth r,
|
||||
|
||||
# python build configuration and headers
|
||||
/usr/include/python{2.[4-7],3.[0-4]}*/pyconfig.h r,
|
||||
/usr/include/python{2.[4-7],3.[0-5]}*/pyconfig.h r,
|
||||
|
@@ -13,7 +13,7 @@
|
||||
/usr/share/samba/*.dat r,
|
||||
/usr/share/samba/codepages/{lowcase,upcase,valid}.dat r,
|
||||
/var/cache/samba/ w,
|
||||
/var/lib/samba/**.tdb rwk,
|
||||
/var/lib/samba/** rwk,
|
||||
/var/log/samba/cores/ rw,
|
||||
/var/log/samba/cores/** rw,
|
||||
/var/log/samba/log.* w,
|
||||
|
@@ -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,
|
||||
@@ -19,3 +23,7 @@
|
||||
/usr/local/share/ca-certificates/** r,
|
||||
/var/lib/ca-certificates/ r,
|
||||
/var/lib/ca-certificates/** r,
|
||||
|
||||
# acmetool
|
||||
/var/lib/acme/certs/*/chain r,
|
||||
/var/lib/acme/certs/*/cert r,
|
||||
|
@@ -16,3 +16,7 @@
|
||||
/etc/ssl/ r,
|
||||
/etc/ssl/** r,
|
||||
|
||||
# acmetool
|
||||
/var/lib/acme/live/* r,
|
||||
/var/lib/acme/certs/** r,
|
||||
/var/lib/acme/keys/** r,
|
||||
|
@@ -5,10 +5,10 @@
|
||||
#
|
||||
@{PROC}/@{pid}/fd/ r,
|
||||
/usr/lib/** rm,
|
||||
/bin/bash ixr,
|
||||
/bin/dash ixr,
|
||||
/bin/grep ixr,
|
||||
/bin/sed ixr,
|
||||
/{,usr/}bin/bash ixr,
|
||||
/{,usr/}bin/dash ixr,
|
||||
/{,usr/}bin/grep ixr,
|
||||
/{,usr/}bin/sed ixr,
|
||||
/usr/bin/m4 ixr,
|
||||
|
||||
# Since all the ubuntu-browsers.d abstractions need this, just include it
|
||||
|
@@ -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
|
||||
|
@@ -8,10 +8,10 @@
|
||||
#include <abstractions/php5>
|
||||
#include <abstractions/python>
|
||||
|
||||
/bin/dash ixr,
|
||||
/bin/df ixr,
|
||||
/bin/mount ixr,
|
||||
/bin/uname ixr,
|
||||
/{,usr/}bin/dash ixr,
|
||||
/{,usr/}bin/df ixr,
|
||||
/{,usr/}bin/mount ixr,
|
||||
/{,usr/}bin/uname ixr,
|
||||
/dev/bus/usb/ r,
|
||||
/dev/bus/usb/** r,
|
||||
/etc/debian_version r,
|
||||
|
@@ -19,7 +19,7 @@
|
||||
capability setuid,
|
||||
network inet raw,
|
||||
|
||||
/bin/ping mixr,
|
||||
/{,usr/}bin/ping mixr,
|
||||
/etc/modules.conf r,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
|
@@ -20,6 +20,7 @@
|
||||
#include <abstractions/consoles>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/mysql>
|
||||
#include <abstractions/openssl>
|
||||
|
||||
capability chown,
|
||||
capability dac_override,
|
||||
@@ -37,7 +38,10 @@
|
||||
/dev/syslog w,
|
||||
/dev/tty10 rw,
|
||||
/dev/xconsole rw,
|
||||
/etc/machine-id r,
|
||||
/etc/syslog-ng/* r,
|
||||
/etc/syslog-ng/conf.d/ r,
|
||||
/etc/syslog-ng/conf.d/* r,
|
||||
@{PROC}/kmsg r,
|
||||
/etc/hosts.deny r,
|
||||
/etc/hosts.allow r,
|
||||
@@ -50,6 +54,10 @@
|
||||
@{CHROOT_BASE}/var/log/** w,
|
||||
@{CHROOT_BASE}/{,var/}run/syslog-ng.pid krw,
|
||||
@{CHROOT_BASE}/{,var/}run/syslog-ng.ctl rw,
|
||||
/{var,var/run,run}/log/journal/ r,
|
||||
/{var,var/run,run}/log/journal/*/ r,
|
||||
/{var,var/run,run}/log/journal/*/*.journal r,
|
||||
/{var/,}run/syslog-ng.ctl a,
|
||||
/{var/,}run/syslog-ng/additional-log-sockets.conf r,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
|
@@ -17,6 +17,7 @@
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/mysql>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/openssl>
|
||||
#include <abstractions/wutmp>
|
||||
#include <abstractions/dovecot-common>
|
||||
|
||||
@@ -37,6 +38,7 @@
|
||||
/var/tmp/smtp_* rw,
|
||||
|
||||
/{var/,}run/dovecot/auth-token-secret.dat{,.tmp} rw,
|
||||
/{var/,}run/dovecot/stats-user w,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
#include <local/usr.lib.dovecot.auth>
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2013 Christian Boltz
|
||||
# Copyright (C) 2013-2016 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
|
||||
@@ -24,10 +24,65 @@
|
||||
|
||||
/etc/dovecot/** r,
|
||||
/proc/*/mounts r,
|
||||
owner /tmp/dovecot.lda.* rw,
|
||||
/{var/,}run/dovecot/mounts r,
|
||||
/usr/bin/doveconf mrix,
|
||||
/usr/lib/dovecot/dovecot-lda mrix,
|
||||
/usr/sbin/sendmail Cx,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
#include <local/usr.lib.dovecot.dovecot-lda>
|
||||
|
||||
|
||||
profile /usr/sbin/sendmail flags=(attach_disconnected) {
|
||||
# this profile is based on the usr.sbin.sendmail profile in extras
|
||||
# and should support both postfix' and sendmail's sendmail binary
|
||||
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/consoles>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/user-tmp>
|
||||
#include <abstractions/postfix-common>
|
||||
|
||||
capability sys_ptrace,
|
||||
|
||||
/etc/aliases rw, # newaliases is a symlink to sendmail, so it's
|
||||
/etc/aliases.db rw, # actually the same binary
|
||||
/etc/fstab r,
|
||||
/etc/hosts.allow r,
|
||||
/etc/hosts.deny r,
|
||||
/etc/mail/* r,
|
||||
/etc/mail/statistics rw,
|
||||
/etc/mtab r,
|
||||
/etc/postfix/aliases r,
|
||||
/etc/postfix/aliases.db rw, # newaliases again
|
||||
/etc/sendmail.cf r,
|
||||
/etc/sendmail.cw r,
|
||||
/etc/shells r,
|
||||
/proc/loadavg r,
|
||||
/proc/net/if_inet6 r,
|
||||
/root/.forward r,
|
||||
/root/dead.letter w,
|
||||
/usr/bin/procmail Px,
|
||||
/usr/lib/postfix/master Px,
|
||||
/usr/lib/postfix/showq Px,
|
||||
/usr/lib/postfix/smtpd Px,
|
||||
/usr/sbin/postalias Px,
|
||||
/usr/sbin/postdrop Px,
|
||||
/usr/sbin/postfix Px,
|
||||
/usr/sbin/postqueue Px,
|
||||
/usr/sbin/sendmail mrix,
|
||||
/usr/sbin/sendmail.postfix mrix,
|
||||
/usr/sbin/sendmail.sendmail mrix,
|
||||
/{var/,}run/sendmail.pid rwl,
|
||||
/{var/,}run/sm-client.pid rwl,
|
||||
/{var/,}run/utmp rw,
|
||||
/var/spool/clientmqueue/* rwl,
|
||||
/var/spool/mail/* rwl,
|
||||
/var/spool/mqueue/* rwl,
|
||||
/var/spool/postfix/maildrop/* rwl,
|
||||
/var/spool/postfix/public/pickup w,
|
||||
/var/spool/postfix/public/qmgr w,
|
||||
/var/spool/postfix/public/showq w,
|
||||
}
|
||||
}
|
||||
|
@@ -26,6 +26,8 @@
|
||||
|
||||
@{HOME} r, # ???
|
||||
/usr/lib/dovecot/imap mr,
|
||||
/{,var/}run/dovecot/auth-master rw,
|
||||
/{,var/}run/dovecot/mounts r,
|
||||
|
||||
# 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,
|
||||
|
||||
|
@@ -16,6 +16,8 @@
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/dovecot-common>
|
||||
#include <abstractions/openssl>
|
||||
#include <abstractions/ssl_keys>
|
||||
|
||||
capability dac_override,
|
||||
capability setuid,
|
||||
|
@@ -26,6 +26,7 @@
|
||||
/{,var/}run/avahi-daemon/ w,
|
||||
/{,var/}run/avahi-daemon/pid krw,
|
||||
/{,var/}run/avahi-daemon/socket w,
|
||||
/{,var/}run/systemd/notify w,
|
||||
|
||||
# Site-specific additions and overrides. See local/README for details.
|
||||
#include <local/usr.sbin.avahi-daemon>
|
||||
|
@@ -12,7 +12,7 @@
|
||||
@{TFTP_DIR}=/var/tftp /srv/tftpboot
|
||||
|
||||
#include <tunables/global>
|
||||
/usr/sbin/dnsmasq {
|
||||
/usr/sbin/dnsmasq flags=(attach_disconnected) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/dbus>
|
||||
#include <abstractions/nameservice>
|
||||
@@ -29,6 +29,8 @@
|
||||
signal (receive) peer=/usr/sbin/libvirtd,
|
||||
ptrace (readby) peer=/usr/sbin/libvirtd,
|
||||
|
||||
owner /dev/tty rw,
|
||||
|
||||
/etc/dnsmasq.conf r,
|
||||
/etc/dnsmasq.d/ r,
|
||||
/etc/dnsmasq.d/* r,
|
||||
@@ -45,6 +47,8 @@
|
||||
|
||||
/var/lib/misc/dnsmasq.leases rw, # Required only for DHCP server usage
|
||||
|
||||
/{,usr/}bin/{ba,da,}sh 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 +68,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,
|
||||
|
@@ -31,6 +31,7 @@
|
||||
/{var/cache,var/run,run}/nscd/{passwd,group,services,hosts,netgroup} rw,
|
||||
/{,var/}run/{nscd/,}nscd.pid rwl,
|
||||
/var/log/nscd.log rw,
|
||||
@{PROC}/@{pid}/cmdline r,
|
||||
@{PROC}/@{pid}/fd/ r,
|
||||
@{PROC}/@{pid}/fd/* r,
|
||||
@{PROC}/@{pid}/mounts r,
|
||||
|
@@ -11,7 +11,7 @@
|
||||
|
||||
#include <tunables/global>
|
||||
#include <tunables/ntpd>
|
||||
/usr/sbin/ntpd {
|
||||
/usr/sbin/ntpd flags=(attach_disconnected) {
|
||||
#include <abstractions/base>
|
||||
#include <abstractions/nameservice>
|
||||
#include <abstractions/openssl>
|
||||
@@ -37,6 +37,7 @@
|
||||
/etc/ntpd.conf.tmp r,
|
||||
|
||||
/tmp/ntp* rwl,
|
||||
/{usr/,usr/local/,}{s,}bin/ r,
|
||||
/usr/sbin/ntpd rmix,
|
||||
/var/lib/ntp/drift rwl,
|
||||
/var/lib/ntp/drift.TEMP rwl,
|
||||
|
@@ -17,6 +17,7 @@
|
||||
capability net_bind_service,
|
||||
capability setgid,
|
||||
capability setuid,
|
||||
capability sys_admin, # needed to store ACLS in the security.NTACL namespace
|
||||
capability sys_resource,
|
||||
capability sys_tty_config,
|
||||
|
||||
|
@@ -8,7 +8,7 @@
|
||||
#include <abstractions/perl>
|
||||
|
||||
/dev/tty rw,
|
||||
/bin/bash ix,
|
||||
/{,usr/}bin/bash ix,
|
||||
/etc/init.d/nscd Cx,
|
||||
/etc/shadow r,
|
||||
/etc/smbldap-tools/smbldap.conf r,
|
||||
@@ -26,9 +26,9 @@
|
||||
|
||||
capability sys_ptrace,
|
||||
|
||||
/bin/bash r,
|
||||
/bin/mountpoint rix,
|
||||
/bin/systemctl rix,
|
||||
/{,usr/}bin/bash r,
|
||||
/{,usr/}bin/mountpoint rix,
|
||||
/{,usr/}bin/systemctl rix,
|
||||
/dev/tty rw,
|
||||
/etc/init.d/nscd r,
|
||||
/etc/rc.status r,
|
||||
|
@@ -10,8 +10,12 @@
|
||||
capability ipc_lock,
|
||||
capability setuid,
|
||||
|
||||
/etc/samba/netlogon_creds_cli.tdb rwk,
|
||||
/etc/samba/passdb.tdb{,.tmp} rwk,
|
||||
/etc/samba/secrets.tdb rwk,
|
||||
/etc/samba/smbd.tmp/ rw,
|
||||
/etc/samba/smbd.tmp/msg/ rw,
|
||||
/etc/samba/smbd.tmp/msg/* rwk,
|
||||
@{PROC}/sys/kernel/core_pattern r,
|
||||
/tmp/.winbindd/ w,
|
||||
/tmp/krb5cc_* rwk,
|
||||
|
@@ -1,6 +1,7 @@
|
||||
# ------------------------------------------------------------------
|
||||
#
|
||||
# Copyright (C) 2002-2005 Novell/SUSE
|
||||
# Copyright (C) 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
|
||||
@@ -25,6 +26,8 @@
|
||||
#include <abstractions/bash>
|
||||
#include <abstractions/nameservice>
|
||||
|
||||
capability net_raw,
|
||||
|
||||
network packet packet,
|
||||
network packet raw,
|
||||
|
||||
@@ -47,13 +50,17 @@
|
||||
/usr/bin/uptime mrix,
|
||||
/usr/bin/vmstat mrix,
|
||||
/usr/bin/w mrix,
|
||||
/usr/lib/nm-dhcp-helper rix,
|
||||
/var/lib/dhcp/dhclient.leases rw,
|
||||
/var/lib/dhcp/dhclient-*.leases rw,
|
||||
/var/lib/dhcp6/dhclient.leases rw,
|
||||
/var/lib/NetworkManager/dhclient-*.conf r,
|
||||
/var/lib/NetworkManager/dhclient-*.lease rw,
|
||||
/var/log/lastlog r,
|
||||
/var/log/messages r,
|
||||
/var/log/wtmp r,
|
||||
/{,var/}run/dhclient.pid rw,
|
||||
/{,var/}run/dhclient-*.pid rw,
|
||||
/{,var/}run/dhclient.pid rw,
|
||||
/{,var/}run/dhclient-*.pid rw,
|
||||
/var/spool r,
|
||||
/var/spool/mail r,
|
||||
|
||||
|
@@ -20,6 +20,7 @@
|
||||
|
||||
@{PROC}/sys/kernel/{ostype,osrelease} r,
|
||||
@{PROC}/@{pid}/net/arp r,
|
||||
@{PROC}/@{pid}/net/dev r,
|
||||
owner @{PROC}/@{pid}/auxv r,
|
||||
owner @{PROC}/@{pid}/cmdline r,
|
||||
owner @{PROC}/@{pid}/fd/ r,
|
||||
|
@@ -21,7 +21,7 @@
|
||||
capability dac_override,
|
||||
|
||||
/etc/postfix/master.cf r,
|
||||
/{var/spool/postfix/,}pid/master.pid rw,
|
||||
/{var/spool/postfix/,}pid/master.pid rwk,
|
||||
/{var/spool/postfix/,}private/* wl,
|
||||
/{var/spool/postfix/,}private/tlsmgr rwl,
|
||||
/{var/spool/postfix/,}public/{cleanup,flush,pickup,qmgr,showq,tlsmgr} rwl,
|
||||
|
@@ -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
|
||||
|
@@ -7,7 +7,9 @@
|
||||
#include <sys/ptrace.h>
|
||||
#include <signal.h>
|
||||
#include <sys/user.h>
|
||||
#include <sys/uio.h>
|
||||
#include <errno.h>
|
||||
#include <elf.h>
|
||||
|
||||
#define NUM_CHLD_SYSCALLS 10
|
||||
|
||||
@@ -34,10 +36,58 @@ int interp_status(int status)
|
||||
return rc;
|
||||
}
|
||||
|
||||
#ifdef PTRACE_GETREGSET
|
||||
# if defined(__x86_64__) || defined(__i386__)
|
||||
# define ARCH_REGS_STRUCT struct user_regs_struct
|
||||
# elif defined(__aarch64__)
|
||||
# if (__GLIBC__ > 2) || ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 20))
|
||||
# define ARCH_REGS_STRUCT struct user_regs_struct
|
||||
# else
|
||||
# define ARCH_REGS_STRUCT struct user_pt_regs
|
||||
# endif
|
||||
# elif defined(__arm__) || defined(__powerpc__) || defined(__powerpc64__)
|
||||
# define ARCH_REGS_STRUCT struct pt_regs
|
||||
# elif defined(__s390__) || defined(__s390x__)
|
||||
# define ARCH_REGS_STRUCT struct _user_regs_struct
|
||||
# else
|
||||
# error "Need to define ARCH_REGS_STRUCT for this architecture"
|
||||
# endif
|
||||
|
||||
int read_ptrace_registers(pid_t pid)
|
||||
{
|
||||
ARCH_REGS_STRUCT regs;
|
||||
struct iovec iov;
|
||||
|
||||
iov.iov_base = ®s;
|
||||
iov.iov_len = sizeof(regs);
|
||||
|
||||
memset(®s, 0, sizeof(regs));
|
||||
if (ptrace(PTRACE_GETREGSET, pid, NT_PRSTATUS, &iov) == -1) {
|
||||
perror("FAIL: parent ptrace(PTRACE_GETREGS) failed - ");
|
||||
return errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#else /* ! PTRACE_GETREGSET so use PTRACE_GETREGS instead */
|
||||
int read_ptrace_registers(pid_t pid)
|
||||
{
|
||||
struct user regs;
|
||||
|
||||
memset(®s, 0, sizeof(regs));
|
||||
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1) {
|
||||
perror("FAIL: parent ptrace(PTRACE_GETREGS) failed - ");
|
||||
return errno;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/* return 0 on success. Child failure -errorno, parent failure errno */
|
||||
int do_parent(pid_t pid, int trace, int num_syscall)
|
||||
{
|
||||
struct user regs;
|
||||
int status, i;
|
||||
unsigned int rc;
|
||||
|
||||
@@ -88,11 +138,9 @@ int do_parent(pid_t pid, int trace, int num_syscall)
|
||||
if (!WIFSTOPPED(status))
|
||||
return interp_status(status);
|
||||
|
||||
memset(®s, 0, sizeof(regs));
|
||||
if (ptrace(PTRACE_GETREGS, pid, NULL, ®s) == -1) {
|
||||
perror("FAIL: parent ptrace(PTRACE_GETREGS) failed - ");
|
||||
return errno;
|
||||
}
|
||||
rc = read_ptrace_registers(pid);
|
||||
if (rc != 0)
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (ptrace(PTRACE_DETACH, pid, NULL, NULL) == -1) {
|
||||
|
@@ -16,21 +16,44 @@
|
||||
#include <string.h>
|
||||
|
||||
#define BUFSIZE 4096
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int save_max_threads, new_max_threads, read_new_max_threads;
|
||||
size_t save_sz = sizeof(save_max_threads);
|
||||
int name[] = {CTL_KERN, KERN_MAX_THREADS};
|
||||
int readonly = 0;
|
||||
|
||||
if ((argc > 1) && strcmp(argv[1],"ro") == 0)
|
||||
readonly = 1;
|
||||
|
||||
if (sysctl(name, sizeof(name), &save_max_threads, &save_sz, NULL, 0) == -1){
|
||||
static int name[] = {CTL_KERN, KERN_MAX_THREADS};
|
||||
|
||||
int read_max_threads(int *max_threads)
|
||||
{
|
||||
size_t save_sz = sizeof(*max_threads);
|
||||
|
||||
if (sysctl(name, sizeof(name), max_threads, &save_sz, NULL, 0) == -1){
|
||||
fprintf(stderr, "FAIL: sysctl read failed - %s\n",
|
||||
strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int write_max_threads(int new_max_threads)
|
||||
{
|
||||
size_t save_sz = sizeof(new_max_threads);
|
||||
|
||||
if (sysctl(name, sizeof(name), NULL, 0, &new_max_threads, save_sz) == -1){
|
||||
fprintf(stderr, "FAIL: sysctl write failed - %s\n",
|
||||
strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
int save_max_threads, new_max_threads, read_new_max_threads;
|
||||
int readonly = 0;
|
||||
|
||||
if ((argc > 1) && strcmp(argv[1],"ro") == 0)
|
||||
readonly = 1;
|
||||
|
||||
if (read_max_threads(&save_max_threads) != 0)
|
||||
return 1;
|
||||
|
||||
/* printf("Kernel max threads (saved) is %d\n", save_max_threads); */
|
||||
|
||||
@@ -41,36 +64,39 @@ int main(int argc, char *argv[])
|
||||
|
||||
new_max_threads = save_max_threads + 1024;
|
||||
|
||||
if (sysctl(name, sizeof(name), NULL, 0, &new_max_threads, save_sz) == -1){
|
||||
fprintf(stderr, "FAIL: sysctl write failed - %s\n",
|
||||
strerror(errno));
|
||||
if (write_max_threads(new_max_threads) != 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sysctl(name, sizeof(name), &read_new_max_threads, &save_sz, NULL, 0) == -1){
|
||||
fprintf(stderr, "FAIL: sysctl read failed - %s\n",
|
||||
strerror(errno));
|
||||
if (read_max_threads(&read_new_max_threads) != 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* printf("Kernel max threads (new) is %d\n", read_new_max_threads); */
|
||||
|
||||
if (read_new_max_threads != new_max_threads) {
|
||||
fprintf(stderr, "FAIL: read value does not match written values\n");
|
||||
return 1;
|
||||
/* the kernel possibly rejected our updated max threads
|
||||
* as being too large; try decreasing max threads. */
|
||||
|
||||
new_max_threads = save_max_threads - 1024;
|
||||
|
||||
if (write_max_threads(new_max_threads) != 0)
|
||||
return 1;
|
||||
|
||||
if (read_max_threads(&read_new_max_threads) != 0)
|
||||
return 1;
|
||||
|
||||
/* printf("Kernel max threads (new, 2nd attempt) is %d\n", read_new_max_threads); */
|
||||
|
||||
if (read_new_max_threads != new_max_threads) {
|
||||
fprintf(stderr, "FAIL: read value does not match written values\n");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (sysctl(name, sizeof(name), NULL, 0, &save_max_threads, save_sz) == -1){
|
||||
fprintf(stderr, "FAIL: sysctl write failed - %s\n",
|
||||
strerror(errno));
|
||||
if (write_max_threads(save_max_threads) != 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (sysctl(name, sizeof(name), &read_new_max_threads, &save_sz, NULL, 0) == -1){
|
||||
fprintf(stderr, "FAIL: sysctl read failed - %s\n",
|
||||
strerror(errno));
|
||||
if (read_max_threads(&read_new_max_threads) != 0)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* printf("Kernel max threads (saved) is %d\n", read_new_max_threads);*/
|
||||
|
||||
|
@@ -52,6 +52,10 @@ runchecktest "TCP (accept, connect) low numbered port/bind cap" pass 23
|
||||
genprofile network:inet
|
||||
runchecktest "TCP (accept, connect) low numbered port/no bind cap" fail 23
|
||||
|
||||
# FAIL TEST - make sure that unspec doesn't match
|
||||
genprofile network:unspec
|
||||
runchecktest "TCP (accept, connect) wrong socket family" fail 23
|
||||
|
||||
exit 0
|
||||
|
||||
# PASS TEST - accept via interface
|
||||
|
@@ -26,6 +26,7 @@ parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('-r', '--remove', action='store_true', help=_('remove audit mode'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
parser.add_argument('--trace', action='store_true', help=_('Show full trace'))
|
||||
parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it'))
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
|
@@ -24,6 +24,7 @@ parser = argparse.ArgumentParser(description=_('Cleanup the profiles for the giv
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
parser.add_argument('-s', '--silent', action='store_true', help=_('Silently overwrite with a clean profile'))
|
||||
parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it'))
|
||||
args = parser.parse_args()
|
||||
|
||||
clean = apparmor.tools.aa_tools('cleanprof', args)
|
||||
|
@@ -23,6 +23,7 @@ _ = init_translation()
|
||||
parser = argparse.ArgumentParser(description=_('Switch the given program to complain mode'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it'))
|
||||
args = parser.parse_args()
|
||||
|
||||
tool = apparmor.tools.aa_tools('complain', args)
|
||||
|
@@ -23,6 +23,7 @@ _ = init_translation()
|
||||
parser = argparse.ArgumentParser(description=_('Disable the profile for the given programs'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not unload the profile after modifying it'))
|
||||
args = parser.parse_args()
|
||||
|
||||
tool = apparmor.tools.aa_tools('disable', args)
|
||||
|
@@ -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
|
||||
|
@@ -23,6 +23,7 @@ _ = init_translation()
|
||||
parser = argparse.ArgumentParser(description=_('Switch the given program to enforce mode'))
|
||||
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
|
||||
parser.add_argument('program', type=str, nargs='+', help=_('name of program'))
|
||||
parser.add_argument('--no-reload', dest='do_reload', action='store_false', default=True, help=_('Do not reload the profile after modifying it'))
|
||||
args = parser.parse_args()
|
||||
|
||||
tool = apparmor.tools.aa_tools('enforce', args)
|
||||
|
@@ -128,7 +128,7 @@ updated profiles to the disk and reload them if AppArmor is running.
|
||||
|
||||
If there are unhandled x accesses generated by the execve(2) of a
|
||||
new process, aa-logprof will display the parent profile and the target
|
||||
program that's being executed and prompt the user to select and execute
|
||||
program that's being executed and prompt the user to select an execute
|
||||
modifier. These modifiers will allow a choice for the target to: have it's
|
||||
own profile (px), inherit the parent's profile (ix), run unconstrained
|
||||
(ux), or deny access for the target. See apparmor.d(5) for details.
|
||||
|
@@ -18,6 +18,7 @@ import os
|
||||
|
||||
import apparmor.aa
|
||||
import apparmor.aamode
|
||||
from apparmor.common import AppArmorException
|
||||
import apparmor.severity
|
||||
import apparmor.cleanprofile as cleanprofile
|
||||
import apparmor.ui as aaui
|
||||
@@ -43,7 +44,7 @@ profiledir = args.dir
|
||||
if profiledir:
|
||||
apparmor.aa.profile_dir = apparmor.aa.get_full_path(profiledir)
|
||||
if not os.path.isdir(apparmor.aa.profile_dir):
|
||||
raise apparmor.AppArmorException(_("%s is not a directory.") %profiledir)
|
||||
raise AppArmorException(_("%s is not a directory.") %profiledir)
|
||||
|
||||
def reset_aa():
|
||||
apparmor.aa.aa = apparmor.aa.hasher()
|
||||
@@ -229,7 +230,7 @@ class Merge(object):
|
||||
return o
|
||||
pass#self.user.aa[profile][hat][allow][path][mode] = (old_mode | new_mode) - (new_mode & conflict_x)
|
||||
else:
|
||||
raise apparmor.aa.AppArmorException(_('Unknown selection'))
|
||||
raise AppArmorException(_('Unknown selection'))
|
||||
done = True
|
||||
|
||||
def ask_the_questions(self, other, profile):
|
||||
@@ -309,7 +310,7 @@ class Merge(object):
|
||||
|
||||
#Add the capabilities
|
||||
for allow in ['allow', 'deny']:
|
||||
if other.aa[profile][hat].get(allow, False):
|
||||
if not other.aa[profile][hat].get(allow, False):
|
||||
continue
|
||||
for capability in sorted(other.aa[profile][hat][allow]['capability'].keys()):
|
||||
severity = sev_db.rank('CAP_%s' % capability)
|
||||
@@ -366,7 +367,7 @@ class Merge(object):
|
||||
|
||||
apparmor.aa.changed[profile] = True
|
||||
|
||||
aaui.UI_Info(_('Adding capability %s to profile.'), capability)
|
||||
aaui.UI_Info(_('Adding capability %s to profile.') % capability)
|
||||
done = True
|
||||
|
||||
elif ans == 'CMD_DENY':
|
||||
|
@@ -262,7 +262,9 @@ sub parse_message {
|
||||
# ignore all but status and denied messages
|
||||
my $type = LibAppArmor::aa_log_record::swig_event_get($test);
|
||||
|
||||
$type == $LibAppArmor::AA_RECORD_DENIED or goto err;
|
||||
if ($type != $LibAppArmor::AA_RECORD_DENIED and $type != $LibAppArmor::AA_RECORD_ALLOWED) {
|
||||
goto err;
|
||||
}
|
||||
|
||||
my $profile = LibAppArmor::aa_log_record::swig_profile_get($test);
|
||||
my $operation = LibAppArmor::aa_log_record::swig_operation_get($test);
|
||||
|
@@ -10,7 +10,7 @@
|
||||
#
|
||||
# ------------------------------------------------------------------
|
||||
|
||||
import re, os, sys
|
||||
import re, os, sys, errno
|
||||
|
||||
def cmd_enabled():
|
||||
'''Returns error code if AppArmor is not enabled'''
|
||||
@@ -85,14 +85,21 @@ def get_profiles():
|
||||
sys.exit(3)
|
||||
|
||||
apparmor_profiles = os.path.join(apparmorfs, "profiles")
|
||||
if not os.access(apparmor_profiles, os.R_OK):
|
||||
errormsg("You do not have enough privilege to read the profile set.")
|
||||
try:
|
||||
f = open(apparmor_profiles)
|
||||
except IOError as e:
|
||||
if e.errno == errno.EACCES:
|
||||
errormsg("You do not have enough privilege to read the profile set.")
|
||||
else:
|
||||
errormsg("Could not open %s: %s" % (apparmor_profiles, os.strerror(e.errno)))
|
||||
sys.exit(4)
|
||||
|
||||
for p in open(apparmor_profiles).readlines():
|
||||
for p in f.readlines():
|
||||
match = re.search("^([^\(]+)\s+\((\w+)\)$", p)
|
||||
profiles[match.group(1)] = match.group(2)
|
||||
|
||||
f.close()
|
||||
|
||||
return profiles
|
||||
|
||||
def get_processes(profiles):
|
||||
@@ -134,10 +141,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
|
||||
|
||||
@@ -142,8 +143,8 @@ def fatal_error(message):
|
||||
# Get the traceback to the message
|
||||
tb_stack = traceback.format_list(traceback.extract_stack())
|
||||
tb_stack = ''.join(tb_stack)
|
||||
# Append the traceback to message
|
||||
message = message + '\n' + tb_stack
|
||||
# Add the traceback to message
|
||||
message = tb_stack + '\n\n' + message
|
||||
debug_logger.error(message)
|
||||
caller = inspect.stack()[1][3]
|
||||
|
||||
@@ -249,8 +250,8 @@ def name_to_prof_filename(prof_filename):
|
||||
prof_filename = get_profile_filename(bin_path)
|
||||
if os.path.isfile(prof_filename):
|
||||
return (prof_filename, bin_path)
|
||||
else:
|
||||
return None, None
|
||||
|
||||
return None, None
|
||||
|
||||
def complain(path):
|
||||
"""Sets the profile to complain mode if it exists"""
|
||||
@@ -271,6 +272,7 @@ def set_complain(filename, program):
|
||||
aaui.UI_Info(_('Setting %s to complain mode.') % (filename if program is None else program))
|
||||
# a force-complain symlink is more packaging-friendly, but breaks caching
|
||||
# create_symlink('force-complain', filename)
|
||||
delete_symlink('disable', filename)
|
||||
change_profile_flags(filename, program, 'complain', True)
|
||||
|
||||
def set_enforce(filename, program):
|
||||
@@ -559,8 +561,6 @@ def activate_repo_profiles(url, profiles, complain):
|
||||
def autodep(bin_name, pname=''):
|
||||
bin_full = None
|
||||
global repo_cfg
|
||||
if not bin_name and pname.startswith('/'):
|
||||
bin_name = pname
|
||||
if not repo_cfg and not cfg['repository'].get('url', False):
|
||||
repo_conf = apparmor.config.Config('shell', CONFDIR)
|
||||
repo_cfg = repo_conf.read_config('repository.conf')
|
||||
@@ -603,9 +603,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 +638,58 @@ 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)
|
||||
# TODO: existing (unrelated) flags of hats and child profiles are overwritten - ideally, we should
|
||||
# keep them and only add or remove a given flag
|
||||
# TODO: change child profile flags even if program is specified
|
||||
|
||||
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]
|
||||
elif RE_PROFILE_HAT_DEF.search(line):
|
||||
matches = RE_PROFILE_HAT_DEF.search(line)
|
||||
space = matches.group('leadingspace') or ''
|
||||
hat_keyword = matches.group('hat_keyword')
|
||||
hat = matches.group('hat')
|
||||
comment = matches.group('comment') or ''
|
||||
if comment:
|
||||
comment = ' %s' % comment
|
||||
|
||||
if newflags:
|
||||
line = '%s%s%s flags=(%s) {%s\n' % (space, hat_keyword, hat, newflags, 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)
|
||||
line = '%s%s%s {%s\n' % (space, hat_keyword, 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"""
|
||||
@@ -1062,6 +1071,7 @@ def handle_children(profile, hat, root):
|
||||
detail = detail.replace('*', '\*')
|
||||
detail = detail.replace('{', '\{')
|
||||
detail = detail.replace('}', '\}')
|
||||
detail = detail.replace('!', '\!')
|
||||
|
||||
# Give Execute dialog if x access requested for something that's not a directory
|
||||
# For directories force an 'ix' Path dialog
|
||||
@@ -1075,25 +1085,7 @@ def handle_children(profile, hat, root):
|
||||
else:
|
||||
do_execute = True
|
||||
|
||||
if mode & apparmor.aamode.AA_MAY_LINK:
|
||||
regex_link = re.compile('^from (.+) to (.+)$')
|
||||
match = regex_link.search(detail)
|
||||
if match:
|
||||
path = match.groups()[0]
|
||||
target = match.groups()[1]
|
||||
|
||||
frommode = str_to_mode('lr')
|
||||
if prelog[aamode][profile][hat]['path'].get(path, False):
|
||||
frommode |= prelog[aamode][profile][hat]['path'][path]
|
||||
prelog[aamode][profile][hat]['path'][path] = frommode
|
||||
|
||||
tomode = str_to_mode('lr')
|
||||
if prelog[aamode][profile][hat]['path'].get(target, False):
|
||||
tomode |= prelog[aamode][profile][hat]['path'][target]
|
||||
prelog[aamode][profile][hat]['path'][target] = tomode
|
||||
else:
|
||||
continue
|
||||
elif mode:
|
||||
if mode:
|
||||
path = detail
|
||||
|
||||
if prelog[aamode][profile][hat]['path'].get(path, False):
|
||||
@@ -1460,10 +1452,6 @@ def handle_children(profile, hat, root):
|
||||
|
||||
return None
|
||||
|
||||
PROFILE_MODE_RE = re.compile('r|w|l|m|k|a|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
|
||||
PROFILE_MODE_NT_RE = re.compile('r|w|l|m|k|a|x|ix|ux|px|cx|pix|cix|Ux|Px|PUx|Cx|Pix|Cix')
|
||||
PROFILE_MODE_DENY_RE = re.compile('r|w|l|m|k|a|x')
|
||||
|
||||
##### Repo related functions
|
||||
|
||||
def UI_SelectUpdatedRepoProfile(profile, p):
|
||||
@@ -1993,10 +1981,10 @@ def ask_the_questions():
|
||||
audit = ''
|
||||
if audit_toggle:
|
||||
audit = 'audit'
|
||||
q.functions = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF',
|
||||
q.functions = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_OFF',
|
||||
'CMD_ABORT', 'CMD_FINISHED']
|
||||
else:
|
||||
q.functions = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW',
|
||||
q.functions = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW',
|
||||
'CMD_ABORT', 'CMD_FINISHED']
|
||||
q.headers = [_('Profile'), combine_name(profile, hat)]
|
||||
q.headers += [_('Network Family'), audit + family]
|
||||
@@ -2523,40 +2511,41 @@ def collapse_log():
|
||||
if not profile_known_network(aa[profile][hat], family, sock_type):
|
||||
log_dict[aamode][profile][hat]['netdomain'][family][sock_type] = True
|
||||
|
||||
PROFILE_MODE_RE = re.compile('^(r|w|l|m|k|a|ix|ux|px|pux|cx|pix|cix|cux|Ux|Px|PUx|Cx|Pix|Cix|CUx)+$')
|
||||
PROFILE_MODE_DENY_RE = re.compile('^(r|w|l|m|k|a|x)+$')
|
||||
|
||||
def validate_profile_mode(mode, allow, nt_name=None):
|
||||
if allow == 'deny':
|
||||
pattern = '^(%s)+$' % PROFILE_MODE_DENY_RE.pattern
|
||||
if re.search(pattern, mode):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
elif nt_name:
|
||||
pattern = '^(%s)+$' % PROFILE_MODE_NT_RE.pattern
|
||||
if re.search(pattern, mode):
|
||||
if PROFILE_MODE_DENY_RE.search(mode):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
else:
|
||||
pattern = '^(%s)+$' % PROFILE_MODE_RE.pattern
|
||||
if re.search(pattern, mode):
|
||||
if PROFILE_MODE_RE.search(mode):
|
||||
return True
|
||||
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 +2609,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 +2667,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
|
||||
@@ -2795,10 +2795,11 @@ def parse_profile_data(data, file, do_include):
|
||||
profile_data[profile][hat]['rlimit'][from_name] = to_name
|
||||
|
||||
elif RE_PROFILE_BOOLEAN.search(line):
|
||||
matches = RE_PROFILE_BOOLEAN.search(line)
|
||||
matches = RE_PROFILE_BOOLEAN.search(line).groups()
|
||||
|
||||
if not profile:
|
||||
raise AppArmorException(_('Syntax Error: Unexpected boolean definition found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 })
|
||||
if profile and not do_include:
|
||||
raise AppArmorException(_('Syntax Error: Unexpected boolean definition found inside profile in file: %(file)s line: %(line)s') % {
|
||||
'file': file, 'line': lineno + 1 })
|
||||
|
||||
bool_var = matches[0]
|
||||
value = matches[1]
|
||||
@@ -2811,7 +2812,7 @@ def parse_profile_data(data, file, do_include):
|
||||
|
||||
list_var = strip_quotes(matches[0])
|
||||
var_operation = matches[1]
|
||||
value = strip_quotes(matches[2])
|
||||
value = matches[2]
|
||||
|
||||
if profile:
|
||||
if not profile_data[profile][hat].get('lvar', False):
|
||||
@@ -2878,7 +2879,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()
|
||||
|
||||
@@ -3106,11 +3107,8 @@ def parse_profile_data(data, file, do_include):
|
||||
if not profile:
|
||||
raise AppArmorException(_('Syntax Error: Unexpected change hat declaration found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 })
|
||||
|
||||
hat = matches[0]
|
||||
hat = strip_quotes(hat)
|
||||
|
||||
if not profile_data[profile][hat].get('declared', False):
|
||||
profile_data[profile][hat]['declared'] = True
|
||||
aaui.UI_Important(_('Ignoring no longer supported change hat declaration "^%(hat)s," found in file: %(file)s line: %(line)s') % {
|
||||
'hat': matches[0], 'file': file, 'line': lineno + 1 })
|
||||
|
||||
elif RE_PROFILE_HAT_DEF.search(line):
|
||||
# An embedded hat syntax definition starts
|
||||
@@ -3131,7 +3129,7 @@ def parse_profile_data(data, file, do_include):
|
||||
if initial_comment:
|
||||
profile_data[profile][hat]['initial_comment'] = initial_comment
|
||||
initial_comment = ''
|
||||
if filelist[file]['profiles'][profile].get(hat, False):
|
||||
if filelist[file]['profiles'][profile].get(hat, False) and not do_include:
|
||||
raise AppArmorException(_('Error: Multiple definitions for hat %(hat)s in profile %(profile)s.') % { 'hat': hat, 'profile': profile })
|
||||
filelist[file]['profiles'][profile][hat] = True
|
||||
|
||||
@@ -3162,7 +3160,7 @@ def parse_profile_data(data, file, do_include):
|
||||
else:
|
||||
lastline = line
|
||||
else:
|
||||
raise AppArmorException(_('Syntax Error: Unknown line found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 })
|
||||
raise AppArmorException(_('Syntax Error: Unknown line found in file %(file)s line %(lineno)s: %(line)s') % { 'file': file, 'lineno': lineno + 1, 'line': line })
|
||||
|
||||
# Below is not required I'd say
|
||||
if not do_include:
|
||||
@@ -3236,14 +3234,17 @@ def parse_unix_rule(line):
|
||||
|
||||
def separate_vars(vs):
|
||||
"""Returns a list of all the values for a variable"""
|
||||
data = []
|
||||
data = set()
|
||||
vs = vs.strip()
|
||||
|
||||
#data = [i.strip('"') for i in vs.split()]
|
||||
RE_VARS = re.compile('\s*((\".+?\")|([^\"]\S+))\s*(.*)$')
|
||||
RE_VARS = re.compile('^(("[^"]*")|([^"\s]+))\s*(.*)$')
|
||||
while RE_VARS.search(vs):
|
||||
matches = RE_VARS.search(vs).groups()
|
||||
data.append(strip_quotes(matches[0]))
|
||||
vs = matches[3]
|
||||
data.add(strip_quotes(matches[0]))
|
||||
vs = matches[3].strip()
|
||||
|
||||
if vs:
|
||||
raise AppArmorException('Variable assignments contains invalid parts (unbalanced quotes?): %s' % vs)
|
||||
|
||||
return data
|
||||
|
||||
@@ -3254,29 +3255,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 +3288,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
|
||||
|
||||
@@ -3365,6 +3372,8 @@ def write_rlimits(prof_data, depth):
|
||||
def var_transform(ref):
|
||||
data = []
|
||||
for value in ref:
|
||||
if not value:
|
||||
value = '""'
|
||||
data.append(quote_if_needed(value))
|
||||
return ' '.join(data)
|
||||
|
||||
@@ -3408,14 +3417,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('')
|
||||
@@ -3517,6 +3530,24 @@ def write_pivot_root(prof_data, depth):
|
||||
data += write_pivot_root_rules(prof_data, depth, 'allow')
|
||||
return data
|
||||
|
||||
def write_unix_rules(prof_data, depth, allow):
|
||||
pre = ' ' * depth
|
||||
data = []
|
||||
|
||||
# no unix rules, so return
|
||||
if not prof_data[allow].get('unix', False):
|
||||
return data
|
||||
|
||||
for unix_rule in prof_data[allow]['unix']:
|
||||
data.append('%s%s' % (pre, unix_rule.serialize()))
|
||||
data.append('')
|
||||
return data
|
||||
|
||||
def write_unix(prof_data, depth):
|
||||
data = write_unix_rules(prof_data, depth, 'deny')
|
||||
data += write_unix_rules(prof_data, depth, 'allow')
|
||||
return data
|
||||
|
||||
def write_link_rules(prof_data, depth, allow):
|
||||
pre = ' ' * depth
|
||||
data = []
|
||||
@@ -3628,6 +3659,7 @@ def write_rules(prof_data, depth):
|
||||
data += write_signal(prof_data, depth)
|
||||
data += write_ptrace(prof_data, depth)
|
||||
data += write_pivot_root(prof_data, depth)
|
||||
data += write_unix(prof_data, depth)
|
||||
data += write_links(prof_data, depth)
|
||||
data += write_paths(prof_data, depth)
|
||||
data += write_change_profile(prof_data, depth)
|
||||
@@ -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 = ''
|
||||
@@ -3780,6 +3820,7 @@ def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
'signal': write_signal,
|
||||
'ptrace': write_ptrace,
|
||||
'pivot_root': write_pivot_root,
|
||||
'unix': write_unix,
|
||||
'link': write_links,
|
||||
'path': write_paths,
|
||||
'change_profile': write_change_profile,
|
||||
@@ -3795,6 +3836,7 @@ def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
'signal',
|
||||
'ptrace',
|
||||
'pivot_root',
|
||||
'unix',
|
||||
'link',
|
||||
'path',
|
||||
'change_profile',
|
||||
@@ -3811,6 +3853,7 @@ def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
'signal': True, # not handled otherwise yet
|
||||
'ptrace': True, # not handled otherwise yet
|
||||
'pivot_root': True, # not handled otherwise yet
|
||||
'unix': True, # not handled otherwise yet
|
||||
'link': False,
|
||||
'path': False,
|
||||
'change_profile': False,
|
||||
@@ -3835,31 +3878,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[hat]['profile'], write_prof_data[hat]['external'], correct)
|
||||
|
||||
if not write_prof_data[hat]['name'] == profile:
|
||||
correct = False
|
||||
@@ -4085,7 +4106,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
|
||||
@@ -4122,7 +4147,7 @@ def serialize_profile_from_old_profile(profile_data, name, options):
|
||||
if matches[0]:
|
||||
audit = mode
|
||||
|
||||
path_rule = write_prof_data[profile][hat][allow]['path'][ALL]
|
||||
path_rule = write_prof_data[hat][allow]['path'][ALL]
|
||||
if path_rule.get('mode', set()) & mode and \
|
||||
(not audit or path_rule.get('audit', set()) & audit) and \
|
||||
path_rule.get('file_prefix', set()):
|
||||
@@ -4147,7 +4172,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 +4182,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():
|
||||
@@ -4305,7 +4333,11 @@ def write_profile(profile):
|
||||
|
||||
os.rename(newprof.name, prof_filename)
|
||||
|
||||
changed.pop(profile)
|
||||
if profile in changed:
|
||||
changed.pop(profile)
|
||||
else:
|
||||
debug_logger.info("Unchanged profile written: %s (not listed in 'changed' list)" % profile)
|
||||
|
||||
original_aa[profile] = deepcopy(aa[profile])
|
||||
|
||||
def matchliteral(aa_regexp, literal):
|
||||
@@ -4349,6 +4381,11 @@ def profile_known_capability(profile, capname):
|
||||
return 1
|
||||
|
||||
for incname in profile['include'].keys():
|
||||
if not include.get(incname):
|
||||
# incname was not read before, might be a directory.
|
||||
# just avoid a crash (2.10 even checks all files inside the directory).
|
||||
# See https://bugs.launchpad.net/apparmor/+bug/1471425 for details.
|
||||
continue
|
||||
if include[incname][incname]['deny']['capability'][capname].get('set', False):
|
||||
return -1
|
||||
if include[incname][incname]['allow']['capability'][capname].get('set', False):
|
||||
@@ -4363,6 +4400,11 @@ def profile_known_network(profile, family, sock_type):
|
||||
return 1
|
||||
|
||||
for incname in profile['include'].keys():
|
||||
if not include.get(incname):
|
||||
# incname was not read before, might be a directory.
|
||||
# just avoid a crash (2.10 even checks all files inside the directory).
|
||||
# See https://bugs.launchpad.net/apparmor/+bug/1471425 for details.
|
||||
continue
|
||||
if netrules_access_check(include[incname][incname]['deny']['netdomain'], family, sock_type):
|
||||
return -1
|
||||
if netrules_access_check(include[incname][incname]['allow']['netdomain'], family, sock_type):
|
||||
@@ -4435,6 +4477,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
|
||||
|
||||
|
@@ -48,7 +48,7 @@ class CleanProf(object):
|
||||
#Process every hat in the profile individually
|
||||
file_includes = list(self.profile.filelist[self.profile.filename]['include'].keys())
|
||||
deleted = 0
|
||||
for hat in self.profile.aa[program].keys():
|
||||
for hat in sorted(self.profile.aa[program].keys()):
|
||||
#The combined list of includes from profile and the file
|
||||
includes = list(self.profile.aa[program][hat]['include'].keys()) + file_includes
|
||||
|
||||
@@ -76,7 +76,7 @@ class CleanProf(object):
|
||||
deleted += delete_net_duplicates(self.profile.aa[program][hat]['allow']['netdomain'], self.other.aa[program][hat]['allow']['netdomain'], self.same_file)
|
||||
deleted += delete_net_duplicates(self.profile.aa[program][hat]['deny']['netdomain'], self.other.aa[program][hat]['deny']['netdomain'], self.same_file)
|
||||
|
||||
return deleted
|
||||
return deleted
|
||||
|
||||
def delete_path_duplicates(profile, profile_other, allow, same_profile=True):
|
||||
deleted = []
|
||||
|
@@ -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
|
||||
@@ -16,7 +17,7 @@ import re
|
||||
import sys
|
||||
import time
|
||||
import LibAppArmor
|
||||
from apparmor.common import AppArmorException, open_file_read, DebugLogger
|
||||
from apparmor.common import AppArmorException, AppArmorBug, open_file_read, DebugLogger
|
||||
|
||||
from apparmor.aamode import validate_log_mode, log_str_to_mode, hide_log_mode, AA_MAY_EXEC
|
||||
|
||||
@@ -25,8 +26,24 @@ 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_audit = re.compile('type=AVC\s+(msg=)?audit\([\d\.\:]+\):\s+apparmor=')
|
||||
RE_audit_time_id = '(msg=)?audit\([\d\.\:]+\):\s+' # 'audit(1282626827.320:411): '
|
||||
RE_kernel_time = '\[[\d\.\s]+\]' # '[ 1612.746129]'
|
||||
RE_type_num = '1[45][0-9][0-9]' # 1400..1599
|
||||
RE_aa_or_op = '(apparmor=|operation=)'
|
||||
|
||||
RE_log_parts = [
|
||||
'kernel:\s+(' + RE_kernel_time + '\s+)?(audit:\s+)?type=' + RE_type_num + '\s+' + RE_audit_time_id + RE_aa_or_op, # v2_6 syslog
|
||||
'kernel:\s+(' + RE_kernel_time + '\s+)?' + RE_audit_time_id + 'type=' + RE_type_num + '\s+' + RE_aa_or_op,
|
||||
'type=(AVC|APPARMOR[_A-Z]*|' + RE_type_num + ')\s+' + RE_audit_time_id + '(type=' + RE_type_num + '\s+)?' + RE_aa_or_op, # v2_6 audit and dmesg
|
||||
'type=USER_AVC\s+' + RE_audit_time_id + '.*apparmor=', # dbus
|
||||
'type=UNKNOWN\[' + RE_type_num + '\]\s+' + RE_audit_time_id + RE_aa_or_op,
|
||||
'dbus\[[0-9]+\]:\s+apparmor=', # dbus
|
||||
]
|
||||
|
||||
# used to pre-filter log lines so that we hand over only relevant lines to LibAppArmor parsing
|
||||
RE_LOG_ALL = re.compile('(' + '|'.join(RE_log_parts) + ')')
|
||||
|
||||
|
||||
# Used by netdomain to identify the operation types
|
||||
# New socket names
|
||||
OPERATION_TYPES = {'create': 'net',
|
||||
@@ -41,6 +58,7 @@ class ReadLog:
|
||||
'getpeername': 'net',
|
||||
'getsockopt': 'net',
|
||||
'setsockopt': 'net',
|
||||
'socket_create': 'net',
|
||||
'sock_shutdown': 'net'
|
||||
}
|
||||
|
||||
@@ -60,7 +78,7 @@ class ReadLog:
|
||||
if self.next_log_entry:
|
||||
sys.stderr.out('A log entry already present: %s' % self.next_log_entry)
|
||||
self.next_log_entry = self.LOG.readline()
|
||||
while not self.RE_LOG_v2_6_syslog.search(self.next_log_entry) and not self.RE_LOG_v2_6_audit.search(self.next_log_entry) and not (self.logmark and self.logmark in self.next_log_entry):
|
||||
while not self.RE_LOG_ALL.search(self.next_log_entry) and not (self.logmark and self.logmark in self.next_log_entry):
|
||||
self.next_log_entry = self.LOG.readline()
|
||||
if not self.next_log_entry:
|
||||
break
|
||||
@@ -111,34 +129,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 +161,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
|
||||
@@ -234,10 +238,10 @@ class ReadLog:
|
||||
if e['operation'] == 'change_hat':
|
||||
if aamode != 'HINT' and aamode != 'PERMITTING':
|
||||
return None
|
||||
profile = e['name']
|
||||
profile = e['name2']
|
||||
#hat = None
|
||||
if '//' in e['name']:
|
||||
profile, hat = e['name'].split('//')[:2]
|
||||
if '//' in e['name2']:
|
||||
profile, hat = e['name2'].split('//')[:2]
|
||||
|
||||
if not hat:
|
||||
hat = profile
|
||||
@@ -248,6 +252,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 +265,36 @@ 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'] ):
|
||||
|
||||
# for some kernel-side reason, we get file-related log events without request_mask, see
|
||||
# https://bugs.launchpad.net/apparmor/+bug/1466812/, https://bugs.launchpad.net/apparmor/+bug/1509030 and https://bugs.launchpad.net/apparmor/+bug/1540562
|
||||
# request_mask can also be '', see https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1525119
|
||||
if not e['request_mask']:
|
||||
self.debug_logger.debug('UNHANDLED (missing request_mask): %s' % e)
|
||||
return None
|
||||
|
||||
# Map c (create) and d (delete) to w (logging is more detailed than the profile language)
|
||||
rmask = e['request_mask']
|
||||
rmask = rmask.replace('c', 'w')
|
||||
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', 'w')
|
||||
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 +311,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']
|
||||
@@ -351,7 +374,14 @@ class ReadLog:
|
||||
event = self.parse_log_record(line)
|
||||
#print(event)
|
||||
if event:
|
||||
self.add_event_to_tree(event)
|
||||
try:
|
||||
self.add_event_to_tree(event)
|
||||
except AppArmorException as e:
|
||||
ex_msg = ('%(msg)s This error was caused by the log line: %(logline)s' %
|
||||
{'msg': e.value, 'logline': line})
|
||||
# when py3 only: Drop the original AppArmorException by passing None as the parent exception
|
||||
raise AppArmorBug(ex_msg) # py3-only: from None
|
||||
|
||||
self.LOG.close()
|
||||
self.logmark = ''
|
||||
return self.log
|
||||
|
@@ -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,9 @@ 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_NAME = '(?P<%s>(\S+|"[^"]+"))' # string without spaces, or quoted string. %s is the match group name
|
||||
RE_PROFILE_PATH = '(?P<%s>(/\S+|"/[^"]+"))' # filename (starting with '/') without spaces, or quoted filename. %s is the match group name
|
||||
|
||||
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)
|
||||
@@ -39,7 +46,7 @@ RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + 'network(.*)' + RE_EOL)
|
||||
RE_NETWORK_FAMILY_TYPE = re.compile('\s+(\S+)\s+(\S+)\s*,$')
|
||||
RE_NETWORK_FAMILY = re.compile('\s+(\S+)\s*,$')
|
||||
RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)' + RE_COMMA_EOL)
|
||||
RE_PROFILE_HAT_DEF = re.compile('^\s*(\^|hat\s+)(?P<hat>\"??.+?\"??)\s+((flags=)?\((?P<flags>.+)\)\s+)*\{' + RE_EOL)
|
||||
RE_PROFILE_HAT_DEF = re.compile('^(?P<leadingspace>\s*)(?P<hat_keyword>\^|hat\s+)(?P<hat>\"??.+?\"??)\s+((flags=)?\((?P<flags>.+)\)\s+)*\{' + RE_EOL)
|
||||
RE_PROFILE_DBUS = re.compile(RE_AUDIT_DENY + '(dbus\s*,|dbus\s+[^#]*\s*,)' + RE_EOL)
|
||||
RE_PROFILE_MOUNT = re.compile(RE_AUDIT_DENY + '((mount|remount|umount|unmount)(\s+[^#]*)?\s*,)' + RE_EOL)
|
||||
RE_PROFILE_SIGNAL = re.compile(RE_AUDIT_DENY + '(signal\s*,|signal\s+[^#]*\s*,)' + RE_EOL)
|
||||
@@ -55,3 +62,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*)' +
|
||||
'(' +
|
||||
RE_PROFILE_PATH % 'plainprofile' + # just a path
|
||||
'|' + # or
|
||||
'(' + 'profile' + '\s+' + RE_PROFILE_NAME % 'namedprofile' + '(\s+' + RE_PROFILE_PATH % 'attachment' + ')?' + ')' + # '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
|
||||
|
@@ -29,12 +29,10 @@ class aa_tools:
|
||||
self.profiling = args.program
|
||||
self.check_profile_dir()
|
||||
self.silent = None
|
||||
self.do_reload = args.do_reload
|
||||
|
||||
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 +48,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'''
|
||||
|
||||
@@ -63,7 +57,7 @@ class aa_tools:
|
||||
|
||||
program = None
|
||||
profile = None
|
||||
if os.path.exists(p):
|
||||
if os.path.exists(p) or p.startswith('/'):
|
||||
fq_path = apparmor.get_full_path(p).strip()
|
||||
if os.path.commonprefix([apparmor.profile_dir, fq_path]) == apparmor.profile_dir:
|
||||
program = None
|
||||
@@ -89,12 +83,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 +111,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 +121,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 +134,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 +149,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 +164,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 +184,21 @@ 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])
|
||||
disable_link = '%s/disable/%s' % (apparmor.profile_dir, os.path.basename(profile))
|
||||
|
||||
if cmd_info[0] != 0:
|
||||
raise apparmor.AppArmorException(cmd_info[1])
|
||||
if os.path.exists(disable_link):
|
||||
aaui.UI_Info(_('\nWarning: the profile %s is disabled. Use aa-enforce or aa-complain to enable it.') % os.path.basename(profile))
|
||||
|
||||
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 +248,22 @@ class aa_tools:
|
||||
|
||||
def disable_profile(self, filename):
|
||||
apparmor.create_symlink('disable', filename)
|
||||
|
||||
def unload_profile(self, profile):
|
||||
if not self.do_reload:
|
||||
return
|
||||
|
||||
# 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):
|
||||
if not self.do_reload:
|
||||
return
|
||||
|
||||
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])
|
||||
|
@@ -106,6 +106,13 @@
|
||||
/usr/bin/killall = icn
|
||||
/usr/bin/nice = icn
|
||||
/usr/bin/perl = icn
|
||||
/usr/bin/python = icn
|
||||
/usr/bin/python2 = icn
|
||||
/usr/bin/python2.7 = icn
|
||||
/usr/bin/python3 = icn
|
||||
/usr/bin/python3.3 = icn
|
||||
/usr/bin/python3.4 = icn
|
||||
/usr/bin/python3.5 = icn
|
||||
/usr/bin/tr = icn
|
||||
|
||||
[required_hats]
|
||||
|
@@ -26,6 +26,27 @@ common/Make.rules: $(COMMONDIR)/Make.rules
|
||||
ln -sf $(COMMONDIR) .
|
||||
endif
|
||||
|
||||
ifdef USE_SYSTEM
|
||||
LD_LIBRARY_PATH=
|
||||
PYTHONPATH=
|
||||
else
|
||||
# PYTHON_DIST_BUILD_PATH based on libapparmor/swig/python/test/Makefile.am
|
||||
PYTHON_DIST_BUILD_PATH = ../../libraries/libapparmor/swig/python/build/$$($(PYTHON) -c "import distutils.util; import platform; print(\"lib.%s-%s\" %(distutils.util.get_platform(), platform.python_version()[:3]))")
|
||||
LD_LIBRARY_PATH=../../libraries/libapparmor/src/.libs/
|
||||
PYTHONPATH=..:$(PYTHON_DIST_BUILD_PATH)
|
||||
endif
|
||||
|
||||
.PHONY: __libapparmor
|
||||
__libapparmor:
|
||||
ifndef USE_SYSTEM
|
||||
@if [ ! -f $(LD_LIBRARY_PATH)libapparmor.so ]; then \
|
||||
echo "error: $(LD_LIBRARY_PATH)libapparmor.so is missing. Pick one of these possible solutions:" 1>&2; \
|
||||
echo " 1) Build against the in-tree libapparmor by building it first and then trying again. See the top-level README for help." 1>&2; \
|
||||
echo " 2) Build against the system libapparmor by adding USE_SYSTEM=1 to your make command." 1>&2; \
|
||||
return 1; \
|
||||
fi
|
||||
endif
|
||||
|
||||
COVERAGE_OMIT=test-*.py,common_test.py
|
||||
ifneq ($(COVERAGE_OUT), )
|
||||
HTML_COVR_ARGS=-d $(COVERAGE_OUT)
|
||||
@@ -39,11 +60,11 @@ endif
|
||||
clean: _clean
|
||||
rm -rf __pycache__/ common .coverage htmlcov
|
||||
|
||||
check:
|
||||
export PYTHONPATH=.. ; $(foreach test, $(wildcard test-*.py), $(call pyalldo, $(test)))
|
||||
check: __libapparmor
|
||||
export PYTHONPATH=$(PYTHONPATH) ; export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) ; $(foreach test, $(wildcard test-*.py), $(call pyalldo, $(test)))
|
||||
|
||||
.coverage: $(wildcard ../aa-* ../apparmor/*.py test-*.py)
|
||||
export PYTHONPATH=.. ; $(foreach test, $(wildcard test-*.py), $(PYTHON) -m coverage run --branch -p $(test); )
|
||||
.coverage: $(wildcard ../aa-* ../apparmor/*.py test-*.py) __libapparmor
|
||||
export PYTHONPATH=$(PYTHONPATH) ; export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH); $(foreach test, $(wildcard test-*.py), $(PYTHON) -m coverage run --branch -p $(test); )
|
||||
$(PYTHON) -m coverage combine
|
||||
|
||||
coverage: .coverage
|
||||
|
@@ -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():
|
||||
|
@@ -7,6 +7,7 @@
|
||||
#Below rule comes from abstractions/base
|
||||
allow /usr/share/X11/locale/** r,
|
||||
allow /home/*/** r,
|
||||
unix (receive) type=dgram,
|
||||
allow /home/foo/bar r,
|
||||
allow /home/foo/** w,
|
||||
}
|
||||
@@ -16,4 +17,4 @@
|
||||
# However this comment will be wiped, need to change that
|
||||
allow /home/*/** rw,
|
||||
allow /home/foo/bar r,
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,8 @@
|
||||
/usr/bin/a/simple/cleanprof/test/profile {
|
||||
#include <abstractions/base>
|
||||
|
||||
unix (receive) type=dgram,
|
||||
|
||||
/home/*/** r,
|
||||
/home/foo/** w,
|
||||
|
||||
|
@@ -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()
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user