2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-05 00:35:13 +00:00

Compare commits

...

33 Commits

Author SHA1 Message Date
John Johansen
50aa7293dd Release: Bump revisions in preparation for 2.12.2 release
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-21 03:52:51 -08:00
Christian Boltz
79940b8e18 revert naming the dnsmasq profile
Changing to "profile dnsmasq /..." broke the peer=/usr/sbin/dnsmasq in
the libvirtd profile. Revert adding the name to avoid breaking the
libvirtd profile in stable branches.

See also https://bugzilla.opensuse.org/show_bug.cgi?id=1118952
which is a request to update the libvirtd profile to allow both
peer=dnsmasq and peer=/usr/sbin/dnsmasq

BugLink: https://bugzilla.opensuse.org/show_bug.cgi?id=1118952
PR: https://gitlab.com/apparmor/apparmor/merge_requests/290
(cherry picked from commit a68e6426f4)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-15 21:21:02 -08:00
Christian Boltz
e6d2ada55c dovecot: allow reading /proc/sys/fs/suid_dumpable
This is needed if a dovecot child process segfaults - in this case,
dovecot provides a helpful error message like

dovecot[6179]: auth-worker: Fatal: master: service(auth-worker): child 8103 killed with signal 11 (core not dumped - https://dovecot.org/bugreport.html#coredumps - set /proc/sys/fs/suid_dumpable to 2)

which involves reading the current value in suid_dumpable.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/286
(cherry picked from commit 2202a8a267)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-08 00:33:45 -08:00
Christian Boltz
60d75132fc Ignore *.orig and *.rej files when loading profiles
or: get rc.apparmor.functions in sync with the tools and libapparmor.

This was "accidently" reported by Ralph on the opensuse-support
mailinglist.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/282
(cherry picked from commit 228b92ce5a)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-08 00:18:29 -08:00
Jamie Strandboge
ccda637998 deny ~/.mutt** in private-files and audit deny ~/.aws in private-files-strict
PR: https://gitlab.com/apparmor/apparmor/merge_requests/276
Signed-Off-By: Jamie Strandboge <jamie@canonical.com>
(cherry picked from commit 170e8d6ac8)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 23:00:49 -08:00
Christian Boltz
44ca6942f0 Fix viewing a local inactive profile in aa-genprof
aa-genprof checks if one of the profiles in the extra profile dir
matches the binary, and proposes to use that profile as a starting
point.

Since 4d722f1839 the "(V)iew profile"
option to display the proposed profile was broken.

The easiest fix is to remember the filename in the extras directory, and
display the file from there.

Sidenote: when choosing to use the extra profile, it gets written to
disk without any problems, so this bug really only affected "(V)iew
profile" to preview the proposed extra profile.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
(cherry picked from commit 8b4e76a7d5)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:49:26 -08:00
Christian Boltz
5e45af1752 serialize_profile(): Fix handling of options
In the 2.13 branch (and older), 'options' is not always a dict, but can
also be None or an empty string.

Adjust the if condition in serialize_profile() so that "View changes
between clean profiles" doesn't error out.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:47:44 -08:00
Christian Boltz
f997977e6b Replace existing_profiles & fix minitools for named profiles
Technical stuff first:

Replace existing_profiles (a dict with the filenames for both active and
inactive profiles) with active_profiles and extra_profiles which are
ProfileList()s and store the active profiles and those in the extra
directory separately. Thanks to ProfileList, now also the relation
between attachments and filenames is easily available.

Also replace all usage of existing_profiles with active_profiles and
extra_profiles, and adjust it to the ProfileList syntax everywhere.

With this change, several bugs in aa-complain and the other minitools
get fixed:
- aa-complain etc. never found profiles that have a profile name
  (the attachment wasn't checked)
- even if the profile name was given as parameter to aa-complain, it
  first did "which $parameter" so it never matched on named profiles
- profile names with alternations (without attachment specification)
  also never matched because the old code didn't use AARE.

References: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=882047#92
(search for "As usual" ;-)

Just for completeness - the matching still doesn't honor/expand
variables in the profile name.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
(cherry picked from commit 4d722f1839)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:47:11 -08:00
Christian Boltz
848fbae814 add ProfileList class to store list of profiles
ProfileList is meant to store the list of profiles (both name and
attachment) and in which files they live.

Also add unittests to make sure everything works as expected.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
(cherry picked from commit 789c4658e2)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:46:28 -08:00
Christian Boltz
368097d8e7 Move updating existing_profiles out of parse_profile_data()
parse_profile_data() returns the parsed profiles, but writes to
existing_profiles directly.

read_profiles() calls parse_profile_data() and already handles adding
the parsed profiles to aa, original_aa or extras, which means updating
existing_profiles there is a much better place.

This commit also includes a hidden change: Previously, when parsing
include files, they were also added to existing_profiles. This is
superfluous, only real profiles need to be stored there.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
(cherry picked from commit 8809218ac8)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:45:45 -08:00
Christian Boltz
9da95e607e split off get_new_profile_filename()
... and call it from get_profile_filename_* if get_new is True
(= always with the current code)

PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
(cherry picked from commit a6b8d14908)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:45:11 -08:00
Christian Boltz
43c4c10501 split get_profile_filename into .._from_profile_name and .._from_attachment
Split get_profile_filename() into
- get_profile_filename_from_profile_name() (parameter: a profile name)
- get_profile_filename_from_attachment() (parameter: an attachment)

Currently both functions call get_profile_filename_orig() (formerly
get_profile_filename()) so the behaviour doesn't change yet.

The most important part of this commit is changing all
get_profile_filename() calls to use one of the new functions to make
clear if they specify a profile or an attachment/executable as
parameter.

As promised, the is_attachment parameter starts to get used in this
patch ;-)

Note: The get_new parameter (which I'll explain in the patch actually
using it) is set to True in all calls to the new functions.
The long term plan is to get rid of it in most cases (hence defaulting
to False), but that will need more testing.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
(cherry picked from commit ec741424f8)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:44:31 -08:00
Christian Boltz
c0766035df Add is_attachment parameter to write_profile
The minitools call write_profile(), write_profile_feedback_ui() and
serialize_profile() with the _attachment_ as parameter.

However, aa-logprof etc. call them with the _profile name_ as parameter.

This patch adds an is_attachment parameter to write_profile() and
write_profile_feedback_ui(). It also passes it through to
serialize_profile() via the options parameter.

If is_attachment is True, the parameter will be handled as attachment,
otherwise it is expected to be a profile name.

tools.py gets changed to set is_attachment to True when calling the
functions listed above to make clear that the parameter is an attachment.

Note: This patch only adds the is_attachment parameter/option, but
doesn't change any behaviour. That will happen in the next patch.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
(cherry picked from commit bc783372b8)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:42:43 -08:00
Petr Vorel
679e670024 dnsmasq: Add pid file used by NetworkManager
PR: https://gitlab.com/apparmor/apparmor/merge_requests/288
Signed-off-by: Petr Vorel <pvorel@suse.cz>
(cherry picked from commit 49848b9081)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:28:27 -08:00
Petr Vorel
0fcb7d032e dnsmasq: Adjust pattern for log files to comply SELinux
i.e. move '*' from beginning to before suffix.

Commit 025c7dc6 ("dnsmasq: Add permission to open log files") added
pattern, which is not compatible with SELinux. As this pattern has been
in SELinux since 2011 (with recent change to accept '.log' suffix +
logrotate patterns which are not relevant to AppArmor) IMHO it's better
to adjust our profile.

Fixes: 025c7dc6 ("dnsmasq: Add permission to open log files")
PR: PR: https://gitlab.com/apparmor/apparmor/merge_requests/288
Signed-off-by: Petr Vorel <pvorel@suse.cz>
(cherry picked from commit 3ef8df6ac0)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-07 22:28:12 -08:00
Vincas Dargis
288c67d639 Merge branch 'backport-sys-212' into 'apparmor-2.12'
Backport for 2.12: Use @{sys} tunable in profiles and abstractions

See merge request apparmor/apparmor!278

Acked-by: Christian Boltz <apparmor@cboltz.de>
2018-12-06 18:42:48 +00:00
Christian Boltz
d24b373138 Merge branch 'certbot' into 'master'
Add /etc/letsencrypt/archive to ssl_key abstraction

See merge request apparmor/apparmor!283

Acked-by: Christian Boltz <apparmor@cboltz.de> for 2.10..master

(cherry picked from commit 0a666b8e48)

cb468786 Add /etc/letsencrypt stuff to ssl_keys/ssl_certs abstraction
2018-11-30 15:44:33 +00:00
Vincas Dargis
699ec8434d Backport: Use @{sys} tunable in profiles and abstractions
Commit aa06528790 made @{sys} tunable
available by default.

Update profiles and abstractions to actually use @{sys} tunable for
better confinement in the future (when @{sys} becomes kernel var).

Closes LP#1728551

(this is backport of 2438179b76 for
AppArmor 2.12)
2018-11-22 20:16:32 +02:00
Vincas Dargis
10d3abf930 Merge branch 'backport-vulkan' into 'apparmor-2.13'
Backport: Add vulkan abstraction

See merge request apparmor/apparmor!266

Acked-by: Christian Boltz <apparmor@cboltz.de> for 2.10..2.13

(cherry picked from commit 6249579842)

31461701 Add vulkan abstraction
2018-11-22 17:36:25 +00:00
Christian Boltz
82d3b322da parse_profile_data(): Ensure last line in a profile is valid
'lastline' gets merged into 'line' (and reset to None) when reading the
next line. If 'lastline' isn't empty after reading the whole profile,
this means there's something unparseable at the end of the profile,
therefore parse_profile_data() should error out.

Also remove some simple_tests testcases from the 'exception_not_raised'
list - they only didn't raise the exception because the invalid rule was
the last line in the affected profile.

Thanks to Eric Chiang for accidently (and maybe even unnoticedly ;-)
discovering this bug while adding some xattr testcases that surprisingly
didn't fail in the tools.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/271
(cherry picked from commit 4efff35bf8)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-11-13 16:03:22 -08:00
Christian Boltz
bceac420a6 Merge branch 'cboltz-view-changes-2.13' into 'apparmor-2.13'
[2.12+2.13] use serialize_profile() for the new profile in (V)iew Changes

See merge request apparmor/apparmor!267

Acked-by: John Johansen <john.johansen@canonical.com> for 2.12 and 2.13

(cherry picked from commit f4d7f8ae57)

dd4c2b05 use serialize_profile() for the new profile in (V)iew Changes
1b32d764 delete serialize_profile_from_old_profile()
2018-11-11 17:28:28 +00:00
Christian Boltz
f4b955ce75 Merge branch 'cboltz-strict-todo-check' into 'master'
error out on superfluous TODOs

See merge request apparmor/apparmor!197

Acked-by: John Johansen <john.johansen@canonical.com>

(cherry picked from commit 39a2031487)

4b26850e error out on superfluous TODOs
2018-11-06 21:15:03 +00:00
Christian Boltz
66ef169a8b Merge branch 'cboltz-disable-some-abi-tests' into 'master'
disable abi/ok_10 and abi/ok_12 tests

See merge request apparmor/apparmor!259

(cherry picked from commit 608af94dff)

a3305b51 disable abi/ok_10 and abi/ok_12 tests
2018-11-06 20:43:26 +00:00
Christian Boltz
5f0dc143fc Merge branch 'cboltz-fixed-todos' into 'master'
Remove TODO notes from no-longer-failing tests

See merge request apparmor/apparmor!180

Acked-by: John Johansen <john.johansen@canonical.com>
Acked-by: intrigeri <intrigeri@debian.org>

(cherry picked from commit c98d8570ee)

d15bdaba Remove TODO notes from no-longer-failing tests
2018-11-06 17:50:24 +00:00
intrigeri
9064c1e185 apparmor(7): Document various debugging options.
Credits go to John Johansen <john@jjmx.net> for most of the information
and the initial phrasing.

Bug-Debian: https://bugs.debian.org/826218

Cherry-picked from commit b95f9bdd3b
2018-11-04 12:04:27 +00:00
Christian Boltz
300760a13e Merge branch 'cboltz-postalias' into 'master'
allow locking /etc/aliases.db

See merge request apparmor/apparmor!250

Acked-by: intrigeri <intrigeri@debian.org>

(cherry picked from commit 473d1f5daa)

f74edd5d allow locking /etc/aliases.db
2018-10-26 14:39:52 +00:00
John Johansen
c23cd2e389 Merge branch 'cboltz-aa-notify.pod' into 'apparmor-2.12'
aa-notify man page: update user's configuration file path

[2.10..2.12] aa-notify man page: update user's configuration file path

PR: https://gitlab.com/apparmor/apparmor/merge_requests/243
(backported from commit 2209e09aef)
Acked-by: John Johansen <john.johansen@canonical.com>
2018-10-22 02:38:17 +00:00
Christian Boltz
7176c1433b add libapparmor/src/PMurHash.{o,lo} to gitignore
(cherry picked from commit db92d96e68)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-10-21 19:05:38 -07:00
Christian Boltz
d88b047b3c add utils/test/common_test.pyc to gitignore
(cache file that gets created when running the tests with python2)

(cherry picked from commit 63d17ecf16)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-10-21 19:05:19 -07:00
Christian Boltz
6e8d5712c7 Merge branch 'cboltz-profile-names' into 'master'
Add profile names to all profiles with {bin,sbin} attachment

See merge request apparmor/apparmor!242

Acked-by: intrigeri <intrigeri@debian.org>

(cherry picked from commit fd68a5eb64)

b77116e6 Add profile names to all profiles with {bin,sbin} attachment
2018-10-21 10:35:31 +00:00
Christian Boltz
500b857d24 Merge branch 'test-includes' into 'apparmor-2.12'
profiles/Makefile: test abstractions against apparmor_parser

See merge request apparmor/apparmor!244

Acked-by: Christian Boltz <apparmor@cboltz.de> for 2.10, 2.11 and 2.12.
2018-10-18 17:14:54 +00:00
Vincas Dargis
93ccf15ce6 profiles/Makefile: test abstractions against apparmor_parser
Update Makefile to test abstractions by generating temporary profile, to
check for missing (not backported) abstractions or other issues.

This is backport of dc7ae28de0 for
2.10..2.12 series (without --config-file option).
2018-10-18 20:00:03 +03:00
Christian Boltz
e908b415d7 aa-notify man page: update user's configuration file path
This is a backport of !239

    commit 2209e09aef
    Author: nl6720 <nl6720@gmail.com>

    aa-notify man page: update user's configuration file path

    Signed-off-by: nl6720 <nl6720@gmail.com>
2018-10-16 18:01:02 +02:00
49 changed files with 449 additions and 537 deletions

3
.gitignore vendored
View File

@@ -95,6 +95,8 @@ libraries/libapparmor/src/.deps
libraries/libapparmor/src/.libs
libraries/libapparmor/src/Makefile
libraries/libapparmor/src/Makefile.in
libraries/libapparmor/src/PMurHash.lo
libraries/libapparmor/src/PMurHash.o
libraries/libapparmor/src/af_protos.h
libraries/libapparmor/src/change_hat.lo
libraries/libapparmor/src/features.lo
@@ -188,6 +190,7 @@ utils/*.tmp
utils/po/*.mo
utils/apparmor/*.pyc
utils/apparmor/rule/*.pyc
utils/test/common_test.pyc
utils/test/.coverage
utils/test/htmlcov/
utils/vim/apparmor.vim

View File

@@ -1 +1 @@
2.12.1
2.12.2

View File

@@ -143,6 +143,56 @@ messages with the KERN facility. Thus, REJECTING and PERMITTING messages
may go to either F</var/log/audit/audit.log> or F</var/log/messages>,
depending upon local configuration.
=head1 DEBUGGING
AppArmor provides a few facilities to log more information,
which can help debugging profiles.
=head2 Enable debug mode
When debug mode is enabled, AppArmor will log a few extra messages to
dmesg (not via the audit subsystem). For example, the logs will tell
whether environment scrubbing has been applied.
To enable debug mode, run:
echo 1 > /sys/module/apparmor/parameters/debug
=head2 Turn off deny audit quieting
By default, operations that trigger C<deny> rules are not logged.
This is called I<deny audit quieting>.
To turn off deny audit quieting, run:
echo -n noquiet >/sys/module/apparmor/parameters/audit
=head2 Force audit mode
AppArmor can log a message for every operation that triggers a rule
configured in the policy. This is called I<force audit mode>.
B<Warning!> Force audit mode can be extremely noisy even for a single profile,
let alone when enabled globally.
To set a specific profile in force audit mode, add the C<audit> flag:
profile foo flags=(audit) { ... }
To enable force audit mode globally, run:
echo -n all > /sys/module/apparmor/parameters/audit
If auditd is not running, to avoid losing too many of the extra log
messages, you will likely have to turn off rate limiting by doing:
echo 0 > /proc/sys/kernel/printk_ratelimit
But even then the kernel ring buffer may overflow and you might
lose messages.
Else, if auditd is running, see auditd(8) and auditd.conf(5).
=head1 FILES
=over 4

View File

@@ -113,6 +113,8 @@ skip_profile() {
local profile=$1
if [ "${profile%.rpmnew}" != "${profile}" -o \
"${profile%.rpmsave}" != "${profile}" -o \
"${profile%.orig}" != "${profile}" -o \
"${profile%.rej}" != "${profile}" -o \
-e "${PROFILE_DIR}/disable/`basename ${profile}`" -o \
"${profile%\~}" != "${profile}" ] ; then
return 1

View File

@@ -131,9 +131,13 @@ sub test_profile {
} elsif ($coredump) {
ok(0, "$profile: Produced core dump (signal $signal): $description");
} elsif ($istodo) {
TODO: {
local $TODO = "Unfixed testcase.";
ok($expass ? !$result : $result, "TODO: $profile: $description");
if ($expass != $result) {
fail("TODO passed unexpectedly: $profile: $description");
} else {
TODO: {
local $TODO = "Unfixed testcase.";
ok($expass ? !$result : $result, "TODO: $profile: $description");
}
}
} else {
ok($expass ? !$result : $result, "$profile: $description");

View File

@@ -2,6 +2,7 @@
#=DESCRIPTION abi testing - abi path quotes in <> with spaces
#=EXRESULT PASS
#=TODO
#=DISABLED - results in "superfluous TODO", but fails after removing TODO
abi < "abi/4.19">,

View File

@@ -2,6 +2,7 @@
#=DESCRIPTION abi testing - abi path quotes in <> with spaces
#=EXRESULT PASS
#=TODO
#=DISABLED - results in "superfluous TODO", but fails after removing TODO
abi < "abi/4.19" >,

View File

@@ -1,6 +1,5 @@
#=DESCRIPTION reference variables in rules that also have alternations
#=EXRESULT PASS
#=TODO
# This test needs check on @{FOO} attachment having leading / post var expansion
@{FOO}=/bar /baz

View File

@@ -1,6 +1,5 @@
#=DESCRIPTION reference variables is null
#=EXRESULT FAIL
#=TODO
#needs post var expansion check that variable contained a value
@{FOO}=

View File

@@ -1,6 +1,5 @@
#=DESCRIPTION reference variables is null
#=EXRESULT FAIL
#=TODO
#needs post var expansion check that variable contained a value
@{FOO}=

View File

@@ -1,7 +1,6 @@
#
#=DESCRIPTION test for conflict resolution in minimization phase of dfa gen
#=EXRESULT PASS
#=TODO
#
/usr/bin/foo {

View File

@@ -1,7 +1,6 @@
#
#=DESCRIPTION test for conflict resolution in minimization phase of dfa gen
#=EXRESULT FAIL
#=TODO
#
/usr/bin/foo {
/b* px,

View File

@@ -29,6 +29,7 @@ DESTDIR=/
PROFILES_DEST=${DESTDIR}/etc/apparmor.d
EXTRAS_DEST=${DESTDIR}/usr/share/apparmor/extra-profiles/
PROFILES_SOURCE=./apparmor.d
ABSTRACTIONS_SOURCE=./apparmor.d/abstractions
EXTRAS_SOURCE=./apparmor/profiles/extras/
SUBDIRS=$(shell find ${PROFILES_SOURCE} -type d -print)
@@ -84,6 +85,8 @@ docs:
IGNORE_FILES=${EXTRAS_SOURCE}/README
CHECK_PROFILES=$(filter-out ${IGNORE_FILES} ${SUBDIRS}, $(wildcard ${PROFILES_SOURCE}/*) $(wildcard ${EXTRAS_SOURCE}/*))
# use find because Make wildcard is not recursive:
CHECK_ABSTRACTIONS=$(shell find ${ABSTRACTIONS_SOURCE} -type f -print)
.PHONY: check
check: check-parser check-logprof
@@ -96,6 +99,14 @@ check-parser: local
${PARSER} -S -b ${PWD}/apparmor.d $${profile} > /dev/null || exit 1; \
done
@echo "*** Checking abstractions from ${ABSTRACTIONS_SOURCE} against apparmor_parser"
$(Q)for abstraction in ${CHECK_ABSTRACTIONS} ; do \
[ -n "${VERBOSE}" ] && echo "Testing $${abstraction}" ; \
echo "#include <tunables/global> profile test { #include <$${abstraction}> }" \
| ${PARSER} -S -b ${PWD}/apparmor.d -I ${PWD} > /dev/null \
|| exit 1; \
done
.PHONY: check-logprof
check-logprof: local
@echo "*** Checking profiles from ${PROFILES_SOURCE} against logprof"

View File

@@ -7,9 +7,9 @@
# Allow unconfined processes to send us signals by default
signal (receive) peer=unconfined,
# Allow apache to send us signals by default
signal (receive) peer=/usr/{bin,sbin}/apache2,
signal (receive) peer=apache2,
# Allow other hats to signal by default
signal peer=/usr/{bin,sbin}/apache2//*,
signal peer=apache2//*,
# Allow us to signal ourselves
signal peer=@{profile_name},

View File

@@ -90,8 +90,8 @@
@{PROC}/meminfo r,
@{PROC}/stat r,
@{PROC}/cpuinfo r,
/sys/devices/system/cpu/ r,
/sys/devices/system/cpu/online r,
@{sys}/devices/system/cpu/ r,
@{sys}/devices/system/cpu/online r,
# glibc's *printf protections read the maps file
@{PROC}/@{pid}/{maps,auxv,status} r,

View File

@@ -14,6 +14,6 @@
deny capability block_suspend,
# dovecot's master can send us signals
signal receive peer=/usr/{bin,sbin}/dovecot,
signal receive peer=dovecot,
/{var/,}run/dovecot/config rw,

View File

@@ -6,6 +6,7 @@
# lot of false positives when reading contents of directories)
deny @{HOME}/.*history mrwkl,
deny @{HOME}/.fetchmail* mrwkl,
deny @{HOME}/.mutt** mrwkl,
deny @{HOME}/.viminfo* mrwkl,
deny @{HOME}/.*~ mrwkl,
deny @{HOME}/.*.swp mrwkl,

View File

@@ -5,6 +5,7 @@
#include <abstractions/private-files>
# potentially extremely sensitive files
audit deny @{HOME}/.aws/{,**} mrwkl,
audit deny @{HOME}/.gnupg/{,**} mrwkl,
audit deny @{HOME}/.ssh/{,**} mrwkl,
audit deny @{HOME}/.gnome2_private/{,**} mrwkl,

View File

@@ -32,3 +32,8 @@
/etc/dehydrated/certs/*/cert-*.pem r,
/etc/dehydrated/certs/*/chain-*.pem r,
/etc/dehydrated/certs/*/fullchain-*.pem r,
# certbot
/etc/letsencrypt/archive/*/cert*.pem r,
/etc/letsencrypt/archive/*/chain*.pem r,
/etc/letsencrypt/archive/*/fullchain*.pem r,

View File

@@ -23,3 +23,6 @@
# dehydrated
/etc/dehydrated/certs/*/privkey-*.pem r,
# certbot / letsencrypt
/etc/letsencrypt/archive/*/privkey*.pem r,

View File

@@ -41,8 +41,8 @@
@{PROC}/@{pid}/ r,
@{PROC}/@{pid}/fd/ r,
@{PROC}/filesystems r,
/sys/devices/system/cpu/ r,
/sys/devices/system/cpu/** r,
@{sys}/devices/system/cpu/ r,
@{sys}/devices/system/cpu/** r,
/usr/share/** r,
/var/lib/dbus/machine-id r,
@@ -88,8 +88,8 @@
@{PROC}/@{pid}/ r,
@{PROC}/@{pid}/fd/ r,
@{PROC}/filesystems r,
/sys/devices/system/cpu/ r,
/sys/devices/system/cpu/** r,
@{sys}/devices/system/cpu/ r,
@{sys}/devices/system/cpu/** r,
/usr/share/** r,
/var/lib/dbus/machine-id r,

View File

@@ -2,5 +2,5 @@
# video device access
# System devices
/sys/class/video4linux r,
/sys/class/video4linux/** r,
@{sys}/class/video4linux r,
@{sys}/class/video4linux/** r,

View File

@@ -0,0 +1,14 @@
# vim:syntax=apparmor
# Vulkan access requirements
# System files
/dev/dri/ r, # libvulkan_radeon.so, libvulkan_intel.so (Mesa)
/etc/vulkan/{explicit,implicit}_layer.d/{,*.json} r,
# for drmGetMinorNameForFD() from libvulkan_intel.so (Mesa)
@{sys}/devices/pci[0-9]*/*/drm/ r,
/usr/share/vulkan/icd.d/{,*.json} r,
/usr/share/vulkan/{explicit,implicit}_layer.d/{,*.json} r,
# User files
owner @{HOME}/.local/share/vulkan/implicit_layer.d/{,*.json} r,

View File

@@ -20,13 +20,13 @@
/etc/phpsysinfo/config.php r,
/etc/udev/udev.conf r,
@{PROC}/** r,
/sys/bus/ r,
/sys/bus/pci/devices/ r,
/sys/bus/pci/slots/ r,
/sys/bus/pci/slots/** r,
/sys/bus/usb/devices/ r,
/sys/class/ r,
/sys/devices/** r,
@{sys}/bus/ r,
@{sys}/bus/pci/devices/ r,
@{sys}/bus/pci/slots/ r,
@{sys}/bus/pci/slots/** r,
@{sys}/bus/usb/devices/ r,
@{sys}/class/ r,
@{sys}/devices/** r,
/usr/bin/ r,
/usr/bin/apt-cache ixr,
/usr/bin/dpkg-query ixr,

View File

@@ -47,7 +47,7 @@ profile syslog-ng /{usr/,}{bin,sbin}/syslog-ng {
/etc/hosts.deny r,
/etc/hosts.allow r,
/{usr/,}{bin,sbin}/syslog-ng mr,
/sys/devices/system/cpu/online r,
@{sys}/devices/system/cpu/online r,
/usr/share/syslog-ng/** r,
/var/lib/syslog-ng/syslog-ng-?????.qf rw,
# chrooted applications

View File

@@ -29,14 +29,14 @@
/run/dovecot/auth-userdb rw,
/usr/bin/doveconf mrix,
/usr/lib/dovecot/dovecot-lda mrix,
/usr/{bin,sbin}/sendmail Cx,
/usr/{bin,sbin}/sendmail Cx -> sendmail,
/usr/share/dovecot/protocols.d/ r,
# Site-specific additions and overrides. See local/README for details.
#include <local/usr.lib.dovecot.dovecot-lda>
profile /usr/{bin,sbin}/sendmail flags=(attach_disconnected) {
profile sendmail /usr/{bin,sbin}/sendmail flags=(attach_disconnected) {
# this profile is based on the usr.sbin.sendmail profile in extras
# and should support both postfix' and sendmail's sendmail binary

View File

@@ -1,7 +1,7 @@
# Author: Marc Deslauriers <marc.deslauriers@ubuntu.com>
#include <tunables/global>
/usr/{bin,sbin}/apache2 flags=(attach_disconnected) {
profile apache2 /usr/{bin,sbin}/apache2 flags=(attach_disconnected) {
# This profile is completely permissive.
# It is designed to target specific applications using mod_apparmor,

View File

@@ -1,5 +1,5 @@
#include <tunables/global>
/usr/{bin,sbin}/avahi-daemon {
profile avahi-daemon /usr/{bin,sbin}/avahi-daemon {
#include <abstractions/base>
#include <abstractions/consoles>
#include <abstractions/dbus>

View File

@@ -12,7 +12,7 @@
@{TFTP_DIR}=/var/tftp /srv/tftpboot
#include <tunables/global>
profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
/usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
#include <abstractions/base>
#include <abstractions/dbus>
#include <abstractions/nameservice>
@@ -45,7 +45,7 @@ profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
/usr/{bin,sbin}/dnsmasq mr,
/var/log/*dnsmasq.log w,
/var/log/dnsmasq*.log w,
/usr/share/dnsmasq/ r,
/usr/share/dnsmasq/* r,
@@ -96,6 +96,7 @@ profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
/{,var/}run/sendsigs.omit.d/*dnsmasq.pid w,
/{,var/}run/NetworkManager/dnsmasq.conf r,
/{,var/}run/NetworkManager/dnsmasq.pid w,
/{,var/}run/NetworkManager/NetworkManager.pid w,
profile libvirt_leaseshelper {
#include <abstractions/base>
@@ -107,9 +108,9 @@ profile dnsmasq /usr/{bin,sbin}/dnsmasq flags=(attach_disconnected) {
owner @{PROC}/@{pid}/net/psched r,
owner @{PROC}/@{pid}/status r,
/sys/devices/system/cpu/ r,
/sys/devices/system/node/ r,
/sys/devices/system/node/*/meminfo r,
@{sys}/devices/system/cpu/ r,
@{sys}/devices/system/node/ r,
@{sys}/devices/system/node/*/meminfo r,
# libvirt lease and status files for dnsmasq
/var/lib/libvirt/dnsmasq/*.leases rw,

View File

@@ -12,7 +12,7 @@
#include <tunables/global>
/usr/{bin,sbin}/dovecot flags=(attach_disconnected) {
profile dovecot /usr/{bin,sbin}/dovecot flags=(attach_disconnected) {
#include <abstractions/authentication>
#include <abstractions/base>
#include <abstractions/dovecot-common>
@@ -38,6 +38,7 @@
/etc/lsb-release r,
/etc/SuSE-release r,
@{PROC}/@{pid}/mounts r,
@{PROC}/sys/fs/suid_dumpable r,
/usr/bin/doveconf rix,
/usr/lib/dovecot/anvil mrPx,
/usr/lib/dovecot/auth mrPx,

View File

@@ -11,7 +11,7 @@
#include <tunables/global>
/usr/{bin,sbin}/identd {
profile identd /usr/{bin,sbin}/identd {
#include <abstractions/base>
#include <abstractions/nameservice>
capability net_bind_service,

View File

@@ -11,7 +11,7 @@
#include <tunables/global>
/usr/{bin,sbin}/mdnsd {
profile mdnsd /usr/{bin,sbin}/mdnsd {
#include <abstractions/base>
#include <abstractions/consoles>
#include <abstractions/nameservice>

View File

@@ -1,6 +1,6 @@
#include <tunables/global>
/usr/{bin,sbin}/nmbd {
profile nmbd /usr/{bin,sbin}/nmbd {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/samba>

View File

@@ -10,7 +10,7 @@
# ------------------------------------------------------------------
#include <tunables/global>
/usr/{bin,sbin}/nscd {
profile nscd /usr/{bin,sbin}/nscd {
#include <abstractions/base>
#include <abstractions/consoles>
#include <abstractions/nameservice>

View File

@@ -11,7 +11,7 @@
#include <tunables/global>
#include <tunables/ntpd>
/usr/{bin,sbin}/{,open}ntpd flags=(attach_disconnected) {
profile ntpd /usr/{bin,sbin}/{,open}ntpd flags=(attach_disconnected) {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/openssl>

View File

@@ -1,6 +1,6 @@
#include <tunables/global>
/usr/{bin,sbin}/smbd {
profile smbd /usr/{bin,sbin}/smbd {
#include <abstractions/authentication>
#include <abstractions/base>
#include <abstractions/consoles>

View File

@@ -1,7 +1,7 @@
# Last Modified: Tue Jan 3 00:17:40 2012
#include <tunables/global>
/usr/{bin,sbin}/smbldap-useradd {
profile smbldap-useradd /usr/{bin,sbin}/smbldap-useradd {
#include <abstractions/base>
#include <abstractions/bash>
#include <abstractions/nameservice>

View File

@@ -1,6 +1,6 @@
#include <tunables/global>
/usr/{bin,sbin}/winbindd {
profile winbindd /usr/{bin,sbin}/winbindd {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/samba>

View File

@@ -17,7 +17,7 @@
#include <abstractions/consoles>
#include <abstractions/postfix-common>
/etc/aliases r,
/etc/aliases.db rwl,
/etc/aliases.db rwlk,
/etc/postfix r,
/etc/postfix/main.cf r,
/etc/postfix/aliases r,

View File

@@ -107,7 +107,7 @@ apparmor.check_qualifiers(program)
apparmor.loadincludes()
profile_filename = apparmor.get_profile_filename(program)
profile_filename = apparmor.get_profile_filename_from_attachment(program, True)
if os.path.exists(profile_filename):
apparmor.helpers[program] = apparmor.get_profile_flags(profile_filename, program)
else:

View File

@@ -1,7 +1,7 @@
#! /usr/bin/python3
# ----------------------------------------------------------------------
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
# Copyright (C) 2014-2017 Christian Boltz <apparmor@cboltz.de>
# Copyright (C) 2014-2018 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
@@ -57,7 +57,7 @@ def reset_aa():
apparmor.aa.aa = apparmor.aa.hasher()
apparmor.aa.filelist = apparmor.aa.hasher()
apparmor.aa.include = dict()
apparmor.aa.existing_profiles = apparmor.aa.hasher()
apparmor.aa.active_profiles = apparmor.aa.ProfileList()
apparmor.aa.original_aa = apparmor.aa.hasher()
def find_profiles_from_files(files):
@@ -75,7 +75,7 @@ def find_files_from_profiles(profiles):
apparmor.aa.read_profiles()
for profile_name in profiles:
profile_to_filename[profile_name] = apparmor.aa.get_profile_filename(profile_name)
profile_to_filename[profile_name] = apparmor.aa.get_profile_filename_from_profile_name(profile_name, True)
reset_aa()

View File

@@ -92,7 +92,8 @@ System-wide configuration for B<aa-notify> is done via
# only people in use_group can use aa-notify
use_group="admin"
Per-user configuration is done via ~/.apparmor/notify.conf:
Per-user configuration is done via $XDG_CONFIG_HOME/apparmor/notify.conf (or
the deprecated ~/.apparmor/notify.conf if it exists):
# set to 'yes' to enable AppArmor DENIED notifications
show_notifications="yes"

View File

@@ -1,6 +1,6 @@
# ----------------------------------------------------------------------
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
# Copyright (C) 2014-2017 Christian Boltz <apparmor@cboltz.de>
# Copyright (C) 2014-2018 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
@@ -49,6 +49,8 @@ from apparmor.regex import (RE_PROFILE_START, RE_PROFILE_END, RE_PROFILE_LINK,
RE_PROFILE_UNIX, RE_RULE_HAS_COMMA, RE_HAS_COMMENT_SPLIT,
strip_quotes, parse_profile_start_line, re_match_include )
from apparmor.profile_list import ProfileList
from apparmor.profile_storage import ProfileStorage, add_or_remove_flag, ruletypes, write_abi
import apparmor.rules as aarules
@@ -88,7 +90,8 @@ extra_profile_dir = None
# To keep track of previously included profile fragments
include = dict()
existing_profiles = dict()
active_profiles = ProfileList()
extra_profiles = ProfileList()
# To store the globs entered by users so they can be provided again
# format: user_globs['/foo*'] = AARE('/foo*')
@@ -216,11 +219,29 @@ def find_executable(bin_path):
return full_bin
return None
def get_profile_filename(profile):
"""Returns the full profile name"""
if existing_profiles.get(profile, False):
return existing_profiles[profile]
elif profile.startswith('/'):
def get_profile_filename_from_profile_name(profile, get_new=False):
"""Returns the full profile name for the given profile name"""
filename = active_profiles.filename_from_profile_name(profile)
if filename:
return filename
if get_new:
return get_new_profile_filename(profile)
def get_profile_filename_from_attachment(profile, get_new=False):
"""Returns the full profile name for the given attachment"""
filename = active_profiles.filename_from_attachment(profile)
if filename:
return filename
if get_new:
return get_new_profile_filename(profile)
def get_new_profile_filename(profile):
'''Compose filename for a new profile'''
if profile.startswith('/'):
# Remove leading /
profile = profile[1:]
else:
@@ -237,7 +258,7 @@ def name_to_prof_filename(prof_filename):
else:
bin_path = find_executable(prof_filename)
if bin_path:
prof_filename = get_profile_filename(bin_path)
prof_filename = get_profile_filename_from_attachment(bin_path, True)
if os.path.isfile(prof_filename):
return (prof_filename, bin_path)
@@ -463,7 +484,7 @@ def create_new_profile(localfile, is_stub=False):
def delete_profile(local_prof):
"""Deletes the specified file from the disk and remove it from our list"""
profile_file = get_profile_filename(local_prof)
profile_file = get_profile_filename_from_profile_name(local_prof, True)
if os.path.isfile(profile_file):
os.remove(profile_file)
if aa.get(local_prof, False):
@@ -497,13 +518,15 @@ def get_profile(prof_name):
if inactive_profile:
uname = 'Inactive local profile for %s' % prof_name
inactive_profile[prof_name][prof_name]['flags'] = 'complain'
orig_filename = inactive_profile[prof_name][prof_name]['filename'] # needed for CMD_VIEW_PROFILE
inactive_profile[prof_name][prof_name]['filename'] = ''
profile_hash[uname]['username'] = uname
profile_hash[uname]['profile_type'] = 'INACTIVE_LOCAL'
profile_hash[uname]['profile'] = serialize_profile(inactive_profile[prof_name], prof_name, None)
profile_hash[uname]['profile_data'] = inactive_profile
existing_profiles.pop(prof_name) # remove profile filename from list to force storing in /etc/apparmor.d/ instead of extra_profile_dir
# no longer necessary after splitting active and extra profiles
# existing_profiles.pop(prof_name) # remove profile filename from list to force storing in /etc/apparmor.d/ instead of extra_profile_dir
# If no profiles in repo and no inactive profiles
if not profile_hash.keys():
@@ -537,11 +560,7 @@ def get_profile(prof_name):
q.selected = options.index(options[arg])
if ans == 'CMD_VIEW_PROFILE':
pager = get_pager()
proc = subprocess.Popen(pager, stdin=subprocess.PIPE)
# proc.communicate('Profile submitted by %s:\n\n%s\n\n' %
# (options[arg], p['profile']))
proc.communicate(p['profile'].encode())
proc.kill()
subprocess.call([pager, orig_filename])
elif ans == 'CMD_USE_PROFILE':
if p['profile_type'] == 'INACTIVE_LOCAL':
profile_data = p['profile_data']
@@ -559,7 +578,7 @@ def activate_repo_profiles(url, profiles, complain):
attach_profile_data(aa, profile_data)
write_profile(pname)
if complain:
fname = get_profile_filename(pname)
fname = get_profile_filename_from_profile_name(pname, True)
change_profile_flags(profile_dir + fname, None, 'complain', True)
aaui.UI_Info(_('Setting %s to complain mode.') % pname)
except Exception as e:
@@ -591,7 +610,7 @@ def autodep(bin_name, pname=''):
# Create a new profile if no existing profile
if not profile_data:
profile_data = create_new_profile(pname)
file = get_profile_filename(pname)
file = get_profile_filename_from_profile_name(pname, True)
profile_data[pname][pname]['filename'] = None # will be stored in /etc/apparmor.d when saving, so it shouldn't carry the extra_profile_dir filename
attach_profile_data(aa, profile_data)
attach_profile_data(original_aa, profile_data)
@@ -691,15 +710,16 @@ def profile_exists(program):
"""Returns True if profile exists, False otherwise"""
# Check cache of profiles
if existing_profiles.get(program, False):
if active_profiles.filename_from_attachment(program):
return True
# Check the disk for profile
prof_path = get_profile_filename(program)
prof_path = get_profile_filename_from_attachment(program, True)
#print(prof_path)
if os.path.isfile(prof_path):
# Add to cache of profile
existing_profiles[program] = prof_path
return True
raise AppArmorBug('Reached strange condition in profile_exists(), please open a bugreport!')
# active_profiles[program] = prof_path
# return True
return False
def sync_profile():
@@ -1088,9 +1108,9 @@ def handle_children(profile, hat, root):
options += 'd'
# Define the default option
default = None
if 'p' in options and os.path.exists(get_profile_filename(exec_target)):
if 'p' in options and os.path.exists(get_profile_filename_from_attachment(exec_target, True)):
default = 'CMD_px'
sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename(exec_target))
sys.stdout.write(_('Target profile exists: %s\n') % get_profile_filename_from_attachment(exec_target, True))
elif 'i' in options:
default = 'CMD_ix'
elif 'c' in options:
@@ -1104,7 +1124,7 @@ def handle_children(profile, hat, root):
parent_uses_ld_xxx = check_for_LD_XXX(profile)
sev_db.unload_variables()
sev_db.load_variables(get_profile_filename(profile))
sev_db.load_variables(get_profile_filename_from_profile_name(profile, True))
severity = sev_db.rank_path(exec_target, 'x')
# Prompt portion starts
@@ -1228,7 +1248,7 @@ def handle_children(profile, hat, root):
profile_changes[pid] = '%s' % profile
# Check profile exists for px
if not os.path.exists(get_profile_filename(exec_target)):
if not os.path.exists(get_profile_filename_from_attachment(exec_target, True)):
ynans = 'y'
if 'i' in exec_mode:
ynans = aaui.UI_YesNo(_('A profile for %s does not exist.\nDo you want to create one?') % exec_target, 'n')
@@ -1362,7 +1382,7 @@ def ask_the_questions(log_dict):
UI_SelectUpdatedRepoProfile(profile, p)
sev_db.unload_variables()
sev_db.load_variables(get_profile_filename(profile))
sev_db.load_variables(get_profile_filename_from_profile_name(profile, True))
# Sorted list of hats with the profile name coming first
hats = list(filter(lambda key: key != profile, sorted(log_dict[aamode][profile].keys())))
@@ -1769,7 +1789,7 @@ def set_logfile(filename):
def do_logprof_pass(logmark='', passno=0, log_pid=log_pid):
# set up variables for this pass
# transitions = hasher()
global existing_profiles
global active_profiles
global sev_db
# aa = hasher()
# profile_changes = hasher()
@@ -1786,13 +1806,13 @@ def do_logprof_pass(logmark='', passno=0, log_pid=log_pid):
if not sev_db:
sev_db = apparmor.severity.Severity(CONFDIR + '/severity.db', _('unknown'))
#print(pid)
#print(existing_profiles)
#print(active_profiles)
##if not repo_cf and cfg['repostory']['url']:
## repo_cfg = read_config('repository.conf')
## if not repo_cfg['repository'].get('enabled', False) or repo_cfg['repository]['enabled'] not in ['yes', 'no']:
## UI_ask_to_enable_repo()
log_reader = apparmor.logparser.ReadLog(log_pid, logfile, existing_profiles, profile_dir)
log_reader = apparmor.logparser.ReadLog(log_pid, logfile, active_profiles, profile_dir)
log = log_reader.read_log(logmark)
#read_log(logmark)
@@ -1867,13 +1887,11 @@ def save_profiles():
if aa[which][which].get('filename', False):
oldprofile = aa[which][which]['filename']
else:
oldprofile = get_profile_filename(which)
oldprofile = get_profile_filename_from_attachment(which, True)
try:
newprofile = serialize_profile_from_old_profile(aa[which], which, '')
except AttributeError:
# see https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1528139
newprofile = "###\n###\n### Internal error while generating diff, please use '%s' instead\n###\n###\n" % _('View Changes b/w (C)lean profiles')
serialize_options = {}
serialize_options['METADATA'] = True
newprofile = serialize_profile(aa[which], which, serialize_options)
aaui.UI_Changes(oldprofile, newprofile, comments=True)
@@ -2085,9 +2103,29 @@ def read_profile(file, active_profile):
if profile_data and active_profile:
attach_profile_data(aa, profile_data)
attach_profile_data(original_aa, profile_data)
for profile in profile_data: # TODO: also honor hats
name = profile_data[profile][profile]['name']
attachment = profile_data[profile][profile]['attachment']
filename = profile_data[profile][profile]['filename']
if not attachment and name.startswith('/'):
active_profiles.add(filename, name, name) # use name as name and attachment
else:
active_profiles.add(filename, name, attachment)
elif profile_data:
attach_profile_data(extras, profile_data)
for profile in profile_data: # TODO: also honor hats
name = profile_data[profile][profile]['name']
attachment = profile_data[profile][profile]['attachment']
filename = profile_data[profile][profile]['filename']
if not attachment and name.startswith('/'):
extra_profiles.add(filename, name, name) # use name as name and attachment
else:
extra_profiles.add(filename, name, attachment)
def attach_profile_data(profiles, profile_data):
# Make deep copy of data to avoid changes to
@@ -2179,9 +2217,6 @@ def parse_profile_data(data, file, do_include):
if pps_set_hat_external:
profile_data[profile][hat]['external'] = True
# Profile stored
existing_profiles[profile] = file
# save profile name and filename
profile_data[profile][hat]['name'] = profile
profile_data[profile][hat]['filename'] = file
@@ -2506,6 +2541,11 @@ def parse_profile_data(data, file, do_include):
else:
raise AppArmorException(_('Syntax Error: Unknown line found in file %(file)s line %(lineno)s:\n %(line)s') % { 'file': file, 'lineno': lineno + 1, 'line': line })
if lastline:
# lastline gets merged into line (and reset to None) when reading the next line.
# If it isn't empty, this means there's something unparseable at the end of the profile
raise AppArmorException(_('Syntax Error: Unknown line found in file %(file)s line %(lineno)s:\n %(line)s') % { 'file': file, 'lineno': lineno + 1, 'line': lastline })
# Below is not required I'd say
if not do_include:
for hatglob in cfg['required_hats'].keys():
@@ -2886,7 +2926,11 @@ def serialize_profile(profile_data, name, options):
# comment.replace('\\n', '\n')
# string += comment + '\n'
prof_filename = get_profile_filename(name)
if options and options.get('is_attachment'):
prof_filename = get_profile_filename_from_attachment(name, True)
else:
prof_filename = get_profile_filename_from_profile_name(name, True)
if filelist.get(prof_filename, False):
data += write_abi(filelist[prof_filename], 0)
data += write_alias(filelist[prof_filename], 0)
@@ -2920,429 +2964,18 @@ def serialize_parse_profile_start(line, file, lineno, profile, hat, prof_data_pr
return (profile, hat, attachment, flags, in_contained_hat, correct)
def serialize_profile_from_old_profile(profile_data, name, options):
data = []
string = ''
include_metadata = False
include_flags = True
prof_filename = get_profile_filename(name)
write_filelist = deepcopy(filelist[prof_filename])
write_prof_data = deepcopy(profile_data)
# XXX profile_data / write_prof_data contain only one profile with its hats
# XXX this will explode if a file contains multiple profiles, see https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1528139
# XXX fixing this needs lots of write_prof_data[hat] -> write_prof_data[profile][hat] changes (and of course also a change in the calling code)
# XXX (the better option is a full rewrite of serialize_profile_from_old_profile())
if options: # and type(options) == dict:
if options.get('METADATA', False):
include_metadata = True
if options.get('NO_FLAGS', False):
include_flags = False
if include_metadata:
string = '# Last Modified: %s\n' % time.asctime()
if (profile_data[name].get('repo', False) and
profile_data[name]['repo']['url'] and
profile_data[name]['repo']['user'] and
profile_data[name]['repo']['id']):
repo = profile_data[name]['repo']
string += '# REPOSITORY: %s %s %s\n' % (repo['url'], repo['user'], repo['id'])
elif profile_data[name]['repo']['neversubmit']:
string += '# REPOSITORY: NEVERSUBMIT\n'
if not os.path.isfile(prof_filename):
raise AppArmorException(_("Can't find existing profile to modify"))
# profiles_list = filelist[prof_filename].keys() # XXX
with open_file_read(prof_filename) as f_in:
profile = None
hat = None
write_methods = {'abi': write_abi,
'alias': write_alias,
'lvar': write_list_vars,
'include': write_includes,
'rlimit': write_rlimits,
'capability': write_capabilities,
'network': write_netdomain,
'dbus': write_dbus,
'mount': write_mount,
'signal': write_signal,
'ptrace': write_ptrace,
'pivot_root': write_pivot_root,
'unix': write_unix,
'link': write_links,
'file': write_file,
'change_profile': write_change_profile,
}
default_write_order = [ 'alias',
'lvar',
'include',
'rlimit',
'capability',
'network',
'dbus',
'mount',
'signal',
'ptrace',
'pivot_root',
'unix',
'link',
'file',
'change_profile',
]
# prof_correct = True # XXX correct?
segments = {'alias': False,
'lvar': False,
'include': False,
'rlimit': False,
'capability': False,
'network': False,
'dbus': False,
'mount': False,
'signal': True, # not handled otherwise yet
'ptrace': True, # not handled otherwise yet
'pivot_root': False,
'unix': False,
'link': False,
'file': False,
'change_profile': False,
'include_local_started': False, # unused
}
def write_prior_segments(prof_data, segments, line):
data = []
for segs in list(filter(lambda x: segments[x], segments.keys())):
depth = len(line) - len(line.lstrip())
data += write_methods[segs](prof_data, int(depth / 2))
segments[segs] = False
# delete rules from prof_data to avoid duplication (they are in data now)
if prof_data['allow'].get(segs, False):
prof_data['allow'].pop(segs)
if prof_data['deny'].get(segs, False):
prof_data['deny'].pop(segs)
if prof_data.get(segs, False):
t = type(prof_data[segs])
prof_data[segs] = t()
return data
#data.append('reading prof')
for line in f_in:
correct = True
line = line.rstrip('\n')
#data.append(' ')#data.append('read: '+line)
if RE_PROFILE_START.search(line):
(profile, hat, attachment, flags, in_contained_hat, correct) = serialize_parse_profile_start(
line, prof_filename, None, profile, hat, write_prof_data[hat]['profile'], write_prof_data[hat]['external'], correct)
if not write_prof_data[hat]['name'] == profile:
correct = False
if not write_filelist['profiles'][profile][hat] is True:
correct = False
if not write_prof_data[hat]['flags'] == flags:
correct = False
#Write the profile start
if correct:
if write_filelist:
data += write_alias(write_filelist, 0)
data += write_list_vars(write_filelist, 0)
data += write_includes(write_filelist, 0)
data.append(line)
else:
if write_prof_data[hat]['name'] == profile:
depth = len(line) - len(line.lstrip())
data += write_header(write_prof_data[name], int(depth / 2), name, False, include_flags)
elif RE_PROFILE_END.search(line):
# DUMP REMAINDER OF PROFILE
if profile:
depth = int(len(line) - len(line.lstrip()) / 2) + 1
# first write sections that were modified
#for segs in write_methods.keys():
for segs in default_write_order:
if segments[segs]:
data += write_methods[segs](write_prof_data[name], depth)
segments[segs] = False
# delete rules from write_prof_data to avoid duplication (they are in data now)
if write_prof_data[name]['allow'].get(segs, False):
write_prof_data[name]['allow'].pop(segs)
if write_prof_data[name]['deny'].get(segs, False):
write_prof_data[name]['deny'].pop(segs)
if write_prof_data[name].get(segs, False):
t = type(write_prof_data[name][segs])
write_prof_data[name][segs] = t()
# then write everything else
for segs in default_write_order:
data += write_methods[segs](write_prof_data[name], depth)
write_prof_data.pop(name)
#Append local includes
data.append(line)
if not in_contained_hat:
# Embedded hats
depth = int((len(line) - len(line.lstrip())) / 2)
pre2 = ' ' * (depth + 1)
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
if not profile_data[hat]['external']:
data.append('')
if profile_data[hat]['profile']:
data += list(map(str, write_header(profile_data[hat], depth + 1, hat, True, include_flags)))
else:
data += list(map(str, write_header(profile_data[hat], depth + 1, '^' + hat, True, include_flags)))
data += list(map(str, write_rules(profile_data[hat], depth + 2)))
data.append('%s}' % pre2)
# External hats
for hat in list(filter(lambda x: x != name, sorted(profile_data.keys()))):
if profile_data[hat].get('external', False):
data.append('')
data += list(map(lambda x: ' %s' % x, write_piece(profile_data, depth - 1, name, name, include_flags)))
data.append(' }')
if in_contained_hat:
#Hat processed, remove it
hat = profile
in_contained_hat = False
else:
profile = None
elif CapabilityRule.match(line):
cap = CapabilityRule.parse(line)
if write_prof_data[hat]['capability'].is_covered(cap, True, True):
if not segments['capability'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['capability'] = True
write_prof_data[hat]['capability'].delete(cap)
data.append(line)
else:
# To-Do
pass
elif RE_PROFILE_LINK.search(line):
matches = RE_PROFILE_LINK.search(line).groups()
audit = False
if matches[0]:
audit = True
allow = 'allow'
if matches[1] and matches[1].strip() == 'deny':
allow = 'deny'
subset = matches[3]
link = strip_quotes(matches[6])
value = strip_quotes(matches[7])
if not write_prof_data[hat][allow]['link'][link]['to'] == value:
correct = False
if not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_MAY_LINK:
correct = False
if subset and not write_prof_data[hat][allow]['link'][link]['mode'] & apparmor.aamode.AA_LINK_SUBSET:
correct = False
if audit and not write_prof_data[hat][allow]['link'][link]['audit'] & apparmor.aamode.AA_LINK_SUBSET:
correct = False
if correct:
if not segments['link'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['link'] = True
write_prof_data[hat][allow]['link'].pop(link)
data.append(line)
else:
# To-Do
pass
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'].delete(change_profile_obj)
data.append(line)
elif RE_PROFILE_ALIAS.search(line):
matches = RE_PROFILE_ALIAS.search(line).groups()
from_name = strip_quotes(matches[0])
to_name = strip_quotes(matches[1])
if profile:
if not write_prof_data[hat]['alias'][from_name] == to_name:
correct = False
else:
if not write_filelist['alias'][from_name] == to_name:
correct = False
if correct:
if not segments['alias'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['alias'] = True
if profile:
write_prof_data[hat]['alias'].pop(from_name)
else:
write_filelist['alias'].pop(from_name)
data.append(line)
else:
#To-Do
pass
elif RlimitRule.match(line):
rlimit_obj = RlimitRule.parse(line)
if write_prof_data[hat]['rlimit'].is_covered(rlimit_obj, True, True):
if not segments['rlimit'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['rlimit'] = True
write_prof_data[hat]['rlimit'].delete(rlimit_obj)
data.append(line)
else:
#To-Do
pass
elif RE_PROFILE_BOOLEAN.search(line):
matches = RE_PROFILE_BOOLEAN.search(line).groups()
bool_var = matches[0]
value = matches[1]
if not write_prof_data[hat]['lvar'][bool_var] == value:
correct = False
if correct:
if not segments['lvar'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['lvar'] = True
write_prof_data[hat]['lvar'].pop(bool_var)
data.append(line)
else:
#To-Do
pass
elif RE_PROFILE_VARIABLE.search(line):
matches = RE_PROFILE_VARIABLE.search(line).groups()
list_var = strip_quotes(matches[0])
var_operation = matches[1]
value = strip_quotes(matches[2])
var_set = hasher()
if var_operation == '+=':
correct = False # adding proper support for "add to variable" needs big changes
# (like storing a variable's "history" - where it was initially defined and what got added where)
# so just skip any comparison and assume a non-match
elif profile:
store_list_var(var_set, list_var, value, var_operation, prof_filename)
if not var_set[list_var] == write_prof_data['lvar'].get(list_var, False):
correct = False
else:
store_list_var(var_set, list_var, value, var_operation, prof_filename)
if not var_set[list_var] == write_filelist['lvar'].get(list_var, False):
correct = False
if correct:
if not segments['lvar'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['lvar'] = True
if profile:
write_prof_data[hat]['lvar'].pop(list_var)
else:
write_filelist['lvar'].pop(list_var)
data.append(line)
else:
#To-Do
pass
elif re_match_include(line):
include_name = re_match_include(line)
if profile:
if write_prof_data[hat]['include'].get(include_name, False):
if not segments['include'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['include'] = True
write_prof_data[hat]['include'].pop(include_name)
data.append(line)
else:
if write_filelist['include'].get(include_name, False):
write_filelist['include'].pop(include_name)
data.append(line)
elif NetworkRule.match(line):
network_obj = NetworkRule.parse(line)
if write_prof_data[hat]['network'].is_covered(network_obj, True, True):
if not segments['network'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['network'] = True
write_prof_data[hat]['network'].delete(network_obj)
data.append(line)
elif RE_PROFILE_CHANGE_HAT.search(line):
# "^hat," declarations are no longer supported, ignore them and don't write out the line
# (parse_profile_data() already prints a warning about that)
pass
elif RE_PROFILE_HAT_DEF.search(line):
matches = RE_PROFILE_HAT_DEF.search(line)
in_contained_hat = True
hat = matches.group('hat')
hat = strip_quotes(hat)
flags = matches.group('flags')
if not write_prof_data[hat]['flags'] == flags:
correct = False
if not write_filelist['profile'][profile][hat]:
correct = False
if correct:
data.append(line)
else:
#To-Do
pass
elif FileRule.match(line):
# leading permissions could look like a keyword, therefore handle file rules after everything else
file_obj = FileRule.parse(line)
if write_prof_data[hat]['file'].is_covered(file_obj, True, True):
if not segments['file'] and True in segments.values():
data += write_prior_segments(write_prof_data[name], segments, line)
segments['file'] = True
write_prof_data[hat]['file'].delete(file_obj)
data.append(line)
else:
#To-Do
pass
else:
if correct:
data.append(line)
else:
#To-Do
pass
# data.append('prof done')
# if write_filelist:
# data += write_alias(write_filelist, 0)
# data += write_list_vars(write_filelist, 0)
# data += write_includes(write_filelist, 0)
# data.append('from filelist over')
# data += write_piece(write_prof_data, 0, name, name, include_flags)
string += '\n'.join(data)
return string + '\n'
def write_profile_ui_feedback(profile):
def write_profile_ui_feedback(profile, is_attachment=False):
aaui.UI_Info(_('Writing updated profile for %s.') % profile)
write_profile(profile)
write_profile(profile, is_attachment)
def write_profile(profile):
def write_profile(profile, is_attachment=False):
prof_filename = None
if aa[profile][profile].get('filename', False):
prof_filename = aa[profile][profile]['filename']
elif is_attachment:
prof_filename = get_profile_filename_from_attachment(profile, True)
else:
prof_filename = get_profile_filename(profile)
prof_filename = get_profile_filename_from_profile_name(profile, True)
newprof = tempfile.NamedTemporaryFile('w', suffix='~', delete=False, dir=profile_dir)
if os.path.exists(prof_filename):
@@ -3352,8 +2985,7 @@ def write_profile(profile):
#os.chmod(newprof.name, permission_600)
pass
serialize_options = {}
serialize_options['METADATA'] = True
serialize_options = {'METADATA': True, 'is_attachment': is_attachment}
profile_string = serialize_profile(aa[profile], profile, serialize_options)
newprof.write(profile_string)
@@ -3476,7 +3108,7 @@ def reload_base(bin_path):
if not check_for_apparmor():
return None
prof_filename = get_profile_filename(bin_path)
prof_filename = get_profile_filename_from_profile_name(bin_path, True)
# XXX use reload_profile() from tools.py instead (and don't hide output in /dev/null)
subprocess.call("cat '%s' | %s -I%s -r >/dev/null 2>&1" % (prof_filename, parser, profile_dir), shell=True)

View File

@@ -1,6 +1,6 @@
# ----------------------------------------------------------------------
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
# Copyright (C) 2015-2016 Christian Boltz <apparmor@cboltz.de>
# Copyright (C) 2015-2018 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
@@ -43,11 +43,11 @@ class ReadLog:
# used to pre-filter log lines so that we hand over only relevant lines to LibAppArmor parsing
RE_LOG_ALL = re.compile('(' + '|'.join(RE_log_parts) + ')')
def __init__(self, pid, filename, existing_profiles, profile_dir):
def __init__(self, pid, filename, active_profiles, profile_dir):
self.filename = filename
self.profile_dir = profile_dir
self.pid = pid
self.existing_profiles = existing_profiles
self.active_profiles = active_profiles
self.log = []
self.debug_logger = DebugLogger('ReadLog')
self.LOG = None
@@ -446,15 +446,16 @@ class ReadLog:
def profile_exists(self, program):
"""Returns True if profile exists, False otherwise"""
# Check cache of profiles
if self.existing_profiles.get(program, False):
if self.active_profiles.filename_from_profile_name(program):
return True
# Check the disk for profile
prof_path = self.get_profile_filename(program)
#print(prof_path)
if os.path.isfile(prof_path):
# Add to cache of profile
self.existing_profiles[program] = prof_path
return True
raise AppArmorBug('This should never happen, please open a bugreport!')
# self.active_profiles[program] = prof_path
# return True
return False
def get_profile_filename(self, profile):

View File

@@ -0,0 +1,73 @@
# ----------------------------------------------------------------------
# Copyright (C) 2018 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# ----------------------------------------------------------------------
from apparmor.aare import AARE
from apparmor.common import AppArmorBug, AppArmorException
# setup module translations
from apparmor.translations import init_translation
_ = init_translation()
class ProfileList:
''' Stores the list of profiles (both name and attachment) and in which files they live '''
def __init__(self):
self.profile_names = {} # profile name -> filename
self.attachments = {} # attachment -> filename
self.attachments_AARE = {} # AARE(attachment) -> filename
def add(self, filename, profile_name, attachment):
''' Add the given profile and attachment to the list '''
if not filename:
raise AppArmorBug('Empty filename given to ProfileList')
if not profile_name and not attachment:
raise AppArmorBug('Neither profile name or attachment given')
if profile_name in self.profile_names:
raise AppArmorException(_('Profile %(profile_name)s exists in %(filename)s and %(filename2)s' % {'profile_name': profile_name, 'filename': filename, 'filename2': self.profile_names[profile_name]}))
if attachment in self.attachments:
raise AppArmorException(_('Profile for %(profile_name)s exists in %(filename)s and %(filename2)s' % {'profile_name': attachment, 'filename': filename, 'filename2': self.attachments[attachment]}))
if profile_name:
self.profile_names[profile_name] = filename
if attachment:
self.attachments[attachment] = filename
self.attachments_AARE[attachment] = AARE(attachment, True)
def filename_from_profile_name(self, name):
''' Return profile filename for the given profile name, or None '''
return self.profile_names.get(name, None)
def filename_from_attachment(self, attachment):
''' Return profile filename for the given attachment/executable path, or None '''
if not attachment.startswith( ('/', '@', '{') ):
raise AppArmorBug('Called filename_from_attachment with non-path attachment: %s' % attachment)
# plain path
if self.attachments.get(attachment):
return self.attachments[attachment]
# try AARE matches to cover profile names with alternations and wildcards
for path in self.attachments.keys():
if self.attachments_AARE[path].match(attachment):
return self.attachments[path] # XXX this returns the first match, not necessarily the best one
return None # nothing found

View File

@@ -1,5 +1,6 @@
# ----------------------------------------------------------------------
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
# Copyright (C) 2015-2018 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
@@ -66,12 +67,12 @@ class aa_tools:
profile = fq_path
else:
program = fq_path
profile = apparmor.get_profile_filename(fq_path)
profile = apparmor.get_profile_filename_from_attachment(fq_path, True)
else:
which = apparmor.which(p)
if which is not None:
program = apparmor.get_full_path(which)
profile = apparmor.get_profile_filename(program)
profile = apparmor.get_profile_filename_from_attachment(program, True)
elif os.path.exists(os.path.join(apparmor.profile_dir, p)):
program = None
profile = apparmor.get_full_path(os.path.join(apparmor.profile_dir, p)).strip()
@@ -190,7 +191,7 @@ class aa_tools:
apparmor.check_qualifiers(program)
if os.path.exists(apparmor.get_profile_filename(program)) and not self.force:
if os.path.exists(apparmor.get_profile_filename_from_attachment(program, True)) and not self.force:
aaui.UI_Info(_('Profile for %s already exists - skipping.') % program)
else:
apparmor.autodep(program)
@@ -198,7 +199,7 @@ class aa_tools:
apparmor.reload(program)
def clean_profile(self, program):
filename = apparmor.get_profile_filename(program)
filename = apparmor.get_profile_filename_from_attachment(program, True)
import apparmor.cleanprofile as cleanprofile
prof = cleanprofile.Prof(filename)
cleanprof = cleanprofile.CleanProf(True, prof, prof)
@@ -220,14 +221,14 @@ class aa_tools:
while ans != 'CMD_SAVE_CHANGES':
ans, arg = q.promptUser()
if ans == 'CMD_SAVE_CHANGES':
apparmor.write_profile_ui_feedback(program)
apparmor.write_profile_ui_feedback(program, True)
self.reload_profile(filename)
elif ans == 'CMD_VIEW_CHANGES':
#oldprofile = apparmor.serialize_profile(apparmor.original_aa[program], program, '')
newprofile = apparmor.serialize_profile(apparmor.aa[program], program, '')
newprofile = apparmor.serialize_profile(apparmor.aa[program], program, {'is_attachment': True})
aaui.UI_Changes(filename, newprofile, comments=True)
else:
apparmor.write_profile_ui_feedback(program)
apparmor.write_profile_ui_feedback(program, True)
self.reload_profile(filename)
else:
raise apparmor.AppArmorException(_('The profile for %s does not exists. Nothing to clean.') % program)

View File

@@ -1,7 +1,7 @@
#! /usr/bin/python3
# ------------------------------------------------------------------
#
# Copyright (C) 2015 Christian Boltz <apparmor@cboltz.de>
# Copyright (C) 2015-2018 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
@@ -17,6 +17,7 @@ from apparmor.common import open_file_read
import apparmor.aa
from apparmor.logparser import ReadLog
from apparmor.profile_list import ProfileList
class TestLibapparmorTestMulti(AATest):
'''Parse all libraries/libapparmor/testsuite/test_multi tests and compare the result with the *.out files'''
@@ -222,9 +223,15 @@ class TestLogToProfile(AATest):
if '//' in profile:
profile, hat = profile.split('//')
apparmor.aa.existing_profiles = {profile: profile_dummy_file}
apparmor.aa.active_profiles = ProfileList()
log_reader = ReadLog(dict(), logfile, apparmor.aa.existing_profiles, '')
# optional for now, might be needed one day
# if profile.startswith('/'):
# apparmor.aa.active_profiles.add(profile_dummy_file, profile, profile)
# else:
apparmor.aa.active_profiles.add(profile_dummy_file, profile, '')
log_reader = ReadLog(dict(), logfile, apparmor.aa.active_profiles, '')
log = log_reader.read_log('')
for root in log:

View File

@@ -40,11 +40,6 @@ skip_startswith = (
# testcases that should raise an exception, but don't
exception_not_raised = [
# most abi/bad_* aren't detected as bad by the basic implementation in the tools
'abi/bad_1.sd',
'abi/bad_2.sd',
'abi/bad_3.sd',
'abi/bad_4.sd',
'abi/bad_5.sd',
'abi/bad_10.sd',
'abi/bad_11.sd',
'abi/bad_12.sd',
@@ -155,13 +150,9 @@ exception_not_raised = [
'unix/bad_regex_04.sd',
'unix/bad_shutdown_1.sd',
'unix/bad_shutdown_2.sd',
'vars/boolean/boolean_bad_1.sd',
'vars/boolean/boolean_bad_2.sd',
'vars/boolean/boolean_bad_3.sd',
'vars/boolean/boolean_bad_4.sd',
'vars/boolean/boolean_bad_6.sd',
'vars/boolean/boolean_bad_7.sd',
'vars/boolean/boolean_bad_8.sd',
'vars/vars_bad_3.sd',
'vars/vars_bad_4.sd',
'vars/vars_bad_5.sd',
@@ -200,7 +191,6 @@ exception_not_raised = [
'vars/vars_recursion_2.sd',
'vars/vars_recursion_3.sd',
'vars/vars_recursion_4.sd',
'vars/vars_simple_assignment_10.sd',
'vars/vars_simple_assignment_3.sd',
'vars/vars_simple_assignment_8.sd',
'vars/vars_simple_assignment_9.sd',

View File

@@ -0,0 +1,114 @@
#! /usr/bin/python3
# ------------------------------------------------------------------
#
# Copyright (C) 2018 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
import unittest
from common_test import AATest, setup_all_loops
from apparmor.common import AppArmorBug, AppArmorException
from apparmor.profile_list import ProfileList
class TestAdd(AATest):
def AASetup(self):
self.pl = ProfileList()
def testEmpty(self):
self.assertEqual(self.pl.profile_names, {})
self.assertEqual(self.pl.attachments, {})
def testAdd_1(self):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
self.assertEqual(self.pl.profile_names, {'foo': '/etc/apparmor.d/bin.foo'})
self.assertEqual(self.pl.attachments, {'/bin/foo': '/etc/apparmor.d/bin.foo'})
def testAdd_2(self):
self.pl.add('/etc/apparmor.d/bin.foo', None, '/bin/foo')
self.assertEqual(self.pl.profile_names, {})
self.assertEqual(self.pl.attachments, {'/bin/foo': '/etc/apparmor.d/bin.foo'})
def testAdd_3(self):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', None)
self.assertEqual(self.pl.profile_names, {'foo': '/etc/apparmor.d/bin.foo'})
self.assertEqual(self.pl.attachments, {})
def testAddError_1(self):
with self.assertRaises(AppArmorBug):
self.pl.add('', 'foo', '/bin/foo') # no filename
def testAddError_2(self):
with self.assertRaises(AppArmorBug):
self.pl.add('/etc/apparmor.d/bin.foo', None, None) # neither attachment or profile name
def testAddError_twice_1(self):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
with self.assertRaises(AppArmorException):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
def testAddError_twice_2(self):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
with self.assertRaises(AppArmorException):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', None)
def testAddError_twice_3(self):
self.pl.add('/etc/apparmor.d/bin.foo', None, '/bin/foo')
with self.assertRaises(AppArmorException):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
def testAddError_twice_4(self):
self.pl.add('/etc/apparmor.d/bin.foo', None, '/bin/foo')
with self.assertRaises(AppArmorException):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
def testAddError_twice_5(self):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', None)
with self.assertRaises(AppArmorException):
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
class TestFilename_from_profile_name(AATest):
tests = [
('foo', '/etc/apparmor.d/bin.foo'),
('/bin/foo', None),
('bar', None),
]
def AASetup(self):
self.pl = ProfileList()
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
def _run_test(self, params, expected):
self.assertEqual(self.pl.filename_from_profile_name(params), expected)
class TestFilename_from_attachment(AATest):
tests = [
('/bin/foo', '/etc/apparmor.d/bin.foo'),
('/bin/baz', '/etc/apparmor.d/bin.baz'),
('/bin/foobar', '/etc/apparmor.d/bin.foobar'),
('@{foo}', None), # XXX variables not supported yet (and @{foo} isn't defined in this test)
('/bin/404', None),
]
def AASetup(self):
self.pl = ProfileList()
self.pl.add('/etc/apparmor.d/bin.foo', 'foo', '/bin/foo')
self.pl.add('/etc/apparmor.d/bin.baz', 'baz', '/bin/ba*')
self.pl.add('/etc/apparmor.d/bin.foobar', 'foobar', '/bin/foo{bar,baz}')
def _run_test(self, params, expected):
self.assertEqual(self.pl.filename_from_attachment(params), expected)
def test_non_path_attachment(self):
with self.assertRaises(AppArmorBug):
self.pl.filename_from_attachment('foo')
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=1)