2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-01 06:45:38 +00:00

Compare commits

...

40 Commits

Author SHA1 Message Date
John Johansen
af4808b5f6 Release: Bump revisions in preparation for 2.13.2 release
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-12-21 03:16:02 -08:00
John Johansen
f811fa9951 Merge branch 'cboltz-2.13-dnsmasq-name' into 'apparmor-2.13'
[2.12+2.13] 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

I propose this revert for 2.12 and 2.13 (older branches didn't get the named profile)

PR: https://gitlab.com/apparmor/apparmor/merge_requests/290

Acked-by: John Johansen <john.johansen@canonical.com>
2018-12-16 05:19:20 +00:00
Christian Boltz
a68e6426f4 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
2018-12-11 19:16:58 +01:00
Christian Boltz
7356f51425 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:32:56 -08:00
Christian Boltz
ef21e9ded7 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:17:29 -08:00
Jamie Strandboge
1a0016ff17 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:14 -08:00
John Johansen
3607865b18 Merge branch 'cboltz-profile-list-2.13' into 'apparmor-2.13'
[2.12+2.13] Replace "existing_profiles" & fix minitools for named profiles

(This is the 2.13 version of !249 (merged) which had a few merge conflicts in the 2.13 branch, and needs a little change (last commit) on top)

This patchset introduces the ProfileList class which replaces "existing_profiles" in aa.py and fixes some bugs in aa-complain and the other minitools:

* 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" ;-)

See the individual commit messages for details.

All changes survived my tests (both manually and unittests), but as always when doing bigger changes to aa.py, more manual testing is always welcome ;-)

I propose this patch for 2.12 and 2.13.

Acked-by: John Johansen <john.johansen@canonical.com>
PR: https://gitlab.com/apparmor/apparmor/merge_requests/268
2018-12-08 06:36:34 +00:00
Petr Vorel
597e17eb67 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:27:06 -08:00
Petr Vorel
7dce58987f 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:26:21 -08:00
Christian Boltz
c044757de9 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:22 +00:00
Vincas Dargis
6249579842 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
2018-11-22 17:35:59 +00:00
Christian Boltz
37edb354ff 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.

(cherry picked from commit 8b4e76a7d5)
2018-11-18 21:41:48 +01:00
Christian Boltz
b8dc8d1394 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:02:23 -08:00
intrigeri
1f2eb0bbbf Merge branch 'use-sys-213' into 'apparmor-2.13'
Backport to 2.13: Use @{sys} tunable in profiles and abstractions

See merge request apparmor/apparmor!265
2018-11-11 18:53:22 +00:00
Christian Boltz
2296c30af5 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.
2018-11-11 18:49:42 +01:00
Christian Boltz
aa328cb058 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.

(cherry picked from commit 4d722f1839)
2018-11-11 18:33:56 +01:00
Christian Boltz
1d183660d5 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.

(cherry picked from commit 789c4658e2)
2018-11-11 18:33:56 +01:00
Christian Boltz
7b07832459 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.

(cherry picked from commit 8809218ac8)
2018-11-11 18:33:56 +01:00
Christian Boltz
b6c96f3933 split off get_new_profile_filename()
... and call it from get_profile_filename_* if get_new is True
(= always with the current code)

(cherry picked from commit a6b8d14908)
2018-11-11 18:33:56 +01:00
Christian Boltz
ad236a59b8 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.

(cherry picked from commit ec741424f8)
2018-11-11 18:33:55 +01:00
Christian Boltz
f8b95d036d 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.

(cherry picked from commit bc783372b8)
2018-11-11 18:33:53 +01:00
Christian Boltz
f4d7f8ae57 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
2018-11-11 17:28:17 +00:00
Christian Boltz
1b32d764ef delete serialize_profile_from_old_profile()
... which is unused since the last commit.

Note: unlike 0eb12a8cbd, this commit does
_not_ delete several write_* function that were only used by this
function. Verifying that these functions are really unused is not worth
the effort in the 2.13 branch.

(cherry picked from commit 0eb12a8cbd -
but only apply partially)
2018-11-11 15:20:14 +01:00
Christian Boltz
dd4c2b05ea use serialize_profile() for the new profile in (V)iew Changes
... instead of serialize_profile_from_old_profile()

This will give a realistic preview of the changes (serialize_profile()
is also used when actually writing the profile) and replaces the
known-buggy serialize_profile_from_old_profile() with known-working
code.

It also fixes the issue reported in
    https://bugs.launchpad.net/ubuntu/+source/apparmor/+bug/1528139
which means we no longer need the workaround of catching AttributeError
(verified in manual before/after test)

References:
- https://bugs.launchpad.net/apparmor/+bug/1394788
- https://bugs.launchpad.net/bugs/1528139
- https://bugs.launchpad.net/apparmor/+bug/1404893

(cherry picked from commit 469eb444de)
2018-11-11 15:14:24 +01:00
Vincas Dargis
314617014a Add vulkan abstraction
Add abstraction for Vulkan API specific file paths.
2018-11-11 10:49:41 +02:00
Vincas Dargis
41ff006f3d 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
2018-11-11 10:18:31 +02:00
Christian Boltz
7fc843d8d0 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:14:51 +00:00
Christian Boltz
fc18647fba 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:16 +00:00
Christian Boltz
064521c236 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:13 +00:00
intrigeri
566ad1fefa 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:03:41 +00:00
Christian Boltz
8def66134d 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:42 +00:00
John Johansen
8661ebcb79 parser: fix failures due to -M only setting compile-features
Split the features file into compile features and kernel features
which is needed for policy versioning and the new caching scheme.

A new flag --kernel-features was added to set the kernel features but
unfortunately -M, --features-file was setup to only specify the
compile features, when it used to effectively specify both the
compile and kernel features.

This broke existing uses of -M.

Fix this by having -M specify both the compile and kernel features,
and a new flag --compile-features that can be used to specify the
compile fature set separate from the kernel feature set.

sbeattie> fixed up error message to refer to compile features when
--compile-features argument fails.

Backport-requested-by: intrigeri <intrigeri@debian.org>
PR: https://gitlab.com/apparmor/apparmor/merge_requests/104
(cherry picked from commit e83fa67edf)
Fixes: 9e48a5da5e ("parser: split kernel features from compile features.")
Signed-off-by: John Johansen <john.johansen@canonical.com>
Signed-off-by: Steve Beattie <steve.beattie@canonical.com>
2018-10-21 19:13:35 -07:00
John Johansen
3ee32c7ed7 Merge branch 'cboltz-2.13-gitignore' into 'apparmor-2.13'
[2.12+2.13] backport some .gitignore additions

I propose this patch for 2.12 and 2.13, which will bring the .gitignore in sync with master.

PR: https://gitlab.com/apparmor/apparmor/merge_requests/241
Acked-by: John Johansen <john.johansen@canonical.com>
2018-10-22 00:29:43 +00:00
Christian Boltz
9687b44842 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:13 +00:00
intrigeri
fac1e427f1 Don't hard code the location of netinet/in.h.
This "will break with non-glibc libcs on Debian and with glibc headers moved to
multiarch locations" (https://bugs.debian.org/798955). Patch based on the one
proposed by Helmut Grohne <helmut@subdivi.de>, amended to replace hard coded
"gcc" with "$(CC)".

PR: https://gitlab.com/apparmor/apparmor/merge_requests/245
Bug-Debian: https://bugs.debian.org/909966
(cherry picked from commit 2d91211842)
Signed-off-by: John Johansen <john.johansen@canonical.com>
2018-10-20 23:32:56 -07:00
Christian Boltz
37d176e72f Fix syntax error in rc.apparmor.functions
This bug was introduced in
- https://gitlab.com/apparmor/apparmor/merge_requests/230
- commit c974dd0d07 (master)
- commit 9987a7ec9c (2.13 branch)
2018-10-20 16:00:56 +00:00
Christian Boltz
6f70502ad1 Merge branch 'test-includes' into 'master'
profiles/Makefile: test abstractions against apparmor_parser

See merge request apparmor/apparmor!237

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

Pre-acked for 2.10..2.12 after removing the --config-file option which is not supported in these branches.

(cherry picked from commit 2863e20f37)

dc7ae28d profiles/Makefile: test abstractions against apparmor_parser
2018-10-17 22:21:03 +00:00
Christian Boltz
37e64d99d1 Merge branch 'aa-notify-manpage' into 'master'
aa-notify man page: update user's configuration file path

See merge request apparmor/apparmor!239

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

(cherry picked from commit f920915dd3)

2209e09a aa-notify man page: update user's configuration file path
2018-10-16 15:55:58 +00:00
Christian Boltz
e17d974330 add utils/test/common_test.pyc to gitignore
(cache file that gets created when running the tests with python2)

(cherry picked from commit 63d17ecf16)
2018-10-14 20:35:57 +02:00
Christian Boltz
1f884d7612 add libapparmor/src/PMurHash.{o,lo} to gitignore
(cherry picked from commit db92d96e68)
2018-10-14 20:35:28 +02:00
58 changed files with 500 additions and 571 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.13.1
2.13.2

View File

@@ -42,8 +42,8 @@ scanner.h: scanner.l
scanner.c: scanner.l
af_protos.h: /usr/include/netinet/in.h
LC_ALL=C sed -n -e "/IPPROTO_MAX/d" -e "s/^\#define[ \\t]\\+IPPROTO_\\([A-Z0-9_]\\+\\)\\(.*\\)$$/AA_GEN_PROTO_ENT(\\UIPPROTO_\\1, \"\\L\\1\")/p" $< > $@
af_protos.h:
echo '#include <netinet/in.h>' | $(CC) -E -dM - | LC_ALL=C sed -n -e "/IPPROTO_MAX/d" -e "s/^\#define[ \\t]\\+IPPROTO_\\([A-Z0-9_]\\+\\)\\(.*\\)$$/AA_GEN_PROTO_ENT(\\UIPPROTO_\\1, \"\\L\\1\")/p" > $@
lib_LTLIBRARIES = libapparmor.la
noinst_HEADERS = grammar.h parser.h scanner.h af_protos.h private.h PMurHash.h

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

@@ -194,7 +194,8 @@ static void display_usage(const char *command)
"-I n, --Include n Add n to the search path\n"
"-f n, --subdomainfs n Set location of apparmor filesystem\n"
"-m n, --match-string n Use only features n\n"
"-M n, --features-file n Compile features set in file n\n"
"-M n, --features-file n Set compile & kernel features to file n\n"
"--compile-features n Compile features set in file n\n"
"--kernel-features n Kernel features set in file n\n"
"-n n, --namespace n Set Namespace for the profile\n"
"-X, --readimpliesX Map profile read permissions to mr\n"
@@ -535,14 +536,21 @@ static int process_arg(int c, char *optarg)
}
break;
case 'M':
if (compile_features)
aa_features_unref(compile_features);
if (kernel_features)
aa_features_unref(kernel_features);
if (aa_features_new(&compile_features, AT_FDCWD, optarg)) {
fprintf(stderr,
"Failed to load features from '%s': %m\n",
optarg);
exit(1);
}
kernel_features = aa_features_ref(compile_features);
break;
case 138:
if (kernel_features)
aa_features_unref(kernel_features);
if (aa_features_new(&kernel_features, AT_FDCWD, optarg)) {
fprintf(stderr,
"Failed to load kernel features from '%s': %m\n",
@@ -550,6 +558,16 @@ static int process_arg(int c, char *optarg)
exit(1);
}
break;
case 139:
if (compile_features)
aa_features_unref(compile_features);
if (aa_features_new(&compile_features, AT_FDCWD, optarg)) {
fprintf(stderr,
"Failed to load compile features from '%s': %m\n",
optarg);
exit(1);
}
break;
case 'q':
conf_verbose = 0;
conf_quiet = 1;

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
@@ -128,7 +130,7 @@ skip_profile() {
return 2
fi
if echo "${profile}" | egrep -q '^.+\.new-[0-9\.]+_[0-9]+$'; then
return 2 ;;
return 2
fi
return 0

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} --config-file=../parser/tst/parser.conf -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} --config-file=../parser/tst/parser.conf -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

@@ -4,6 +4,5 @@
# needs to enumerate graphic devices (as with drmParsePciDeviceInfo() from
# libdrm).
# TODO: use @{sys} after it's moved into tunables/kernelvars (LP: #1728551)
/sys/devices/pci[0-9]*/**/{device,subsystem_device,subsystem_vendor,uevent,vendor} r,
@{sys}/devices/pci[0-9]*/**/{device,subsystem_device,subsystem_vendor,uevent,vendor} r,

View File

@@ -17,7 +17,7 @@
@{PROC}/driver/nvidia/params r,
@{PROC}/modules r,
/sys/devices/system/memory/block_size_bytes r,
@{sys}/devices/system/memory/block_size_bytes r,
owner @{HOME}/.nv/ w,
owner @{HOME}/.nv/GLCache/ rw,

View File

@@ -4,7 +4,7 @@
# System files
/etc/OpenCL/** r,
/sys/bus/pci/devices/ r, # libpocl.so -> libhwlock.so, libnvidia-opencl.so, beignet/libcl.so -> libdrm_intel.so
/sys/devices/system/node/ r, # for clGetPlatformIDs() from libOpenCL.so
/sys/devices/system/node/node[0-9]*/meminfo r, # for clGetPlatformIDs() from libOpenCL.so
@{sys}/bus/pci/devices/ r, # libpocl.so -> libhwlock.so, libnvidia-opencl.so, beignet/libcl.so -> libdrm_intel.so
@{sys}/devices/system/node/ r, # for clGetPlatformIDs() from libOpenCL.so
@{sys}/devices/system/node/node[0-9]*/meminfo r, # for clGetPlatformIDs() from libOpenCL.so

View File

@@ -12,6 +12,6 @@
# System files
/dev/dri/card[0-9]* rw, # beignet/libcl.so
/sys/devices/pci[0-9]*/**/{class,config,resource,revision} r, # libcl.so -> libdrm_intel.so -> libpciaccess.so (move to dri-enumerate ?)
@{sys}/devices/pci[0-9]*/**/{class,config,resource,revision} r, # libcl.so -> libdrm_intel.so -> libpciaccess.so (move to dri-enumerate ?)
/usr/lib/@{multiarch}/beignet/** r,

View File

@@ -16,8 +16,8 @@
# libnvidia-opencl.so rules:
/dev/nvidia-uvm rw,
/dev/nvidia-uvm-tools rw,
/sys/devices/pci[0-9]*/**/config r,
/sys/devices/system/memory/block_size_bytes r,
@{sys}/devices/pci[0-9]*/**/config r,
@{sys}/devices/system/memory/block_size_bytes r,
/usr/share/nvidia/** r,
@{PROC}/devices r,
@{PROC}/sys/vm/mmap_min_addr r,

View File

@@ -11,22 +11,22 @@
# System files
/ r, # libpocl.so -> libhwloc.so
/sys/bus/pci/slots/ r, # libpocl.so -> hwloc_topology_load() from libhwloc.so
/sys/bus/{cpu,node}/devices/ r, # libpocl.so -> libhwlock.so
/sys/class/net/ r, # libpocl.so -> hwloc_pci_traverse_lookuposdevices_cb() from libhwloc.so
/sys/devices/pci[0-9]*/**/ r, # for libpocl -> hwloc_linux_lookup_block_class() from libhwloc.so
/sys/devices/pci[0-9]*/**/block/*/dev r, # libpocl.so -> hwloc_linux_lookup_host_block_class() from libhwloc.so
/sys/devices/pci[0-9]*/**/{class,local_cpus} r, # libpocl.so -> libhwlock.so
/sys/devices/pci[0-9]*/*/net/*/address r, # libpocl.so -> hwloc_pci_traverse_lookuposdevices_cb() from libhwloc.so
/sys/devices/system/cpu/ r, # libpocl.so -> libnuma.so
/sys/devices/system/cpu/cpu[0-9]*/cache/index[0-9]*/* r, # libpocl.so -> libhwloc.so
/sys/devices/system/cpu/cpu[0-9]*/online r, # libpocl.so -> libhwlock.so
/sys/devices/system/cpu/cpu[0-9]*/topology/* r, # *_siblings, physical_package_id and lot's of others, for libpocl.so -> libhwloc.so
/sys/devices/system/cpu/cpufreq/policy[0-9]*/* r, # for clGetPlatformIDs() from libpocl.so
/sys/devices/system/cpu/possible r, # libpocl.so -> libhwloc.so
/sys/devices/virtual/dmi/id/{,*} r, # libpocl.so -> libhwloc.so
/sys/fs/cgroup/cpuset/cpuset.{cpus,mems} r, # libpocl.so -> libhwloc.so
/sys/kernel/mm/hugepages{/,/**} r, # libpocl.so -> libhwloc.so
@{sys}/bus/pci/slots/ r, # libpocl.so -> hwloc_topology_load() from libhwloc.so
@{sys}/bus/{cpu,node}/devices/ r, # libpocl.so -> libhwlock.so
@{sys}/class/net/ r, # libpocl.so -> hwloc_pci_traverse_lookuposdevices_cb() from libhwloc.so
@{sys}/devices/pci[0-9]*/**/ r, # for libpocl -> hwloc_linux_lookup_block_class() from libhwloc.so
@{sys}/devices/pci[0-9]*/**/block/*/dev r, # libpocl.so -> hwloc_linux_lookup_host_block_class() from libhwloc.so
@{sys}/devices/pci[0-9]*/**/{class,local_cpus} r, # libpocl.so -> libhwlock.so
@{sys}/devices/pci[0-9]*/*/net/*/address r, # libpocl.so -> hwloc_pci_traverse_lookuposdevices_cb() from libhwloc.so
@{sys}/devices/system/cpu/ r, # libpocl.so -> libnuma.so
@{sys}/devices/system/cpu/cpu[0-9]*/cache/index[0-9]*/* r, # libpocl.so -> libhwloc.so
@{sys}/devices/system/cpu/cpu[0-9]*/online r, # libpocl.so -> libhwlock.so
@{sys}/devices/system/cpu/cpu[0-9]*/topology/* r, # *_siblings, physical_package_id and lot's of others, for libpocl.so -> libhwloc.so
@{sys}/devices/system/cpu/cpufreq/policy[0-9]*/* r, # for clGetPlatformIDs() from libpocl.so
@{sys}/devices/system/cpu/possible r, # libpocl.so -> libhwloc.so
@{sys}/devices/virtual/dmi/id/{,*} r, # libpocl.so -> libhwloc.so
@{sys}/fs/cgroup/cpuset/cpuset.{cpus,mems} r, # libpocl.so -> libhwloc.so
@{sys}/kernel/mm/hugepages{/,/**} r, # libpocl.so -> libhwloc.so
/usr/share/pocl/** r,
/{,var/}run/udev/data/*:* r, # libpocl.so -> hwloc_linux_block_class_fillinfos() from libhwloc.so

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

@@ -24,8 +24,8 @@ profile nvidia_modprobe {
/dev/nvidia-uvm w,
/dev/nvidia-uvm-tools w,
/sys/bus/pci/devices/ r,
/sys/devices/pci[0-9]*/**/config r,
@{sys}/bus/pci/devices/ r,
@{sys}/devices/pci[0-9]*/**/config r,
@{PROC}/devices r,
@{PROC}/modules r,
@{PROC}/sys/kernel/modprobe r,
@@ -51,9 +51,9 @@ profile nvidia_modprobe {
/etc/modprobe.d/{,*.conf} r,
/etc/nvidia/current/*.conf r,
/sys/module/ipmi_devintf/initstate r,
/sys/module/ipmi_msghandler/initstate r,
/sys/module/nvidia/initstate r,
@{sys}/module/ipmi_devintf/initstate r,
@{sys}/module/ipmi_msghandler/initstate r,
@{sys}/module/nvidia/initstate r,
@{PROC}/cmdline r,
}

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,7 +29,7 @@
/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,
/usr/share/dovecot/protocols.d/** r,
@@ -37,7 +37,7 @@
#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

@@ -98,7 +98,8 @@ System-wide configuration for B<aa-notify> is done via
# OPTIONAL - custom notification message footer
message_footer="For more information visit https://foo.com"
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():
@@ -2880,7 +2920,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)
@@ -2914,429 +2958,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):
@@ -3346,8 +2979,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)
@@ -3470,7 +3102,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
@@ -44,11 +44,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
@@ -447,15 +447,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)