From bb56f039a8411bd9cb1e034e1c4483330fdd6574 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Mon, 18 May 2015 01:35:51 +0200 Subject: [PATCH 001/143] Fix raising AppArmorException in aa-mergeprof aa-mergeprof failed to fail ;-) when it should raise an AppArmorException. Instead, it failed with AttributeError: 'module' object has no attribute 'AppArmorException' I confirmed this bug in trunk and 2.9. Acked-by: Steve Beattie for trunk and 2.9. --- utils/aa-mergeprof | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index 46cfebb0e..a7054ebad 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -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): From c2973f0b7bd39af9660cf6e0dcb5b06bc12b8da1 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Tue, 19 May 2015 01:20:49 +0200 Subject: [PATCH 002/143] Add the attach_disconnected flag to the ntpd profile I noticed "disconnected path" (run/nscd/*) events for ntpd while updating to the latest openSUSE Tumbleweed. Acked-by: Seth Arnold for trunk and 2.9. --- profiles/apparmor.d/usr.sbin.ntpd | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/profiles/apparmor.d/usr.sbin.ntpd b/profiles/apparmor.d/usr.sbin.ntpd index e7a403d14..d1c3f6f98 100644 --- a/profiles/apparmor.d/usr.sbin.ntpd +++ b/profiles/apparmor.d/usr.sbin.ntpd @@ -11,7 +11,7 @@ #include #include -/usr/sbin/ntpd { +/usr/sbin/ntpd flags=(attach_disconnected) { #include #include #include From 9b5ff659b0dcee9d846e0307b84affffacbe5a2c Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Tue, 19 May 2015 01:25:26 +0200 Subject: [PATCH 003/143] Update Samba profiles for Samba 4.2 Samba 4.2 needs some more permissions for nmbd and winbindd. To avoid overcomplicated profiles, change abstractions/samba to allow /var/lib/samba/** rwk, (instead of **.tdb rwk) - this change already fixes the nmbd profile. winbindd additionally needs some more write permissions in /etc/samba/ (and also in /var/lib/samba/, which is covered by the abstractions/samba change and also results in some profile cleanup) References: https://bugzilla.opensuse.org/show_bug.cgi?id=921098 and https://bugzilla.opensuse.org/show_bug.cgi?id=923201 Acked-by: Seth Arnold --- profiles/apparmor.d/abstractions/samba | 2 +- profiles/apparmor.d/usr.sbin.winbindd | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/profiles/apparmor.d/abstractions/samba b/profiles/apparmor.d/abstractions/samba index 87e0b2d15..87a6f90bb 100644 --- a/profiles/apparmor.d/abstractions/samba +++ b/profiles/apparmor.d/abstractions/samba @@ -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, diff --git a/profiles/apparmor.d/usr.sbin.winbindd b/profiles/apparmor.d/usr.sbin.winbindd index 5a3e214ff..497d1a5ab 100644 --- a/profiles/apparmor.d/usr.sbin.winbindd +++ b/profiles/apparmor.d/usr.sbin.winbindd @@ -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/* rw, @{PROC}/sys/kernel/core_pattern r, /tmp/.winbindd/ w, /tmp/krb5cc_* rwk, @@ -21,9 +25,6 @@ /usr/sbin/winbindd mr, /var/cache/krb5rcache/* rw, /var/cache/samba/*.tdb rwk, - /var/lib/samba/smb_krb5/krb5.conf.* rw, - /var/lib/samba/smb_tmp_krb5.* rw, - /var/lib/samba/winbindd_cache.tdb* rwk, /var/log/samba/log.winbindd rw, /{var/,}run/samba/winbindd.pid rwk, /{var/,}run/samba/winbindd/ rw, From 81f932531c5961bc01fc5b50e17a6d3554e25c3a Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Tue, 19 May 2015 21:19:56 -0500 Subject: [PATCH 004/143] tests: Verify aa_getpeercon() return value This patch modifies the socketpair.c test to verify the return value of aa_getpeercon() based upon the expected label and expected mode lengths. The test had to be changed slightly so that the returned mode, from aa_getpeercon(), was preserved. It was being overwritten with the special NO_MODE value. This change helps to make sure that future changes to the code behind aa_getpeercon() does not unintentionally change the function's return value. Signed-off-by: Tyler Hicks Acked-by: Steve Beattie --- tests/regression/apparmor/socketpair.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/tests/regression/apparmor/socketpair.c b/tests/regression/apparmor/socketpair.c index 2959ed32f..491907f2a 100644 --- a/tests/regression/apparmor/socketpair.c +++ b/tests/regression/apparmor/socketpair.c @@ -55,7 +55,7 @@ static int verify_confinement_context(int fd, const char *fd_name, const char *expected_mode) { char *label, *mode; - int rc; + int expected_rc, rc; rc = aa_getpeercon(fd, &label, &mode); if (rc < 0) { @@ -64,9 +64,6 @@ static int verify_confinement_context(int fd, const char *fd_name, return 1; } - if (!mode) - mode = NO_MODE; - if (strcmp(label, expected_label)) { fprintf(stderr, "FAIL - %s: label \"%s\" != expected_label \"%s\"\n", @@ -75,7 +72,8 @@ static int verify_confinement_context(int fd, const char *fd_name, goto out; } - if (strcmp(mode, expected_mode)) { + if ((!expected_mode && mode) || (expected_mode && !mode) || + (expected_mode && mode && strcmp(mode, expected_mode))) { fprintf(stderr, "FAIL - %s: mode \"%s\" != expected_mode \"%s\"\n", fd_name, mode, expected_mode); @@ -83,6 +81,20 @@ static int verify_confinement_context(int fd, const char *fd_name, goto out; } + expected_rc = strlen(expected_label); + if (expected_mode) { + /* ' ' + '(' + expected_mode + ')' */ + expected_rc += 1 + 1 + strlen(expected_mode) + 1; + } + expected_rc++; /* Trailing NUL terminator */ + + if (rc != expected_rc) { + fprintf(stderr, "FAIL - %s: rc (%d) != expected_rc (%d)\n", + fd_name, rc, expected_rc); + rc = 4; + goto out; + } + rc = 0; out: free(label); @@ -163,7 +175,7 @@ int main(int argc, char **argv) exit(2); expected_label = argv[1]; - expected_mode = argv[2]; + expected_mode = !strcmp(argv[2], NO_MODE) ? NULL : argv[2]; if (verify_confinement_context(pair[0], "pair[0]", expected_label, expected_mode)) { From 6d8827594af4e7025600b47dd63d6b808a5aef17 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Tue, 19 May 2015 21:20:21 -0500 Subject: [PATCH 005/143] libapparmor: Don't count NUL terminator byte When passing the size of the confinement context to parse_confinement_mode(), don't include the NUL terminator byte in the size. It is confusing to count the NUL terminator as part of the string's length. This change makes it so that, after a few additional changes, parse_confinement_mode() can be exposed as part of libapparmor's public API. Signed-off-by: Tyler Hicks Acked-by: Seth Arnold --- libraries/libapparmor/src/kernel.c | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/libraries/libapparmor/src/kernel.c b/libraries/libapparmor/src/kernel.c index 9d5f45df1..14593b732 100644 --- a/libraries/libapparmor/src/kernel.c +++ b/libraries/libapparmor/src/kernel.c @@ -154,7 +154,7 @@ static char *procattr_path(pid_t pid, const char *attr) /** * parse_confinement_mode - get the mode from the confinement context * @con: the confinement context - * @size: size of the confinement context + * @size: size of the confinement context (not including the NUL terminator) * * Modifies con to NUL-terminate the label string and the mode string. * @@ -164,14 +164,14 @@ static char *procattr_path(pid_t pid, const char *attr) static char *parse_confinement_mode(char *con, int size) { if (strcmp(con, "unconfined") != 0 && - size > 4 && con[size - 2] == ')') { - int pos = size - 3; + size > 3 && con[size - 1] == ')') { + int pos = size - 2; while (pos > 0 && !(con[pos] == ' ' && con[pos + 1] == '(')) pos--; if (pos > 0) { con[pos] = 0; /* overwrite ' ' */ - con[size - 2] = 0; /* overwrite trailing ) */ + con[size - 1] = 0; /* overwrite trailing ) */ return &con[pos + 2]; /* skip '(' */ } } @@ -236,18 +236,21 @@ int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len, errno = saved; goto out; } else if (size > 0 && buf[size - 1] != 0) { + char *nul; + /* check for null termination */ if (buf[size - 1] == '\n') { - buf[size - 1] = 0; + nul = &buf[size - 1]; } else if (len == 0) { errno = ERANGE; goto out2; } else { - buf[size] = 0; + nul = &buf[size]; size++; } - mode_str = parse_confinement_mode(buf, size); + *nul = 0; + mode_str = parse_confinement_mode(buf, nul - buf); if (mode) *mode = mode_str; } @@ -614,7 +617,7 @@ int aa_getpeercon_raw(int fd, char *buf, int *len, char **mode) } } - mode_str = parse_confinement_mode(buf, optlen); + mode_str = parse_confinement_mode(buf, optlen - 1); if (mode) *mode = mode_str; From f6df1c75166ca913b89ea55dbc3419c4da5331b9 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Tue, 19 May 2015 21:20:37 -0500 Subject: [PATCH 006/143] libapparmor: Clean up confinement context's unconfined check Use the passed in confinement context string size to improve the comparison by only doing the string comparison if the size matches and removing the possibility of reading past the end of the buffer. Signed-off-by: Tyler Hicks Acked-by: Seth Arnold --- libraries/libapparmor/src/kernel.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/libraries/libapparmor/src/kernel.c b/libraries/libapparmor/src/kernel.c index 14593b732..b1670503f 100644 --- a/libraries/libapparmor/src/kernel.c +++ b/libraries/libapparmor/src/kernel.c @@ -43,6 +43,9 @@ #define default_symbol_version(real, name, version) \ __asm__ (".symver " #real "," #name "@@" #version) +#define UNCONFINED "unconfined" +#define UNCONFINED_SIZE strlen(UNCONFINED) + /** * aa_find_mountpoint - find where the apparmor interface filesystem is mounted * @mnt: returns buffer with the mountpoint string @@ -151,6 +154,19 @@ static char *procattr_path(pid_t pid, const char *attr) return NULL; } +/** + * parse_unconfined - check for the unconfined label + * @con: the confinement context + * @size: size of the confinement context (not including the NUL terminator) + * + * Returns: True if the con is the unconfined label or false otherwise + */ +static bool parse_unconfined(char *con, int size) +{ + return size == UNCONFINED_SIZE && + strncmp(con, UNCONFINED, UNCONFINED_SIZE) == 0; +} + /** * parse_confinement_mode - get the mode from the confinement context * @con: the confinement context @@ -163,8 +179,7 @@ static char *procattr_path(pid_t pid, const char *attr) */ static char *parse_confinement_mode(char *con, int size) { - if (strcmp(con, "unconfined") != 0 && - size > 3 && con[size - 1] == ')') { + if (!parse_unconfined(con, size) && size > 3 && con[size - 1] == ')') { int pos = size - 2; while (pos > 0 && !(con[pos] == ' ' && con[pos + 1] == '(')) From 4879b46b1397fce0c73fba5daedd005d7ac6fe53 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Tue, 19 May 2015 21:20:51 -0500 Subject: [PATCH 007/143] libapparmor: Detect errors when splitting confinement contexts The parse_confinement_mode() function returned NULL when a confinement mode was not present (unconfined) and when it could not properly parse the confinement context. The two situations should be differentiated since the latter should be treated as an error. This patch reworks parse_confinement_mode() to split a confinement context and, optionally, assign the mode string. If a parsing error is encountered, NULL is returned to indicate error. Signed-off-by: Tyler Hicks Acked-by: Seth Arnold --- libraries/libapparmor/src/kernel.c | 48 ++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/libraries/libapparmor/src/kernel.c b/libraries/libapparmor/src/kernel.c index b1670503f..b792ac2fc 100644 --- a/libraries/libapparmor/src/kernel.c +++ b/libraries/libapparmor/src/kernel.c @@ -168,18 +168,29 @@ static bool parse_unconfined(char *con, int size) } /** - * parse_confinement_mode - get the mode from the confinement context + * splitcon - split the confinement context into a label and mode * @con: the confinement context * @size: size of the confinement context (not including the NUL terminator) + * @mode: if non-NULL and a mode is present, will point to mode string in @con + * on success * - * Modifies con to NUL-terminate the label string and the mode string. + * Modifies the @con string to split it into separate label and mode strings. + * The @mode argument is optional. If @mode is NULL, @con will still be split + * between the label and mode (if present) but @mode will not be set. * - * Returns: a pointer to the NUL-terminated mode inside the confinement context - * or NULL if the mode was not found + * Returns: a pointer to the label string or NULL on error */ -static char *parse_confinement_mode(char *con, int size) +static char *splitcon(char *con, int size, char **mode) { - if (!parse_unconfined(con, size) && size > 3 && con[size - 1] == ')') { + char *label = NULL; + char *mode_str = NULL; + + if (parse_unconfined(con, size)) { + label = con; + goto out; + } + + if (size > 3 && con[size - 1] == ')') { int pos = size - 2; while (pos > 0 && !(con[pos] == ' ' && con[pos + 1] == '(')) @@ -187,10 +198,14 @@ static char *parse_confinement_mode(char *con, int size) if (pos > 0) { con[pos] = 0; /* overwrite ' ' */ con[size - 1] = 0; /* overwrite trailing ) */ - return &con[pos + 2]; /* skip '(' */ + mode_str = &con[pos + 2]; /* skip '(' */ + label = con; } } - return NULL; +out: + if (mode) + *mode = mode_str; + return label; } /** @@ -209,7 +224,6 @@ int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len, int rc = -1; int fd, ret; char *tmp = NULL; - char *mode_str; int size = 0; if (!buf || len <= 0) { @@ -265,9 +279,10 @@ int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len, } *nul = 0; - mode_str = parse_confinement_mode(buf, nul - buf); - if (mode) - *mode = mode_str; + if (splitcon(buf, nul - buf, mode) != buf) { + errno = EINVAL; + goto out2; + } } rc = size; @@ -606,7 +621,6 @@ int aa_getcon(char **label, char **mode) int aa_getpeercon_raw(int fd, char *buf, int *len, char **mode) { socklen_t optlen = *len; - char *mode_str; int rc; if (optlen <= 0 || buf == NULL) { @@ -632,9 +646,11 @@ int aa_getpeercon_raw(int fd, char *buf, int *len, char **mode) } } - mode_str = parse_confinement_mode(buf, optlen - 1); - if (mode) - *mode = mode_str; + if (splitcon(buf, optlen - 1, mode) != buf) { + rc = -1; + errno = EINVAL; + goto out; + } rc = optlen; out: From 014093dedc02283203c7f143762e9b37ec47d84c Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Tue, 19 May 2015 21:28:47 -0500 Subject: [PATCH 008/143] libapparmor: Add aa_splitcon() public function Create a new libapparmor public function that allows external code to split an AppArmor confinement context. This is immediately useful for code that retrieves a D-Bus peer's AppArmor confinement context using the org.freedesktop.DBus.GetConnectionCredentials bus method. https://launchpad.net/bugs/1430532 Signed-off-by: Tyler Hicks Acked-by: Seth Arnold --- libraries/libapparmor/doc/Makefile.am | 13 +++- libraries/libapparmor/doc/aa_getcon.pod | 4 +- libraries/libapparmor/doc/aa_splitcon.pod | 65 ++++++++++++++++++++ libraries/libapparmor/include/sys/apparmor.h | 1 + libraries/libapparmor/src/kernel.c | 17 +++++ libraries/libapparmor/src/libapparmor.map | 1 + 6 files changed, 97 insertions(+), 4 deletions(-) create mode 100644 libraries/libapparmor/doc/aa_splitcon.pod diff --git a/libraries/libapparmor/doc/Makefile.am b/libraries/libapparmor/doc/Makefile.am index 39d741d66..b102137a9 100644 --- a/libraries/libapparmor/doc/Makefile.am +++ b/libraries/libapparmor/doc/Makefile.am @@ -5,9 +5,9 @@ PODCHECKER = podchecker if ENABLE_MAN_PAGES -man_MANS = aa_change_hat.2 aa_change_profile.2 aa_getcon.2 aa_find_mountpoint.2 +man_MANS = aa_change_hat.2 aa_change_profile.2 aa_getcon.2 aa_find_mountpoint.2 aa_splitcon.3 -PODS = $(subst .2,.pod,$(man_MANS)) +PODS = $(subst .2,.pod,$(man_MANS)) $(subst .3,.pod,$(man_MANS)) EXTRA_DIST = $(man_MANS) $(PODS) @@ -23,4 +23,13 @@ CLEANFILES = $(man_MANS) --stderr \ $< > $@ +%.3: %.pod + $(PODCHECKER) -warnings -warnings $< + $(POD2MAN) \ + --section=3 \ + --release="AppArmor $(VERSION)" \ + --center="AppArmor" \ + --stderr \ + $< > $@ + endif diff --git a/libraries/libapparmor/doc/aa_getcon.pod b/libraries/libapparmor/doc/aa_getcon.pod index d944fecee..32ef61fc8 100644 --- a/libraries/libapparmor/doc/aa_getcon.pod +++ b/libraries/libapparmor/doc/aa_getcon.pod @@ -131,7 +131,7 @@ L. =head1 SEE ALSO -apparmor(7), apparmor.d(5), apparmor_parser(8), aa_change_profile(2) and -L. +apparmor(7), apparmor.d(5), apparmor_parser(8), aa_change_profile(2), +aa_splitcon(3) and L. =cut diff --git a/libraries/libapparmor/doc/aa_splitcon.pod b/libraries/libapparmor/doc/aa_splitcon.pod new file mode 100644 index 000000000..a85b61902 --- /dev/null +++ b/libraries/libapparmor/doc/aa_splitcon.pod @@ -0,0 +1,65 @@ +# This publication is intellectual property of Canonical Ltd. Its contents +# can be duplicated, either in part or in whole, provided that a copyright +# label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither Canonical Ltd, the authors, nor the translators shall be held +# liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. Canonical Ltd. +# essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa_splitcon - split the confinement context into a label and mode + +=head1 SYNOPSIS + +B<#include Esys/apparmor.hE> + +B + +Link with B<-lapparmor> when compiling. + +=head1 DESCRIPTION + +The aa_splitcon() function splits a confinement context into separate label +and mode strings. The @con string is modified so that the label portion is NUL +terminated. The enforcement mode is also NUL terminated and the parenthesis +surrounding the mode are removed. If @mode is non-NULL, it will point to the +first character in the enforcement mode string on success. + +=head1 RETURN VALUE + +Returns a pointer to the first character in the label string. NULL is returned +on error. + +=head1 EXAMPLE + + Context Label Mode + ----------------------------- ------------------ ------- + unconfined unconfined NULL + /bin/ping (enforce) /bin/ping enforce + /usr/sbin/rsyslogd (complain) /usr/sbin/rsyslogd complain + +=head1 BUGS + +None known. If you find any, please report them at +L. + +=head1 SEE ALSO + +aa_getcon(2) and L. + +=cut diff --git a/libraries/libapparmor/include/sys/apparmor.h b/libraries/libapparmor/include/sys/apparmor.h index 99ce36b96..a7f90985e 100644 --- a/libraries/libapparmor/include/sys/apparmor.h +++ b/libraries/libapparmor/include/sys/apparmor.h @@ -58,6 +58,7 @@ extern int aa_change_onexec(const char *profile); extern int aa_change_hatv(const char *subprofiles[], unsigned long token); extern int (aa_change_hat_vargs)(unsigned long token, int count, ...); +extern char *aa_splitcon(char *con, char **mode); /* Protypes for introspecting task confinement * Please see the aa_getcon(2) manpage for information */ diff --git a/libraries/libapparmor/src/kernel.c b/libraries/libapparmor/src/kernel.c index b792ac2fc..5f2d83521 100644 --- a/libraries/libapparmor/src/kernel.c +++ b/libraries/libapparmor/src/kernel.c @@ -208,6 +208,23 @@ out: return label; } +/** + * aa_splitcon - split the confinement context into a label and mode + * @con: the confinement context + * @mode: if non-NULL and a mode is present, will point to mode string in @con + * on success + * + * Modifies the @con string to split it into separate label and mode strings. + * The @mode argument is optional. If @mode is NULL, @con will still be split + * between the label and mode (if present) but @mode will not be set. + * + * Returns: a pointer to the label string or NULL on error + */ +char *aa_splitcon(char *con, char **mode) +{ + return splitcon(con, strlen(con), mode); +} + /** * aa_getprocattr_raw - get the contents of @attr for @tid into @buf * @tid: tid of task to query diff --git a/libraries/libapparmor/src/libapparmor.map b/libraries/libapparmor/src/libapparmor.map index 3f434941f..28f245f37 100644 --- a/libraries/libapparmor/src/libapparmor.map +++ b/libraries/libapparmor/src/libapparmor.map @@ -80,6 +80,7 @@ APPARMOR_2.10 { aa_policy_cache_create; aa_policy_cache_remove; aa_policy_cache_replace_all; + aa_splitcon; local: *; } APPARMOR_2.9; From 8fffe4c721d1b3c5a9f1165d940e3c30433eb27f Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Tue, 19 May 2015 21:30:39 -0500 Subject: [PATCH 009/143] libapparmor: Add unit tests for aa_splitcon() Test confinement context splitting, using aa_splitcon(3), with and without a valid mode pointer. Signed-off-by: Tyler Hicks Acked-by: Seth Arnold --- libraries/libapparmor/src/Makefile.am | 6 +- libraries/libapparmor/src/tst_kernel.c | 133 +++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 libraries/libapparmor/src/tst_kernel.c diff --git a/libraries/libapparmor/src/Makefile.am b/libraries/libapparmor/src/Makefile.am index 505d1f70b..deca53ed6 100644 --- a/libraries/libapparmor/src/Makefile.am +++ b/libraries/libapparmor/src/Makefile.am @@ -67,7 +67,11 @@ tst_aalogmisc_LDADD = .libs/libapparmor.a tst_features_SOURCES = tst_features.c tst_features_LDADD = .libs/libapparmor.a -check_PROGRAMS = tst_aalogmisc tst_features +tst_kernel_SOURCES = tst_kernel.c +tst_kernel_LDADD = .libs/libapparmor.a +tst_kernel_LDFLAGS = -pthread + +check_PROGRAMS = tst_aalogmisc tst_features tst_kernel TESTS = $(check_PROGRAMS) EXTRA_DIST = grammar.y scanner.l libapparmor.map libapparmor.pc diff --git a/libraries/libapparmor/src/tst_kernel.c b/libraries/libapparmor/src/tst_kernel.c new file mode 100644 index 000000000..8a8e70ea5 --- /dev/null +++ b/libraries/libapparmor/src/tst_kernel.c @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2015 + * Canonical, Ltd. (All rights reserved) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU General Public + * License published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact Novell, Inc. or Canonical + * Ltd. + */ + +#include +#include + +#include "features.c" + +static int nullcmp_and_strcmp(const void *s1, const void *s2) +{ + /* Return 0 if both pointers are NULL & non-zero if only one is NULL */ + if (!s1 || !s2) + return s1 != s2; + + return strcmp(s1, s2); +} + +static int do_test_aa_splitcon(char *con, char **mode, + const char *expected_label, + const char *expected_mode, const char *error) +{ + char *label; + int rc = 0; + + label = aa_splitcon(con, mode); + + if (nullcmp_and_strcmp(label, expected_label)) { + fprintf(stderr, "FAIL: %s: label \"%s\" != \"%s\"\n", + error, label, expected_label); + rc = 1; + } + + if (mode && nullcmp_and_strcmp(*mode, expected_mode)) { + fprintf(stderr, "FAIL: %s: mode \"%s\" != \"%s\"\n", + error, *mode, expected_mode); + rc = 1; + } + + return rc; +} + +#define TEST_SPLITCON(con, expected_label, expected_mode, error) \ + do { \ + char c1[] = con; \ + char c2[] = con; \ + char *mode; \ + \ + if (do_test_aa_splitcon(c1, &mode, expected_label, \ + expected_mode, error)) { \ + rc = 1; \ + } else if (do_test_aa_splitcon(c2, NULL, expected_label,\ + NULL, \ + error " (NULL mode)")) { \ + rc = 1; \ + } \ + } while (0) + + +static int test_aa_splitcon(void) +{ + int rc = 0; + + TEST_SPLITCON("label (mode)", "label", "mode", "basic split"); + + TEST_SPLITCON("/a/b/c (enforce)", "/a/b/c", "enforce", + "path enforce split"); + + TEST_SPLITCON("/a/b/c (complain)", "/a/b/c", "complain", + "path complain split"); + + TEST_SPLITCON("profile_name (enforce)", "profile_name", "enforce", + "name enforce split"); + + TEST_SPLITCON("profile_name (complain)", "profile_name", "complain", + "name complain split"); + + TEST_SPLITCON("unconfined", "unconfined", NULL, "unconfined"); + + TEST_SPLITCON("(odd) (enforce)", "(odd)", "enforce", + "parenthesized label #1"); + + TEST_SPLITCON("(odd) (enforce) (enforce)", "(odd) (enforce)", "enforce", + "parenthesized label #2"); + + TEST_SPLITCON("/usr/bin/😺 (enforce)", "/usr/bin/😺", "enforce", + "non-ASCII path"); + + TEST_SPLITCON("👍 (enforce)", "👍", "enforce", "non-ASCII profile name"); + + /* Negative tests */ + + TEST_SPLITCON("", NULL, NULL, "empty string test"); + + TEST_SPLITCON("/a/b/c (complain)\n", NULL, NULL, + "path split w/ invalid trailing newline"); + + TEST_SPLITCON("unconfined\n", NULL, NULL, + "unconfined w/ invalid trailing newline"); + + TEST_SPLITCON("profile\t(enforce)", NULL, NULL, + "invalid tab separator"); + + TEST_SPLITCON("profile(enforce)", NULL, NULL, + "invalid missing separator"); + + return rc; +} + +int main(void) +{ + int retval, rc = 0; + + retval = test_aa_splitcon(); + if (retval) + rc = retval; + + return rc; +} From c6e395c08fe3e303f179a14d1b1c8ab58a71602f Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Tue, 19 May 2015 21:31:53 -0500 Subject: [PATCH 010/143] libapparmor: Strip a trailing newline character in aa_splitcon(3) Adjust the internal splitcon() function to strip a single trailing newline character when the bool strip_newline argument is true. aa_getprocattr_raw(2) needs to set strip_newline to true since the kernel appends a newline character to the end of the AppArmor contexts read from /proc/>PID>/attr/current. aa_splitcon(3) also sets strip_newline to true since it is unknown whether the context is originated from a location that appends a newline or not. aa_getpeercon_raw(2) does not set strip_newline to true since it is unexpected for the kernel to append a newline to the the buffer returned from getsockopt(2). This patch also creates tests specifically for splitcon() and updates the aa_splitcon(3) man page. Signed-off-by: Tyler Hicks Acked-by: Seth Arnold --- libraries/libapparmor/doc/aa_splitcon.pod | 7 + libraries/libapparmor/src/kernel.c | 50 ++++--- libraries/libapparmor/src/tst_kernel.c | 165 +++++++++++++++++----- 3 files changed, 168 insertions(+), 54 deletions(-) diff --git a/libraries/libapparmor/doc/aa_splitcon.pod b/libraries/libapparmor/doc/aa_splitcon.pod index a85b61902..2a46dd25a 100644 --- a/libraries/libapparmor/doc/aa_splitcon.pod +++ b/libraries/libapparmor/doc/aa_splitcon.pod @@ -40,6 +40,11 @@ terminated. The enforcement mode is also NUL terminated and the parenthesis surrounding the mode are removed. If @mode is non-NULL, it will point to the first character in the enforcement mode string on success. +The Linux kernel's /proc//attr/current interface appends a trailing +newline character to AppArmor contexts that are read from that file. If @con +contains a single trailing newline character, it will be stripped by +aa_splitcon() prior to all other processing. + =head1 RETURN VALUE Returns a pointer to the first character in the label string. NULL is returned @@ -50,7 +55,9 @@ on error. Context Label Mode ----------------------------- ------------------ ------- unconfined unconfined NULL + unconfined\n unconfined NULL /bin/ping (enforce) /bin/ping enforce + /bin/ping (enforce)\n /bin/ping enforce /usr/sbin/rsyslogd (complain) /usr/sbin/rsyslogd complain =head1 BUGS diff --git a/libraries/libapparmor/src/kernel.c b/libraries/libapparmor/src/kernel.c index 5f2d83521..07bc9b44e 100644 --- a/libraries/libapparmor/src/kernel.c +++ b/libraries/libapparmor/src/kernel.c @@ -171,19 +171,31 @@ static bool parse_unconfined(char *con, int size) * splitcon - split the confinement context into a label and mode * @con: the confinement context * @size: size of the confinement context (not including the NUL terminator) + * @strip_newline: true if a trailing newline character should be stripped * @mode: if non-NULL and a mode is present, will point to mode string in @con * on success * * Modifies the @con string to split it into separate label and mode strings. - * The @mode argument is optional. If @mode is NULL, @con will still be split - * between the label and mode (if present) but @mode will not be set. + * If @strip_newline is true and @con contains a single trailing newline, it + * will be stripped on success (it will not be stripped on error). The @mode + * argument is optional. If @mode is NULL, @con will still be split between the + * label and mode (if present) but @mode will not be set. * * Returns: a pointer to the label string or NULL on error */ -static char *splitcon(char *con, int size, char **mode) +static char *splitcon(char *con, int size, bool strip_newline, char **mode) { char *label = NULL; char *mode_str = NULL; + char *newline = NULL; + + if (size == 0) + goto out; + + if (strip_newline && con[size - 1] == '\n') { + newline = &con[size - 1]; + size--; + } if (parse_unconfined(con, size)) { label = con; @@ -203,6 +215,8 @@ static char *splitcon(char *con, int size, char **mode) } } out: + if (label && strip_newline && newline) + *newline = 0; /* overwrite '\n', if requested, on success */ if (mode) *mode = mode_str; return label; @@ -214,15 +228,16 @@ out: * @mode: if non-NULL and a mode is present, will point to mode string in @con * on success * - * Modifies the @con string to split it into separate label and mode strings. - * The @mode argument is optional. If @mode is NULL, @con will still be split + * Modifies the @con string to split it into separate label and mode strings. A + * single trailing newline character will be stripped from @con, if found. The + * @mode argument is optional. If @mode is NULL, @con will still be split * between the label and mode (if present) but @mode will not be set. * * Returns: a pointer to the label string or NULL on error */ char *aa_splitcon(char *con, char **mode) { - return splitcon(con, strlen(con), mode); + return splitcon(con, strlen(con), true, mode); } /** @@ -282,21 +297,18 @@ int aa_getprocattr_raw(pid_t tid, const char *attr, char *buf, int len, errno = saved; goto out; } else if (size > 0 && buf[size - 1] != 0) { - char *nul; - /* check for null termination */ - if (buf[size - 1] == '\n') { - nul = &buf[size - 1]; - } else if (len == 0) { - errno = ERANGE; - goto out2; - } else { - nul = &buf[size]; - size++; + if (buf[size - 1] != '\n') { + if (len == 0) { + errno = ERANGE; + goto out2; + } else { + buf[size] = 0; + size++; + } } - *nul = 0; - if (splitcon(buf, nul - buf, mode) != buf) { + if (splitcon(buf, size, true, mode) != buf) { errno = EINVAL; goto out2; } @@ -663,7 +675,7 @@ int aa_getpeercon_raw(int fd, char *buf, int *len, char **mode) } } - if (splitcon(buf, optlen - 1, mode) != buf) { + if (splitcon(buf, optlen - 1, false, mode) != buf) { rc = -1; errno = EINVAL; goto out; diff --git a/libraries/libapparmor/src/tst_kernel.c b/libraries/libapparmor/src/tst_kernel.c index 8a8e70ea5..a383774e3 100644 --- a/libraries/libapparmor/src/tst_kernel.c +++ b/libraries/libapparmor/src/tst_kernel.c @@ -19,7 +19,7 @@ #include #include -#include "features.c" +#include "kernel.c" static int nullcmp_and_strcmp(const void *s1, const void *s2) { @@ -30,6 +30,30 @@ static int nullcmp_and_strcmp(const void *s1, const void *s2) return strcmp(s1, s2); } +static int do_test_splitcon(char *con, int size, bool strip_nl, char **mode, + const char *expected_label, + const char *expected_mode, const char *error) +{ + char *label; + int rc = 0; + + label = splitcon(con, size, strip_nl, mode); + + if (nullcmp_and_strcmp(label, expected_label)) { + fprintf(stderr, "FAIL: %s: label \"%s\" != \"%s\"\n", + error, label, expected_label); + rc = 1; + } + + if (mode && nullcmp_and_strcmp(*mode, expected_mode)) { + fprintf(stderr, "FAIL: %s: mode \"%s\" != \"%s\"\n", + error, *mode, expected_mode); + rc = 1; + } + + return rc; +} + static int do_test_aa_splitcon(char *con, char **mode, const char *expected_label, const char *expected_mode, const char *error) @@ -54,69 +78,136 @@ static int do_test_aa_splitcon(char *con, char **mode, return rc; } -#define TEST_SPLITCON(con, expected_label, expected_mode, error) \ +#define TEST_SPLITCON(con, size, strip_nl, expected_label, \ + expected_mode, error) \ do { \ char c1[] = con; \ char c2[] = con; \ + size_t sz = size < 0 ? strlen(con) : size; \ char *mode; \ \ - if (do_test_aa_splitcon(c1, &mode, expected_label, \ - expected_mode, error)) { \ + if (do_test_splitcon(c1, sz, strip_nl, &mode, \ + expected_label, expected_mode, \ + "splitcon: " error)) { \ rc = 1; \ - } else if (do_test_aa_splitcon(c2, NULL, expected_label,\ - NULL, \ - error " (NULL mode)")) { \ + } else if (do_test_splitcon(c2, sz, strip_nl, NULL, \ + expected_label, NULL, \ + "splitcon: " error " (NULL mode)")) { \ rc = 1; \ } \ } while (0) +#define TEST_AA_SPLITCON(con, expected_label, expected_mode, error) \ + do { \ + char c1[] = con; \ + char c2[] = con; \ + char c3[] = con "\n"; \ + char *mode; \ + \ + if (do_test_aa_splitcon(c1, &mode, expected_label, \ + expected_mode, "aa_splitcon: " error)) {\ + rc = 1; \ + } else if (do_test_aa_splitcon(c2, NULL, expected_label,\ + NULL, \ + "aa_splitcon: " error " (NULL mode)")) {\ + rc = 1; \ + } else if (do_test_aa_splitcon(c3, &mode, \ + expected_label, expected_mode, \ + "aa_splitcon: " error " (newline)")) { \ + rc = 1; \ + } \ + } while (0) + +static int test_splitcon(void) +{ + int rc = 0; + + /** + * NOTE: the TEST_SPLITCON() macro automatically generates + * corresponding tests with a NULL mode pointer. + */ + + TEST_SPLITCON("", 0, true, NULL, NULL, "empty string test #1"); + TEST_SPLITCON("", 0, false, NULL, NULL, "empty string test #2"); + + TEST_SPLITCON("unconfined", -1, true, "unconfined", NULL, + "unconfined #1"); + TEST_SPLITCON("unconfined", -1, false, "unconfined", NULL, + "unconfined #2"); + TEST_SPLITCON("unconfined\n", -1, true, "unconfined", NULL, + "unconfined #3"); + TEST_SPLITCON("unconfined\n", -1, false, NULL, NULL, + "unconfined #4"); + + TEST_SPLITCON("label (mode)", -1, true, "label", "mode", + "basic split #1"); + TEST_SPLITCON("label (mode)", -1, false, "label", "mode", + "basic split #2"); + TEST_SPLITCON("label (mode)\n", -1, true, "label", "mode", + "basic split #3"); + TEST_SPLITCON("label (mode)\n", -1, false, NULL, NULL, + "basic split #4"); + + TEST_SPLITCON("/a/b/c (enforce)", -1, true, "/a/b/c", "enforce", + "path enforce split #1"); + TEST_SPLITCON("/a/b/c (enforce)", -1, false, "/a/b/c", "enforce", + "path enforce split #2"); + TEST_SPLITCON("/a/b/c (enforce)\n", -1, true, "/a/b/c", "enforce", + "path enforce split #3"); + TEST_SPLITCON("/a/b/c (enforce)\n", -1, false, NULL, NULL, + "path enforce split #4"); + + return rc; +} + static int test_aa_splitcon(void) { int rc = 0; - TEST_SPLITCON("label (mode)", "label", "mode", "basic split"); + /** + * NOTE: the TEST_AA_SPLITCON() macro automatically generates + * corresponding tests with a NULL mode pointer and contexts with + * trailing newline characters. + */ - TEST_SPLITCON("/a/b/c (enforce)", "/a/b/c", "enforce", - "path enforce split"); + TEST_AA_SPLITCON("label (mode)", "label", "mode", "basic split"); - TEST_SPLITCON("/a/b/c (complain)", "/a/b/c", "complain", - "path complain split"); + TEST_AA_SPLITCON("/a/b/c (enforce)", "/a/b/c", "enforce", + "path enforce split"); - TEST_SPLITCON("profile_name (enforce)", "profile_name", "enforce", - "name enforce split"); + TEST_AA_SPLITCON("/a/b/c (complain)", "/a/b/c", "complain", + "path complain split"); - TEST_SPLITCON("profile_name (complain)", "profile_name", "complain", - "name complain split"); + TEST_AA_SPLITCON("profile_name (enforce)", "profile_name", "enforce", + "name enforce split"); - TEST_SPLITCON("unconfined", "unconfined", NULL, "unconfined"); + TEST_AA_SPLITCON("profile_name (complain)", "profile_name", "complain", + "name complain split"); - TEST_SPLITCON("(odd) (enforce)", "(odd)", "enforce", - "parenthesized label #1"); + TEST_AA_SPLITCON("unconfined", "unconfined", NULL, "unconfined"); - TEST_SPLITCON("(odd) (enforce) (enforce)", "(odd) (enforce)", "enforce", - "parenthesized label #2"); + TEST_AA_SPLITCON("(odd) (enforce)", "(odd)", "enforce", + "parenthesized label #1"); - TEST_SPLITCON("/usr/bin/😺 (enforce)", "/usr/bin/😺", "enforce", - "non-ASCII path"); + TEST_AA_SPLITCON("(odd) (enforce) (enforce)", "(odd) (enforce)", + "enforce", "parenthesized label #2"); - TEST_SPLITCON("👍 (enforce)", "👍", "enforce", "non-ASCII profile name"); + TEST_AA_SPLITCON("/usr/bin/😺 (enforce)", "/usr/bin/😺", "enforce", + "non-ASCII path"); + + TEST_AA_SPLITCON("👍 (enforce)", "👍", "enforce", + "non-ASCII profile name"); /* Negative tests */ - TEST_SPLITCON("", NULL, NULL, "empty string test"); + TEST_AA_SPLITCON("", NULL, NULL, "empty string test"); - TEST_SPLITCON("/a/b/c (complain)\n", NULL, NULL, - "path split w/ invalid trailing newline"); + TEST_AA_SPLITCON("profile\t(enforce)", NULL, NULL, + "invalid tab separator"); - TEST_SPLITCON("unconfined\n", NULL, NULL, - "unconfined w/ invalid trailing newline"); - - TEST_SPLITCON("profile\t(enforce)", NULL, NULL, - "invalid tab separator"); - - TEST_SPLITCON("profile(enforce)", NULL, NULL, - "invalid missing separator"); + TEST_AA_SPLITCON("profile(enforce)", NULL, NULL, + "invalid missing separator"); return rc; } @@ -125,6 +216,10 @@ int main(void) { int retval, rc = 0; + retval = test_splitcon(); + if (retval) + rc = retval; + retval = test_aa_splitcon(); if (retval) rc = retval; From 20c2c4c17144457431a65b9612e71c8d540649bc Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Tue, 19 May 2015 21:46:23 -0500 Subject: [PATCH 011/143] libapparmor: Fix pod2man warning in aa_splitcon(3) *** WARNING: 2 unescaped <> in paragraph at line 43 in file aa_splitcon.pod Signed-off-by: Tyler Hicks --- libraries/libapparmor/doc/aa_splitcon.pod | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/libapparmor/doc/aa_splitcon.pod b/libraries/libapparmor/doc/aa_splitcon.pod index 2a46dd25a..21bdb902d 100644 --- a/libraries/libapparmor/doc/aa_splitcon.pod +++ b/libraries/libapparmor/doc/aa_splitcon.pod @@ -40,9 +40,9 @@ terminated. The enforcement mode is also NUL terminated and the parenthesis surrounding the mode are removed. If @mode is non-NULL, it will point to the first character in the enforcement mode string on success. -The Linux kernel's /proc//attr/current interface appends a trailing -newline character to AppArmor contexts that are read from that file. If @con -contains a single trailing newline character, it will be stripped by +The Linux kernel's /proc/EPIDE/attr/current interface appends a +trailing newline character to AppArmor contexts that are read from that file. +If @con contains a single trailing newline character, it will be stripped by aa_splitcon() prior to all other processing. =head1 RETURN VALUE From a566935d64122613c7db8ee65e01e6e99d9c8556 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Thu, 28 May 2015 14:48:46 -0500 Subject: [PATCH 012/143] tests: Make query_label accept file queries Signed-off-by: Tyler Hicks Acked-by: John Johansen --- tests/regression/apparmor/query_label.c | 93 +++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/tests/regression/apparmor/query_label.c b/tests/regression/apparmor/query_label.c index be945cbd8..bf8dfe936 100644 --- a/tests/regression/apparmor/query_label.c +++ b/tests/regression/apparmor/query_label.c @@ -12,6 +12,53 @@ #define OPT_TYPE_DBUS "--dbus=" #define OPT_TYPE_DBUS_LEN strlen(OPT_TYPE_DBUS) +#define OPT_TYPE_FILE "--file=" +#define OPT_TYPE_FILE_LEN strlen(OPT_TYPE_FILE) + +#ifndef AA_CLASS_FILE +#define AA_CLASS_FILE 2 +#endif + +#ifndef AA_MAY_EXEC +#define AA_MAY_EXEC (1 << 0) +#endif + +#ifndef AA_MAY_WRITE +#define AA_MAY_WRITE (1 << 1) +#endif + +#ifndef AA_MAY_READ +#define AA_MAY_READ (1 << 2) +#endif + +#ifndef AA_MAY_APPEND +#define AA_MAY_APPEND (1 << 3) +#endif + +#ifndef AA_MAY_LINK +#define AA_MAY_LINK (1 << 4) +#endif + +#ifndef AA_MAY_LOCK +#define AA_MAY_LOCK (1 << 5) +#endif + +#ifndef AA_EXEC_MMAP +#define AA_EXEC_MMAP (1 << 6) +#endif + +#ifndef AA_EXEC_PUX +#define AA_EXEC_PUX (1 << 7) +#endif + +#ifndef AA_EXEC_UNSAFE +#define AA_EXEC_UNSAFE (1 << 8) +#endif + +#ifndef AA_EXEC_INHERIT +#define AA_EXEC_INHERIT (1 << 9) +#endif + static char *progname = NULL; void usage(void) @@ -26,9 +73,11 @@ void usage(void) fprintf(stderr, " LABEL\t\tThe AppArmor label to use in the query\n"); fprintf(stderr, " CLASS\t\tThe rule class and may consist of:\n"); fprintf(stderr, "\t\t dbus\n"); + fprintf(stderr, "\t\t file\n"); fprintf(stderr, " PERMS\t\tA comma separated list of permissions. Possibilities\n"); fprintf(stderr, "\t\tfor the supported rule classes are:\n"); fprintf(stderr, "\t\t dbus: send,receive,bind\n"); + fprintf(stderr, "\t\t file: exec,write,read,append,link,lock,exec_mmap,exec_pux,exec_unsafe,exec_inherit\n"); fprintf(stderr, "\t\tAdditionaly, PERMS can be empty to indicate an empty mask\n"); exit(1); } @@ -83,6 +132,45 @@ static int parse_dbus_perms(uint32_t *mask, char *perms) return 0; } +static int parse_file_perms(uint32_t *mask, char *perms) +{ + char *perm; + + *mask = 0; + + perm = strtok(perms, ","); + while (perm) { + if (!strcmp(perm, "exec")) + *mask |= AA_MAY_EXEC; + else if (!strcmp(perm, "write")) + *mask |= AA_MAY_WRITE; + else if (!strcmp(perm, "read")) + *mask |= AA_MAY_READ; + else if (!strcmp(perm, "append")) + *mask |= AA_MAY_APPEND; + else if (!strcmp(perm, "link")) + *mask |= AA_MAY_LINK; + else if (!strcmp(perm, "lock")) + *mask |= AA_MAY_LOCK; + else if (!strcmp(perm, "exec_mmap")) + *mask |= AA_EXEC_MMAP; + else if (!strcmp(perm, "exec_pux")) + *mask |= AA_EXEC_PUX; + else if (!strcmp(perm, "exec_unsafe")) + *mask |= AA_EXEC_UNSAFE; + else if (!strcmp(perm, "exec_inherit")) + *mask |= AA_EXEC_INHERIT; + else { + fprintf(stderr, "FAIL: unknown perm: %s\n", perm); + return 1; + } + + perm = strtok(NULL, ","); + } + + return 0; +} + static ssize_t build_query(char **qstr, const char *label, int class, int argc, char **argv) { @@ -149,6 +237,11 @@ int main(int argc, char **argv) rc = parse_dbus_perms(&mask, class_str + OPT_TYPE_DBUS_LEN); if (rc) usage(); + } else if (!strncmp(class_str, OPT_TYPE_FILE, OPT_TYPE_FILE_LEN)) { + class = AA_CLASS_FILE; + rc = parse_file_perms(&mask, class_str + OPT_TYPE_FILE_LEN); + if (rc) + usage(); } else { fprintf(stderr, "FAIL: unknown rule class: %s\n", class_str); usage(); From afde1cc53ae706914802a80631d9a3269a3998f2 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Thu, 28 May 2015 14:48:50 -0500 Subject: [PATCH 013/143] tests: Adjust query_label.sh to query a different profile The test program was querying its own profile. Adjust the profile generation so that a separate profile is generated and have query_label query the separate profile. Signed-off-by: Tyler Hicks Acked-by: John Johansen --- tests/regression/apparmor/query_label.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/regression/apparmor/query_label.sh b/tests/regression/apparmor/query_label.sh index 92e588e0a..099233a83 100755 --- a/tests/regression/apparmor/query_label.sh +++ b/tests/regression/apparmor/query_label.sh @@ -23,7 +23,8 @@ settest query_label expect="" perms="" -label="--label=$test" +qprof="/profile/to/query" +label="--label=$qprof" dbus_send="--dbus=send" dbus_receive="--dbus=receive" @@ -35,13 +36,16 @@ dbus_none="--dbus=" dbus_msg_query="session com.foo.bar /usr/bin/bar /com/foo/bar com.foo.bar Method" dbus_svc_query="session com.foo.baz" -# Generate a profile for $test, granting all file access and anything specified -# in $@ +# Generate a profile for $test, granting all file accesses, and $qprof, +# granting anything specified in $@. genqueryprofile() { genprofile --stdin < Date: Thu, 28 May 2015 14:48:53 -0500 Subject: [PATCH 014/143] tests: Add query_label.sh tests for file queries A number of simple query tests based on read and write perms of files and directories. Signed-off-by: Tyler Hicks Acked-by: John Johansen --- tests/regression/apparmor/query_label.sh | 32 ++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/regression/apparmor/query_label.sh b/tests/regression/apparmor/query_label.sh index 099233a83..01ec6d139 100755 --- a/tests/regression/apparmor/query_label.sh +++ b/tests/regression/apparmor/query_label.sh @@ -209,3 +209,35 @@ perms dbus send querytest "QUERY dbus (svc send)" fail $dbus_svc_query perms dbus receive querytest "QUERY dbus (svc receive)" fail $dbus_svc_query + +genqueryprofile "file," +expect allow +perms file exec,write,read,append,link,lock +querytest "QUERY file (all base perms #1)" pass /anything +querytest "QUERY file (all base perms #2)" pass /everything + +genqueryprofile "/etc/passwd r," +expect allow +perms file read +querytest "QUERY file (passwd)" pass /etc/passwd +querytest "QUERY file (passwd bad path #1)" fail /etc/pass +querytest "QUERY file (passwd bad path #2)" fail /etc/passwdXXX +querytest "QUERY file (passwd bad path #3)" fail /etc/passwd/XXX +perms file write +querytest "QUERY file (passwd bad perms #1)" fail /etc/passwd +perms file read,write +querytest "QUERY file (passwd bad perms #2)" fail /etc/passwd + +genqueryprofile "/tmp/ rw," +expect allow +perms file read,write +querytest "QUERY file (/tmp/)" pass /tmp/ +querytest "QUERY file (/tmp/ bad path)" fail /tmp +querytest "QUERY file (/tmp/ bad path)" fail /tmp/tmp/ +perms file read +querytest "QUERY file (/tmp/ read only)" pass /tmp/ +perms file write +querytest "QUERY file (/tmp/ write only)" pass /tmp/ +expect audit +perms file read,write +querytest "QUERY file (/tmp/ wrong dir)" pass /etc/ From f26b035e90a9bcc9bd2c99ffabfe31bcd8c0fe48 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Thu, 28 May 2015 22:14:37 +0200 Subject: [PATCH 015/143] Let set_profile_flags() change the flags for all hats It did this in the old 2.8 code, but didn't in 2.9.x (first there was a broken hat regex, then I commented out the hat handling to avoid breakage caused by the broken regex). This patch makes sure the hat flags get set when setting the flags for the main profile. Also change RE_PROFILE_HAT_DEF to use more named matches (leadingwhitespace and hat_keyword). Luckily all code that uses the regex uses named matches already, which means adding another (...) pair doesn't hurt. Finally adjust the tests: - change _test_set_flags to accept another optional parameter expected_more_rules (used to specify the expected hat definition) - add tests for hats (with '^foobar' and 'hat foobar' syntax) - add tests for child profiles, one of them commented out (see below) Remaining known issues (also added as TODO notes): - The hat and child profile flags are *overwritten* with the flags used for the main profile. (That's well-known behaviour from 2.8 :-/ but we have more flags now, which makes this more annoying.) The correct behaviour would be to add or remove the specified flag, while keeping other flags unchanged. - Child profiles are not handled/changed if you specify the 'program' parameter. This means: - 'aa-complain smbldap-useradd' or 'aa-complain /usr/sbin/smbldap-useradd' _will not_ change the flags for the nscd child profile - 'aa-complain /etc/apparmor.d/usr.sbin.smbldap-useradd' _will_ change the flags for the nscd child profile (and any other profile and child profile in that file) Even with those remaining issues (which need bigger changes in set_profile_flags() and maybe also in the whole flags handling), the patch improves things and fixes the regression from the 2.8 code. Acked-by: Steve Beattie for trunk and 2.9 --- utils/apparmor/aa.py | 26 ++++++++++++-------- utils/apparmor/regex.py | 2 +- utils/test/test-aa.py | 53 +++++++++++++++++++++++++++++++++++------ 3 files changed, 63 insertions(+), 18 deletions(-) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index 36614ce8b..a774e2f43 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -652,8 +652,9 @@ def set_profile_flags(prof_filename, program, newflags): """Reads the old profile file and updates the flags accordingly""" # 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 - # TODO: use RE_PROFILE_HAT_DEF for matching the hat (regex_hat_flag is totally broken!) - #regex_hat_flag = re.compile('^([a-z]*)\s+([A-Z]*)\s*(#.*)?$') + # 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 @@ -680,14 +681,19 @@ def set_profile_flags(prof_filename, program, newflags): } line = write_header(header_data, len(space)/2, profile, False, True) line = '%s\n' % line[0] - #else: - # match = regex_hat_flag.search(line) - # if match: - # hat, flags = match.groups()[:2] - # if newflags: - # line = '%s flags=(%s) {%s\n' % (hat, newflags, comment) - # else: - # line = '%s {%s\n' % (hat, comment) + 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: + line = '%s%s%s {%s\n' % (space, hat_keyword, hat, comment) f_out.write(line) os.rename(temp_file.name, prof_filename) diff --git a/utils/apparmor/regex.py b/utils/apparmor/regex.py index d164b3664..2bf2ff278 100644 --- a/utils/apparmor/regex.py +++ b/utils/apparmor/regex.py @@ -44,7 +44,7 @@ RE_PROFILE_BARE_FILE_ENTRY = re.compile(RE_AUDIT_DENY + RE_OWNER + 'file' + RE_C RE_PROFILE_PATH_ENTRY = re.compile(RE_AUDIT_DENY + RE_OWNER + '(file\s+)?([\"@/].*?)\s+(\S+)(\s+->\s*(.*?))?' + RE_COMMA_EOL) RE_PROFILE_NETWORK = re.compile(RE_AUDIT_DENY + 'network(?P
\s+.*)?' + RE_COMMA_EOL) RE_PROFILE_CHANGE_HAT = re.compile('^\s*\^(\"??.+?\"??)' + RE_COMMA_EOL) -RE_PROFILE_HAT_DEF = re.compile('^\s*(\^|hat\s+)(?P\"??.+?\"??)\s+((flags=)?\((?P.+)\)\s+)*\{' + RE_EOL) +RE_PROFILE_HAT_DEF = re.compile('^(?P\s*)(?P\^|hat\s+)(?P\"??.+?\"??)\s+((flags=)?\((?P.+)\)\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) diff --git a/utils/test/test-aa.py b/utils/test/test-aa.py index 9284806ac..f2234f9c9 100644 --- a/utils/test/test-aa.py +++ b/utils/test/test-aa.py @@ -106,7 +106,8 @@ class AaTest_get_profile_flags(AaTestWithTempdir): self._test_get_flags('/no-such-profile flags=(complain)', 'complain') class AaTest_set_profile_flags(AaTestWithTempdir): - def _test_set_flags(self, profile, old_flags, new_flags, whitespace='', comment='', more_rules='', + def _test_set_flags(self, profile, old_flags, new_flags, whitespace='', comment='', + more_rules='', expected_more_rules='@-@-@', expected_flags='@-@-@', check_new_flags=True, profile_name='/foo'): if old_flags: old_flags = ' %s' % old_flags @@ -119,13 +120,16 @@ class AaTest_set_profile_flags(AaTestWithTempdir): else: expected_flags = '' + if expected_more_rules == '@-@-@': + expected_more_rules = more_rules + if comment: comment = ' %s' % comment dummy_profile_content = ' #include \n capability chown,\n /bar r,' prof_template = '%s%s%s {%s\n%s\n%s\n}\n' - old_prof = prof_template % (whitespace, profile, old_flags, comment, more_rules, dummy_profile_content) - new_prof = prof_template % (whitespace, profile, expected_flags, comment, more_rules, dummy_profile_content) + old_prof = prof_template % (whitespace, profile, old_flags, comment, more_rules, dummy_profile_content) + new_prof = prof_template % (whitespace, profile, expected_flags, comment, expected_more_rules, dummy_profile_content) self.file = write_file(self.tmpdir, 'profile', old_prof) set_profile_flags(self.file, profile_name, new_flags) @@ -184,11 +188,46 @@ class AaTest_set_profile_flags(AaTestWithTempdir): def test_set_flags_12(self): self._test_set_flags('profile xy "/foo bar"', 'flags=(complain)', 'audit', profile_name='xy') + # test handling of hat flags + def test_set_flags_with_hat_01(self): + self._test_set_flags('/foo', 'flags=(complain)', 'audit', + more_rules='\n ^foobar {\n}\n', + expected_more_rules='\n ^foobar flags=(audit) {\n}\n' + ) - # XXX regex_hat_flag in set_profile_flags() is totally broken - it matches for ' ' and ' X ', but doesn't match for ' ^foo {' - # oh, it matches on a line like 'dbus' and changes it to 'dbus flags=(...)' if there's no leading whitespace (and no comma) - #def test_set_flags_hat_01(self): - # self._test_set_flags(' ^hat', '', 'audit') + def test_set_flags_with_hat_02(self): + self._test_set_flags('/foo', 'flags=(complain)', 'audit', + profile_name=None, + more_rules='\n ^foobar {\n}\n', + expected_more_rules='\n ^foobar flags=(audit) {\n}\n' + ) + + def test_set_flags_with_hat_03(self): + self._test_set_flags('/foo', 'flags=(complain)', 'audit', + more_rules='\n^foobar (attach_disconnected) { # comment\n}\n', # XXX attach_disconnected will be lost! + expected_more_rules='\n^foobar flags=(audit) { # comment\n}\n' + ) + + def test_set_flags_with_hat_04(self): + self._test_set_flags('/foo', '', 'audit', + more_rules='\n hat foobar (attach_disconnected) { # comment\n}\n', # XXX attach_disconnected will be lost! + expected_more_rules='\n hat foobar flags=(audit) { # comment\n}\n' + ) + + # test handling of child profiles + def test_set_flags_with_child_01(self): + self._test_set_flags('/foo', 'flags=(complain)', 'audit', + profile_name=None, + more_rules='\n profile /bin/bar {\n}\n', + expected_more_rules='\n profile /bin/bar flags=(audit) {\n}\n' + ) + + #def test_set_flags_with_child_02(self): + # XXX child profile flags aren't changed if profile parameter is not None + #self._test_set_flags('/foo', 'flags=(complain)', 'audit', + # more_rules='\n profile /bin/bar {\n}\n', + # expected_more_rules='\n profile /bin/bar flags=(audit) {\n}\n' + #) def test_set_flags_invalid_01(self): From 59d4011033656cf3eecf744119f9aa185d7a41ec Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Thu, 28 May 2015 22:22:56 +0200 Subject: [PATCH 016/143] Add ChangeProfileRule and ChangeProfileRuleset classes Add utils/apparmor/rule/change_profile.py with the ChangeProfileRule and ChangeProfileRuleset classes. These classes are meant to handle change_profile rules. In comparison to the current code in aa.py, ChangeProfileRule has some added features: - support for audit and allow/deny keywords (for which John promised a parser patch really soon) - support for change_profile rules with an exec condition Also add the improved regex RE_PROFILE_CHANGE_PROFILE_2 to regex.py. Acked-by: Steve Beattie --- utils/apparmor/regex.py | 10 ++ utils/apparmor/rule/change_profile.py | 172 ++++++++++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 utils/apparmor/rule/change_profile.py diff --git a/utils/apparmor/regex.py b/utils/apparmor/regex.py index 2bf2ff278..5cf269442 100644 --- a/utils/apparmor/regex.py +++ b/utils/apparmor/regex.py @@ -72,6 +72,16 @@ RE_PROFILE_START = re.compile( '\s+((flags=)?\((?P.+)\)\s+)?\{' + RE_EOL) + +RE_PROFILE_CHANGE_PROFILE_2 = re.compile( + RE_AUDIT_DENY + + 'change_profile' + + '(\s+' + RE_PROFILE_PATH % 'execcond' + ')?' + # optionally exec condition + '(\s+->\s*' + RE_PROFILE_NAME % 'targetprofile' + ')?' + # optionally '->' target profile + RE_COMMA_EOL) + + + def parse_profile_start_line(line, filename): matches = RE_PROFILE_START.search(line) diff --git a/utils/apparmor/rule/change_profile.py b/utils/apparmor/rule/change_profile.py new file mode 100644 index 000000000..8d3e3fa0b --- /dev/null +++ b/utils/apparmor/rule/change_profile.py @@ -0,0 +1,172 @@ +#!/usr/bin/env python +# ---------------------------------------------------------------------- +# Copyright (C) 2013 Kshitij Gupta +# 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 +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# ---------------------------------------------------------------------- + +from apparmor.regex import RE_PROFILE_CHANGE_PROFILE_2, strip_quotes +from apparmor.common import AppArmorBug, AppArmorException +from apparmor.rule import BaseRule, BaseRuleset, parse_modifiers, quote_if_needed + +# setup module translations +from apparmor.translations import init_translation +_ = init_translation() + + +class ChangeProfileRule(BaseRule): + '''Class to handle and store a single change_profile rule''' + + # Nothing external should reference this class, all external users + # should reference the class field ChangeProfileRule.ALL + class __ChangeProfileAll(object): + pass + + ALL = __ChangeProfileAll + + def __init__(self, execcond, targetprofile, audit=False, deny=False, allow_keyword=False, + comment='', log_event=None): + + ''' + CHANGE_PROFILE RULE = 'change_profile' [ EXEC COND ] [ -> PROGRAMCHILD ] + ''' + + super(ChangeProfileRule, self).__init__(audit=audit, deny=deny, + allow_keyword=allow_keyword, + comment=comment, + log_event=log_event) + + self.execcond = None + self.all_execconds = False + if execcond == ChangeProfileRule.ALL: + self.all_execconds = True + elif type(execcond) == str: + if not execcond.strip(): + raise AppArmorBug('Empty exec condition in change_profile rule') + elif execcond.startswith('/') or execcond.startswith('@'): + self.execcond = execcond + else: + raise AppArmorException('Exec condition in change_profile rule does not start with /: %s' % str(execcond)) + else: + raise AppArmorBug('Passed unknown object to ChangeProfileRule: %s' % str(execcond)) + + self.targetprofile = None + self.all_targetprofiles = False + if targetprofile == ChangeProfileRule.ALL: + self.all_targetprofiles = True + elif type(targetprofile) == str: + if targetprofile.strip(): + self.targetprofile = targetprofile + else: + raise AppArmorBug('Empty target profile in change_profile rule') + else: + raise AppArmorBug('Passed unknown object to ChangeProfileRule: %s' % str(targetprofile)) + + @classmethod + def _match(cls, raw_rule): + return RE_PROFILE_CHANGE_PROFILE_2.search(raw_rule) + + @classmethod + def _parse(cls, raw_rule): + '''parse raw_rule and return ChangeProfileRule''' + + matches = cls._match(raw_rule) + if not matches: + raise AppArmorException(_("Invalid change_profile rule '%s'") % raw_rule) + + audit, deny, allow_keyword, comment = parse_modifiers(matches) + + if matches.group('execcond'): + execcond = strip_quotes(matches.group('execcond')) + else: + execcond = ChangeProfileRule.ALL + + if matches.group('targetprofile'): + targetprofile = strip_quotes(matches.group('targetprofile')) + else: + targetprofile = ChangeProfileRule.ALL + + return ChangeProfileRule(execcond, targetprofile, + audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment) + + def get_clean(self, depth=0): + '''return rule (in clean/default formatting)''' + + space = ' ' * depth + + if self.all_execconds: + execcond = '' + elif self.execcond: + execcond = ' %s' % quote_if_needed(self.execcond) + else: + raise AppArmorBug('Empty execcond in change_profile rule') + + if self.all_targetprofiles: + targetprofile = '' + elif self.targetprofile: + targetprofile = ' -> %s' % quote_if_needed(self.targetprofile) + else: + raise AppArmorBug('Empty target profile in change_profile rule') + + return('%s%schange_profile%s%s,%s' % (space, self.modifiers_str(), execcond, targetprofile, self.comment)) + + def is_covered_localvars(self, other_rule): + '''check if other_rule is covered by this rule object''' + + if not other_rule.execcond and not other_rule.all_execconds: + raise AppArmorBug('No execcond specified in other change_profile rule') + + if not other_rule.targetprofile and not other_rule.all_targetprofiles: + raise AppArmorBug('No target profile specified in other change_profile rule') + + if not self.all_execconds: + if other_rule.all_execconds: + return False + if other_rule.execcond != self.execcond: + # TODO: honor globbing and variables + return False + + if not self.all_targetprofiles: + if other_rule.all_targetprofiles: + return False + if other_rule.targetprofile != self.targetprofile: + return False + + # still here? -> then it is covered + return True + + def is_equal_localvars(self, rule_obj): + '''compare if rule-specific variables are equal''' + + if not type(rule_obj) == ChangeProfileRule: + raise AppArmorBug('Passed non-change_profile rule: %s' % str(rule_obj)) + + if (self.execcond != rule_obj.execcond + or self.all_execconds != rule_obj.all_execconds): + return False + + if (self.targetprofile != rule_obj.targetprofile + or self.all_targetprofiles != rule_obj.all_targetprofiles): + return False + + return True + + +class ChangeProfileRuleset(BaseRuleset): + '''Class to handle and store a collection of change_profile rules''' + + def get_glob(self, path_or_rule): + '''Return the next possible glob. For change_profile rules, that can be "change_profile EXECCOND,", + "change_profile -> TARGET_PROFILE," or "change_profile," (all change_profile). + Also, EXECCOND filename can be globbed''' + # XXX implement all options mentioned above ;-) + return 'change_profile,' From 70f9334cd9f658dce4ba9aba6a0b076bd996ef99 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Thu, 28 May 2015 22:23:32 +0200 Subject: [PATCH 017/143] Add tests for ChangeProfileRule and ChangeProfileRuleset As usual, those classes have 100% test coverage. Acked-by: Steve Beattie --- utils/test/test-change_profile.py | 443 ++++++++++++++++++++++++++++++ 1 file changed, 443 insertions(+) create mode 100644 utils/test/test-change_profile.py diff --git a/utils/test/test-change_profile.py b/utils/test/test-change_profile.py new file mode 100644 index 000000000..100ef3fc6 --- /dev/null +++ b/utils/test/test-change_profile.py @@ -0,0 +1,443 @@ +#!/usr/bin/env python +# ---------------------------------------------------------------------- +# 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 +# License as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# ---------------------------------------------------------------------- + +import unittest +from collections import namedtuple +from common_test import AATest, setup_all_loops + +from apparmor.rule.change_profile import ChangeProfileRule, ChangeProfileRuleset +from apparmor.rule import BaseRule +from apparmor.common import AppArmorException, AppArmorBug +from apparmor.logparser import ReadLog + +exp = namedtuple('exp', ['audit', 'allow_keyword', 'deny', 'comment', + 'execcond', 'all_execconds', 'targetprofile', 'all_targetprofiles']) + +# --- tests for single ChangeProfileRule --- # + +class ChangeProfileTest(AATest): + def _compare_obj(self, obj, expected): + self.assertEqual(expected.allow_keyword, obj.allow_keyword) + self.assertEqual(expected.audit, obj.audit) + self.assertEqual(expected.execcond, obj.execcond) + self.assertEqual(expected.targetprofile, obj.targetprofile) + self.assertEqual(expected.all_execconds, obj.all_execconds) + self.assertEqual(expected.all_targetprofiles, obj.all_targetprofiles) + self.assertEqual(expected.deny, obj.deny) + self.assertEqual(expected.comment, obj.comment) + +class ChangeProfileTestParse(ChangeProfileTest): + tests = [ + # rawrule audit allow deny comment execcond all? targetprof all? + ('change_profile,' , exp(False, False, False, '' , None , True , None , True )), + ('change_profile /foo,' , exp(False, False, False, '' , '/foo', False, None , True )), + ('change_profile /foo -> /bar,' , exp(False, False, False, '' , '/foo', False, '/bar' , False)), + ('deny change_profile /foo -> /bar, # comment' , exp(False, False, True , ' # comment' , '/foo', False, '/bar' , False)), + ('audit allow change_profile /foo,' , exp(True , True , False, '' , '/foo', False, None , True )), + ('change_profile -> /bar,' , exp(False, False, False, '' , None , True , '/bar' , False)), + ('audit allow change_profile -> /bar,' , exp(True , True , False, '' , None , True , '/bar' , False)), + # quoted versions + ('change_profile "/foo",' , exp(False, False, False, '' , '/foo', False, None , True )), + ('change_profile "/foo" -> "/bar",' , exp(False, False, False, '' , '/foo', False, '/bar' , False)), + ('deny change_profile "/foo" -> "/bar", # cmt' , exp(False, False, True, ' # cmt' , '/foo', False, '/bar' , False)), + ('audit allow change_profile "/foo",' , exp(True , True , False, '' , '/foo', False, None , True )), + ('change_profile -> "/bar",' , exp(False, False, False, '' , None , True , '/bar' , False)), + ('audit allow change_profile -> "/bar",' , exp(True , True , False, '' , None , True , '/bar' , False)), + # with globbing and/or named profiles + ('change_profile,' , exp(False, False, False, '' , None , True , None , True )), + ('change_profile /*,' , exp(False, False, False, '' , '/*' , False, None , True )), + ('change_profile /* -> bar,' , exp(False, False, False, '' , '/*' , False, 'bar' , False)), + ('deny change_profile /** -> bar, # comment' , exp(False, False, True , ' # comment' , '/**' , False, 'bar' , False)), + ('audit allow change_profile /**,' , exp(True , True , False, '' , '/**' , False, None , True )), + ('change_profile -> "ba r",' , exp(False, False, False, '' , None , True , 'ba r' , False)), + ('audit allow change_profile -> "ba r",' , exp(True , True , False, '' , None , True , 'ba r' , False)), + ] + + def _run_test(self, rawrule, expected): + self.assertTrue(ChangeProfileRule.match(rawrule)) + obj = ChangeProfileRule.parse(rawrule) + self.assertEqual(rawrule.strip(), obj.raw_rule) + self._compare_obj(obj, expected) + +class ChangeProfileTestParseInvalid(ChangeProfileTest): + tests = [ + ('change_profile -> ,' , AppArmorException), + ('change_profile foo -> ,' , AppArmorException), + ] + + def _run_test(self, rawrule, expected): + self.assertFalse(ChangeProfileRule.match(rawrule)) + with self.assertRaises(expected): + ChangeProfileRule.parse(rawrule) + +class ChangeProfileTestParseFromLog(ChangeProfileTest): + def test_net_from_log(self): + parser = ReadLog('', '', '', '', '') + + event = 'type=AVC msg=audit(1428699242.551:386): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"' + + # libapparmor doesn't understand this log format (from JJ) + # event = '[ 97.492562] audit: type=1400 audit(1431116353.523:77): apparmor="DENIED" operation="change_profile" profile="/foo/changeprofile" pid=3459 comm="changeprofile" target="/foo/rename"' + + parsed_event = parser.parse_event(event) + + self.assertEqual(parsed_event, { + 'request_mask': None, + 'denied_mask': None, + 'error_code': 0, + #'family': 'inet', + 'magic_token': 0, + 'parent': 0, + 'profile': '/foo/changeprofile', + 'operation': 'change_profile', + 'resource': None, + 'info': None, + 'aamode': 'REJECTING', + 'time': 1428699242, + 'active_hat': None, + 'pid': 3459, + 'task': 0, + 'attr': None, + 'name2': '/foo/rename', # target + 'name': None, + }) + + obj = ChangeProfileRule(ChangeProfileRule.ALL, parsed_event['name2'], log_event=parsed_event) + + # audit allow deny comment execcond all? targetprof all? + expected = exp(False, False, False, '' , None, True, '/foo/rename', False) + + self._compare_obj(obj, expected) + + self.assertEqual(obj.get_raw(1), ' change_profile -> /foo/rename,') + + +class ChangeProfileFromInit(ChangeProfileTest): + tests = [ + # ChangeProfileRule object audit allow deny comment execcond all? targetprof all? + (ChangeProfileRule('/foo', '/bar', deny=True) , exp(False, False, True , '' , '/foo', False, '/bar' , False)), + (ChangeProfileRule('/foo', '/bar') , exp(False, False, False, '' , '/foo', False, '/bar' , False)), + (ChangeProfileRule('/foo', ChangeProfileRule.ALL) , exp(False, False, False, '' , '/foo', False, None , True )), + (ChangeProfileRule(ChangeProfileRule.ALL, '/bar') , exp(False, False, False, '' , None , True , '/bar' , False)), + (ChangeProfileRule(ChangeProfileRule.ALL, + ChangeProfileRule.ALL) , exp(False, False, False, '' , None , True , None , True )), + ] + + def _run_test(self, obj, expected): + self._compare_obj(obj, expected) + + +class InvalidChangeProfileInit(AATest): + tests = [ + # init params expected exception + (['/foo', '' ] , AppArmorBug), # empty targetprofile + (['' , '/bar' ] , AppArmorBug), # empty execcond + ([' ', '/bar' ] , AppArmorBug), # whitespace execcond + (['/foo', ' ' ] , AppArmorBug), # whitespace targetprofile + (['xyxy', '/bar' ] , AppArmorException), # invalid execcond + ([dict(), '/bar' ] , AppArmorBug), # wrong type for execcond + ([None , '/bar' ] , AppArmorBug), # wrong type for execcond + (['/foo', dict() ] , AppArmorBug), # wrong type for targetprofile + (['/foo', None ] , AppArmorBug), # wrong type for targetprofile + ] + + def _run_test(self, params, expected): + with self.assertRaises(expected): + ChangeProfileRule(params[0], params[1]) + + def test_missing_params_1(self): + with self.assertRaises(TypeError): + ChangeProfileRule() + + def test_missing_params_2(self): + with self.assertRaises(TypeError): + ChangeProfileRule('inet') + + +class InvalidChangeProfileTest(AATest): + def _check_invalid_rawrule(self, rawrule): + obj = None + self.assertFalse(ChangeProfileRule.match(rawrule)) + with self.assertRaises(AppArmorException): + obj = ChangeProfileRule(ChangeProfileRule.parse(rawrule)) + + self.assertIsNone(obj, 'ChangeProfileRule handed back an object unexpectedly') + + def test_invalid_net_missing_comma(self): + self._check_invalid_rawrule('change_profile') # missing comma + + def test_invalid_net_non_ChangeProfileRule(self): + self._check_invalid_rawrule('dbus,') # not a change_profile rule + + def test_empty_net_data_1(self): + obj = ChangeProfileRule('/foo', '/bar') + obj.execcond = '' + # no execcond set, and ALL not set + with self.assertRaises(AppArmorBug): + obj.get_clean(1) + + def test_empty_net_data_2(self): + obj = ChangeProfileRule('/foo', '/bar') + obj.targetprofile = '' + # no targetprofile set, and ALL not set + with self.assertRaises(AppArmorBug): + obj.get_clean(1) + + +class WriteChangeProfileTestAATest(AATest): + tests = [ + # raw rule clean rule + (' change_profile , # foo ' , 'change_profile, # foo'), + (' audit change_profile /foo,' , 'audit change_profile /foo,'), + (' deny change_profile /foo -> bar,# foo bar' , 'deny change_profile /foo -> bar, # foo bar'), + (' deny change_profile /foo ,# foo bar' , 'deny change_profile /foo, # foo bar'), + (' allow change_profile -> /bar ,# foo bar' , 'allow change_profile -> /bar, # foo bar'), + (' allow change_profile /** -> /bar ,# foo bar' , 'allow change_profile /** -> /bar, # foo bar'), + (' allow change_profile "/fo o" -> "/b ar",' , 'allow change_profile "/fo o" -> "/b ar",'), + ] + + def _run_test(self, rawrule, expected): + self.assertTrue(ChangeProfileRule.match(rawrule)) + obj = ChangeProfileRule.parse(rawrule) + clean = obj.get_clean() + raw = obj.get_raw() + + self.assertEqual(expected.strip(), clean, 'unexpected clean rule') + self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule') + + def test_write_manually(self): + obj = ChangeProfileRule('/foo', 'bar', allow_keyword=True) + + expected = ' allow change_profile /foo -> bar,' + + self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') + self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') + + +class ChangeProfileCoveredTest(AATest): + def _run_test(self, param, expected): + obj = ChangeProfileRule.parse(self.rule) + check_obj = ChangeProfileRule.parse(param) + + self.assertTrue(ChangeProfileRule.match(param)) + + self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected %s' % expected[0]) + self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected %s' % expected[1]) + + self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected %s' % expected[2]) + self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected %s' % expected[3]) + +class ChangeProfileCoveredTest_01(ChangeProfileCoveredTest): + rule = 'change_profile /foo,' + + tests = [ + # rule equal strict equal covered covered exact + (' change_profile,' , [ False , False , False , False ]), + (' change_profile /foo,' , [ True , True , True , True ]), + (' change_profile /foo, # comment', [ True , False , True , True ]), + (' allow change_profile /foo,' , [ True , False , True , True ]), + (' change_profile /foo,' , [ True , False , True , True ]), + (' change_profile /foo -> /bar,' , [ False , False , True , True ]), + (' change_profile /foo -> bar,' , [ False , False , True , True ]), + ('audit change_profile /foo,' , [ False , False , False , False ]), + ('audit change_profile,' , [ False , False , False , False ]), + (' change_profile /asdf,' , [ False , False , False , False ]), + (' change_profile -> /bar,' , [ False , False , False , False ]), + ('audit deny change_profile /foo,' , [ False , False , False , False ]), + (' deny change_profile /foo,' , [ False , False , False , False ]), + ] + +class ChangeProfileCoveredTest_02(ChangeProfileCoveredTest): + rule = 'audit change_profile /foo,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'change_profile /foo,' , [ False , False , True , False ]), + ('audit change_profile /foo,' , [ True , True , True , True ]), + ( 'change_profile /foo -> /bar,' , [ False , False , True , False ]), + ('audit change_profile /foo -> /bar,' , [ False , False , True , True ]), # XXX is "covered exact" correct here? + ( 'change_profile,' , [ False , False , False , False ]), + ('audit change_profile,' , [ False , False , False , False ]), + (' change_profile -> /bar,' , [ False , False , False , False ]), + ] + + +class ChangeProfileCoveredTest_03(ChangeProfileCoveredTest): + rule = 'change_profile /foo -> /bar,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'change_profile /foo -> /bar,' , [ True , True , True , True ]), + ('allow change_profile /foo -> /bar,' , [ True , False , True , True ]), + ( 'change_profile /foo,' , [ False , False , False , False ]), + ( 'change_profile,' , [ False , False , False , False ]), + ( 'change_profile /foo -> /xyz,' , [ False , False , False , False ]), + ('audit change_profile,' , [ False , False , False , False ]), + ('audit change_profile /foo -> /bar,' , [ False , False , False , False ]), + ( 'change_profile -> /bar,' , [ False , False , False , False ]), + ( 'change_profile,' , [ False , False , False , False ]), + ] + +class ChangeProfileCoveredTest_04(ChangeProfileCoveredTest): + rule = 'change_profile,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'change_profile,' , [ True , True , True , True ]), + ('allow change_profile,' , [ True , False , True , True ]), + ( 'change_profile /foo,' , [ False , False , True , True ]), + ( 'change_profile /xyz -> bar,' , [ False , False , True , True ]), + ( 'change_profile -> /bar,' , [ False , False , True , True ]), + ( 'change_profile /foo -> /bar,' , [ False , False , True , True ]), + ('audit change_profile,' , [ False , False , False , False ]), + ('deny change_profile,' , [ False , False , False , False ]), + ] + +class ChangeProfileCoveredTest_05(ChangeProfileCoveredTest): + rule = 'deny change_profile /foo,' + + tests = [ + # rule equal strict equal covered covered exact + ( 'deny change_profile /foo,' , [ True , True , True , True ]), + ('audit deny change_profile /foo,' , [ False , False , False , False ]), + ( 'change_profile /foo,' , [ False , False , False , False ]), # XXX should covered be true here? + ( 'deny change_profile /bar,' , [ False , False , False , False ]), + ( 'deny change_profile,' , [ False , False , False , False ]), + ] + + +class ChangeProfileCoveredTest_Invalid(AATest): + def test_borked_obj_is_covered_1(self): + obj = ChangeProfileRule.parse('change_profile /foo,') + + testobj = ChangeProfileRule('/foo', '/bar') + testobj.execcond = '' + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_borked_obj_is_covered_2(self): + obj = ChangeProfileRule.parse('change_profile /foo,') + + testobj = ChangeProfileRule('/foo', '/bar') + testobj.targetprofile = '' + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_invalid_is_covered(self): + obj = ChangeProfileRule.parse('change_profile /foo,') + + testobj = BaseRule() # different type + + with self.assertRaises(AppArmorBug): + obj.is_covered(testobj) + + def test_invalid_is_equal(self): + obj = ChangeProfileRule.parse('change_profile -> /bar,') + + testobj = BaseRule() # different type + + with self.assertRaises(AppArmorBug): + obj.is_equal(testobj) + +# --- tests for ChangeProfileRuleset --- # + +class ChangeProfileRulesTest(AATest): + def test_empty_ruleset(self): + ruleset = ChangeProfileRuleset() + ruleset_2 = ChangeProfileRuleset() + self.assertEqual([], ruleset.get_raw(2)) + self.assertEqual([], ruleset.get_clean(2)) + self.assertEqual([], ruleset_2.get_raw(2)) + self.assertEqual([], ruleset_2.get_clean(2)) + + def test_ruleset_1(self): + ruleset = ChangeProfileRuleset() + rules = [ + 'change_profile -> /bar,', + 'change_profile /foo,', + ] + + expected_raw = [ + 'change_profile -> /bar,', + 'change_profile /foo,', + '', + ] + + expected_clean = [ + 'change_profile -> /bar,', + 'change_profile /foo,', + '', + ] + + for rule in rules: + ruleset.add(ChangeProfileRule.parse(rule)) + + self.assertEqual(expected_raw, ruleset.get_raw()) + self.assertEqual(expected_clean, ruleset.get_clean()) + + def test_ruleset_2(self): + ruleset = ChangeProfileRuleset() + rules = [ + 'change_profile /foo -> /bar,', + 'allow change_profile /asdf,', + 'deny change_profile -> xy, # example comment', + ] + + expected_raw = [ + ' change_profile /foo -> /bar,', + ' allow change_profile /asdf,', + ' deny change_profile -> xy, # example comment', + '', + ] + + expected_clean = [ + ' deny change_profile -> xy, # example comment', + '', + ' allow change_profile /asdf,', + ' change_profile /foo -> /bar,', + '', + ] + + for rule in rules: + ruleset.add(ChangeProfileRule.parse(rule)) + + self.assertEqual(expected_raw, ruleset.get_raw(1)) + self.assertEqual(expected_clean, ruleset.get_clean(1)) + + +class ChangeProfileGlobTestAATest(AATest): + def setUp(self): + self.ruleset = ChangeProfileRuleset() + + def test_glob_1(self): + self.assertEqual(self.ruleset.get_glob('change_profile /foo,'), 'change_profile,') + + # not supported or used yet, glob behaviour not decided yet + # def test_glob_2(self): + # self.assertEqual(self.ruleset.get_glob('change_profile /foo -> /bar,'), 'change_profile -> /bar,') + + def test_glob_ext(self): + with self.assertRaises(AppArmorBug): + # get_glob_ext is not available for change_profile rules + self.ruleset.get_glob_ext('change_profile /foo -> /bar,') + +class ChangeProfileDeleteTestAATest(AATest): + pass + +setup_all_loops(__name__) +if __name__ == '__main__': + unittest.main(verbosity=2) From 98383c0816781de922375af3b29e1d6f47ab19db Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Thu, 28 May 2015 22:25:30 +0200 Subject: [PATCH 018/143] Change aa.py to use ChangeProfileRule and ChangeProfileRuleset Change aa.py to use ChangeProfileRule and ChangeProfileRuleset instead of a sub-hasher to store and write change_profile rules. In detail: - drop all the change_profile rule parsing from parse_profile_data() and serialize_profile_from_old_profile() - instead, just call ChangeProfileRule.parse() - change write_change_profile to use ChangeProfileRuleset - add removal of superfluous/duplicate change_profile rules (the old code didn't do this) Note that this patch is much smaller than the NetworkRule and CapabilityRule patches because aa-logprof doesn't ask for adding change_profile rules - adding that is something for a later patch. Acked-by: Steve Beattie --- utils/apparmor/aa.py | 34 +++++++++++++++------------------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index a774e2f43..113c87273 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -41,7 +41,7 @@ from apparmor.aamode import (str_to_mode, mode_to_str, contains, split_mode, flatten_mode, owner_flatten_mode) from apparmor.regex import (RE_PROFILE_START, RE_PROFILE_END, RE_PROFILE_LINK, - RE_PROFILE_CHANGE_PROFILE, RE_PROFILE_ALIAS, RE_PROFILE_RLIMIT, + RE_PROFILE_ALIAS, RE_PROFILE_RLIMIT, RE_PROFILE_BOOLEAN, RE_PROFILE_VARIABLE, RE_PROFILE_CONDITIONAL, RE_PROFILE_CONDITIONAL_VARIABLE, RE_PROFILE_CONDITIONAL_BOOLEAN, RE_PROFILE_BARE_FILE_ENTRY, RE_PROFILE_PATH_ENTRY, @@ -54,6 +54,7 @@ from apparmor.regex import (RE_PROFILE_START, RE_PROFILE_END, RE_PROFILE_LINK, import apparmor.rules as aarules from apparmor.rule.capability import CapabilityRuleset, CapabilityRule +from apparmor.rule.change_profile import ChangeProfileRuleset, ChangeProfileRule from apparmor.rule.network import NetworkRuleset, NetworkRule from apparmor.rule import parse_modifiers, quote_if_needed @@ -2132,6 +2133,7 @@ def delete_duplicates(profile, incname): if include.get(incname, False): deleted += profile['network'].delete_duplicates(include[incname][incname]['network']) deleted += profile['capability'].delete_duplicates(include[incname][incname]['capability']) + deleted += profile['change_profile'].delete_duplicates(include[incname][incname]['change_profile']) deleted += delete_path_duplicates(profile, incname, 'allow') deleted += delete_path_duplicates(profile, incname, 'deny') @@ -2139,6 +2141,7 @@ def delete_duplicates(profile, incname): elif filelist.get(incname, False): deleted += profile['network'].delete_duplicates(filelist[incname][incname]['network']) deleted += profile['capability'].delete_duplicates(filelist[incname][incname]['capability']) + deleted += profile['change_profile'].delete_duplicates(filelist[incname][incname]['change_profile']) deleted += delete_path_duplicates(profile, incname, 'allow') deleted += delete_path_duplicates(profile, incname, 'deny') @@ -2673,6 +2676,7 @@ def parse_profile_data(data, file, do_include): profile_data[profile][hat]['flags'] = flags profile_data[profile][hat]['network'] = NetworkRuleset() + profile_data[profile][hat]['change_profile'] = ChangeProfileRuleset() profile_data[profile][hat]['allow']['path'] = hasher() profile_data[profile][hat]['allow']['dbus'] = list() profile_data[profile][hat]['allow']['mount'] = list() @@ -2745,14 +2749,11 @@ def parse_profile_data(data, file, do_include): else: profile_data[profile][hat][allow]['link'][link]['audit'] = set() - elif RE_PROFILE_CHANGE_PROFILE.search(line): - matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() - + elif ChangeProfileRule.match(line): if not profile: raise AppArmorException(_('Syntax Error: Unexpected change profile entry found in file: %(file)s line: %(line)s') % { 'file': file, 'line': lineno + 1 }) - cp = strip_quotes(matches[0]) - profile_data[profile][hat]['change_profile'][cp] = True + profile_data[profile][hat]['change_profile'].add(ChangeProfileRule.parse(line)) elif RE_PROFILE_ALIAS.search(line): matches = RE_PROFILE_ALIAS.search(line).groups() @@ -3297,7 +3298,10 @@ def write_includes(prof_data, depth): return write_single(prof_data, depth, '', 'include', '#include <', '>') def write_change_profile(prof_data, depth): - return write_single(prof_data, depth, '', 'change_profile', 'change_profile -> ', ',') + data = [] + if prof_data.get('change_profile', False): + data = prof_data['change_profile'].get_clean(depth) + return data def write_alias(prof_data, depth): return write_pair(prof_data, depth, '', 'alias', 'alias ', ' -> ', ',', quote_if_needed) @@ -3872,22 +3876,14 @@ def serialize_profile_from_old_profile(profile_data, name, options): # To-Do pass - elif RE_PROFILE_CHANGE_PROFILE.search(line): - matches = RE_PROFILE_CHANGE_PROFILE.search(line).groups() - cp = strip_quotes(matches[0]) - - if not write_prof_data[hat]['change_profile'][cp] is True: - correct = False - - if correct: + elif ChangeProfileRule.match(line): + change_profile_obj = ChangeProfileRule.parse(line) + if write_prof_data[hat]['change_profile'].is_covered(change_profile_obj, True, True): if not segments['change_profile'] and True in segments.values(): data += write_prior_segments(write_prof_data[name], segments, line) segments['change_profile'] = True - write_prof_data[hat]['change_profile'].pop(cp) + write_prof_data[hat]['change_profile'].delete(change_profile_obj) data.append(line) - else: - #To-Do - pass elif RE_PROFILE_ALIAS.search(line): matches = RE_PROFILE_ALIAS.search(line).groups() From 534715c2e247b5906e32d687cb36737a5b9855e3 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Thu, 28 May 2015 22:26:34 +0200 Subject: [PATCH 019/143] Drop old RE_PROFILE_CHANGE_PROFILE regex from regex.py Also rename RE_PROFILE_CHANGE_PROFILE_2 to RE_PROFILE_CHANGE_PROFILE and update apparmor/rule/change_profile.py to use the changed name. Acked-by: Steve Beattie --- utils/apparmor/regex.py | 3 +-- utils/apparmor/rule/change_profile.py | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/utils/apparmor/regex.py b/utils/apparmor/regex.py index 5cf269442..93f6d047a 100644 --- a/utils/apparmor/regex.py +++ b/utils/apparmor/regex.py @@ -32,7 +32,6 @@ RE_PROFILE_PATH = '(?P<%s>(/\S+|"/[^"]+"))' # filename (starting with ' RE_PROFILE_END = re.compile('^\s*\}' + RE_EOL) RE_PROFILE_CAP = re.compile(RE_AUDIT_DENY + 'capability(?P(\s+\S+)+)?' + RE_COMMA_EOL) RE_PROFILE_LINK = re.compile(RE_AUDIT_DENY + 'link\s+(((subset)|(<=))\s+)?([\"\@\/].*?"??)\s+->\s*([\"\@\/].*?"??)' + RE_COMMA_EOL) -RE_PROFILE_CHANGE_PROFILE = re.compile('^\s*change_profile\s+->\s*("??.+?"??)' + RE_COMMA_EOL) RE_PROFILE_ALIAS = re.compile('^\s*alias\s+("??.+?"??)\s+->\s*("??.+?"??)' + RE_COMMA_EOL) RE_PROFILE_RLIMIT = re.compile('^\s*set\s+rlimit\s+(.+)\s+(<=)?\s*(.+)' + RE_COMMA_EOL) RE_PROFILE_BOOLEAN = re.compile('^\s*(\$\{?\w*\}?)\s*=\s*(true|false)\s*,?' + RE_EOL, flags=re.IGNORECASE) @@ -73,7 +72,7 @@ RE_PROFILE_START = re.compile( RE_EOL) -RE_PROFILE_CHANGE_PROFILE_2 = re.compile( +RE_PROFILE_CHANGE_PROFILE = re.compile( RE_AUDIT_DENY + 'change_profile' + '(\s+' + RE_PROFILE_PATH % 'execcond' + ')?' + # optionally exec condition diff --git a/utils/apparmor/rule/change_profile.py b/utils/apparmor/rule/change_profile.py index 8d3e3fa0b..4f8dbc661 100644 --- a/utils/apparmor/rule/change_profile.py +++ b/utils/apparmor/rule/change_profile.py @@ -14,7 +14,7 @@ # # ---------------------------------------------------------------------- -from apparmor.regex import RE_PROFILE_CHANGE_PROFILE_2, strip_quotes +from apparmor.regex import RE_PROFILE_CHANGE_PROFILE, strip_quotes from apparmor.common import AppArmorBug, AppArmorException from apparmor.rule import BaseRule, BaseRuleset, parse_modifiers, quote_if_needed @@ -73,7 +73,7 @@ class ChangeProfileRule(BaseRule): @classmethod def _match(cls, raw_rule): - return RE_PROFILE_CHANGE_PROFILE_2.search(raw_rule) + return RE_PROFILE_CHANGE_PROFILE.search(raw_rule) @classmethod def _parse(cls, raw_rule): From 7728556972fbcba422f5f140e26ce316a142742e Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 00:17:02 +0200 Subject: [PATCH 020/143] Update aa-mergeprof to use the CapabilityRule(set) class layout aa-mergeprof still used the old aa[profile][hat][allow]['capability'] which no longer gets populated - which resulted in not asking for merging any capabilities. Actually (and funnily), - if other.aa[profile][hat].get(allow, False): - continue resulted in never merging capability rules even before the change to CapabilityRule(set) - this was meant as optimization, but a "not" was missing in the condition ;-) so it always skipped capability rules. The patch changes ask_the_question to the CapabilityRule(set) layout. Besides that, - include the audit and deny keywords in the "Capability" headline (I'd prefer to just use the get_clean() rule, but that's another topic) - hide "(A)llow" when merging a deny rule - don't ask for capabilities that are already covered Also delete match_cap_includes() from aa.py, which is no longer used. Acked-by: Steve Beattie Bug: https://launchpad.net/bugs/1382241 --- utils/aa-mergeprof | 50 +++++++++++++++++++++++++++++++------------- utils/apparmor/aa.py | 5 ----- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index a7054ebad..54ecd1211 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -309,32 +309,51 @@ class Merge(object): return #Add the capabilities - for allow in ['allow', 'deny']: - if 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) + if other.aa[profile][hat].get('capability', False): # needed until we have proper profile initialization + for cap_obj in other.aa[profile][hat]['capability'].rules: + + if apparmor.aa.is_known_rule(self.user.aa[profile][hat], 'capability', cap_obj): + continue + + if cap_obj.all_caps: + severity = 10 + cap_txt = 'ALL' + else: + cap_txt = ' '.join(cap_obj.capability) + severity = 0 + for cap in cap_obj.capability: + severity = max(severity, sev_db.rank('CAP_%s' % cap)) + + if cap_obj.deny: + cap_txt = 'deny %s' % cap_txt + + if cap_obj.audit: + cap_txt = 'audit %s' % cap_txt + default_option = 1 options = [] - newincludes = apparmor.aa.match_cap_includes(self.user.aa[profile][hat], capability) + newincludes = apparmor.aa.match_includes(self.user.aa[profile][hat], 'capability', cap_obj) q = aaui.PromptQuestion() if newincludes: options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes)))) if options: - options.append('capability %s' % capability) + options.append(cap_obj.get_clean()) q.options = options q.selected = default_option - 1 q.headers = [_('Profile'), apparmor.aa.combine_name(profile, hat)] - q.headers += [_('Capability'), capability] + q.headers += [_('Capability'), cap_txt] q.headers += [_('Severity'), severity] audit_toggle = 0 - q.functions = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] + q.functions = [] + if not cap_obj.deny: + q.functions += ['CMD_ALLOW'] + q.functions += ['CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] - q.default = 'CMD_ALLOW' + q.default = q.functions[0] done = False while not done: @@ -362,19 +381,20 @@ class Merge(object): if deleted: aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - self.user.aa[profile][hat]['allow']['capability'][capability]['set'] = True - self.user.aa[profile][hat]['allow']['capability'][capability]['audit'] = other.aa[profile][hat]['allow']['capability'][capability]['audit'] + self.user.aa[profile][hat]['capability'].add(cap_obj) apparmor.aa.changed[profile] = True - aaui.UI_Info(_('Adding capability %s to profile.'), capability) + aaui.UI_Info(_('Adding %s to profile.') % cap_obj.get_clean()) done = True elif ans == 'CMD_DENY': - self.user.aa[profile][hat]['deny']['capability'][capability]['set'] = True + cap_obj.deny = True + cap_obj.raw_rule = None # reset raw rule after manually modifying cap_obj + self.user.aa[profile][hat]['capability'].add(cap_obj) apparmor.aa.changed[profile] = True - aaui.UI_Info(_('Denying capability %s to profile.') % capability) + aaui.UI_Info(_('Adding %s to profile.') % cap_obj.get_clean()) done = True else: done = False diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index 113c87273..18851a76d 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -2154,11 +2154,6 @@ def match_net_include(incname, family, type): return match_includes(incname, 'network', network_obj) -def match_cap_includes(profile, capability): - # still used by aa-mergeprof - capability_obj = CapabilityRule(capability) - return match_includes(profile, 'capability', capability_obj) - def match_includes(profile, rule_type, rule_obj): newincludes = [] for incname in include.keys(): From c795a1f228c6d59c5397476b98c67c88735aa9ae Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 01:12:38 +0200 Subject: [PATCH 021/143] Update aa-mergeprof to use the NetworkRule(set) class layout aa-mergeprof still used the old aa[profile][hat][allow]['netdomain'] which no longer gets populated. This resulted in not asking for merging any network rules. This patch changes ask_the_question() to the NetworkRule(set) layout. Besides that, - don't ask for network rules that are already covered. Using is_known_rule() also fixes https://bugs.launchpad.net/apparmor/+bug/1382241 - include the audit keyword in the "Network Family" headline (I'd prefer to just use the get_clean() rule, but that's another topic) - hide "(A)llow" when merging a deny rule - as a side effect of using NetworkRule, fix crashes for 'network,' and 'network foo,' rules To avoid having to repeat the list of available "buttons" and the logic to update that list, add a available_buttons() function that returns the list of available buttons depending on rule_obj.deny and rule_obj.audit to aa.py, and import it into mergeprof. I tested all changes manually. Acked-by: Steve Beattie --- utils/aa-mergeprof | 70 ++++++++++++++++++++++++++------------------ utils/apparmor/aa.py | 17 +++++++++++ 2 files changed, 59 insertions(+), 28 deletions(-) diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index 54ecd1211..aebc88386 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -17,6 +17,7 @@ import re import os import apparmor.aa +from apparmor.aa import available_buttons import apparmor.aamode from apparmor.common import AppArmorException import apparmor.severity @@ -714,37 +715,45 @@ class Merge(object): elif re.search('\d', ans): default_option = ans - # - for allow in ['allow', 'deny']: - for family in sorted(other.aa[profile][hat][allow]['netdomain']['rule'].keys()): - # severity handling for net toggles goes here + if 1 == 1: # avoid whitespace change + if other.aa[profile][hat].get('network', False): # needed until we have proper profile initialization + for net_obj in other.aa[profile][hat]['network'].rules: + # severity handling for net toggles goes here - for sock_type in sorted(other.aa[profile][hat][allow]['netdomain']['rule'][family].keys()): - #if apparmor.aa.profile_known_network(self.user.aa[profile][hat], family, sock_type): - # continue - # disabled for now because it crashes, for details and impact see - # https://bugs.launchpad.net/apparmor/+bug/1382241 + if apparmor.aa.is_known_rule(self.user.aa[profile][hat], 'network', net_obj): + continue + + if net_obj.all_domains: + family = 'ALL' + else: + family = net_obj.domain + + if net_obj.all_type_or_protocols: + sock_type = 'ALL' + else: + sock_type = net_obj.type_or_protocol default_option = 1 options = [] - newincludes = apparmor.aa.match_net_includes(self.user.aa[profile][hat], family, sock_type) + newincludes = apparmor.aa.match_includes(self.user.aa[profile][hat], 'network', net_obj) q = aaui.PromptQuestion() if newincludes: options += list(map(lambda s: '#include <%s>'%s, sorted(set(newincludes)))) if True:#options: - options.append('network %s %s' % (family, sock_type)) + options.append(net_obj.get_clean()) q.options = options q.selected = default_option - 1 + audit = '' + if net_obj.audit: + audit = 'audit ' + q.headers = [_('Profile'), apparmor.aa.combine_name(profile, hat)] - q.headers += [_('Network Family'), family] + q.headers += [_('Network Family'), audit + family] q.headers += [_('Socket Type'), sock_type] - audit_toggle = 0 - q.functions = ['CMD_ALLOW', 'CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_AUDIT_NEW', - 'CMD_ABORT', 'CMD_FINISHED'] - - q.default = 'CMD_ALLOW' + q.functions = available_buttons(net_obj) + q.default = q.functions[0] done = False while not done: @@ -757,15 +766,19 @@ class Merge(object): return if ans.startswith('CMD_AUDIT'): - audit_toggle = not audit_toggle - audit = '' - if audit_toggle: - audit = 'audit' - q.functions = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_OFF', - 'CMD_ABORT', 'CMD_FINISHED'] + if ans == 'CMD_AUDIT_NEW': + net_obj.audit = True + net_obj.raw_rule = None + audit = 'audit ' else: - q.functions = ['CMD_ALLOW', 'CMD_DENY', 'CMD_AUDIT_NEW', - 'CMD_ABORT', 'CMD_FINISHED'] + net_obj.audit = False + net_obj.raw_rule = None + audit = '' + + q.functions = available_buttons(net_obj) + options[len(options) - 1] = net_obj.get_clean() + q.options = options + q.headers = [_('Profile'), apparmor.aa.combine_name(profile, hat)] q.headers += [_('Network Family'), audit + family] q.headers += [_('Socket Type'), sock_type] @@ -788,8 +801,7 @@ class Merge(object): aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: - self.user.aa[profile][hat]['allow']['netdomain']['audit'][family][sock_type] = audit_toggle - self.user.aa[profile][hat]['allow']['netdomain']['rule'][family][sock_type] = True + self.user.aa[profile][hat]['network'].add(net_obj) apparmor.aa.changed[profile] = True @@ -797,7 +809,9 @@ class Merge(object): elif ans == 'CMD_DENY': done = True - self.user.aa[profile][hat]['deny']['netdomain']['rule'][family][sock_type] = True + net_obj.deny = True + net_obj.raw_rule = None + self.user.aa[profile][hat]['network'].add(net_obj) apparmor.aa.changed[profile] = True aaui.UI_Info(_('Denying network access %(family)s %(type)s to profile') % { 'family': family, 'type': sock_type }) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index 18851a76d..0b7776b2c 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -2053,6 +2053,23 @@ def ask_the_questions(): else: done = False +def available_buttons(rule_obj): + buttons = [] + + if not rule_obj.deny: + buttons += ['CMD_ALLOW'] + + buttons += ['CMD_DENY', 'CMD_IGNORE_ENTRY'] + + if rule_obj.audit: + buttons += ['CMD_AUDIT_OFF'] + else: + buttons += ['CMD_AUDIT_NEW'] + + buttons += ['CMD_ABORT', 'CMD_FINISHED'] + + return buttons + def add_to_options(options, newpath): if newpath not in options: options.append(newpath) From 78505f305721350e8299ec1210ef94f35300af7a Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 01:41:35 +0200 Subject: [PATCH 022/143] drop unused match_net_include() and match_net_includes() from aa.py aa-mergeprof no longer calls match_net_includes(), which means the function can be dropped. After that, match_net_include() is also unused, so also drop it. Acked-by: Seth Arnold Acked-by: Steve Beattie --- utils/apparmor/aa.py | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index 0b7776b2c..84decfed8 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -2165,12 +2165,6 @@ def delete_duplicates(profile, incname): return deleted -def match_net_include(incname, family, type): - # still used by aa-mergeprof - network_obj = NetworkRule(family, type) - return match_includes(incname, 'network', network_obj) - - def match_includes(profile, rule_type, rule_obj): newincludes = [] for incname in include.keys(): @@ -2203,15 +2197,6 @@ def valid_include(profile, incname): return False -def match_net_includes(profile, family, nettype): - newincludes = [] - for incname in include.keys(): - - if valid_include(profile, incname) and match_net_include(incname, family, nettype): - newincludes.append(incname) - - return newincludes - def set_logfile(filename): ''' set logfile to a) the specified filename or b) if not given, the first existing logfile from logprof.conf''' From 5fa5125fd46d298df815f97d8782838e54a8ba3d Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 12:55:38 +0200 Subject: [PATCH 023/143] Add tempdir and tempfile handling to AATest Add writeTmpfile() to AATest to write a file into the tmpdir. If no tmpdir exists yet, automatically create one. createTmpdir() is a separate function so that it's possible to manually create the tmpdir (for example, if a test needs an empty tmpdir). Also add a tearDown() function to delete the tmpdir again. This function calls self.AATeardown() to avoid the need for super() in child classes. Finally, simplify AaTestWithTempdir in test-aa.py to use createTmpdir() and add an example for AATeardown() to test-example.py. Acked-by: Steve Beattie --- utils/test/common_test.py | 21 +++++++++++++++++++++ utils/test/test-aa.py | 11 ++--------- utils/test/test-example.py | 4 ++++ 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/utils/test/common_test.py b/utils/test/common_test.py index b5223fd09..1f48082c3 100755 --- a/utils/test/common_test.py +++ b/utils/test/common_test.py @@ -16,7 +16,9 @@ import unittest import inspect import os import re +import shutil import sys +import tempfile import apparmor.common import apparmor.config @@ -47,7 +49,26 @@ class AATest(unittest.TestCase): '''override this function if a test needs additional setup steps (instead of overriding setUp())''' pass + def tearDown(self): + if self.tmpdir and os.path.exists(self.tmpdir): + shutil.rmtree(self.tmpdir) + + self.AATeardown() + + def AATeardown(self): + '''override this function if a test needs additional teardown steps (instead of overriding tearDown())''' + pass + + def createTmpdir(self): + self.tmpdir = tempfile.mkdtemp(prefix='aa-test-') + + def writeTmpfile(self, file, contents): + if not self.tmpdir: + self.createTmpdir() + return write_file(self.tmpdir, file, contents) + tests = [] + tmpdir = None class AAParseTest(unittest.TestCase): parse_function = None diff --git a/utils/test/test-aa.py b/utils/test/test-aa.py index f2234f9c9..15ae1c465 100644 --- a/utils/test/test-aa.py +++ b/utils/test/test-aa.py @@ -11,21 +11,14 @@ import unittest from common_test import AATest, setup_all_loops -import os -import shutil -import tempfile from common_test import read_file, write_file from apparmor.aa import check_for_apparmor, get_profile_flags, set_profile_flags, is_skippable_file, is_skippable_dir, parse_profile_start, separate_vars, store_list_var, write_header, serialize_parse_profile_start from apparmor.common import AppArmorException, AppArmorBug class AaTestWithTempdir(AATest): - def setUp(self): - self.tmpdir = tempfile.mkdtemp(prefix='aa-py-') - - def tearDown(self): - if os.path.exists(self.tmpdir): - shutil.rmtree(self.tmpdir) + def AASetup(self): + self.createTmpdir() class AaTest_check_for_apparmor(AaTestWithTempdir): diff --git a/utils/test/test-example.py b/utils/test/test-example.py index 0617262b7..a744bff99 100644 --- a/utils/test/test-example.py +++ b/utils/test/test-example.py @@ -39,6 +39,10 @@ class TestBaz(AATest): # called by setUp() - use AASetup() to avoid the need for using super(...) pass + def AATeardown(self): + # called by tearDown() - use AATeardown() to avoid the need for using super(...) + pass + def test_Baz_only_one_test(self): self.assertEqual("baz", "baz") From 8d348b328b7b2bd4e9ccfa7f5da52f4f8ff39a5a Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 13:00:32 +0200 Subject: [PATCH 024/143] let parse_profile_data() check for in-file duplicate profiles Add a check to parse_profile_data() to detect if a file contains two profiles with the same name. Note: Two profiles with the same name, but in different files, won't be detected by this check. Also add basic tests to ensure that a valid profile gets parsed, and two profiles with the same name inside the same file raise an exception. (Sidenote: these simple tests improve aa.py coverage from 9% to 12%, which also confirms the function is too long ;-) Acked-by: Steve Beattie --- utils/apparmor/aa.py | 5 +++++ utils/test/test-aa.py | 18 +++++++++++++++++- 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/utils/apparmor/aa.py b/utils/apparmor/aa.py index 84decfed8..e4850ba61 100644 --- a/utils/apparmor/aa.py +++ b/utils/apparmor/aa.py @@ -2655,6 +2655,11 @@ def parse_profile_data(data, file, do_include): # Starting line of a profile if RE_PROFILE_START.search(line): (profile, hat, attachment, flags, in_contained_hat, pps_set_profile, pps_set_hat_external) = parse_profile_start(line, file, lineno, profile, hat) + + if profile_data[profile].get(hat, False): + raise AppArmorException('Profile %(profile)s defined twice in %(file)s, last found in line %(line)s' % + { 'file': file, 'line': lineno + 1, 'profile': combine_name(profile, hat) }) + if attachment: profile_data[profile][hat]['attachment'] = attachment if pps_set_profile: diff --git a/utils/test/test-aa.py b/utils/test/test-aa.py index 15ae1c465..20b012105 100644 --- a/utils/test/test-aa.py +++ b/utils/test/test-aa.py @@ -13,7 +13,8 @@ import unittest from common_test import AATest, setup_all_loops from common_test import read_file, write_file -from apparmor.aa import check_for_apparmor, get_profile_flags, set_profile_flags, is_skippable_file, is_skippable_dir, parse_profile_start, separate_vars, store_list_var, write_header, serialize_parse_profile_start +from apparmor.aa import (check_for_apparmor, get_profile_flags, set_profile_flags, is_skippable_file, is_skippable_dir, + parse_profile_start, parse_profile_data, separate_vars, store_list_var, write_header, serialize_parse_profile_start) from apparmor.common import AppArmorException, AppArmorBug class AaTestWithTempdir(AATest): @@ -381,6 +382,21 @@ class AaTest_parse_profile_start(AATest): with self.assertRaises(AppArmorBug): self._parse('xy', '/bar', '/bar') # not a profile start +class AaTest_parse_profile_data(AATest): + def test_parse_empty_profile_01(self): + prof = parse_profile_data('/foo {\n}\n'.split(), 'somefile', False) + + self.assertEqual(list(prof.keys()), ['/foo']) + self.assertEqual(list(prof['/foo'].keys()), ['/foo']) + self.assertEqual(prof['/foo']['/foo']['name'], '/foo') + self.assertEqual(prof['/foo']['/foo']['filename'], 'somefile') + self.assertEqual(prof['/foo']['/foo']['flags'], None) + + def test_parse_empty_profile_02(self): + with self.assertRaises(AppArmorException): + # file contains two profiles with the same name + parse_profile_data('profile /foo {\n}\nprofile /foo {\n}\n'.split(), 'somefile', False) + class AaTest_separate_vars(AATest): tests = [ ('' , set() ), From 0461c60a6e555cd198b5c07160ba0a8cbaf1cb34 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 13:01:37 +0200 Subject: [PATCH 025/143] cleanup superfluous variable assignments in aa-mergeprof aa-mergeprof has some sections where it first resets the 'deleted' variable, and then overwrites it again a line or two later. This patch removes the superfluous variable resets. Acked-by: Steve Beattie --- utils/aa-mergeprof | 3 --- 1 file changed, 3 deletions(-) diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index aebc88386..0dc0f1d23 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -373,7 +373,6 @@ class Merge(object): selection = options[selected] match = apparmor.aa.re_match_include(selection) if match: - deleted = False inc = match deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True @@ -615,7 +614,6 @@ class Merge(object): match = apparmor.aa.re_match_include(path) if match: inc = match - deleted = 0 deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True apparmor.aa.changed[profile] = True @@ -789,7 +787,6 @@ class Merge(object): done = True if apparmor.aa.re_match_include(selection): #re.search('#include\s+<.+>$', selection): inc = apparmor.aa.re_match_include(selection) #re.search('#include\s+<(.+)>$', selection).groups()[0] - deleted = 0 deleted = apparmor.aa.delete_duplicates(self.user.aa[profile][hat], inc) self.user.aa[profile][hat]['include'][inc] = True From 0fd30653fd3ad41886178013b726798ffedb249d Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 23:03:51 +0200 Subject: [PATCH 026/143] Use generic names in aa-mergeprof Replace rule-specific names with generic names: - s/'capability'/ruletype/ - s/cap_obj/rule_obj/ - s/'network'/ruletype/ - s/net_obj/rule_obj/ Also set ruletype at the beginning of each block. The long-term goal is to have for ruletype in ['capability', 'network', ...]: with common code to handle all rule types, and having common names makes it easier to compare the blocks. Acked-by: Steve Beattie --- utils/aa-mergeprof | 78 ++++++++++++++++++++++++---------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/utils/aa-mergeprof b/utils/aa-mergeprof index 0dc0f1d23..087f0c07f 100755 --- a/utils/aa-mergeprof +++ b/utils/aa-mergeprof @@ -310,36 +310,37 @@ class Merge(object): return #Add the capabilities - if other.aa[profile][hat].get('capability', False): # needed until we have proper profile initialization - for cap_obj in other.aa[profile][hat]['capability'].rules: + ruletype = 'capability' + if other.aa[profile][hat].get(ruletype, False): # needed until we have proper profile initialization + for rule_obj in other.aa[profile][hat][ruletype].rules: - if apparmor.aa.is_known_rule(self.user.aa[profile][hat], 'capability', cap_obj): + if apparmor.aa.is_known_rule(self.user.aa[profile][hat], ruletype, rule_obj): continue - if cap_obj.all_caps: + if rule_obj.all_caps: severity = 10 cap_txt = 'ALL' else: - cap_txt = ' '.join(cap_obj.capability) + cap_txt = ' '.join(rule_obj.capability) severity = 0 - for cap in cap_obj.capability: + for cap in rule_obj.capability: severity = max(severity, sev_db.rank('CAP_%s' % cap)) - if cap_obj.deny: + if rule_obj.deny: cap_txt = 'deny %s' % cap_txt - if cap_obj.audit: + if rule_obj.audit: cap_txt = 'audit %s' % cap_txt default_option = 1 options = [] - newincludes = apparmor.aa.match_includes(self.user.aa[profile][hat], 'capability', cap_obj) + newincludes = apparmor.aa.match_includes(self.user.aa[profile][hat], ruletype, rule_obj) q = aaui.PromptQuestion() if newincludes: options += list(map(lambda inc: '#include <%s>' %inc, sorted(set(newincludes)))) if options: - options.append(cap_obj.get_clean()) + options.append(rule_obj.get_clean()) q.options = options q.selected = default_option - 1 @@ -350,7 +351,7 @@ class Merge(object): audit_toggle = 0 q.functions = [] - if not cap_obj.deny: + if not rule_obj.deny: q.functions += ['CMD_ALLOW'] q.functions += ['CMD_DENY', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED'] @@ -381,20 +382,20 @@ class Merge(object): if deleted: aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) - self.user.aa[profile][hat]['capability'].add(cap_obj) + self.user.aa[profile][hat][ruletype].add(rule_obj) apparmor.aa.changed[profile] = True - aaui.UI_Info(_('Adding %s to profile.') % cap_obj.get_clean()) + aaui.UI_Info(_('Adding %s to profile.') % rule_obj.get_clean()) done = True elif ans == 'CMD_DENY': - cap_obj.deny = True - cap_obj.raw_rule = None # reset raw rule after manually modifying cap_obj - self.user.aa[profile][hat]['capability'].add(cap_obj) + rule_obj.deny = True + rule_obj.raw_rule = None # reset raw rule after manually modifying rule_obj + self.user.aa[profile][hat][ruletype].add(rule_obj) apparmor.aa.changed[profile] = True - aaui.UI_Info(_('Adding %s to profile.') % cap_obj.get_clean()) + aaui.UI_Info(_('Adding %s to profile.') % rule_obj.get_clean()) done = True else: done = False @@ -714,43 +715,44 @@ class Merge(object): default_option = ans if 1 == 1: # avoid whitespace change - if other.aa[profile][hat].get('network', False): # needed until we have proper profile initialization - for net_obj in other.aa[profile][hat]['network'].rules: + ruletype = 'network' + if other.aa[profile][hat].get(ruletype, False): # needed until we have proper profile initialization + for rule_obj in other.aa[profile][hat][ruletype].rules: # severity handling for net toggles goes here - if apparmor.aa.is_known_rule(self.user.aa[profile][hat], 'network', net_obj): + if apparmor.aa.is_known_rule(self.user.aa[profile][hat], ruletype, rule_obj): continue - if net_obj.all_domains: + if rule_obj.all_domains: family = 'ALL' else: - family = net_obj.domain + family = rule_obj.domain - if net_obj.all_type_or_protocols: + if rule_obj.all_type_or_protocols: sock_type = 'ALL' else: - sock_type = net_obj.type_or_protocol + sock_type = rule_obj.type_or_protocol default_option = 1 options = [] - newincludes = apparmor.aa.match_includes(self.user.aa[profile][hat], 'network', net_obj) + newincludes = apparmor.aa.match_includes(self.user.aa[profile][hat], ruletype, rule_obj) q = aaui.PromptQuestion() if newincludes: options += list(map(lambda s: '#include <%s>'%s, sorted(set(newincludes)))) if True:#options: - options.append(net_obj.get_clean()) + options.append(rule_obj.get_clean()) q.options = options q.selected = default_option - 1 audit = '' - if net_obj.audit: + if rule_obj.audit: audit = 'audit ' q.headers = [_('Profile'), apparmor.aa.combine_name(profile, hat)] q.headers += [_('Network Family'), audit + family] q.headers += [_('Socket Type'), sock_type] - q.functions = available_buttons(net_obj) + q.functions = available_buttons(rule_obj) q.default = q.functions[0] done = False @@ -765,16 +767,16 @@ class Merge(object): if ans.startswith('CMD_AUDIT'): if ans == 'CMD_AUDIT_NEW': - net_obj.audit = True - net_obj.raw_rule = None + rule_obj.audit = True + rule_obj.raw_rule = None audit = 'audit ' else: - net_obj.audit = False - net_obj.raw_rule = None + rule_obj.audit = False + rule_obj.raw_rule = None audit = '' - q.functions = available_buttons(net_obj) - options[len(options) - 1] = net_obj.get_clean() + q.functions = available_buttons(rule_obj) + options[len(options) - 1] = rule_obj.get_clean() q.options = options q.headers = [_('Profile'), apparmor.aa.combine_name(profile, hat)] @@ -798,7 +800,7 @@ class Merge(object): aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted) else: - self.user.aa[profile][hat]['network'].add(net_obj) + self.user.aa[profile][hat][ruletype].add(rule_obj) apparmor.aa.changed[profile] = True @@ -806,9 +808,9 @@ class Merge(object): elif ans == 'CMD_DENY': done = True - net_obj.deny = True - net_obj.raw_rule = None - self.user.aa[profile][hat]['network'].add(net_obj) + rule_obj.deny = True + rule_obj.raw_rule = None + self.user.aa[profile][hat][ruletype].add(rule_obj) apparmor.aa.changed[profile] = True aaui.UI_Info(_('Denying network access %(family)s %(type)s to profile') % { 'family': family, 'type': sock_type }) From 99b2f67d3c9c21844920543780bf71abf602b733 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 23:10:11 +0200 Subject: [PATCH 027/143] severity.py: rename handle_capability() to rank_capability() It's only used inside severity.py until now, but I plan to change that and want a better function name ;-) Acked-by: Steve Beattie . --- utils/apparmor/severity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/apparmor/severity.py b/utils/apparmor/severity.py index 07d8bbd36..fb6101e56 100644 --- a/utils/apparmor/severity.py +++ b/utils/apparmor/severity.py @@ -75,7 +75,7 @@ class Severity(object): else: raise AppArmorException("Unexpected line in file: %s\n\t[Line %s]: %s" % (dbname, lineno, line)) - def handle_capability(self, resource): + def rank_capability(self, resource): """Returns the severity of for the capability resource, default value if no match""" cap = resource.upper() if cap in self.severity['CAPABILITIES'].keys(): @@ -136,7 +136,7 @@ class Severity(object): elif resource[0] == '/': # file resource return self.handle_file(resource, mode) elif resource[0:4] == 'CAP_': # capability resource - return self.handle_capability(resource) + return self.rank_capability(resource) else: raise AppArmorException("Unexpected rank input: %s" % resource) From cf4eb0182cdc6944764f1fe547b6fb6fb1cf0c89 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 23:29:52 +0200 Subject: [PATCH 028/143] severity.py: change rank_capability() to not expect the CAP_ prefix Change rank_capability() so that it doesn't expect the CAP_ prefix. This makes usage easier because callers can simply hand over the capability name. Also change rank() to call rank_capability() without the CAP_ prefix. Acked-by: Steve Beattie --- utils/apparmor/severity.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/apparmor/severity.py b/utils/apparmor/severity.py index fb6101e56..0dbab2165 100644 --- a/utils/apparmor/severity.py +++ b/utils/apparmor/severity.py @@ -77,7 +77,7 @@ class Severity(object): def rank_capability(self, resource): """Returns the severity of for the capability resource, default value if no match""" - cap = resource.upper() + cap = 'CAP_%s' % resource.upper() if cap in self.severity['CAPABILITIES'].keys(): return self.severity['CAPABILITIES'][cap] # raise ValueError("unexpected capability rank input: %s"%resource) @@ -136,7 +136,7 @@ class Severity(object): elif resource[0] == '/': # file resource return self.handle_file(resource, mode) elif resource[0:4] == 'CAP_': # capability resource - return self.rank_capability(resource) + return self.rank_capability(resource[4:]) else: raise AppArmorException("Unexpected rank input: %s" % resource) From 2f6059767e5c7cc006d9a2ef53067cfaf91ce34f Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 23:31:56 +0200 Subject: [PATCH 029/143] Convert test-severity.py to use the AATest class This simplifies test-severity.py a lot: - lots of test functions are replaced with tests[] arrays - tempdir handling and cleanup is now done automagically Even if test-severity.py shrunk by 65 lines, all tests are still there. There's even an addition - SeverityTestCap now additionally verifies the result of rank_capability(). Acked-by: Steve Beattie --- utils/test/test-severity.py | 201 ++++++++++++------------------------ 1 file changed, 68 insertions(+), 133 deletions(-) diff --git a/utils/test/test-severity.py b/utils/test/test-severity.py index c9065fc9f..1af3b762e 100755 --- a/utils/test/test-severity.py +++ b/utils/test/test-severity.py @@ -2,6 +2,7 @@ # ---------------------------------------------------------------------- # Copyright (C) 2013 Kshitij Gupta # Copyright (C) 2014 Canonical, Ltd. +# 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 @@ -13,23 +14,17 @@ # GNU General Public License for more details. # # ---------------------------------------------------------------------- -import os -import shutil -import tempfile import unittest +from common_test import AATest, setup_all_loops import apparmor.severity as severity from apparmor.common import AppArmorException -from common_test import write_file -class SeverityBaseTest(unittest.TestCase): +class SeverityBaseTest(AATest): - def setUp(self): + def AASetup(self): self.sev_db = severity.Severity('severity.db') - def tearDown(self): - pass - def _simple_severity_test(self, path, expected_rank): rank = self.sev_db.rank(path) self.assertEqual(rank, expected_rank, @@ -41,58 +36,44 @@ class SeverityBaseTest(unittest.TestCase): 'expected rank %d, got %d' % (expected_rank, rank)) class SeverityTest(SeverityBaseTest): - def test_perm_x(self): - self._simple_severity_w_perm('/usr/bin/whatis', 'x', 5) + tests = [ + (['/usr/bin/whatis', 'x' ], 5), + (['/etc', 'x' ], 10), + (['/dev/doublehit', 'x' ], 0), + (['/dev/doublehit', 'rx' ], 4), + (['/dev/doublehit', 'rwx' ], 8), + (['/dev/tty10', 'rwx' ], 9), + (['/var/adm/foo/**', 'rx' ], 3), + (['/etc/apparmor/**', 'r' ], 6), + (['/etc/**', 'r' ], 10), + (['/usr/foo@bar', 'r' ], 10), ## filename containing @ + (['/home/foo@bar', 'rw' ], 6), ## filename containing @ + ] - def test_perm_etc_x(self): - self._simple_severity_w_perm('/etc', 'x', 10) - - def test_perm_dev_x(self): - self._simple_severity_w_perm('/dev/doublehit', 'x', 0) - - def test_perm_dev_rx(self): - self._simple_severity_w_perm('/dev/doublehit', 'rx', 4) - - def test_perm_dev_rwx(self): - self._simple_severity_w_perm('/dev/doublehit', 'rwx', 8) - - def test_perm_tty_rwx(self): - self._simple_severity_w_perm('/dev/tty10', 'rwx', 9) - - def test_perm_glob_1(self): - self._simple_severity_w_perm('/var/adm/foo/**', 'rx', 3) - - def test_cap_kill(self): - self._simple_severity_test('CAP_KILL', 8) - - def test_cap_setpcap(self): - self._simple_severity_test('CAP_SETPCAP', 9) - - def test_cap_setpcap_lowercase(self): - self._simple_severity_test('CAP_setpcap', 9) - - def test_cap_unknown_1(self): - self._simple_severity_test('CAP_UNKNOWN', 10) - - def test_cap_unknown_2(self): - self._simple_severity_test('CAP_K*', 10) - - def test_perm_apparmor_glob(self): - self._simple_severity_w_perm('/etc/apparmor/**', 'r' , 6) - - def test_perm_etc_glob(self): - self._simple_severity_w_perm('/etc/**', 'r' , 10) - - def test_perm_filename_w_at_r(self): - self._simple_severity_w_perm('/usr/foo@bar', 'r' , 10) ## filename containing @ - - def test_perm_filename_w_at_rw(self): - self._simple_severity_w_perm('/home/foo@bar', 'rw', 6) ## filename containing @ + def _run_test(self, params, expected): + self._simple_severity_w_perm(params[0], params[1], expected) ## filename containing @ def test_invalid_rank(self): with self.assertRaises(AppArmorException): self._simple_severity_w_perm('unexpected_unput', 'rw', 6) +class SeverityTestCap(SeverityBaseTest): + tests = [ + ('KILL', 8), + ('SETPCAP', 9), + ('setpcap', 9), + ('UNKNOWN', 10), + ('K*', 10), + ] + + def _run_test(self, params, expected): + cap_with_prefix = 'CAP_%s' % params + self._simple_severity_test(cap_with_prefix, expected) + + rank = self.sev_db.rank_capability(params) + self.assertEqual(rank, expected, 'expected rank %d, got %d' % (expected, rank)) + + class SeverityVarsTest(SeverityBaseTest): VARIABLE_DEFINITIONS = ''' @@ -108,40 +89,27 @@ class SeverityVarsTest(SeverityBaseTest): @{pids}=@{pid} ''' - def setUp(self): - super(SeverityVarsTest, self).setUp() - self.tmpdir = tempfile.mkdtemp(prefix='aa-severity-') - def _init_tunables(self, content=''): if not content: content = self.VARIABLE_DEFINITIONS - self.rules_file = write_file(self.tmpdir, 'tunables', content) + self.rules_file = self.writeTmpfile('tunables', content) self.sev_db.load_variables(self.rules_file) - def tearDown(self): + def AATeardown(self): self.sev_db.unload_variables() - if os.path.exists(self.tmpdir): - shutil.rmtree(self.tmpdir) - super(SeverityVarsTest, self).tearDown() + tests = [ + (['@{PROC}/sys/vm/overcommit_memory', 'r'], 6), + (['@{HOME}/sys/@{PROC}/overcommit_memory', 'r'], 10), + (['/overco@{multiarch}mmit_memory', 'r'], 10), + (['@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'], 6), + ] - def test_proc_var(self): + def _run_test(self, params, expected): self._init_tunables() - self._simple_severity_w_perm('@{PROC}/sys/vm/overcommit_memory', 'r', 6) - - def test_home_var(self): - self._init_tunables() - self._simple_severity_w_perm('@{HOME}/sys/@{PROC}/overcommit_memory', 'r', 10) - - def test_multiarch_var(self): - self._init_tunables() - self._simple_severity_w_perm('/overco@{multiarch}mmit_memory', 'r', 10) - - def test_proc_tftp_vars(self): - self._init_tunables() - self._simple_severity_w_perm('@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r', 6) + self._simple_severity_w_perm(params[0], params[1], expected) def test_include(self): self._init_tunables('#include ') # including non-existing files doesn't raise an exception @@ -156,20 +124,30 @@ class SeverityVarsTest(SeverityBaseTest): with self.assertRaises(AppArmorException): self._init_tunables('@{foo} = /home/\n@{foo} = /root/') -class SeverityDBTest(unittest.TestCase): - - def setUp(self): - self.tmpdir = tempfile.mkdtemp(prefix='aa-severity-db-') - - def tearDown(self): - if os.path.exists(self.tmpdir): - shutil.rmtree(self.tmpdir) - +class SeverityDBTest(AATest): def _test_db(self, contents): - self.db_file = write_file(self.tmpdir, 'severity.db', contents) + self.db_file = self.writeTmpfile('severity.db', contents) self.sev_db = severity.Severity(self.db_file) return self.sev_db + tests = [ + ("CAP_LEASE 18\n" , AppArmorException), # out of range + ("CAP_LEASE -1\n" , AppArmorException), # out of range + ("/etc/passwd* 0 4\n" , AppArmorException), # insufficient vals + ("/etc/passwd* 0 4 5 6\n" , AppArmorException), # too many vals + ("/etc/passwd* -2 4 6\n" , AppArmorException), # out of range + ("/etc/passwd* 12 4 6\n" , AppArmorException), # out of range + ("/etc/passwd* 2 -4 6\n" , AppArmorException), # out of range + ("/etc/passwd 2 14 6\n" , AppArmorException), # out of range + ("/etc/passwd 2 4 -12\n" , AppArmorException), # out of range + ("/etc/passwd 2 4 4294967297\n" , AppArmorException), # out of range + ("garbage line\n" , AppArmorException), + ] + + def _run_test(self, params, expected): + with self.assertRaises(expected): + self._test_db(params) + def test_simple_db(self): self._test_db(''' CAP_LEASE 8 @@ -182,50 +160,6 @@ class SeverityDBTest(unittest.TestCase): def test_cap_val_min_range(self): self._test_db("CAP_LEASE 0\n") - def test_cap_val_out_of_range_1(self): - with self.assertRaises(AppArmorException): - self._test_db("CAP_LEASE 18\n") - - def test_cap_val_out_of_range_2(self): - with self.assertRaises(AppArmorException): - self._test_db("CAP_LEASE -1\n") - - def test_path_insufficient_vals(self): - with self.assertRaises(AppArmorException): - self._test_db("/etc/passwd* 0 4\n") - - def test_path_too_many_vals(self): - with self.assertRaises(AppArmorException): - self._test_db("/etc/passwd* 0 4 5 6\n") - - def test_path_outside_range_1(self): - with self.assertRaises(AppArmorException): - self._test_db("/etc/passwd* -2 4 6\n") - - def test_path_outside_range_2(self): - with self.assertRaises(AppArmorException): - self._test_db("/etc/passwd* 12 4 6\n") - - def test_path_outside_range_3(self): - with self.assertRaises(AppArmorException): - self._test_db("/etc/passwd* 2 -4 6\n") - - def test_path_outside_range_4(self): - with self.assertRaises(AppArmorException): - self._test_db("/etc/passwd 2 14 6\n") - - def test_path_outside_range_5(self): - with self.assertRaises(AppArmorException): - self._test_db("/etc/passwd 2 4 -12\n") - - def test_path_outside_range_6(self): - with self.assertRaises(AppArmorException): - self._test_db("/etc/passwd 2 4 4294967297\n") - - def test_garbage_line(self): - with self.assertRaises(AppArmorException): - self._test_db("garbage line\n") - def test_invalid_db(self): self.assertRaises(AppArmorException, severity.Severity, 'severity_broken.db') @@ -236,5 +170,6 @@ class SeverityDBTest(unittest.TestCase): with self.assertRaises(AppArmorException): severity.Severity() -if __name__ == "__main__": +setup_all_loops(__name__) +if __name__ == '__main__': unittest.main(verbosity=2) From 5eb796044b6f4f16f618f0b022c748aaf2fb8b53 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 23:39:14 +0200 Subject: [PATCH 030/143] Change test-severity.py to use 'unknown' as default rank, and fix the bugs it found To be able to distinguish between severity 10 and unknown severity, change AASetup to specify 'unknown' as default rank, and change the expected result to 'unknown' where it's expected. Also change the "expected rank %d" to "%s" because it can be a string now, and add a test that contains directories with different severity in one variable. After these changes, handle_variable_rank() errors out with TypeError: unorderable types: str() > int() so fix it by - initializing rank with the default rank (instead of none) - explicitely check that rank and rank_new are != the default rank before doing a comparison A side effect is another bugfix - '@{HOME}/sys/@{PROC}/overcommit_memory' is severity 4, not 10 or unknown (confirmed by reading severity.db). Acked-by: Steve Beattie --- utils/apparmor/severity.py | 7 ++++--- utils/test/test-severity.py | 24 +++++++++++++----------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/utils/apparmor/severity.py b/utils/apparmor/severity.py index 0dbab2165..c2205ae6b 100644 --- a/utils/apparmor/severity.py +++ b/utils/apparmor/severity.py @@ -143,16 +143,17 @@ class Severity(object): def handle_variable_rank(self, resource, mode): """Returns the max possible rank for file resources containing variables""" regex_variable = re.compile('@{([^{.]*)}') - rank = None matches = regex_variable.search(resource) if matches: + rank = self.severity['DEFAULT_RANK'] variable = '@{%s}' % matches.groups()[0] #variables = regex_variable.findall(resource) for replacement in self.severity['VARIABLES'][variable]: resource_replaced = self.variable_replace(variable, replacement, resource) rank_new = self.handle_variable_rank(resource_replaced, mode) - #rank_new = self.handle_variable_rank(resource.replace('@{'+variable+'}', replacement), mode) - if rank is None or rank_new > rank: + if rank == self.severity['DEFAULT_RANK']: + rank = rank_new + elif rank_new != self.severity['DEFAULT_RANK'] and rank_new > rank: rank = rank_new return rank else: diff --git a/utils/test/test-severity.py b/utils/test/test-severity.py index 1af3b762e..b8aedcb0a 100755 --- a/utils/test/test-severity.py +++ b/utils/test/test-severity.py @@ -23,30 +23,30 @@ from apparmor.common import AppArmorException class SeverityBaseTest(AATest): def AASetup(self): - self.sev_db = severity.Severity('severity.db') + self.sev_db = severity.Severity('severity.db', 'unknown') def _simple_severity_test(self, path, expected_rank): rank = self.sev_db.rank(path) self.assertEqual(rank, expected_rank, - 'expected rank %d, got %d' % (expected_rank, rank)) + 'expected rank %s, got %s' % (expected_rank, rank)) def _simple_severity_w_perm(self, path, perm, expected_rank): rank = self.sev_db.rank(path, perm) self.assertEqual(rank, expected_rank, - 'expected rank %d, got %d' % (expected_rank, rank)) + 'expected rank %s, got %s' % (expected_rank, rank)) class SeverityTest(SeverityBaseTest): tests = [ (['/usr/bin/whatis', 'x' ], 5), - (['/etc', 'x' ], 10), + (['/etc', 'x' ], 'unknown'), (['/dev/doublehit', 'x' ], 0), (['/dev/doublehit', 'rx' ], 4), (['/dev/doublehit', 'rwx' ], 8), (['/dev/tty10', 'rwx' ], 9), (['/var/adm/foo/**', 'rx' ], 3), (['/etc/apparmor/**', 'r' ], 6), - (['/etc/**', 'r' ], 10), - (['/usr/foo@bar', 'r' ], 10), ## filename containing @ + (['/etc/**', 'r' ], 'unknown'), + (['/usr/foo@bar', 'r' ], 'unknown'), ## filename containing @ (['/home/foo@bar', 'rw' ], 6), ## filename containing @ ] @@ -62,8 +62,8 @@ class SeverityTestCap(SeverityBaseTest): ('KILL', 8), ('SETPCAP', 9), ('setpcap', 9), - ('UNKNOWN', 10), - ('K*', 10), + ('UNKNOWN', 'unknown'), + ('K*', 'unknown'), ] def _run_test(self, params, expected): @@ -71,7 +71,7 @@ class SeverityTestCap(SeverityBaseTest): self._simple_severity_test(cap_with_prefix, expected) rank = self.sev_db.rank_capability(params) - self.assertEqual(rank, expected, 'expected rank %d, got %d' % (expected, rank)) + self.assertEqual(rank, expected, 'expected rank %s, got %s' % (expected, rank)) class SeverityVarsTest(SeverityBaseTest): @@ -87,6 +87,7 @@ class SeverityVarsTest(SeverityBaseTest): @{pid}={[1-9],[1-9][0-9],[1-9][0-9][0-9],[1-9][0-9][0-9][0-9],[1-9][0-9][0-9][0-9][0-9],[1-9][0-9][0-9][0-9][0-9][0-9]} @{tid}=@{pid} @{pids}=@{pid} +@{somepaths}=/home/foo/downloads @{HOMEDIRS}/foo/.ssh/ ''' def _init_tunables(self, content=''): @@ -102,9 +103,10 @@ class SeverityVarsTest(SeverityBaseTest): tests = [ (['@{PROC}/sys/vm/overcommit_memory', 'r'], 6), - (['@{HOME}/sys/@{PROC}/overcommit_memory', 'r'], 10), - (['/overco@{multiarch}mmit_memory', 'r'], 10), + (['@{HOME}/sys/@{PROC}/overcommit_memory', 'r'], 4), + (['/overco@{multiarch}mmit_memory', 'r'], 'unknown'), (['@{PROC}/sys/@{TFTP_DIR}/overcommit_memory', 'r'], 6), + (['@{somepaths}/somefile', 'r'], 7), ] def _run_test(self, params, expected): From 01a43e5f1b0f88c8ea201852150db9961128ce15 Mon Sep 17 00:00:00 2001 From: Christian Boltz Date: Fri, 29 May 2015 23:43:27 +0200 Subject: [PATCH 031/143] Convert test-capability.py to AATest I decided to use a "small" solution for now, which basically means s/unittest.TestCase/AATest/, cleanup of some setUp() and renaming the remaining setUp() functions to AASetup(). This doesn't mean an instant win (like in test-severity.py), but allows to add tests with a tests[] array. Acked-by: Steve Beattie --- utils/test/test-capability.py | 46 +++++++++++------------------------ 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/utils/test/test-capability.py b/utils/test/test-capability.py index fcd607329..7240f2afb 100644 --- a/utils/test/test-capability.py +++ b/utils/test/test-capability.py @@ -14,6 +14,7 @@ # ---------------------------------------------------------------------- import unittest +from common_test import AATest, setup_all_loops from apparmor.rule.capability import CapabilityRule, CapabilityRuleset from apparmor.rule import BaseRule @@ -22,10 +23,7 @@ from apparmor.logparser import ReadLog # --- tests for single CapabilityRule --- # -class CapabilityTest(unittest.TestCase): - def setUp(self): - self.maxDiff = None - +class CapabilityTest(AATest): def _compare_obj_with_rawrule(self, rawrule, expected): obj = CapabilityRule.parse(rawrule) @@ -212,10 +210,7 @@ class CapabilityTest(unittest.TestCase): }) -class InvalidCapabilityTest(unittest.TestCase): - def setUp(self): - self.maxDiff = None - +class InvalidCapabilityTest(AATest): def _check_invalid_rawrule(self, rawrule): obj = None with self.assertRaises(AppArmorException): @@ -262,10 +257,7 @@ class InvalidCapabilityTest(unittest.TestCase): CapabilityRule(dict()) -class WriteCapabilityTest(unittest.TestCase): - def setUp(self): - self.maxDiff = None - +class WriteCapabilityTest(AATest): def _check_write_rule(self, rawrule, cleanrule): obj = CapabilityRule.parse(rawrule) clean = obj.get_clean() @@ -292,10 +284,7 @@ class WriteCapabilityTest(unittest.TestCase): self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule') self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule') -class CapabilityCoveredTest(unittest.TestCase): - def setUp(self): - self.maxDiff = None - +class CapabilityCoveredTest(AATest): def _is_covered(self, obj, rule_to_test): self.assertTrue(CapabilityRule.match(rule_to_test)) return obj.is_covered(CapabilityRule.parse(rule_to_test)) @@ -432,10 +421,7 @@ class CapabilityCoveredTest(unittest.TestCase): # --- tests for CapabilityRuleset --- # -class CapabilityRulesTest(unittest.TestCase): - def setUp(self): - self.maxDiff = None - +class CapabilityRulesTest(AATest): def test_empty_ruleset(self): ruleset = CapabilityRuleset() ruleset_2 = CapabilityRuleset() @@ -515,10 +501,8 @@ class CapabilityRulesTest(unittest.TestCase): self.assertEqual(expected_clean, ruleset.get_clean(1)) -class CapabilityRulesCoveredTest(unittest.TestCase): - def setUp(self): - self.maxDiff = None - +class CapabilityRulesCoveredTest(AATest): + def AASetup(self): self.ruleset = CapabilityRuleset() rules = [ 'capability chown,', @@ -611,9 +595,8 @@ class CapabilityRulesCoveredTest(unittest.TestCase): # parser = ReadLog('', '', '', '', '') # self.assertEqual(True, self.ruleset.is_log_covered(parser.parse_event(event_base%'chgrp'), False)) # ignores allow/deny -class CapabilityGlobTest(unittest.TestCase): - def setUp(self): - self.maxDiff = None +class CapabilityGlobTest(AATest): + def AASetup(self): self.ruleset = CapabilityRuleset() def test_glob(self): @@ -623,10 +606,8 @@ class CapabilityGlobTest(unittest.TestCase): with self.assertRaises(AppArmorBug): self.ruleset.get_glob_ext('capability net_raw,') -class CapabilityDeleteTest(unittest.TestCase): - def setUp(self): - self.maxDiff = None - +class CapabilityDeleteTest(AATest): + def AASetup(self): self.ruleset = CapabilityRuleset() rules = [ 'capability chown,', @@ -974,5 +955,6 @@ class CapabilityDeleteTest(unittest.TestCase): self._check_test_delete_duplicates_in_profile(rules, expected_raw, expected_clean, expected_deleted) -if __name__ == "__main__": +setup_all_loops(__name__) +if __name__ == '__main__': unittest.main(verbosity=2) From 631804e8a7c7dadc256daa6be925f952cb7fc1d5 Mon Sep 17 00:00:00 2001 From: Tyler Hicks Date: Mon, 1 Jun 2015 11:29:37 -0500 Subject: [PATCH 032/143] parser: Document the --features-file option in apparmor_parser(8) This option was previously only documented in the --help output. Signed-off-by: Tyler Hicks Acked-by: Steve Beattie --- parser/apparmor_parser.pod | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/parser/apparmor_parser.pod b/parser/apparmor_parser.pod index 428b0581e..bab5979ae 100644 --- a/parser/apparmor_parser.pod +++ b/parser/apparmor_parser.pod @@ -183,6 +183,12 @@ defined as an absolute paths. Set the location of the apparmor security filesystem (default is "/sys/kernel/security/apparmor"). +=item -M n, --features-file n + +Use the features file located at path "n" (default is +/etc/apparmor.d/cache/.features). If the --cache-loc option is present, the +".features" file in the specified cache directory is used. + =item -m n, --match-string n Only use match features "n". From 8ffec9357dbad1323cf2044a8d3ae3f7e30ae23d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2015 01:00:29 -0700 Subject: [PATCH 033/143] apparmor.d.pod: create RULES grouping and cleanup profile PROFILE rule Signed-off-by: John Johansen Acked-by: Christian Boltz --- parser/apparmor.d.pod | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/parser/apparmor.d.pod b/parser/apparmor.d.pod index 11b93e58d..f0a8cea92 100644 --- a/parser/apparmor.d.pod +++ b/parser/apparmor.d.pod @@ -50,11 +50,19 @@ B = '"' path '"' (the path is passed to open(2)) B = 'E' relative path 'E' (the path is relative to F) -B = '#' I +B = '#' I [ '\r' ] '\n' B = any characters -B = [ I ... ] [ I ... ] ( '"' I '"' | I ) [ 'flags=(complain)' ]'{' [ ( I | I | I | I | I | I | I | I | I | I | I | I | I | I) ... ] '}' +B = [ I ... ] [ I ... ] ( '"' I '"' | I ) [ 'flags=(complain)' ]'{' ( I )* '}' + +B = [ ( I | I ',' | I ) + +B = ( I | I ) [ '\r' ] '\n' + +B = ( I | I | I | I | I | I | I | I | I | I ) + +B = I B = [ I ... ] ( I | 'profile ' I ) '{' [ ( I | I | I ) ... ] '}' From d506ecfd4dbf5ce88188469ab2da4a04acceff42 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2015 01:00:29 -0700 Subject: [PATCH 034/143] apparmor.d.pod: refactor profile file, profile, subprofile, hat patterns Signed-off-by: John Johansen Acked-by: Christian Boltz --- parser/apparmor.d.pod | 48 ++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/parser/apparmor.d.pod b/parser/apparmor.d.pod index f0a8cea92..a60b51d5e 100644 --- a/parser/apparmor.d.pod +++ b/parser/apparmor.d.pod @@ -44,6 +44,10 @@ to the policy; this behaviour is modelled after cpp(1). =over 4 +B = ( [ I ] [ I ] )* + +B = ( I | I | I )* (variable assignment must come before the profile) + B = '#include' ( I | I ) B = '"' path '"' (the path is passed to open(2)) @@ -54,7 +58,21 @@ B = '#' I [ '\r' ] '\n' B = any characters -B = [ I ... ] [ I ... ] ( '"' I '"' | I ) [ 'flags=(complain)' ]'{' ( I )* '}' +B = ( I ) [ I ] [ ] '{' ( I )* '}' + +B = [ 'profile' ] I | 'profile' I + +B ( I | I ) + +B = '"' I '"' + +B = (must start with alphanumeric character (after variable expansion), or '/' B have special meanings; see below. May include I. Rules with embedded spaces or tabs must be quoted.) + +B = I + +B = [ 'flags=' ] '(' comma or white space separated list of I ')' + +B = 'complain' | 'audit' | 'enforce' | 'mediate_deleted' | 'attach_disconnected' | 'chroot_relative' B = [ ( I | I ',' | I ) @@ -62,9 +80,13 @@ B = ( I | I ) [ '\r' ] '\n' B = ( I | I | I | I | I | I | I | I | I | I ) -B = I +B = ( I | I ) -B = [ I ... ] ( I | 'profile ' I ) '{' [ ( I | I | I ) ... ] '}' +B = 'profile' I [ I ] [ ] '{' ( I )* '}' + +B = ('hat' | '^') I [ ] '{' ( I )* '}' + +B = ( must start with alphanumeric character. see aa_change_hat(2) for a description of how this "hat" is used. IF '^' is used to start a hat then there is no space between the '^' and I) B = ( 'allow' | 'deny' ) @@ -77,7 +99,7 @@ B = ( I )+ B = (lowercase capability name without 'CAP_' prefix; see capabilities(7)) -B = [ I 'network' [ [ I [ I | I ] ] | [ I ] ] ',' +B = [ I 'network' [ [ I [ I | I ] ] | [ I ] ] B = ( 'inet' | 'ax25' | 'ipx' | 'appletalk' | 'netrom' | 'bridge' | 'atmpvc' | 'x25' | 'inet6' | 'rose' | 'netbeui' | 'security' | 'key' | 'packet' | 'ash' | 'econet' | 'atmsvc' | 'sna' | 'irda' | 'pppox' | 'wanpipe' | 'bluetooth' | 'netlink' | 'unix' | 'rds' | 'llc' | 'can' | 'tipc' | 'iucv' | 'rxrpc' | 'isdn' | 'phonet' | 'ieee802154' | 'caif' | 'alg' | 'nfc' | 'vsock' ) ',' @@ -85,12 +107,6 @@ B = ( 'stream' | 'dgram' | 'seqpacket' | 'rdm' | 'raw' | 'packet' ) B = ( 'tcp' | 'udp' | 'icmp' ) -B = (non-whitespace characters except for '^', must start with '/'. Embedded spaces or tabs must be quoted.) - -B = '^' (non-whitespace characters; see aa_change_hat(2) for a description of how this "hat" is used.) - -B = I name - B = ( I | I | I ) B = [ I ] 'mount' [ I ] [ I ] [ -E [ I ] @@ -113,7 +129,7 @@ B = ( 'ro' | 'rw' | 'nosuid' | 'suid' | 'nodev' | 'dev' | 'noexec' B = ( I | I ) ... -B = [ I ] pivot_root [ oldroot=I ] [ I ] [ -E I ] +B = [ I ] pivot_root [ oldroot=I ] [ I ] [ -E I ] B = I @@ -209,7 +225,7 @@ B 'attr' '=' ( I | '(' '"' I '"' | I ')' ) B 'opt' '=' ( I | '(' '"' I '"' | I ')' ) -B = 'set' 'rlimit' [I 'E=' I ] ',' +B = 'set' 'rlimit' [I 'E=' I ] B = ( 'cpu' | 'fsize' | 'data' | 'stack' | 'core' | 'rss' | 'nofile' | 'ofile' | 'as' | 'nproc' | 'memlock' | 'locks' | 'sigpending' | 'msgqueue' | 'nice' | 'rtprio' | 'rttime' ) @@ -221,7 +237,7 @@ B = number from 0 to max rlimit value. Only applies ot RLIMIT of B = a number between -20 and 19. Only applies to RLIMIT of 'nice' -B = [ I ] [ 'owner' ] ( 'file' | [ 'file' ] ( I I | I I ) [ -E ] ) ',' +B = [ I ] [ 'owner' ] ( 'file' | [ 'file' ] ( I I | I I ) [ -E ] ) B = ( I | I ) @@ -235,19 +251,19 @@ B = ( 'ix' | 'ux' | 'Ux' | 'px' | 'Px' | 'cx' | 'Cx' | 'pix' | B = name (requires I specified) -B = I [ 'owner' ] 'link' [ 'subset' ] ( 'to' | '-E' ) ',' +B = I [ 'owner' ] 'link' [ 'subset' ] ( 'to' | '-E' ) B = '@{' I [ ( I | '_' ) ... ] '}' B = I ('=' | '+=') (space separated values) -B = I '-E' I ',' +B = I '-E' I B = ('a', 'b', 'c', ... 'z', 'A', 'B', ... 'Z') B = ('0', '1', '2', ... '9', 'a', 'b', 'c', ... 'z', 'A', 'B', ... 'Z') -B = 'change_profile' [ I ] [ -E I ] +B = 'change_profile' [ I ] [ -E I ] B = I From 04dfc5d975b770c69b791ce5984b7e6cf173dc7c Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2015 01:00:29 -0700 Subject: [PATCH 035/143] Add missing I to B pattern Signed-off-by: John Johansen Acked-by: Christian Boltz --- parser/apparmor.d.pod | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/parser/apparmor.d.pod b/parser/apparmor.d.pod index a60b51d5e..3d3e83af5 100644 --- a/parser/apparmor.d.pod +++ b/parser/apparmor.d.pod @@ -78,7 +78,7 @@ B = [ ( I | I ',' | I ) B = ( I | I ) [ '\r' ] '\n' -B = ( I | I | I | I | I | I | I | I | I | I ) +B = ( I | I | I | I | I | I | I | I | I | I | I ) B = ( I | I ) From 4afcf91162da4995b6022a0a6c8c623279138994 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2015 01:00:29 -0700 Subject: [PATCH 036/143] Add documentation of qualifier blocks to apparmor.d man page Signed-off-by: John Johansen Acked-by: Christian Boltz --- parser/apparmor.d.pod | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/parser/apparmor.d.pod b/parser/apparmor.d.pod index 3d3e83af5..2040be40a 100644 --- a/parser/apparmor.d.pod +++ b/parser/apparmor.d.pod @@ -80,7 +80,7 @@ B = ( I | I ) [ '\r' ] '\n' B = ( I | I | I | I | I | I | I | I | I | I | I ) -B = ( I | I ) +B = ( I | I | I ) B = 'profile' I [ I ] [ ] '{' ( I )* '}' @@ -88,6 +88,8 @@ B = ('hat' | '^') I [ ] '{' ( I )* '}' B = ( must start with alphanumeric character. see aa_change_hat(2) for a description of how this "hat" is used. IF '^' is used to start a hat then there is no space between the '^' and I) +B = I I + B = ( 'allow' | 'deny' ) B = [ 'audit' ] [ I ] @@ -1325,6 +1327,12 @@ Rule qualifiers can modify the rule and/or permissions within the rule. =over 4 +=item B + +Specifies that permissions requests that match the rule are allowed. This +is the default value for rules and does not need to be specified. Conflicts +with the I qualifier. + =item B Specifies that permissions requests that match the rule should be recorded @@ -1333,13 +1341,24 @@ to the audit log. =item B Specifies that permissions requests that match the rule should be denied -without logging. Can be combined with 'audit' to enable logging. +without logging. Can be combined with 'audit' to enable logging. Conflicts +with the I qualifier. =item B Specifies that the task must have the same euid/fsuid as the object being referenced by the permission check. +=head3 Qualifier Blocks + +Rule Qualifiers can be applied to multiple rules at a time by grouping the +rules into a rule block. + + audit { + /foo r, + network, + } + =back =head2 #include mechanism From 7cc75c44fa1310852b17c7038cc6fa9a52949d6a Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2015 01:00:29 -0700 Subject: [PATCH 037/143] Add log parser test for change_profile Signed-off-by: John Johansen Acked-by: Tyler Hicks --- .../test_multi/testcase_changeprofile_01.err | 0 .../testsuite/test_multi/testcase_changeprofile_01.in | 1 + .../test_multi/testcase_changeprofile_01.out | 11 +++++++++++ 3 files changed, 12 insertions(+) create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.out diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.err b/libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.in b/libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.in new file mode 100644 index 000000000..9337c8832 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.in @@ -0,0 +1 @@ +Sep 9 12:51:36 ubuntu-desktop kernel: [ 97.492562] audit: type=1400 audit(1431116353.523:77): apparmor="DENIED" operation="change_profile" profile="/tests/regression/apparmor/changeprofile" pid=3459 comm="changeprofile" target="/tests/regression/apparmor/rename" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.out b/libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.out new file mode 100644 index 000000000..74b4f714f --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_changeprofile_01.out @@ -0,0 +1,11 @@ +START +File: testcase_changeprofile_01.in +Event type: AA_RECORD_DENIED +Audit ID: 1431116353.523:77 +Operation: change_profile +Profile: /tests/regression/apparmor/changeprofile +Command: changeprofile +Name2: /tests/regression/apparmor/rename +PID: 3459 +Epoch: 1431116353 +Audit subid: 77 From 2d31e2c113d0a01512c5a8be04d40daf8caae90d Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2015 01:00:29 -0700 Subject: [PATCH 038/143] add ability to parser dmesg output as a log file Signed-off-by: John Johansen Acked-by: Steve Beattie --- libraries/libapparmor/src/grammar.y | 5 +++++ libraries/libapparmor/src/scanner.l | 1 + .../test_multi/testcase_dmesg_capability.err | 0 .../test_multi/testcase_dmesg_capability.in | 1 + .../test_multi/testcase_dmesg_capability.out | 12 ++++++++++++ .../testcase_dmesg_changehat_negative_error.err | 0 .../testcase_dmesg_changehat_negative_error.in | 1 + .../testcase_dmesg_changehat_negative_error.out | 11 +++++++++++ .../testcase_dmesg_changeprofile_01.err | 0 .../testcase_dmesg_changeprofile_01.in | 1 + .../testcase_dmesg_changeprofile_01.out | 11 +++++++++++ .../test_multi/testcase_dmesg_link_01.err | 0 .../test_multi/testcase_dmesg_link_01.in | 1 + .../test_multi/testcase_dmesg_link_01.out | 17 +++++++++++++++++ .../test_multi/testcase_dmesg_mkdir.err | 0 .../test_multi/testcase_dmesg_mkdir.in | 1 + .../test_multi/testcase_dmesg_mkdir.out | 15 +++++++++++++++ .../test_multi/testcase_dmesg_rename_dest.err | 0 .../test_multi/testcase_dmesg_rename_dest.in | 1 + .../test_multi/testcase_dmesg_rename_dest.out | 15 +++++++++++++++ .../test_multi/testcase_dmesg_rename_src.err | 0 .../test_multi/testcase_dmesg_rename_src.in | 1 + .../test_multi/testcase_dmesg_rename_src.out | 15 +++++++++++++++ .../test_multi/testcase_dmesg_status_offset.err | 0 .../test_multi/testcase_dmesg_status_offset.in | 1 + .../test_multi/testcase_dmesg_status_offset.out | 11 +++++++++++ .../test_multi/testcase_dmesg_truncate.err | 0 .../test_multi/testcase_dmesg_truncate.in | 1 + .../test_multi/testcase_dmesg_truncate.out | 15 +++++++++++++++ 29 files changed, 137 insertions(+) create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.out create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.err create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.in create mode 100644 libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.out diff --git a/libraries/libapparmor/src/grammar.y b/libraries/libapparmor/src/grammar.y index 56d43285a..108e54dac 100644 --- a/libraries/libapparmor/src/grammar.y +++ b/libraries/libapparmor/src/grammar.y @@ -169,6 +169,7 @@ aa_record_event_type lookup_aa_event(unsigned int type) %% log_message: audit_type + | dmesg_type | syslog_type | audit_dispatch ; @@ -199,6 +200,10 @@ other_audit: TOK_TYPE_OTHER audit_msg TOK_MSG_REST } ; +dmesg_type: TOK_DMESG_STAMP TOK_AUDIT TOK_COLON key_type audit_id key_list + { ret_record->version = AA_RECORD_SYNTAX_V2; } + ; + syslog_type: syslog_date TOK_ID TOK_SYSLOG_KERNEL audit_id key_list { ret_record->version = AA_RECORD_SYNTAX_V2; free($2); } diff --git a/libraries/libapparmor/src/scanner.l b/libraries/libapparmor/src/scanner.l index b5b179413..c78f198ce 100644 --- a/libraries/libapparmor/src/scanner.l +++ b/libraries/libapparmor/src/scanner.l @@ -355,6 +355,7 @@ yy_flex_debug = 0; {syslog_time} { yylval->t_str = strdup(yytext); BEGIN(hostname); return(TOK_TIME); } {audit} { yy_push_state(audit_id, yyscanner); return(TOK_AUDIT); } +{dmesg_timestamp} { yylval->t_str = strdup(yytext); return(TOK_DMESG_STAMP); } . { /* ignore any non-matched input */ BEGIN(unknown_message); yyless(0); } diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.in new file mode 100644 index 000000000..7cd948de8 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.in @@ -0,0 +1 @@ +[ 1612.746129] audit: type=1400 audit(1284061910.975:672): apparmor="DENIED" operation="capable" parent=2663 profile="/home/ubuntu/bzr/apparmor/tests/regression/apparmor/syscall_setpriority" pid=7292 comm="syscall_setprio" capability=23 capname="sys_nice" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.out new file mode 100644 index 000000000..612308c63 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_capability.out @@ -0,0 +1,12 @@ +START +File: testcase_dmesg_capability.in +Event type: AA_RECORD_DENIED +Audit ID: 1284061910.975:672 +Operation: capable +Profile: /home/ubuntu/bzr/apparmor/tests/regression/apparmor/syscall_setpriority +Name: sys_nice +Command: syscall_setprio +Parent: 2663 +PID: 7292 +Epoch: 1284061910 +Audit subid: 672 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.in new file mode 100644 index 000000000..592778855 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.in @@ -0,0 +1 @@ +[ 1597.774866] audit: type=1400 audit(1284061896.005:28): apparmor="DENIED" operation="change_hat" info="unconfined" error=-1 pid=2698 comm="syscall_ptrace" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.out new file mode 100644 index 000000000..64cd6252d --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changehat_negative_error.out @@ -0,0 +1,11 @@ +START +File: testcase_dmesg_changehat_negative_error.in +Event type: AA_RECORD_DENIED +Audit ID: 1284061896.005:28 +Operation: change_hat +Command: syscall_ptrace +Info: unconfined +ErrorCode: 1 +PID: 2698 +Epoch: 1284061896 +Audit subid: 28 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.in new file mode 100644 index 000000000..089d75634 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.in @@ -0,0 +1 @@ +[ 97.492562] audit: type=1400 audit(1431116353.523:77): apparmor="DENIED" operation="change_profile" profile="/tests/regression/apparmor/changeprofile" pid=3459 comm="changeprofile" target="/tests/regression/apparmor/rename" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.out new file mode 100644 index 000000000..32ebb3c57 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_changeprofile_01.out @@ -0,0 +1,11 @@ +START +File: testcase_dmesg_changeprofile_01.in +Event type: AA_RECORD_DENIED +Audit ID: 1431116353.523:77 +Operation: change_profile +Profile: /tests/regression/apparmor/changeprofile +Command: changeprofile +Name2: /tests/regression/apparmor/rename +PID: 3459 +Epoch: 1431116353 +Audit subid: 77 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.in new file mode 100644 index 000000000..fba0c31fb --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.in @@ -0,0 +1 @@ +[ 2010.738449] audit: type=1400 audit(1284062308.965:276251): apparmor="DENIED" operation="link" parent=19088 profile="/home/ubuntu/bzr/apparmor/tests/regression/apparmor/link" name="/tmp/sdtest.19088-12382-HWH57d/linkfile" pid=19142 comm="link" requested_mask="l" denied_mask="l" fsuid=0 ouid=0 target="/tmp/sdtest.19088-12382-HWH57d/target" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.out new file mode 100644 index 000000000..c1b335bc4 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_link_01.out @@ -0,0 +1,17 @@ +START +File: testcase_dmesg_link_01.in +Event type: AA_RECORD_DENIED +Audit ID: 1284062308.965:276251 +Operation: link +Mask: l +Denied Mask: l +fsuid: 0 +ouid: 0 +Profile: /home/ubuntu/bzr/apparmor/tests/regression/apparmor/link +Name: /tmp/sdtest.19088-12382-HWH57d/linkfile +Command: link +Name2: /tmp/sdtest.19088-12382-HWH57d/target +Parent: 19088 +PID: 19142 +Epoch: 1284062308 +Audit subid: 276251 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.in new file mode 100644 index 000000000..aa0bf19ec --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.in @@ -0,0 +1 @@ +[45334.755142] audit: type=1503 audit(1282671283.411:2199): operation="mkdir" pid=4786 parent=4708 profile="/usr/sbin/sshd//ubuntu" requested_mask="c::" denied_mask="c::" fsuid=1000 ouid=1000 name="/tmp/ssh-gRozJw4786/" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.out new file mode 100644 index 000000000..4e362d8a2 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_mkdir.out @@ -0,0 +1,15 @@ +START +File: testcase_dmesg_mkdir.in +Event type: AA_RECORD_DENIED +Audit ID: 1282671283.411:2199 +Operation: mkdir +Mask: c:: +Denied Mask: c:: +fsuid: 1000 +ouid: 1000 +Profile: /usr/sbin/sshd//ubuntu +Name: /tmp/ssh-gRozJw4786/ +Parent: 4708 +PID: 4786 +Epoch: 1282671283 +Audit subid: 2199 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.in new file mode 100644 index 000000000..2c5d6c858 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.in @@ -0,0 +1 @@ +[ 878.663418] audit: type=1502 audit(1282626827.320:413): operation="rename_dest" pid=1881 parent=650 profile="/usr/sbin/sshd" requested_mask="wc::" denied_mask="wc::" fsuid=0 ouid=0 name="/var/run/motd" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.out new file mode 100644 index 000000000..90364234c --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_dest.out @@ -0,0 +1,15 @@ +START +File: testcase_dmesg_rename_dest.in +Event type: AA_RECORD_ALLOWED +Audit ID: 1282626827.320:413 +Operation: rename_dest +Mask: wc:: +Denied Mask: wc:: +fsuid: 0 +ouid: 0 +Profile: /usr/sbin/sshd +Name: /var/run/motd +Parent: 650 +PID: 1881 +Epoch: 1282626827 +Audit subid: 413 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.in new file mode 100644 index 000000000..135531b48 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.in @@ -0,0 +1 @@ +[ 878.663410] audit: type=1502 audit(1282626827.320:412): operation="rename_src" pid=1881 parent=650 profile="/usr/sbin/sshd" requested_mask="r::" denied_mask="r::" fsuid=0 ouid=0 name="/var/run/motd.new" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.out new file mode 100644 index 000000000..6c89300fb --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_rename_src.out @@ -0,0 +1,15 @@ +START +File: testcase_dmesg_rename_src.in +Event type: AA_RECORD_ALLOWED +Audit ID: 1282626827.320:412 +Operation: rename_src +Mask: r:: +Denied Mask: r:: +fsuid: 0 +ouid: 0 +Profile: /usr/sbin/sshd +Name: /var/run/motd.new +Parent: 650 +PID: 1881 +Epoch: 1282626827 +Audit subid: 412 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.in new file mode 100644 index 000000000..5b4dd12ab --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.in @@ -0,0 +1 @@ +[ 2143.902340] audit: type=1400 audit(1283989336.064:272335): apparmor="STATUS" info="failed to unpack profile" error=-71 pid=4958 comm="apparmor_parser" name="/home/jj/master/tests/regression/apparmor/net_raw" offset=159 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.out new file mode 100644 index 000000000..b12d58cac --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_status_offset.out @@ -0,0 +1,11 @@ +START +File: testcase_dmesg_status_offset.in +Event type: AA_RECORD_STATUS +Audit ID: 1283989336.064:272335 +Name: /home/jj/master/tests/regression/apparmor/net_raw +Command: apparmor_parser +Info: failed to unpack profile +ErrorCode: 71 +PID: 4958 +Epoch: 1283989336 +Audit subid: 272335 diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.err b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.err new file mode 100644 index 000000000..e69de29bb diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.in b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.in new file mode 100644 index 000000000..86b2770e7 --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.in @@ -0,0 +1 @@ +[ 878.662172] audit: type=1503 audit(1282626827.320:411): operation="truncate" pid=1957 parent=1 profile="/etc/update-motd.d/91-release-upgrade" requested_mask="w::" denied_mask="w::" fsuid=0 ouid=0 name="/var/lib/update-notifier/release-upgrade-available" diff --git a/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.out b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.out new file mode 100644 index 000000000..fbc1bb42f --- /dev/null +++ b/libraries/libapparmor/testsuite/test_multi/testcase_dmesg_truncate.out @@ -0,0 +1,15 @@ +START +File: testcase_dmesg_truncate.in +Event type: AA_RECORD_DENIED +Audit ID: 1282626827.320:411 +Operation: truncate +Mask: w:: +Denied Mask: w:: +fsuid: 0 +ouid: 0 +Profile: /etc/update-motd.d/91-release-upgrade +Name: /var/lib/update-notifier/release-upgrade-available +Parent: 1 +PID: 1957 +Epoch: 1282626827 +Audit subid: 411 From 119c7519515f1207cb6569369dd37d0512d7bd16 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2015 01:00:29 -0700 Subject: [PATCH 039/143] The regression tests have issue on backport kernels when the userspace has not been updated. The issue is that the regression tests detect the kernel features set and generate policy that the parser may not be able to compile. Augment the regressions tests with a couple simple functions to test what is supported by the parser, and update the test conditionals to use them. Signed-off-by: John Johansen Acked-by: Seth Arnold --- tests/regression/apparmor/dbus_eavesdrop.sh | 1 + tests/regression/apparmor/dbus_message.sh | 1 + tests/regression/apparmor/dbus_service.sh | 1 + .../apparmor/dbus_unrequested_reply.sh | 1 + tests/regression/apparmor/deleted.sh | 2 +- tests/regression/apparmor/mount.sh | 2 +- tests/regression/apparmor/named_pipe.sh | 2 +- tests/regression/apparmor/pivot_root.sh | 4 ++-- tests/regression/apparmor/prologue.inc | 24 +++++++++++++++++++ tests/regression/apparmor/ptrace.sh | 2 +- tests/regression/apparmor/socketpair.sh | 2 +- tests/regression/apparmor/unix_fd_server.sh | 4 ++-- .../apparmor/unix_socket_abstract.sh | 1 + .../apparmor/unix_socket_pathname.sh | 2 +- .../apparmor/unix_socket_unnamed.sh | 1 + 15 files changed, 40 insertions(+), 10 deletions(-) diff --git a/tests/regression/apparmor/dbus_eavesdrop.sh b/tests/regression/apparmor/dbus_eavesdrop.sh index fe26b9114..279290044 100755 --- a/tests/regression/apparmor/dbus_eavesdrop.sh +++ b/tests/regression/apparmor/dbus_eavesdrop.sh @@ -19,6 +19,7 @@ bin=$pwd . $bin/prologue.inc requires_features dbus +requires_parser_support "dbus," . $bin/dbus.inc args="--session" diff --git a/tests/regression/apparmor/dbus_message.sh b/tests/regression/apparmor/dbus_message.sh index 30b159245..cc52745e0 100755 --- a/tests/regression/apparmor/dbus_message.sh +++ b/tests/regression/apparmor/dbus_message.sh @@ -19,6 +19,7 @@ bin=$pwd . $bin/prologue.inc requires_features dbus +requires_parser_support "dbus," . $bin/dbus.inc listnames="--type=method_call --session --name=org.freedesktop.DBus /org/freedesktop/DBus org.freedesktop.DBus.ListNames" diff --git a/tests/regression/apparmor/dbus_service.sh b/tests/regression/apparmor/dbus_service.sh index 451a6612a..322853b82 100755 --- a/tests/regression/apparmor/dbus_service.sh +++ b/tests/regression/apparmor/dbus_service.sh @@ -18,6 +18,7 @@ bin=$pwd . $bin/prologue.inc requires_features dbus +requires_parser_support "dbus," . $bin/dbus.inc service="--$bus --name=$dest $path $iface" diff --git a/tests/regression/apparmor/dbus_unrequested_reply.sh b/tests/regression/apparmor/dbus_unrequested_reply.sh index 1cfd8d403..e91f3ad99 100644 --- a/tests/regression/apparmor/dbus_unrequested_reply.sh +++ b/tests/regression/apparmor/dbus_unrequested_reply.sh @@ -18,6 +18,7 @@ bin=$pwd . $bin/prologue.inc requires_features dbus +requires_parser_support "dbus," . $bin/dbus.inc service="--$bus --name=$dest $path $iface" diff --git a/tests/regression/apparmor/deleted.sh b/tests/regression/apparmor/deleted.sh index 84a51fc40..8d4c5b458 100755 --- a/tests/regression/apparmor/deleted.sh +++ b/tests/regression/apparmor/deleted.sh @@ -65,7 +65,7 @@ okperm=rwl badperm=wl af_unix="" -if [ "$(have_features network/af_unix)" == "true" ]; then +if [ "$(have_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then af_unix="unix:create" fi diff --git a/tests/regression/apparmor/mount.sh b/tests/regression/apparmor/mount.sh index 86bfecb17..084019975 100755 --- a/tests/regression/apparmor/mount.sh +++ b/tests/regression/apparmor/mount.sh @@ -102,7 +102,7 @@ runchecktest "UMOUNT (confined no perm)" fail umount ${loop_device} ${mount_poin remove_mnt -if [ "$(have_features mount)" != "true" ] ; then +if [ "$(have_features mount)" != "true" -o "$(parser_supports 'mount,')" != "true" ] ; then genprofile capability:sys_admin runchecktest "MOUNT (confined cap)" pass mount ${loop_device} ${mount_point} remove_mnt diff --git a/tests/regression/apparmor/named_pipe.sh b/tests/regression/apparmor/named_pipe.sh index e63456fcc..52037e56a 100755 --- a/tests/regression/apparmor/named_pipe.sh +++ b/tests/regression/apparmor/named_pipe.sh @@ -38,7 +38,7 @@ badchild=r # Add genprofile params that are common to all hats here common="" -if [ "$(have_features signal)" == "true" ] ; then +if [ "$(have_features signal)" == "true" -a "$(parser_supports 'signal,')" == "true" ] ; then # Allow send/receive of all signals common="${common} signal:ALL" fi diff --git a/tests/regression/apparmor/pivot_root.sh b/tests/regression/apparmor/pivot_root.sh index 35004fc9b..faea75569 100755 --- a/tests/regression/apparmor/pivot_root.sh +++ b/tests/regression/apparmor/pivot_root.sh @@ -106,8 +106,8 @@ do_test "unconfined, bad context" fail "$put_old" "$new_root" "$bad" genprofile do_test "no perms" fail "$put_old" "$new_root" "$test" -if [ "$(have_features mount)" != "true" ] ; then - # pivot_root mediation isn't supported by this kernel, so verify that +if [ "$(have_features mount)" != "true" -o "$(parser_supports 'mount,')" != "true" ] ; then + # pivot_root mediation isn't supported by this kernel/parser, so verify that # capability sys_admin is sufficient and skip the remaining tests genprofile $cur $cap do_test "cap" pass "$put_old" "$new_root" "$test" diff --git a/tests/regression/apparmor/prologue.inc b/tests/regression/apparmor/prologue.inc index 3036cbbf5..bcbe7ea7a 100755 --- a/tests/regression/apparmor/prologue.inc +++ b/tests/regression/apparmor/prologue.inc @@ -58,6 +58,30 @@ requires_query_interface() fi } +parser_supports() +{ + for R in $@ ; do + echo "/test { $R }" | $subdomain ${parser_args} -qQT 2>/dev/null 1>/dev/null + if [ $? -ne 0 ] ; then + echo "Compiler does not support rule '$R'" + return 1; + fi + done + + echo "true" + return 0; +} + +requires_parser_support() +{ + local res=$(parser_supports $@) + if [ "$res" != "true" ] ; then + echo "$res. Skipping tests ..." + exit 0 + fi +} + + fatalerror() { # global _fatal diff --git a/tests/regression/apparmor/ptrace.sh b/tests/regression/apparmor/ptrace.sh index 64cdf2412..17771d06c 100755 --- a/tests/regression/apparmor/ptrace.sh +++ b/tests/regression/apparmor/ptrace.sh @@ -52,7 +52,7 @@ runchecktest "test 2 -h prog" pass -h -n 100 $helper /bin/true runchecktest "test 2 -hc prog" pass -h -c -n 100 $helper /bin/true -if [ "$(have_features ptrace)" == "true" ] ; then +if [ "$(have_features ptrace)" == "true" -a "$(parser_supports 'ptrace,')" == "true" ] ; then . $bin/ptrace_v6.inc else . $bin/ptrace_v5.inc diff --git a/tests/regression/apparmor/socketpair.sh b/tests/regression/apparmor/socketpair.sh index 378fc0820..4e5670789 100755 --- a/tests/regression/apparmor/socketpair.sh +++ b/tests/regression/apparmor/socketpair.sh @@ -34,7 +34,7 @@ af_unix_create="" af_unix_create_label="" af_unix_inherit="" -if [ "$(have_features network/af_unix)" == "true" ]; then +if [ "$(have_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then # AppArmor requires that the process inheriting the sock file # descriptors have send,receive perms in its profile af_unix_create="unix:(create,getopt)" diff --git a/tests/regression/apparmor/unix_fd_server.sh b/tests/regression/apparmor/unix_fd_server.sh index b38ec689a..fc2b9473b 100755 --- a/tests/regression/apparmor/unix_fd_server.sh +++ b/tests/regression/apparmor/unix_fd_server.sh @@ -27,7 +27,7 @@ okperm=rw badperm=w af_unix="" -if [ "$(have_features network/af_unix)" == "true" ]; then +if [ "$(have_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then af_unix="unix:create" fi @@ -137,7 +137,7 @@ runchecktest "fd passing; confined -> confined (no perm)" fail $file $socket $fd sleep 1 rm -f ${socket} -if [ "$(have_features policy/versions/v6)" == "true" ] ; then +if [ "$(have_features policy/versions/v6)" == "true" -a "$(parser_supports 'unix,')" == "true" ] ; then # FAIL - confined client, no access to the socket file genprofile $file:$okperm $af_unix $socket:rw $fd_client:px -- image=$fd_client $file:$okperm $af_unix diff --git a/tests/regression/apparmor/unix_socket_abstract.sh b/tests/regression/apparmor/unix_socket_abstract.sh index 7c14f3e67..6a949c1a6 100644 --- a/tests/regression/apparmor/unix_socket_abstract.sh +++ b/tests/regression/apparmor/unix_socket_abstract.sh @@ -30,6 +30,7 @@ bin=$pwd . $bin/unix_socket.inc requires_features policy/versions/v7 requires_features network/af_unix +requires_parser_support "unix," settest unix_socket diff --git a/tests/regression/apparmor/unix_socket_pathname.sh b/tests/regression/apparmor/unix_socket_pathname.sh index 078e557e1..be3631de6 100755 --- a/tests/regression/apparmor/unix_socket_pathname.sh +++ b/tests/regression/apparmor/unix_socket_pathname.sh @@ -52,7 +52,7 @@ fi # af_unix support requires 'unix getattr' to call getsockname() af_unix_okserver= af_unix_okclient= -if [ "$(have_features network/af_unix)" == "true" ] ; then +if [ "$(have_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ] ; then af_unix_okserver="create,setopt" af_unix_okclient="create,getopt,setopt,getattr" fi diff --git a/tests/regression/apparmor/unix_socket_unnamed.sh b/tests/regression/apparmor/unix_socket_unnamed.sh index 3293fece7..b834888c5 100644 --- a/tests/regression/apparmor/unix_socket_unnamed.sh +++ b/tests/regression/apparmor/unix_socket_unnamed.sh @@ -30,6 +30,7 @@ bin=$pwd . $bin/unix_socket.inc requires_features policy/versions/v7 requires_features network/af_unix +requires_parser_support "unix," settest unix_socket From d7436a872c968ad307a55db14d1a93aa7a4b8dd1 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Tue, 2 Jun 2015 01:00:29 -0700 Subject: [PATCH 040/143] Rename requires_features and have_features Rename require_features to require_kernel_features and have_features to kernel_features to indicate they are tests for kernel features, as now there are tests for parser features and in the future there might be library features as well. Signed-off-by: John Johansen Acked-by: Tyler Hicks --- tests/regression/apparmor/capabilities.sh | 8 ++++---- tests/regression/apparmor/dbus_eavesdrop.sh | 2 +- tests/regression/apparmor/dbus_message.sh | 2 +- tests/regression/apparmor/dbus_service.sh | 2 +- tests/regression/apparmor/dbus_unrequested_reply.sh | 2 +- tests/regression/apparmor/deleted.sh | 2 +- tests/regression/apparmor/mount.sh | 2 +- tests/regression/apparmor/named_pipe.sh | 2 +- tests/regression/apparmor/pivot_root.sh | 2 +- tests/regression/apparmor/prologue.inc | 6 +++--- tests/regression/apparmor/ptrace.sh | 2 +- tests/regression/apparmor/socketpair.sh | 2 +- tests/regression/apparmor/tcp.sh | 2 +- tests/regression/apparmor/unix_fd_server.sh | 4 ++-- tests/regression/apparmor/unix_socket_abstract.sh | 4 ++-- tests/regression/apparmor/unix_socket_pathname.sh | 6 +++--- tests/regression/apparmor/unix_socket_unnamed.sh | 4 ++-- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/tests/regression/apparmor/capabilities.sh b/tests/regression/apparmor/capabilities.sh index 1b5044529..74a3c9024 100644 --- a/tests/regression/apparmor/capabilities.sh +++ b/tests/regression/apparmor/capabilities.sh @@ -97,7 +97,7 @@ for TEST in ${TESTS} ; do # no capabilities allowed genprofile ${my_entries} - if [ "${TEST}" == "syscall_ptrace" -a "$(have_features ptrace)" == "true" ] ; then + if [ "${TEST}" == "syscall_ptrace" -a "$(kernel_features ptrace)" == "true" ] ; then # ptrace between profiles confining tasks of same pid is controlled by the ptrace rule # capability + ptrace rule needed between pids runchecktest "${TEST} -- no caps" pass ${my_arg} @@ -113,7 +113,7 @@ for TEST in ${TESTS} ; do for cap in ${CAPABILITIES} ; do if [ "X$(eval echo \${${TEST}_${cap}})" == "XTRUE" ] ; then expected_result=pass - elif [ "${TEST}" == "syscall_ptrace" -a "$(have_features ptrace)" == "true" ]; then + elif [ "${TEST}" == "syscall_ptrace" -a "$(kernel_features ptrace)" == "true" ]; then expected_result=pass else expected_result=fail @@ -126,7 +126,7 @@ for TEST in ${TESTS} ; do # a subprofile. settest ${testwrapper} genprofile hat:$bin/${TEST} addimage:${bin}/${TEST} ${my_entries} - if [ "${TEST}" == "syscall_ptrace" -a "$(have_features ptrace)" == "true" ] ; then + if [ "${TEST}" == "syscall_ptrace" -a "$(kernel_features ptrace)" == "true" ] ; then # ptrace between profiles confining tasks of same pid is controlled by the ptrace rule # capability + ptrace rule needed between pids runchecktest "${TEST} changehat -- no caps" pass $bin/${TEST} ${my_arg} @@ -141,7 +141,7 @@ for TEST in ${TESTS} ; do for cap in ${CAPABILITIES} ; do if [ "X$(eval echo \${${TEST}_${cap}})" == "XTRUE" ] ; then expected_result=pass - elif [ "${TEST}" == "syscall_ptrace" -a "$(have_features ptrace)" == "true" ]; then + elif [ "${TEST}" == "syscall_ptrace" -a "$(kernel_features ptrace)" == "true" ]; then expected_result=pass else expected_result=fail diff --git a/tests/regression/apparmor/dbus_eavesdrop.sh b/tests/regression/apparmor/dbus_eavesdrop.sh index 279290044..a7f21552f 100755 --- a/tests/regression/apparmor/dbus_eavesdrop.sh +++ b/tests/regression/apparmor/dbus_eavesdrop.sh @@ -18,7 +18,7 @@ pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc -requires_features dbus +requires_kernel_features dbus requires_parser_support "dbus," . $bin/dbus.inc diff --git a/tests/regression/apparmor/dbus_message.sh b/tests/regression/apparmor/dbus_message.sh index cc52745e0..27807c429 100755 --- a/tests/regression/apparmor/dbus_message.sh +++ b/tests/regression/apparmor/dbus_message.sh @@ -18,7 +18,7 @@ pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc -requires_features dbus +requires_kernel_features dbus requires_parser_support "dbus," . $bin/dbus.inc diff --git a/tests/regression/apparmor/dbus_service.sh b/tests/regression/apparmor/dbus_service.sh index 322853b82..5cd698a28 100755 --- a/tests/regression/apparmor/dbus_service.sh +++ b/tests/regression/apparmor/dbus_service.sh @@ -17,7 +17,7 @@ pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc -requires_features dbus +requires_kernel_features dbus requires_parser_support "dbus," . $bin/dbus.inc diff --git a/tests/regression/apparmor/dbus_unrequested_reply.sh b/tests/regression/apparmor/dbus_unrequested_reply.sh index e91f3ad99..e69c8b458 100644 --- a/tests/regression/apparmor/dbus_unrequested_reply.sh +++ b/tests/regression/apparmor/dbus_unrequested_reply.sh @@ -17,7 +17,7 @@ pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc -requires_features dbus +requires_kernel_features dbus requires_parser_support "dbus," . $bin/dbus.inc diff --git a/tests/regression/apparmor/deleted.sh b/tests/regression/apparmor/deleted.sh index 8d4c5b458..9ca937f6d 100755 --- a/tests/regression/apparmor/deleted.sh +++ b/tests/regression/apparmor/deleted.sh @@ -65,7 +65,7 @@ okperm=rwl badperm=wl af_unix="" -if [ "$(have_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then +if [ "$(kernel_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then af_unix="unix:create" fi diff --git a/tests/regression/apparmor/mount.sh b/tests/regression/apparmor/mount.sh index 084019975..8dc1a88ed 100755 --- a/tests/regression/apparmor/mount.sh +++ b/tests/regression/apparmor/mount.sh @@ -102,7 +102,7 @@ runchecktest "UMOUNT (confined no perm)" fail umount ${loop_device} ${mount_poin remove_mnt -if [ "$(have_features mount)" != "true" -o "$(parser_supports 'mount,')" != "true" ] ; then +if [ "$(kernel_features mount)" != "true" -o "$(parser_supports 'mount,')" != "true" ] ; then genprofile capability:sys_admin runchecktest "MOUNT (confined cap)" pass mount ${loop_device} ${mount_point} remove_mnt diff --git a/tests/regression/apparmor/named_pipe.sh b/tests/regression/apparmor/named_pipe.sh index 52037e56a..72bc7361f 100755 --- a/tests/regression/apparmor/named_pipe.sh +++ b/tests/regression/apparmor/named_pipe.sh @@ -38,7 +38,7 @@ badchild=r # Add genprofile params that are common to all hats here common="" -if [ "$(have_features signal)" == "true" -a "$(parser_supports 'signal,')" == "true" ] ; then +if [ "$(kernel_features signal)" == "true" -a "$(parser_supports 'signal,')" == "true" ] ; then # Allow send/receive of all signals common="${common} signal:ALL" fi diff --git a/tests/regression/apparmor/pivot_root.sh b/tests/regression/apparmor/pivot_root.sh index faea75569..b68f6cf52 100755 --- a/tests/regression/apparmor/pivot_root.sh +++ b/tests/regression/apparmor/pivot_root.sh @@ -106,7 +106,7 @@ do_test "unconfined, bad context" fail "$put_old" "$new_root" "$bad" genprofile do_test "no perms" fail "$put_old" "$new_root" "$test" -if [ "$(have_features mount)" != "true" -o "$(parser_supports 'mount,')" != "true" ] ; then +if [ "$(kernel_features mount)" != "true" -o "$(parser_supports 'mount,')" != "true" ] ; then # pivot_root mediation isn't supported by this kernel/parser, so verify that # capability sys_admin is sufficient and skip the remaining tests genprofile $cur $cap diff --git a/tests/regression/apparmor/prologue.inc b/tests/regression/apparmor/prologue.inc index bcbe7ea7a..f6707ab6e 100755 --- a/tests/regression/apparmor/prologue.inc +++ b/tests/regression/apparmor/prologue.inc @@ -22,7 +22,7 @@ # For this file, functions are first, entry point code is at end, see "MAIN" #use $() to retreive the failure message or "true" if success -have_features() +kernel_features() { if [ ! -e "/sys/kernel/security/apparmor/features/" ] ; then echo "Kernel feature masks not supported." @@ -40,9 +40,9 @@ have_features() return 0; } -requires_features() +requires_kernel_features() { - local res=$(have_features $@) + local res=$(kernel_features $@) if [ "$res" != "true" ] ; then echo "$res. Skipping tests ..." exit 0 diff --git a/tests/regression/apparmor/ptrace.sh b/tests/regression/apparmor/ptrace.sh index 17771d06c..c33634795 100755 --- a/tests/regression/apparmor/ptrace.sh +++ b/tests/regression/apparmor/ptrace.sh @@ -52,7 +52,7 @@ runchecktest "test 2 -h prog" pass -h -n 100 $helper /bin/true runchecktest "test 2 -hc prog" pass -h -c -n 100 $helper /bin/true -if [ "$(have_features ptrace)" == "true" -a "$(parser_supports 'ptrace,')" == "true" ] ; then +if [ "$(kernel_features ptrace)" == "true" -a "$(parser_supports 'ptrace,')" == "true" ] ; then . $bin/ptrace_v6.inc else . $bin/ptrace_v5.inc diff --git a/tests/regression/apparmor/socketpair.sh b/tests/regression/apparmor/socketpair.sh index 4e5670789..423a51d07 100755 --- a/tests/regression/apparmor/socketpair.sh +++ b/tests/regression/apparmor/socketpair.sh @@ -34,7 +34,7 @@ af_unix_create="" af_unix_create_label="" af_unix_inherit="" -if [ "$(have_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then +if [ "$(kernel_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then # AppArmor requires that the process inheriting the sock file # descriptors have send,receive perms in its profile af_unix_create="unix:(create,getopt)" diff --git a/tests/regression/apparmor/tcp.sh b/tests/regression/apparmor/tcp.sh index 73eff1b27..076ca00e7 100755 --- a/tests/regression/apparmor/tcp.sh +++ b/tests/regression/apparmor/tcp.sh @@ -15,7 +15,7 @@ pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc -requires_features network +requires_kernel_features network port=34567 ip="127.0.0.1" diff --git a/tests/regression/apparmor/unix_fd_server.sh b/tests/regression/apparmor/unix_fd_server.sh index fc2b9473b..0bba807e9 100755 --- a/tests/regression/apparmor/unix_fd_server.sh +++ b/tests/regression/apparmor/unix_fd_server.sh @@ -27,7 +27,7 @@ okperm=rw badperm=w af_unix="" -if [ "$(have_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then +if [ "$(kernel_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ]; then af_unix="unix:create" fi @@ -137,7 +137,7 @@ runchecktest "fd passing; confined -> confined (no perm)" fail $file $socket $fd sleep 1 rm -f ${socket} -if [ "$(have_features policy/versions/v6)" == "true" -a "$(parser_supports 'unix,')" == "true" ] ; then +if [ "$(kernel_features policy/versions/v6)" == "true" -a "$(parser_supports 'unix,')" == "true" ] ; then # FAIL - confined client, no access to the socket file genprofile $file:$okperm $af_unix $socket:rw $fd_client:px -- image=$fd_client $file:$okperm $af_unix diff --git a/tests/regression/apparmor/unix_socket_abstract.sh b/tests/regression/apparmor/unix_socket_abstract.sh index 6a949c1a6..21c35e263 100644 --- a/tests/regression/apparmor/unix_socket_abstract.sh +++ b/tests/regression/apparmor/unix_socket_abstract.sh @@ -28,8 +28,8 @@ bin=$pwd . $bin/prologue.inc . $bin/unix_socket.inc -requires_features policy/versions/v7 -requires_features network/af_unix +requires_kernel_features policy/versions/v7 +requires_kernel_features network/af_unix requires_parser_support "unix," settest unix_socket diff --git a/tests/regression/apparmor/unix_socket_pathname.sh b/tests/regression/apparmor/unix_socket_pathname.sh index be3631de6..c14ac9c99 100755 --- a/tests/regression/apparmor/unix_socket_pathname.sh +++ b/tests/regression/apparmor/unix_socket_pathname.sh @@ -27,7 +27,7 @@ pwd=`cd $pwd ; /bin/pwd` bin=$pwd . $bin/prologue.inc -requires_features policy/versions/v6 +requires_kernel_features policy/versions/v6 settest unix_socket @@ -41,7 +41,7 @@ message=4a0c83d87aaa7afa2baab5df3ee4df630f0046d5bfb7a3080c550b721f401b3b\ okserver=w badserver1=r badserver2= -if [ "$(have_features policy/versions/v7)" == "true" ] ; then +if [ "$(kernel_features policy/versions/v7)" == "true" ] ; then okserver=rw badserver2=w fi @@ -52,7 +52,7 @@ fi # af_unix support requires 'unix getattr' to call getsockname() af_unix_okserver= af_unix_okclient= -if [ "$(have_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ] ; then +if [ "$(kernel_features network/af_unix)" == "true" -a "$(parser_supports 'unix,')" == "true" ] ; then af_unix_okserver="create,setopt" af_unix_okclient="create,getopt,setopt,getattr" fi diff --git a/tests/regression/apparmor/unix_socket_unnamed.sh b/tests/regression/apparmor/unix_socket_unnamed.sh index b834888c5..66bea0a5c 100644 --- a/tests/regression/apparmor/unix_socket_unnamed.sh +++ b/tests/regression/apparmor/unix_socket_unnamed.sh @@ -28,8 +28,8 @@ bin=$pwd . $bin/prologue.inc . $bin/unix_socket.inc -requires_features policy/versions/v7 -requires_features network/af_unix +requires_kernel_features policy/versions/v7 +requires_kernel_features network/af_unix requires_parser_support "unix," settest unix_socket From 23a2d8b68c8c3d03f436ab7ca5dc78fb490cf969 Mon Sep 17 00:00:00 2001 From: Steve Beattie Date: Tue, 2 Jun 2015 16:05:37 -0700 Subject: [PATCH 041/143] This patch fixes several formatting issues with the apparmor.d man page: - missing formatting code prefixes, usually I for BNFish arguments - added blank lines before preformatted sections as the html formatter wasn't treating them as seperate from the preceding text (also, they generated podchecker warnings) - fixed a grammar issue - fixed link description text block that was mistakenly indented and thus treated as preformatted text - moved the "Qualifier Blocks" subsection out of the =over/=back as all the pod tools did not like this and it caused podchecker to exit with an error, breaking builds that ran make check on the parser tree. Signed-off-by: Steve Beattie Acked-by: Seth Arnold --- parser/apparmor.d.pod | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/parser/apparmor.d.pod b/parser/apparmor.d.pod index 2040be40a..9ed59610a 100644 --- a/parser/apparmor.d.pod +++ b/parser/apparmor.d.pod @@ -46,7 +46,7 @@ to the policy; this behaviour is modelled after cpp(1). B = ( [ I ] [ I ] )* -B = ( I | I | I )* (variable assignment must come before the profile) +B = ( I | I | I )* (variable assignment must come before the profile) B = '#include' ( I | I ) @@ -58,7 +58,7 @@ B = '#' I [ '\r' ] '\n' B = any characters -B = ( I ) [ I ] [ ] '{' ( I )* '}' +B = ( I ) [ I ] [ I ] '{' ( I )* '}' B = [ 'profile' ] I | 'profile' I @@ -82,9 +82,9 @@ B = ( I | I | I | I = ( I | I | I ) -B = 'profile' I [ I ] [ ] '{' ( I )* '}' +B = 'profile' I [ I ] [ I ] '{' ( I )* '}' -B = ('hat' | '^') I [ ] '{' ( I )* '}' +B = ('hat' | '^') I [ I ] '{' ( I )* '}' B = ( must start with alphanumeric character. see aa_change_hat(2) for a description of how this "hat" is used. IF '^' is used to start a hat then there is no space between the '^' and I) @@ -231,7 +231,7 @@ B = 'set' 'rlimit' [I 'E=' I ] B = ( 'cpu' | 'fsize' | 'data' | 'stack' | 'core' | 'rss' | 'nofile' | 'ofile' | 'as' | 'nproc' | 'memlock' | 'locks' | 'sigpending' | 'msgqueue' | 'nice' | 'rtprio' | 'rttime' ) -B = ( I | I | I ) +B = ( I | I | I ) B = I ( 'K' | 'M' | 'G' ) Only applies to RLIMIT of 'fsize', 'data', 'stack', 'core', 'rss', 'as', 'memlock', 'msgqueue'. @@ -239,7 +239,7 @@ B = number from 0 to max rlimit value. Only applies ot RLIMIT of B = a number between -20 and 19. Only applies to RLIMIT of 'nice' -B = [ I ] [ 'owner' ] ( 'file' | [ 'file' ] ( I I | I I ) [ -E ] ) +B = [ I ] [ 'owner' ] ( 'file' | [ 'file' ] ( I I | I I ) [ -E I ] ) B = ( I | I ) @@ -253,7 +253,7 @@ B = ( 'ix' | 'ux' | 'Ux' | 'px' | 'Px' | 'cx' | 'Cx' | 'pix' | B = name (requires I specified) -B = I [ 'owner' ] 'link' [ 'subset' ] ( 'to' | '-E' ) +B = I [ 'owner' ] 'link' [ 'subset' ] I ( 'to' | '-E' ) I B = '@{' I [ ( I | '_' ) ... ] '}' @@ -532,7 +532,7 @@ determine the profile to transition to from the executable name. It is however possible to specify the name of the profile that the transition should use. -The name of the profile to transition to is specified using the '->' +The name of the profile to transition to is specified using the '-E' followed by the name of the profile to transition to. Eg. /bin/** px -> profile, @@ -572,8 +572,9 @@ or trailing the file glob. Eg. /** rw, # trailing permissions -When a leading permissions is used further rule options and context +When leading permissions are used further rule options and context may be allowed, Eg. + l /foo -> /bar, # lead 'l' link permission is equivalent to link rules =back @@ -593,25 +594,27 @@ Eg. /link* rw, link subset /link* -> /**, - The link rule allows linking of /link to both /file1 or /file2 by - name however because the /link file has 'rw' permissions it is not - allowed to link to /file1 because that would grant an access path - to /file1 with more permissions than the 'r' permissions the profile - specifies. +The link rule allows linking of /link to both /file1 or /file2 by +name however because the /link file has 'rw' permissions it is not +allowed to link to /file1 because that would grant an access path +to /file1 with more permissions than the 'r' permissions the profile +specifies. - A link of /link to /file2 would be allowed because the 'rw' permissions - of /link are a subset of the 'rwk' permissions for /file1. +A link of /link to /file2 would be allowed because the 'rw' permissions +of /link are a subset of the 'rwk' permissions for /file1. The link rule is equivalent to specifying the 'l' link permission as a leading permission with no other file access permissions. When this is done the link rule options can be specified. The following link rule is equivalent to the 'l' permission file rule + link /foo -> bar, l /foo -> /bar, File rules that specify the 'l' permission and don't specify the extend link permissions map to link rules as follows. + /foo l, l /foo, link subset /foo -> /**, @@ -1349,6 +1352,8 @@ with the I qualifier. Specifies that the task must have the same euid/fsuid as the object being referenced by the permission check. +=back + =head3 Qualifier Blocks Rule Qualifiers can be applied to multiple rules at a time by grouping the @@ -1359,8 +1364,6 @@ rules into a rule block. network, } -=back - =head2 #include mechanism AppArmor provides an easy abstraction mechanism to group common file From 69868cda18fec64d13e309cf3781eb348aa90065 Mon Sep 17 00:00:00 2001 From: John Johansen Date: Thu, 4 Jun 2015 02:59:32 -0700 Subject: [PATCH 042/143] add man page for aa_query_label Signed-off-by: John Johansen Acked-by: Tyler Hicks --- libraries/libapparmor/doc/Makefile.am | 2 +- libraries/libapparmor/doc/aa_query_label.pod | 107 +++++++++++++++++++ 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 libraries/libapparmor/doc/aa_query_label.pod diff --git a/libraries/libapparmor/doc/Makefile.am b/libraries/libapparmor/doc/Makefile.am index b102137a9..2642517fa 100644 --- a/libraries/libapparmor/doc/Makefile.am +++ b/libraries/libapparmor/doc/Makefile.am @@ -5,7 +5,7 @@ PODCHECKER = podchecker if ENABLE_MAN_PAGES -man_MANS = aa_change_hat.2 aa_change_profile.2 aa_getcon.2 aa_find_mountpoint.2 aa_splitcon.3 +man_MANS = aa_change_hat.2 aa_change_profile.2 aa_getcon.2 aa_find_mountpoint.2 aa_splitcon.3 aa_query_label.2 PODS = $(subst .2,.pod,$(man_MANS)) $(subst .3,.pod,$(man_MANS)) diff --git a/libraries/libapparmor/doc/aa_query_label.pod b/libraries/libapparmor/doc/aa_query_label.pod new file mode 100644 index 000000000..3bba71b02 --- /dev/null +++ b/libraries/libapparmor/doc/aa_query_label.pod @@ -0,0 +1,107 @@ +# This publication is intellectual property of Canonical Ltd. Its contents +# can be duplicated, either in part or in whole, provided that a copyright +# label is visibly located on each copy. +# +# All information found in this book has been compiled with utmost +# attention to detail. However, this does not guarantee complete accuracy. +# Neither Canonical Ltd, the authors, nor the translators shall be held +# liable for possible errors or the consequences thereof. +# +# Many of the software and hardware descriptions cited in this book +# are registered trademarks. All trade names are subject to copyright +# restrictions and may be registered trade marks. Canonical Ltd. +# essentially adhere to the manufacturer's spelling. +# +# Names of products and trademarks appearing in this book (with or without +# specific notation) are likewise subject to trademark and trade protection +# laws and may thus fall under copyright restrictions. +# + + +=pod + +=head1 NAME + +aa_query_label - query access permission associated with a label + +=head1 SYNOPSIS + +B<#include Esys/apparmor.hE> + +B + +Link with B<-lapparmor> when compiling. + +=head1 DESCRIPTION + +The aa_query_label function fetches the current permissions granted by the +specified I