2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-04 08:15:21 +00:00

Compare commits

...

41 Commits

Author SHA1 Message Date
Jamie Strandboge
766f5e160e aa-notify currently calls notify-send with urgency of 'critical'. In gnome-shell
critical urgency notifications result in a notification that must be explictly
clicked to dismiss (ie, they don't time out) and gnome-shell does not honor --
expire-time with (at least) critical urgency. In other popular DEs critical
urgency notifications time out. This patch updates the urgency to 'normal' to
obtain intended behavior across DEs.

Signed-off-by: Jamie Strandboge <jamie@canonical.com>
Acked-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
2017-04-12 15:57:31 -05:00
Christian Boltz
68cba4fe27 update dovecot-lda profile
dovecot-lda needs
- the attach_disconnected flags
- read access to /usr/share/dovecot/protocols.d/
- rw for /run/dovecot/auth-userdb

References: https://bugs.launchpad.net/bugs/1650827


Acked-by: Steve Beattie <steve@nxnw.org> for 2.9, 2.10 and trunk.
2017-04-07 00:12:53 +02:00
Steve Beattie
5452095203 tests: readdir - test both getdents() and getdents64() if available
In commit 3649, Colin King fixed the readdir test build issue where
aarch64 only supports getdetns64(), not getdents(). Realistically,
however, we want to ensure mediation occurs on both syscalls where
they exist. This patch changes the test to attempt performing both
versions of getdents(). Because we want to catch the situation where
the result of getdents differs from getdents64, we now pass in the
expected result.

Also add a test to verify that having write access does not grant
the ability to read a directory's contents.

Bug: https://bugs.launchpad.net/bugs/1674245

Signed-off-by: Steve Beattie <steve@nxnw.org>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
Acked-by: Tyler Hicks <tyhicks@canonical.com>
2017-04-05 21:34:24 -07:00
Colin King
929b1acf6f tests: where necessary use getdents64 to fix arm64 build failure
https://launchpad.net/bugs/1674245
  
arm64 build of the tests breaks because getdents is not available.
Where available, use gendents64 as the preferred choice.
  
Fixes:
  
cc -g -O0 -Wall -Wstrict-prototypes readdir.c -lapparmor -o readdir
readdir.c: In function ‘main’:
readdir.c:45:14: error: ‘SYS_getdents’ undeclared (first use in this function)
if (syscall(SYS_getdents, fd, &dir, sizeof(struct dirent)) == -1){
            ^~~~~~~~~~~~
readdir.c:45:14: note: each undeclared identifier is reported only once for each function it appears in
<builtin>: recipe for target 'readdir' failed
make: *** [readdir] Error 1

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
2017-04-04 15:40:16 +00:00
Tyler Hicks
e04b50ce95 utils: Add aa-remove-unknown utility to unload unknown profiles
https://launchpad.net/bugs/1668892

This patch creates a new utility, with the code previously used in the
init script 'restart' action, that removes unknown profiles which are
not found in /etc/apparmor.d/. The functionality was removed from the
common init script code in the fix for CVE-2017-6507.

The new utility prints a message containing the name of each unknown
profile before the profiles are removed. It also supports a dry run mode
so that an administrator can check which profiles will be removed before
unloading any unknown profiles.

If you backport this utility with the fix for CVE-2017-6507 to an
apparmor 2.10 release and your backported aa-remove-unknown utility is
sourcing the upstream rc.apparmor.functions file, you'll want to include
the following bug fix to prevent the aa-remove-unknown utility from
removing child profiles that it shouldn't remove:

  r3440 - Fix: parser: incorrect output of child profile names

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2017-03-24 05:08:01 +00:00
Tyler Hicks
8901b3e835 parser: Preserve unknown profiles when restarting apparmor init/job/unit
CVE-2017-6507

https://launchpad.net/bugs/1668892

The common AppArmor 'restart' code used by some init scripts, upstart
jobs, and/or systemd units contained functionality that is no longer
appropriate to retain. Any profiles not found /etc/apparmor.d/ were
assumed to be obsolete and were unloaded. That behavior became
problematic now that there's a growing number of projects that maintain
their own internal set of AppArmor profiles outside of /etc/apparmor.d/.
It resulted in the AppArmor 'restart' code leaving some important
processes running unconfined. A couple examples are profiles managed by
LXD and Docker.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
Acked-by: John Johansen <john.johansen@canonical.com>
2017-03-24 05:06:07 +00:00
Seth Arnold
1285d81547 parser: Fix delete after new[] -- patch from Oleg Strikov <oleg.strikov@gmail.com> 2017-03-21 12:09:59 -07:00
Tyler Hicks
8ce02c20fa profiles: Update nvidia abstraction for newer nvidia drivers
Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Jamie Strandboge <jamie@ubuntu.com>
2017-03-16 02:51:03 +00:00
Olivier Tilloy
71566d36e3 Specify device nodes instead of being too permissive. 2017-03-06 19:59:43 +01:00
Olivier Tilloy
fe421f6952 Update nvidia abstraction for newer nvidia drivers. 2017-03-06 19:46:43 +01:00
Christian Boltz
566b053bdf Fix regressions caused by init_aa()
With the init_aa() patch series commited, minitools_test.py showed
several test failures - which effectively means the -d option of
aa-complain, aa-cleanprof etc. was broken.

These failures were caused by
- calling init_aa() too late in tools.py - _after_ setting the
  profiledir, which then got overwritten by init_aa()
- calling init_aa() twice (because apparmor.aa gets imported in two
  modules used by aa-cleanprof), which overwrote the manually set values
  on the second run

This patch fixes the call order in tools.py and adds a check to
init_aa() so that it only runs once and ignores additional calls.


Acked-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-03-03 13:14:55 +01:00
Christian Boltz
054d8f795f test-parser-simple-tests.py: No longer skip testing generated_perms_leading profiles
FileRule understands leading permissions, so the reason to skip those
(generated) test profiles in test-parser-simple-tests.py is gone.

However, the gen-xtrans.pl script generates profiles with a not-so-valid
mix of uppercase and lowercase, for example "Pux" and "Cux". The parser
accepts this, but the tools complain about such rules. Therefore add the
affected profiles to the exception list.

In total, this means we now test 319 of the 380 generated_perms_leading
test profiles.

The patch also moves some lines around to get the \-escaped profiles
out of the mixed uppercase/lowercase exec rule section.


Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-03-03 13:14:03 +01:00
Tyler Hicks
9a8c6885cb utils: Fix apparmor.easyprof import in test-aa-easyprof.py
The test-aa-easyprof.py script was attempting to do its own special
setup to import the in-tree easyprof module. However, this proved to be
very flaky and resulted in the test periodically failing due to an
AttributeError the first time easyprof.parse_args() was called.

This patch removes the flakiness by trusting that PYTHONPATH is set up
appropriately before the test script is ran. PYTHONPATH is already
initialized appropriately by utils/test/Makefile according to the
USE_SYSTEM make variable.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
2017-03-02 21:25:01 +00:00
Tyler Hicks
7ab65fa5f1 utils: Set parser executable path according to USE_SYSTEM make variable
if USE_SYSTEM is not set, the utils make check target will instruct
test-aa-easyprof.py to provide the path of the in-tree parser executable
to aa-easyprof.

If USE_SYSTEM is set, the default parser path (/sbin/apparmor_parser or
the result of `which apparmor_parser`) is used.

The test-aa-easyprof.py script receives the parser path by checking the
__AA_PARSER environment variable. This environment variable is strictly
used by the test script and not any user-facing code so two leading
underscores were used.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-03-02 21:24:33 +00:00
Tyler Hicks
b98e9df766 utils: Add option to aa-easyprof to specify the apparmor_parser path
When testing against a clean system without the apparmor_parser binary
installed, the test-aa-easyprof.py script ends up skipping profile
verification because it can't find the parser binary. This even causes a
test failure due to the test_genpolicy_invalid_template_policy test.

Adding a --parser option to aa-easyprof is the first step in addressing
this problem.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-03-02 21:24:05 +00:00
Tyler Hicks
7066649144 utils: Set parser base path according to USE_SYSTEM make variable
If USE_SYSTEM is not set, the utils make check target will instruct
test-aa-easyprof.py to provide the path of the in-tree
profiles/apparmor.d directory to aa-easyprof as the parser base
directory.

If USE_SYSTEM is set, the default base directory (/etc/apparmor.d) is
used.

The test-aa-easyprof.py script receives the base path by checking the
__AA_BASEDIR environment variable. This environment variable is strictly
used by the test script and not any user-facing code so two leading
underscores were used.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
Acked-by: Seth Arnold <seth.arnold@canonical.com>

Bug: https://launchpad.net/bugs/1538306
2017-03-02 21:23:32 +00:00
Tyler Hicks
361b63d30b utils: Accept parser base and include options in aa-easyprof
https://launchpad.net/bugs/1521031

aa-easyprof accepts a list of abstractions to include and, by default,
execs apparmor_parser to verify the generated profile including any
abstractions. However, aa-easyprof didn't provide the same flexibility
as apparmor_parser when it came to where in the filesystem the
abstraction files could exist.

The parser supports --base (defaulting to /etc/apparmor.d) and --Include
(defaulting to unset) options to specify the search paths for
abstraction files. This patch adds the same options to aa-easyprof to
aide in two different situations:

 1) Some Ubuntu packages use aa-easyprof to generate AppArmor profiles
    at build time. Something that has been previously needed is a way
    for those packages to ship their own abstractions file(s) that are
    #included in the easyprof-generated profile. That's not been
    possible since the abstraction file(s) have not yet been installed
    during the package build.

 2) The test-aa-easyprof.py script contains some tests that specify
    abstractions that should be #included. Without the ability to
    specify a different --base or --Include directory, the abstractions
    were required to be present in /etc/apparmor.d/abstractions/ or the
    tests would fail. This prevents the Python utils from being able to
    strictly test against in-tree code/profiles/etc.

I don't like the names of the command line options --base and --Include.
They're not particularly descriptive and the capital 'I' is not user
friendly. However, I decided to preserve the name of the options from
apparmor_parser.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-03-02 21:22:57 +00:00
Tyler Hicks
ea0732becc utils: Require apparmor.aa users to call init_aa()
Introduce an apparmor.aa.init_aa() method and move the initialization
code of the apparmor.aa module into it. Note that this change will break
any external users of apparmor.aa because global variables that were
previously initialized when importing apparmor.aa will not be
initialized unless a call to the new apparmor.aa.init_aa() method is
made.

The main purpose of this change is to allow the utils tests to be able
to set a non-default location for configuration files. Instead of
hard-coding the location of logprof.conf and other utils related
configuration files to /etc/apparmor/, this patch allows it to be
configured by calling apparmor.aa.init_aa(confdir=PATH).

This allows for the make check target to use the in-tree config file,
profiles, and parser by default. A helper method, setup_aa(), is added
to common_test.py that checks for an environment variable containing a
non-default configuration directory path prior to calling
apparmor.aa.init_aa(). All test scripts that use apparmor.aa are updated
to call setup_aa().

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Suggested-by: Christian Boltz <apparmor@cboltz.de>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
2017-03-02 21:21:53 +00:00
Tyler Hicks
f30ab46af7 utils: Update the logprof.conf in the test dir to point to in-tree paths
The utils tests should make use of the logprof.conf that resides in
utils/test/ when testing against the in-tree parser and profiles. When
testing against the system, it the utils tests should continue to use
the system logprof.conf.

This patch updates the parser and profiles paths to point to the in-tree
paths. Another patch is needed to get aa.py to honor a non-hardcoded
search path for logprof.conf and other configuration files.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-03-02 21:21:21 +00:00
Tyler Hicks
2db1b83869 utils: Improve error messages when profiles/parser is not found
When aa.py is imported, it looks for a set of profiles and it also looks
for the parser. Both of these paths are configured by logprof.conf but
it isn't always obvious which logprof.conf file was used and, therefore,
it isn't always obvious where aa.py is looking. This patch includes the
paths in the error messages.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-03-02 21:20:45 +00:00
Tyler Hicks
8935457c63 utils: Don't enforce ordering of dbus rule attributes
https://launchpad.net/bugs/1628286

The utils were enforcing that the dbus rule attributes were strictly
ordered in the following fashion:

 bus -> path -> interface -> member -> peer

However, the parser has always accepted the attributes in any order. If
the system contained a profile which did not use the strict ordering
enforced by the utils, the utils would refuse to operate at all.

This patch eases the restriction on the ordering at the expense of the
utils no longer being able to detect and reject a single attribute that
is repeated multiple times. In that situation, only the last occurrence
of the attribute will be honored by the utils.

Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
2017-02-28 23:04:24 +00:00
Tyler Hicks
d4d4d50d84 utils: Fix failing tests in test-aa.py
The merged /usr patches to the policy broke some utils tests due to a
change in the expected output.

Fixes: r3600 update lots of profiles for usrMerge
Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
Acked-by: Christian Boltz <apparmor@cboltz.de>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-02-28 23:03:25 +00:00
Christian Boltz
984ed2801e Ignore change_hat events with error=-1 and "unconfined can not change_hat"
That's much better than crashing aa-logprof ;-)  (use the log line in
the added testcase if you want to see the crash)

Reported by pfak on IRC.


Acked-by: Seth Arnold <seth.arnold@canonical.com> for trunk, 2.10 and 2.9.
2017-02-23 01:00:36 +01:00
Christian Boltz
633f833a6e Remove re.LOCALE flag
Starting with python 3.6, the re.LOCALE flag can only be used with byte
patterns, and errors out if used with str. This patch removes the flag
in get_translated_hotkey().


References: https://bugs.launchpad.net/apparmor/+bug/1661766


Acked-by: Steve Beattie <steve@nxnw.org> for trunk, 2.10 and 2.9
2017-02-21 18:46:36 +01:00
Steve Beattie
20817ef77b regression tests: fix environ fail case
In the environ regression test, when the exec() of the child process
fails, we don't report FAIL to stdout, so the regression tests consider
it an error rather than a failure and abort, short-circuiting the
test script.

This commit fixes this by emitting the FAIL message when the result
from the wait() syscall indicates the child process did not succeed.

Signed-off-by: Steve Beattie <steve@nxnw.org>
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-02-01 21:41:52 -08:00
Christian Boltz
20091ca87d Rename global variable "pid" to "log_pid"
aa.py has a global variable "pid", but it also has several functions
that use "pid" as a local variable name. do_logprof_pass() even uses
both - first, it passes the global variable to ReadLog, and then it
creates a local variable in the "for pid in ..." loop.

This patch renames the global variable to log_pid to get rid of the
confusion.

Note that the global variable is only handed over to ReadLog, and the
only case where its previous content _might_ be used is aa-genprof which
does multipe do_logprof_pass() runs.

Maybe we could even get rid of this variable in aa.py and make it local
to the ReadLog class, but I'm not sure if that would affect aa-genprof
in interesting[tm] ways.


Acked-by: John Johansen <john.johansen@canonical.com>
2017-01-30 20:48:50 +01:00
Christian Boltz
2eee4d6acb Dovecot profile: change Px to mrPx for /usr/lib/dovecot/*
Some of the /usr/lib/dovecot/* rules already have mrPx permissions,
while others don't.

With a more recent kernel, I noticed that at least auth, config, dict,
lmtp, pop3 and ssl-params need mrPx instead of just Px (confirmed by the
audit.log and actual breakage caused by the missing mr permissions).

The mr additions for anvil, log and managesieve are just a wild guess,
but I would be very surprised if they don't need mr.


Acked-by: Seth Arnold <seth.arnold@canonical.com> for trunk, 2.10 and 2.9.
2017-01-30 20:43:47 +01:00
Christian Boltz
04240fe6de Dovecot profile update
Add several permissions to the dovecot profiles that are needed on ubuntu
(surprisingly not on openSUSE, maybe it depends on the dovecot config?)

As discussed some weeks ago, the added permissions use only /run/
instead of /{var/,}run/ (which is hopefully superfluous nowadays).


References: https://bugs.launchpad.net/apparmor/+bug/1512131


Acked-by: Seth Arnold <seth.arnold@canonical.com> for trunk, 2.10 and 2.9.
2017-01-26 21:41:38 +01:00
Kees Cook
2c4119d98c glibc uses /proc/*/auxv and /proc/*/status files, too
Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-01-20 17:01:50 -08:00
Kees Cook
2e3a871b11 Apache2 profile updates for proper signal handling, optional saslauth,
and OCSP stapling

Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-01-20 16:58:46 -08:00
Christian Boltz
50623fca92 Drop unused global variables in aa.py
Grepping through the code shows that running_under_genprof,
unimplemented_warning, ALL, t, seen and skip are unused, so drop them.


Acked-by: Steve Beattie <steve@nxnw.org>


Also drop a '#    t = hasher()" comment, as noticed by Steve.
2017-01-20 01:20:41 +01:00
Kees Cook
f5384469b5 pass LDFLAGS fully into build
Acked-by: John Johansen <john.johansen@canonical.com>
Signed-off-by: Tyler Hicks <tyhicks@canonical.com>
2017-01-19 23:04:34 +00:00
Christian Boltz
85178293f5 [7/7] Drop most of aa-mergeprof ask_the_questions()
Replace most of aa-mergeprof ask_merge_questions() with a call to
aa.py ask_the_questions() (which is, besides some small exceptions that
are not relevant for aa-mergeprof, in sync with the dropped code).

The remaining part gets renamed to ask_merge_questions() to avoid
confusion with the function name in aa.py. Also drop the (now
superfluous) parameter.

aa.py ask_the_questions() needs to allow 'merge' as aamode.
While on it, replace the fatal_error() call for unknown aamode with
raising an AppArmorBug.


Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-01-19 16:54:47 +01:00
Christian Boltz
d1fa70ac22 [6/7] make log_dict a parameter of ask_the_questions()
This allows to hand over any source instead of using the global variable.

Now that the function expects its input as parameter,  get rid of the
global log_dict, which means
- change collapse_log() to initialize log_dict as local variable and
  return it
- change do_logprof_pass() to catch collapse_log()'s return value and
  hand it over to ask_the_questions()
- drop all references to the global log_dict variable
- update test-libapparmor-test_multi to follow the changes

Also fix an if condition that would fail if aa[profile][hat] does not
exist - get() defaults to None if the requested item doesn't exist, and
None.get('file') will raise an Exception.


Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-01-19 16:52:38 +01:00
Christian Boltz
4ec82daa00 [5/7] move ask_conflict_mode() to aa.py
The function is an exact copy of the code in aa-mergeprof (except
removing the 'self' function parameter and changing the whitespace
level)

Also add a ask_conflict_mode() call to aa.py ask_the_questions().
This is needed for aa-mergeprof, and won't hurt in aa-logprof mode
because handle_children() already handles all exec events.


Acked-by: Seth Arnold <seth.arnold@canonical.com>

Bug: https://launchpad.net/bugs/1522938
2017-01-19 16:48:44 +01:00
Christian Boltz
627856d6b4 [4/7] Copy code to ask for adding hats to aa.py ask_the_questions()
Everything below "if aamode == 'merge':" is an exact copy of the code in
aa-mergeprof (with whitespace changed).

aa-logprof and aa-mergeprof will continue to ignore events from unknown
hats and subprofiles.


Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-01-19 16:47:35 +01:00
Christian Boltz
cfa0a37e58 [3/7] Copy code to ask for adding includes to aa.py ask_the_questions()
This is an exact copy of the code in aa-mergeprof (with whitespace changed).


Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-01-19 16:47:05 +01:00
Christian Boltz
ca093f7223 [2/7] replace other.aa with log_dict['merge']
Set log_dict['merge'] = other.aa and aamode = 'merge', and use
log_dict[aamode] everywhere.

This brings aa-mergeprof ask_the_questions() closer to the code in aa.py.


Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-01-19 16:46:23 +01:00
Christian Boltz
1cae419b4d [1/7] drop traces of 3-way-merge in aa-mergeprof
3-way-merge was never really implemented.

This patch drops all traces of it to make the code more readable and
easier to maintain.


Acked-by: Seth Arnold <seth.arnold@canonical.com>
2017-01-19 16:45:29 +01:00
Christian Boltz
dd27256bb3 sshd profile: drop local/ include
The local/ include in the sshd profile in extras causes some trouble:
- it breaks "make check" because the parser can't find the local/ file
- it results in a broken profile if someone uses this profile as
  starting point, but doesn't notice it needs the local include


Acked-by: Steve Beattie <steve@nxnw.org>
2017-01-12 22:01:11 +01:00
Christian Boltz
67b75e84d0 Update /etc/cron.daily/logrotate profile
Thanks to Daniel Curtis for working on this!


Acked-by: Seth Arnold <seth.arnold@canonical.com> for whichever branches
it makes sense for

-> trunk (includes 2.11) only - if we want it in 2.10 and 2.9, we'll
   also need to backport the usrMerge changes
2017-01-11 18:34:37 +01:00
54 changed files with 856 additions and 521 deletions

View File

@@ -55,7 +55,7 @@ libapparmor by adding USE_SYSTEM=1 to your make command.${nl}\
AA_LDLIBS = -lapparmor
endif
EXTRA_CFLAGS=$(CFLAGS) $(CPPFLAGS) -fPIC -shared -Wall $(LIBAPPARMOR_INCLUDE)
LINK_FLAGS=-Xlinker -x $(AA_LINK_FLAGS)
LINK_FLAGS=-Xlinker -x $(AA_LINK_FLAGS) $(LDFLAGS)
LIBS=-lpam $(AA_LDLIBS)
OBJECTS=${NAME}.o get_options.o

View File

@@ -13,5 +13,6 @@ WriteMakefile(
'INC' => q[@CPPFLAGS@ -I@top_srcdir@/include @CFLAGS@],
'LIBS' => q[-L@top_builddir@/src/.libs/ -lapparmor @LIBS@],
'OBJECT' => 'libapparmor_wrap.o', # $(OBJ_EXT)
'dynamic_lib' => { 'OTHERLDFLAGS' => q[@LDFLAGS@], },
) ;

View File

@@ -0,0 +1 @@
Feb 21 23:22:01 mail-20170118 kernel: [1222198.459750] audit: type=1400 audit(1487719321.954:218): apparmor="ALLOWED" operation="change_hat" info="unconfined can not change_hat" error=-1 profile="unconfined" pid=19941 comm="apache2"

View File

@@ -0,0 +1,12 @@
START
File: unconfined-change_hat.in
Event type: AA_RECORD_ALLOWED
Audit ID: 1487719321.954:218
Operation: change_hat
Profile: unconfined
Command: apache2
Info: unconfined can not change_hat
ErrorCode: 1
PID: 19941
Epoch: 1487719321
Audit subid: 218

View File

@@ -0,0 +1,2 @@
profile unconfined {
}

View File

@@ -86,7 +86,7 @@ OBJECTS = $(patsubst %.cc, %.o, $(SRCS:.c=.o))
AAREDIR= libapparmor_re
AAREOBJECT = ${AAREDIR}/libapparmor_re.a
AAREOBJECTS = $(AAREOBJECT)
AARE_LDFLAGS = -static-libgcc -static-libstdc++ -L.
AARE_LDFLAGS = -static-libgcc -static-libstdc++ -L. $(LDFLAGS)
AALIB = -Wl,-Bstatic -lapparmor -Wl,-Bdynamic -lpthread
ifdef USE_SYSTEM

View File

@@ -672,7 +672,7 @@ public:
~hashedNodeVec()
{
delete nodes;
delete [] nodes;
}
unsigned long size()const { return len; }

View File

@@ -451,34 +451,7 @@ __apparmor_restart() {
configure_owlsm
parse_profiles reload
# Clean out running profiles not associated with the current profile
# set, excluding the libvirt dynamically generated profiles.
# Note that we reverse sort the list of profiles to remove to
# ensure that child profiles (e.g. hats) are removed before the
# parent. We *do* need to remove the child profile and not rely
# on removing the parent profile when the profile has had its
# child profile names changed.
profiles_names_list | awk '
BEGIN {
while (getline < "'${SFS_MOUNTPOINT}'/profiles" ) {
str = sub(/ \((enforce|complain)\)$/, "", $0);
if (match($0, /^libvirt-[0-9a-f\-]+$/) == 0)
arr[$str] = $str
}
}
{ if (length(arr[$0]) > 0) { delete arr[$0] } }
END {
for (key in arr)
if (length(arr[key]) > 0) {
printf("%s\n", arr[key])
}
}
' | LC_COLLATE=C sort -r | while IFS= read profile ; do
echo -n "$profile" > "$SFS_MOUNTPOINT/.remove"
done
# will not catch all errors, but still better than nothing
rc=$?
aa_log_end_msg $rc
return $rc

View File

@@ -8,6 +8,8 @@
signal (receive) peer=unconfined,
# Allow apache to send us signals by default
signal (receive) peer=/usr/sbin/apache2,
# Allow other hats to signal by default
signal peer=/usr/sbin/apache2//*,
# Allow us to signal ourselves
signal peer=@{profile_name},
@@ -25,3 +27,8 @@
/dev/urandom r,
# sasl-auth
/run/saslauthd/mux rw,
# OCSP stapling
/var/log/apache2/stapling-cache rw,

View File

@@ -85,7 +85,7 @@
/sys/devices/system/cpu/online r,
# glibc's *printf protections read the maps file
@{PROC}/@{pid}/maps r,
@{PROC}/@{pid}/{maps,auxv,status} r,
# libgcrypt reads some flags from /proc
@{PROC}/sys/crypto/* r,

View File

@@ -8,8 +8,9 @@
/etc/vdpau_wrapper.cfg r,
# device files
/dev/nvidia0 rw,
/dev/nvidiactl rw,
/dev/nvidiactl rw,
/dev/nvidia-modeset rw,
/dev/nvidia[0-9]* rw,
@{PROC}/interrupts r,
@{PROC}/sys/vm/max_map_count r,
@@ -18,3 +19,5 @@
owner @{HOME}/.nv/GLCache/ r,
owner @{HOME}/.nv/GLCache/** rwk,
unix (send, receive) type=dgram peer=(addr="@nvidia[0-9a-f]*"),

View File

@@ -18,6 +18,7 @@
capability setuid,
capability sys_chroot,
/run/dovecot/anvil rw,
/usr/lib/dovecot/anvil mr,
# Site-specific additions and overrides. See local/README for details.

View File

@@ -37,6 +37,9 @@
/var/tmp/sieve_* rw,
/var/tmp/smtp_* rw,
/run/dovecot/auth-master rw,
/run/dovecot/auth-worker rw,
/run/dovecot/login/login rw,
/{var/,}run/dovecot/auth-token-secret.dat{,.tmp} rw,
/{var/,}run/dovecot/stats-user rw,
/{var/,}run/dovecot/anvil-auth-penalty rw,

View File

@@ -12,7 +12,7 @@
#include <tunables/global>
#include <tunables/dovecot>
/usr/lib/dovecot/dovecot-lda {
/usr/lib/dovecot/dovecot-lda flags=(attach_disconnected) {
#include <abstractions/base>
#include <abstractions/nameservice>
#include <abstractions/dovecot-common>
@@ -26,9 +26,11 @@
/proc/*/mounts r,
owner /tmp/dovecot.lda.* rw,
/{var/,}run/dovecot/mounts r,
/run/dovecot/auth-userdb rw,
/usr/bin/doveconf mrix,
/usr/lib/dovecot/dovecot-lda mrix,
/usr/sbin/sendmail Cx,
/usr/share/dovecot/protocols.d/ r,
# Site-specific additions and overrides. See local/README for details.
#include <local/usr.lib.dovecot.dovecot-lda>

View File

@@ -21,6 +21,8 @@
capability setuid,
deny capability block_suspend,
network unix stream,
@{DOVECOT_MAILSTORE}/ rw,
@{DOVECOT_MAILSTORE}/** rwkl,
@@ -33,6 +35,7 @@
/usr/bin/doveconf rix,
/usr/lib/dovecot/imap mrix,
/usr/share/dovecot/** r,
/run/dovecot/login/imap rw,
/{,var/}run/dovecot/auth-master rw,
/{,var/}run/dovecot/mounts r,

View File

@@ -22,6 +22,7 @@
network inet stream,
network inet6 stream,
network unix stream,
/usr/lib/dovecot/imap-login mr,
/{,var/}run/dovecot/anvil rw,

View File

@@ -15,6 +15,7 @@
#include <abstractions/base>
#include <abstractions/dovecot-common>
/run/dovecot/login/ssl-params rw,
/usr/lib/dovecot/ssl-params mr,
/var/lib/dovecot/ssl-parameters.dat rw,
/var/lib/dovecot/ssl-parameters.dat.tmp rwk,

View File

@@ -36,21 +36,21 @@
/etc/SuSE-release r,
@{PROC}/@{pid}/mounts r,
/usr/bin/doveconf rix,
/usr/lib/dovecot/anvil Px,
/usr/lib/dovecot/auth Px,
/usr/lib/dovecot/config Px,
/usr/lib/dovecot/dict Px,
/usr/lib/dovecot/anvil mrPx,
/usr/lib/dovecot/auth mrPx,
/usr/lib/dovecot/config mrPx,
/usr/lib/dovecot/dict mrPx,
/usr/lib/dovecot/dovecot-auth Pxmr,
/usr/lib/dovecot/imap Pxmr,
/usr/lib/dovecot/imap-login Pxmr,
/usr/lib/dovecot/lmtp Px,
/usr/lib/dovecot/log Px,
/usr/lib/dovecot/managesieve Px,
/usr/lib/dovecot/lmtp mrPx,
/usr/lib/dovecot/log mrPx,
/usr/lib/dovecot/managesieve mrPx,
/usr/lib/dovecot/managesieve-login Pxmr,
/usr/lib/dovecot/pop3 Px,
/usr/lib/dovecot/pop3 mrPx,
/usr/lib/dovecot/pop3-login Pxmr,
/usr/lib/dovecot/ssl-build-param rix,
/usr/lib/dovecot/ssl-params Px,
/usr/lib/dovecot/ssl-params mrPx,
/usr/sbin/dovecot mrix,
/usr/share/dovecot/protocols.d/ r,
/usr/share/dovecot/protocols.d/** r,

View File

@@ -2,6 +2,8 @@
# ------------------------------------------------------------------
#
# Copyright (C) 2002-2006 Novell/SUSE
# Copyright (C) 2016 Seth Arnold
# Copyright (C) 2016 Daniel Curtis
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
@@ -16,38 +18,58 @@
#include <abstractions/bash>
#include <abstractions/nameservice>
/{usr/,}bin/bash mixr,
capability chown,
capability dac_override,
capability dac_read_search,
capability fowner,
capability fsetid,
/{usr/,}bin/{ba,da,}sh mixr,
/{usr/,}bin/cat mixr,
/{usr/,}bin/gzip mixr,
/{usr/,}bin/kill mixr,
/{usr/,}bin/logger mixr,
/{usr/,}bin/mv mixr,
/{usr/,}bin/sed mixr,
/{usr/,}bin/sleep mrix,
/{usr/,}bin/true mixr,
/etc/init.d/* mixr,
/usr/bin/head mrix,
/usr/bin/killall mixr,
/usr/sbin/invoke-rc.d mrix,
/usr/sbin/logrotate mixr,
/var/log r,
/var/log/** wrl,
## see https://lists.ubuntu.com/archives/apparmor/2016-December/010359.html
/{usr/,}sbin/initctl Ux,
/{usr/,}sbin/runlevel Ux,
/var/log/ r,
/var/log/** rwl,
/var/lib/privoxy/log/** rwl,
/var/lib64/privoxy/log/** rwl,
/ r,
/dev/tty wr,
/dev/tty rw,
/etc/cron.daily/logrotate r,
/etc/logrotate.conf r,
/etc/logrotate.d r,
/etc/logrotate.d/ r,
/etc/logrotate.d/* r,
/etc/subdomain.d r,
@{PROC} r,
@{PROC}/@{pid} r,
/tmp w,
/tmp/file* wl,
/tmp/logrot* wlr,
/var/lib/logrotate.status wr,
/etc/lsb-base-logging.sh r,
# @{PROC} r,
# @{PROC}/@{pid} r,
owner /tmp/file* wl,
owner /tmp/logrot* rwl,
/var/lib/logrotate/ r,
/var/lib/logrotate/* rw,
/{run,var}/lock/samba r,
/{,var/}run/httpd.pid r,
/{,var/}run/syslogd.pid r,
/var/spool/slrnpull wr,
/{,var/}run/rsyslogd.pid r,
/var/spool/slrnpull/ wr,
/var/spool/slrnpull/log* wrl,
}

View File

@@ -140,5 +140,5 @@
/usr/lib/openssh/sftp-server PUx,
# Site-specific additions and overrides. See local/README for details.
#include <local/usr.sbin.sshd>
## include <local/usr.sbin.sshd>
}

View File

@@ -63,6 +63,8 @@ int main(int argc, char *argv[])
if (retval == RET_CHLD_SUCCESS) {
printf("PASS\n");
retval = 0;
} else {
printf("FAIL: Child failed\n");
}
} else if (pid == 0) {

View File

@@ -1,13 +1,20 @@
/*
* Copyright (C) 2002-2006 Novell/SUSE
* Copyright (C) 2017 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation, version 2 of the
* License.
*
* We attempt to test both getdents() and getdents64() here, but
* some architectures like aarch64 only support getdents64().
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
@@ -17,37 +24,118 @@
#include <string.h>
#include <sys/syscall.h>
int main(int argc, char *argv[])
/* error if neither SYS_getdents or SYS_getdents64 is defined */
#if !defined(SYS_getdents) && !defined(SYS_getdents64)
#error "Neither SYS_getdents or SYS_getdents64 is defined, something has gone wrong!"
#endif
/* define DEBUG to enable debugging statements i.e. gcc -DDEBUG */
#ifdef DEBUG
void pdebug(const char *format, ...)
{
va_list arg;
va_start(arg, format);
vprintf(format, arg);
va_end(arg);
}
#else
void pdebug(const char *format, ...) { return; }
#endif
#ifdef SYS_getdents
int my_readdir(char *dirname)
{
int fd;
struct dirent dir;
if (argc != 2){
fprintf(stderr, "usage: %s dir\n",
argv[0]);
return 1;
fd = open(dirname, O_RDONLY, 0);
if (fd == -1) {
pdebug("open failed: %s\n", strerror(errno));
return errno;
}
fd = open(argv[1], O_RDONLY, 0);
if (fd == -1){
printf("FAIL - open %s\n", strerror(errno));
return 1;
}
/*
if (fchdir(fd) == -1){
printf("FAIL - fchdir %s\n", strerror(errno));
return 1;
}
*/
/* getdents isn't exported by glibc, so must use syscall() */
if (syscall(SYS_getdents, fd, &dir, sizeof(struct dirent)) == -1){
printf("FAIL - getdents %s\n", strerror(errno));
return 1;
if (syscall(SYS_getdents, fd, &dir, sizeof(dir)) == -1){
pdebug("getdents failed: %s\n", strerror(errno));
close(fd);
return errno;
}
close(fd);
return 0;
}
#endif
#ifdef SYS_getdents64
int my_readdir64(char *dirname)
{
int fd;
struct dirent64 dir;
fd = open(dirname, O_RDONLY, 0);
if (fd == -1) {
pdebug("open failed: %s\n", strerror(errno));
return errno;
}
/* getdents isn't exported by glibc, so must use syscall() */
if (syscall(SYS_getdents64, fd, &dir, sizeof(dir)) == -1){
pdebug("getdents64 failed: %s\n", strerror(errno));
close(fd);
return errno;
}
close(fd);
return 0;
}
#endif
int main(int argc, char *argv[])
{
int rc = 0, err = 0;
char *dirpath, *endptr;
int expected;
if (argc != 3) {
fprintf(stderr, "usage: %s [dir] [expected retval]\n",
argv[0]);
err = 1;
goto err;
}
dirpath = argv[1];
errno = 0;
expected = (int) strtol(argv[2], &endptr, 10);
if (errno != 0 || endptr == argv[2]) {
fprintf(stderr, "ERROR: couldn't convert '%s' to an integer\n",
argv[2]);
err = 1;
goto err;
}
pdebug("expected = %d\n", expected);
#ifdef SYS_getdents
rc = my_readdir(dirpath);
if (rc != expected) {
printf("FAIL - my_readdir returned %d, expected %d\n", rc, expected);
err = rc;
goto err;
}
#endif
#ifdef SYS_getdents64
rc = my_readdir64(argv[1]);
if (rc != expected) {
printf("FAIL - my_readdir64 returned %d, expected %d\n", rc, expected);
err = rc;
goto err;
}
#endif
printf("PASS\n");
return 0;
err:
return err;
}

View File

@@ -1,5 +1,6 @@
#! /bin/bash
# Copyright (C) 2002-2005 Novell/SUSE
# Copyright (C) 2017 Canonical, Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
@@ -8,7 +9,7 @@
#=NAME readdir
#=DESCRIPTION
# AppArmor requires 'r' permission on a directory in order for a confined task
# AppArmor requires 'r' permission on a directory in order for a confined task
# to be able to read the directory contents. This test verifies this.
#=END
@@ -26,20 +27,31 @@ badperm=ix
mkdir $dir
# The readdir utility expects the return value to be passed as the
# second argument and returns success if the succeeding or failing calls
# match the expected value. It will fail the test if they don't, so for
# example the result differs acrorss getdents() and getdents64() this
# will be detected.
# READDIR TEST
genprofile $dir/:$okperm
runchecktest "READDIR" pass $dir
runchecktest "READDIR" pass $dir 0
EACCES=13
# READDIR TEST (no perm)
genprofile $dir/:$badperm
runchecktest "READDIR (no perm)" fail $dir
runchecktest "READDIR (no perm)" pass $dir ${EACCES}
# READDIR TEST (write perm) - ensure write perm isn't sufficient
genprofile $dir/:w
runchecktest "READDIR (write perm)" pass $dir ${EACCES}
# this test is to make sure the raw 'file' rule allows access
# to directories
genprofile file
runchecktest "READDIR 'file' dir" pass $dir
runchecktest "READDIR 'file' dir" pass $dir 0
# this test is to make sure the raw 'file' rule allows access
# to '/'
genprofile file
runchecktest "READDIR 'file' '/'" pass '/'
runchecktest "READDIR 'file' '/'" pass '/' 0

View File

@@ -24,7 +24,7 @@ PERLTOOLS = aa-notify
PYTOOLS = aa-easyprof aa-genprof aa-logprof aa-cleanprof aa-mergeprof \
aa-autodep aa-audit aa-complain aa-enforce aa-disable \
aa-status aa-unconfined
TOOLS = ${PERLTOOLS} ${PYTOOLS} aa-decode
TOOLS = ${PERLTOOLS} ${PYTOOLS} aa-decode aa-remove-unknown
PYSETUP = python-tools-setup.py
PYMODULES = $(wildcard apparmor/*.py apparmor/rule/*.py)

View File

@@ -57,6 +57,12 @@ for supported policy groups. The available policy groups are in
AppArmor rules or policies. They are similar to AppArmor abstractions, but
usually encompass more policy rules.
=item --parser PATH
Specify the PATH of the apparmor_parser binary to use when verifying
policy. If this option is not specified, aa-easyprof will attempt to
locate the path starting with /sbin/apparmor_parser.
=item -a ABSTRACTIONS, --abstractions=ABSTRACTIONS
Specify ABSTRACTIONS as a comma-separated list of AppArmor abstractions. It is
@@ -64,6 +70,16 @@ usually recommended you use policy groups instead, but this is provided as a
convenience. AppArmor abstractions are located in /etc/apparmor.d/abstractions.
See apparmor.d(5) for details.
=item -b PATH, --base=PATH
Set the base PATH for resolving abstractions specified by --abstractions.
See the same option in apparmor_parser(8) for details.
=item -I PATH, --Include=PATH
Add PATH to the search paths used for resolving abstractions specified by
--abstractions. See the same option in apparmor_parser(8) for details.
=item -r PATH, --read-path=PATH
Specify a PATH to allow owner reads. May be specified multiple times. If the

View File

@@ -66,6 +66,7 @@ args = parser.parse_args()
profiling = args.program
profiledir = args.dir
apparmor.init_aa()
apparmor.set_logfile(args.file)
aa_mountpoint = apparmor.check_for_apparmor()

View File

@@ -34,6 +34,7 @@ args = parser.parse_args()
profiledir = args.dir
logmark = args.mark or ''
apparmor.init_aa()
apparmor.set_logfile(args.file)
aa_mountpoint = apparmor.check_for_apparmor()

View File

@@ -1,7 +1,7 @@
#! /usr/bin/python3
# ----------------------------------------------------------------------
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
# Copyright (C) 2014-2016 Christian Boltz <apparmor@cboltz.de>
# Copyright (C) 2014-2017 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
@@ -23,10 +23,6 @@ import apparmor.severity
import apparmor.cleanprofile as cleanprofile
import apparmor.ui as aaui
from apparmor.aa import (add_to_options, available_buttons, combine_name, delete_duplicates,
get_profile_filename, is_known_rule, match_includes, profile_storage,
set_options_audit_mode, propose_file_rules, selection_to_rule_obj)
from apparmor.aare import AARE
from apparmor.common import AppArmorException
from apparmor.regex import re_match_include
@@ -41,16 +37,15 @@ _ = init_translation()
parser = argparse.ArgumentParser(description=_('Merge the given profiles into /etc/apparmor.d/ (or the directory specified with -d)'))
parser.add_argument('files', nargs='+', type=str, help=_('Profile(s) to merge'))
#parser.add_argument('other', nargs='?', type=str, help=_('other profile'))
parser.add_argument('-d', '--dir', type=str, help=_('path to profiles'))
#parser.add_argument('-a', '--auto', action='store_true', help=_('Automatically merge profiles, exits incase of *x conflicts'))
args = parser.parse_args()
args.other = None
# 2-way merge or 3-way merge based on number of params
merge_mode = 2 #if args.other == None else 3
profiles = [args.files, [args.other]]
apparmor.aa.init_aa()
profiles = args.files
profiledir = args.dir
if profiledir:
@@ -87,61 +82,29 @@ def find_files_from_profiles(profiles):
return profile_to_filename
def main():
profiles_to_merge = set()
base_profile_to_file = find_profiles_from_files(profiles)
base_files, other_files = profiles
base_profile_to_file = find_profiles_from_files(base_files)
profiles_to_merge = profiles_to_merge.union(set(base_profile_to_file.keys()))
other_profile_to_file = dict()
if merge_mode == 3:
other_profile_to_file = find_profiles_from_files(other_files)
profiles_to_merge.add(other_profile_to_file.keys())
profiles_to_merge = set(base_profile_to_file.keys())
user_profile_to_file = find_files_from_profiles(profiles_to_merge)
# print(base_files,"\n",other_files)
# print(base_profile_to_file,"\n",other_profile_to_file,"\n",user_profile_to_file)
# print(profiles_to_merge)
for profile_name in profiles_to_merge:
aaui.UI_Info("\n\n" + _("Merging profile for %s" % profile_name))
user_file = user_profile_to_file[profile_name]
base_file = base_profile_to_file.get(profile_name, None)
other_file = None
if merge_mode == 3:
other_file = other_profile_to_file.get(profile_name, None)
if base_file == None:
if other_file == None:
continue
act([user_file, other_file, None], 2, profile_name)
else:
if other_file == None:
act([user_file, base_file, None], 2, profile_name)
else:
act([user_file, base_file, other_file], 3, profile_name)
act([user_file, base_file], profile_name)
reset_aa()
def act(files, merge_mode, merging_profile):
def act(files, merging_profile):
mergeprofiles = Merge(files)
#Get rid of common/superfluous stuff
mergeprofiles.clear_common()
# if not args.auto:
if 1 == 1: # workaround to avoid lots of whitespace changes
if merge_mode == 3:
mergeprofiles.ask_the_questions('other', merging_profile)
mergeprofiles.clear_common()
mergeprofiles.ask_the_questions('base', merging_profile)
mergeprofiles.ask_merge_questions()
q = aaui.PromptQuestion()
q.title = _('Changed Local Profiles')
@@ -172,7 +135,7 @@ def act(files, merge_mode, merging_profile):
class Merge(object):
def __init__(self, profiles):
user, base, other = profiles
user, base = profiles
#Read and parse base profile and save profile data, include data from it and reset them
apparmor.aa.read_profile(base, True)
@@ -180,12 +143,6 @@ class Merge(object):
reset_aa()
#Read and parse other profile and save profile data, include data from it and reset them
if merge_mode == 3:
apparmor.aa.read_profile(other, True)
self.other = cleanprofile.Prof(other)
reset_aa()
#Read and parse user profile
apparmor.aa.read_profile(user, True)
self.user = cleanprofile.Prof(user)
@@ -193,67 +150,18 @@ class Merge(object):
def clear_common(self):
deleted = 0
if merge_mode == 3:
#Remove off the parts in other profile which are common/superfluous from user profile
user_other = cleanprofile.CleanProf(False, self.user, self.other)
deleted += user_other.compare_profiles()
#Remove off the parts in base profile which are common/superfluous from user profile
user_base = cleanprofile.CleanProf(False, self.user, self.base)
deleted += user_base.compare_profiles()
if merge_mode == 3:
#Remove off the parts in other profile which are common/superfluous from base profile
base_other = cleanprofile.CleanProf(False, self.base, self.other)
deleted += base_other.compare_profiles()
def ask_merge_questions(self):
other = self.base
log_dict = {'merge': other.aa}
def ask_conflict_mode(self, profile, hat, old_profile, merge_profile):
'''ask user about conflicting exec rules'''
for oldrule in old_profile['file'].rules:
conflictingrules = merge_profile['file'].get_exec_conflict_rules(oldrule)
if conflictingrules.rules:
q = aaui.PromptQuestion()
q.headers = [_('Path'), oldrule.path.regex]
q.headers += [_('Select the appropriate mode'), '']
options = []
options.append(oldrule.get_clean())
for rule in conflictingrules.rules:
options.append(rule.get_clean())
q.options = options
q.functions = ['CMD_ALLOW', 'CMD_ABORT']
done = False
while not done:
ans, selected = q.promptUser()
if ans == 'CMD_ALLOW':
if selected == 0:
pass # just keep the existing rule
elif selected > 0:
# replace existing rule with merged one
old_profile['file'].delete(oldrule)
old_profile['file'].add(conflictingrules.rules[selected - 1])
else:
raise AppArmorException(_('Unknown selection'))
for rule in conflictingrules.rules:
merge_profile['file'].delete(rule) # make sure aa-mergeprof doesn't ask to add conflicting rules later
done = True
def ask_the_questions(self, other, profile):
aa = self.user.aa # keep references so that the code in this function can use the short name
changed = apparmor.aa.changed # (and be more in sync with aa.py ask_the_questions())
if other == 'other':
other = self.other
else:
other = self.base
#print(other.aa)
#Add the file-wide includes from the other profile to the user profile
apparmor.aa.loadincludes()
done = False
#Add the file-wide includes from the other profile to the user profile
options = []
for inc in other.filelist[other.filename]['include'].keys():
if not inc in self.user.filelist[self.user.filename]['include'].keys():
@@ -281,211 +189,10 @@ class Merge(object):
elif ans == 'CMD_FINISHED':
return
sev_db = apparmor.aa.sev_db
if not sev_db:
sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown'))
if not apparmor.aa.sev_db:
apparmor.aa.sev_db = apparmor.severity.Severity(apparmor.aa.CONFDIR + '/severity.db', _('unknown'))
sev_db.unload_variables()
sev_db.load_variables(get_profile_filename(profile))
for hat in sorted(other.aa[profile].keys()):
if not aa[profile].get(hat):
ans = ''
while ans not in ['CMD_ADDHAT', 'CMD_ADDSUBPROFILE', 'CMD_DENY']:
q = aaui.PromptQuestion()
q.headers += [_('Profile'), profile]
if other.aa[profile][hat]['profile']:
q.headers += [_('Requested Subprofile'), hat]
q.functions.append('CMD_ADDSUBPROFILE')
else:
q.headers += [_('Requested Hat'), hat]
q.functions.append('CMD_ADDHAT')
q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
q.default = 'CMD_DENY'
ans = q.promptUser()[0]
if ans == 'CMD_FINISHED':
return
if ans == 'CMD_DENY':
continue # don't ask about individual rules if the user doesn't want the additional subprofile/hat
if other.aa[profile][hat]['profile']:
aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing subprofile')
aa[profile][hat]['profile'] = True
else:
aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing hat')
aa[profile][hat]['profile'] = False
#Add the includes from the other profile to the user profile
done = False
options = []
for inc in other.aa[profile][hat]['include'].keys():
if not inc in aa[profile][hat]['include'].keys():
options.append('#include <%s>' %inc)
default_option = 1
q = aaui.PromptQuestion()
q.options = options
q.selected = default_option - 1
q.headers = [_('File includes'), _('Select the ones you wish to add')]
q.functions = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED']
q.default = 'CMD_ALLOW'
while not done and options:
ans, selected = q.promptUser()
if ans == 'CMD_IGNORE_ENTRY':
done = True
elif ans == 'CMD_ALLOW':
selection = options[selected]
inc = re_match_include(selection)
deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc)
aa[profile][hat]['include'][inc] = True
options.pop(selected)
aaui.UI_Info(_('Adding %s to the file.') % selection)
if deleted:
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
elif ans == 'CMD_FINISHED':
return
# check for and ask about conflicting exec modes
self.ask_conflict_mode(profile, hat, aa[profile][hat], other.aa[profile][hat])
for ruletype in apparmor.aa.ruletypes:
if other.aa[profile][hat].get(ruletype, False): # needed until we have proper profile initialization
for rule_obj in other.aa[profile][hat][ruletype].rules:
if is_known_rule(aa[profile][hat], ruletype, rule_obj):
continue
default_option = 1
options = []
newincludes = match_includes(aa[profile][hat], ruletype, rule_obj)
q = aaui.PromptQuestion()
if newincludes:
options += list(map(lambda inc: '#include <%s>' % inc, sorted(set(newincludes))))
if ruletype == 'file' and rule_obj.path:
options += propose_file_rules(aa[profile][hat], rule_obj)
else:
options.append(rule_obj.get_clean())
done = False
while not done:
q.options = options
q.selected = default_option - 1
q.headers = [_('Profile'), combine_name(profile, hat)]
q.headers += rule_obj.logprof_header()
# Load variables into sev_db? Not needed/used for capabilities and network rules.
severity = rule_obj.severity(sev_db)
if severity != sev_db.NOT_IMPLEMENTED:
q.headers += [_('Severity'), severity]
q.functions = available_buttons(rule_obj)
q.default = q.functions[0]
ans, selected = q.promptUser()
selection = options[selected]
if ans == 'CMD_IGNORE_ENTRY':
done = True
break
elif ans == 'CMD_FINISHED':
return
elif ans.startswith('CMD_AUDIT'):
if ans == 'CMD_AUDIT_NEW':
rule_obj.audit = True
rule_obj.raw_rule = None
else:
rule_obj.audit = False
rule_obj.raw_rule = None
options = set_options_audit_mode(rule_obj, options)
elif ans == 'CMD_ALLOW':
done = True
changed[profile] = True
inc = re_match_include(selection)
if inc:
deleted = delete_duplicates(aa[profile][hat], inc)
aa[profile][hat]['include'][inc] = True
aaui.UI_Info(_('Adding %s to profile.') % selection)
if deleted:
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
else:
rule_obj = selection_to_rule_obj(rule_obj, selection)
deleted = aa[profile][hat][ruletype].add(rule_obj, cleanup=True)
aaui.UI_Info(_('Adding %s to profile.') % rule_obj.get_clean())
if deleted:
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
elif ans == 'CMD_DENY':
if re_match_include(selection):
aaui.UI_Important("Denying via an include file isn't supported by the AppArmor tools")
else:
done = True
changed[profile] = True
rule_obj = selection_to_rule_obj(rule_obj, selection)
rule_obj.deny = True
rule_obj.raw_rule = None # reset raw rule after manually modifying rule_obj
deleted = aa[profile][hat][ruletype].add(rule_obj, cleanup=True)
aaui.UI_Info(_('Adding %s to profile.') % rule_obj.get_clean())
if deleted:
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
elif ans == 'CMD_GLOB':
if not re_match_include(selection):
globbed_rule_obj = selection_to_rule_obj(rule_obj, selection)
globbed_rule_obj.glob()
options, default_option = add_to_options(options, globbed_rule_obj.get_raw())
elif ans == 'CMD_GLOBEXT':
if not re_match_include(selection):
globbed_rule_obj = selection_to_rule_obj(rule_obj, selection)
globbed_rule_obj.glob_ext()
options, default_option = add_to_options(options, globbed_rule_obj.get_raw())
elif ans == 'CMD_NEW':
if not re_match_include(selection):
edit_rule_obj = selection_to_rule_obj(rule_obj, selection)
prompt, oldpath = edit_rule_obj.edit_header()
newpath = aaui.UI_GetString(prompt, oldpath)
if newpath:
try:
input_matches_path = rule_obj.validate_edit(newpath) # note that we check against the original rule_obj here, not edit_rule_obj (which might be based on a globbed path)
except AppArmorException:
aaui.UI_Important(_('The path you entered is invalid (not starting with / or a variable)!'))
continue
if not input_matches_path:
ynprompt = _('The specified path does not match this log entry:\n\n Log Entry: %(path)s\n Entered Path: %(ans)s\nDo you really want to use this path?') % { 'path': oldpath, 'ans': newpath }
key = aaui.UI_YesNo(ynprompt, 'n')
if key == 'n':
continue
edit_rule_obj.store_edit(newpath)
options, default_option = add_to_options(options, edit_rule_obj.get_raw())
apparmor.aa.user_globs[newpath] = AARE(newpath, True)
else:
done = False
apparmor.aa.ask_the_questions(log_dict)
if __name__ == '__main__':
main()

View File

@@ -341,7 +341,7 @@ sub send_message {
# 'system' uses execvp() so no shell metacharacters here.
# $notify_exe is an absolute path so execvp won't search PATH.
system "$notify_exe", "-i", "gtk-dialog-warning", "-u", "critical", "--", "AppArmor Message", "$msg";
system "$notify_exe", "-i", "gtk-dialog-warning", "-u", "normal", "--", "AppArmor Message", "$msg";
my $exit_code = $? >> 8;
exit($exit_code);
}

108
utils/aa-remove-unknown Normal file
View File

@@ -0,0 +1,108 @@
#!/bin/sh
# ----------------------------------------------------------------------
# Copyright (c) 2017 Canonical Ltd. (All rights reserved)
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# ----------------------------------------------------------------------
APPARMOR_FUNCTIONS=/lib/apparmor/rc.apparmor.functions
APPARMORFS=/sys/kernel/security/apparmor
PROFILES="${APPARMORFS}/profiles"
REMOVE="${APPARMORFS}/.remove"
DRY_RUN=0
. $APPARMOR_FUNCTIONS
usage() {
local progname="$1"
local rc="$2"
local msg="usage: ${progname} [options]\n
Remove profiles unknown to the system
Options:
-h, --help Show this help message and exit
-n Dry run; don't remove profiles"
if [ "$rc" -ne 0 ] ; then
echo "$msg" 1>&2
else
echo "$msg"
fi
exit "$rc"
}
if [ "$#" -gt 1 ] ; then
usage "$0" 1
elif [ "$#" -eq 1 ] ; then
if [ "$1" = "-h" -o "$1" = "--help" ] ; then
usage "$0" 0
elif [ "$1" = "-n" ] ; then
DRY_RUN=1
else
usage "$0" 1
fi
fi
# We can't use a -r test here because while $PROFILES is world-readable,
# apparmorfs may still return EACCES from open()
#
# We have to do this check because error checking awk's getline() below is
# tricky and, as is, results in an infinite loop when apparmorfs returns an
# error from open().
if ! IFS= read line < "$PROFILES" ; then
echo "ERROR: Unable to read apparmorfs profiles file" 1>&2
exit 1
elif [ ! -w "$REMOVE" ] ; then
echo "ERROR: Unable to write to apparmorfs remove file" 1>&2
exit 1
fi
# Clean out running profiles not associated with the current profile
# set, excluding the libvirt dynamically generated profiles.
# Note that we reverse sort the list of profiles to remove to
# ensure that child profiles (e.g. hats) are removed before the
# parent. We *do* need to remove the child profile and not rely
# on removing the parent profile when the profile has had its
# child profile names changed.
profiles_names_list | awk '
BEGIN {
while (getline < "'${PROFILES}'" ) {
str = sub(/ \((enforce|complain)\)$/, "", $0);
if (match($0, /^libvirt-[0-9a-f\-]+$/) == 0)
arr[$str] = $str
}
}
{ if (length(arr[$0]) > 0) { delete arr[$0] } }
END {
for (key in arr)
if (length(arr[key]) > 0) {
printf("%s\n", arr[key])
}
}
' | LC_COLLATE=C sort -r | \
while IFS= read profile ; do
if [ "$DRY_RUN" -ne 0 ]; then
echo "Would remove '${profile}'"
else
echo "Removing '${profile}'"
echo -n "$profile" > "${REMOVE}"
fi
done
# will not catch all errors, but still better than nothing
exit $?

View File

@@ -0,0 +1,51 @@
=pod
=head1 NAME
aa-remove-unknown - remove unknown AppArmor profiles
=head1 SYNOPSIS
B<aa-remove-unknown> [option]
=head1 DESCRIPTION
B<aa-remove-unknown> will inventory all profiles in /etc/apparmor.d/, compare
that list to the profiles currently loaded into the kernel, and then remove all
of the loaded profiles that were not found in /etc/apparmor.d/. It will also
report the name of each profile that it removes on standard out.
=head1 OPTIONS
=over 4
=item -h, --help
displays a short usage statement.
=item -n
dry run; only prints the names of profiles that would be removed
=back
=head1 EXAMPLES
$ sudo ./aa-remove-unknown -n
Would remove 'test//null-/usr/bin/whoami'
Would remove 'test'
$ sudo ./aa-remove-unknown
Removing 'test//null-/usr/bin/whoami'
Removing 'test'
=head1 BUGS
None. Please report any you find to Launchpad at
L<https://bugs.launchpad.net/apparmor/+filebug>.
=head1 SEE ALSO
apparmor(7)
=cut

View File

@@ -40,6 +40,7 @@ args = parser.parse_args()
paranoid = args.paranoid
aa.init_aa()
aa_mountpoint = aa.check_for_apparmor()
if not aa_mountpoint:
raise aa.AppArmorException(_("It seems AppArmor was not started. Please enable AppArmor and try again."))

View File

@@ -1,6 +1,6 @@
# ----------------------------------------------------------------------
# Copyright (C) 2013 Kshitij Gupta <kgupta8592@gmail.com>
# Copyright (C) 2014-2016 Christian Boltz <apparmor@cboltz.de>
# Copyright (C) 2014-2017 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
@@ -73,16 +73,14 @@ _ = init_translation()
# Setup logging incase of debugging is enabled
debug_logger = DebugLogger('aa')
CONFDIR = '/etc/apparmor'
running_under_genprof = False
unimplemented_warning = False
# The database for severity
sev_db = None
# The file to read log messages from
### Was our
logfile = None
CONFDIR = None
conf = None
cfg = None
repo_cfg = None
@@ -99,12 +97,7 @@ existing_profiles = dict()
# format: user_globs['/foo*'] = AARE('/foo*')
user_globs = {}
# The key for representing bare "file," rules
ALL = '\0ALL'
## Variables used under logprof
### Were our
t = hasher() # dict()
transitions = hasher()
aa = hasher() # Profiles originally in sd, replace by aa
@@ -112,15 +105,12 @@ original_aa = hasher()
extras = hasher() # Inactive profiles from extras
### end our
log = []
pid = dict()
log_pid = dict() # handed over to ReadLog, gets filled in logparser.py. The only case the previous content of this variable _might_(?) be used is aa-genprof (multiple do_logprof_pass() runs)
seen = hasher() # dir()
profile_changes = hasher()
prelog = hasher()
log_dict = hasher() # dict()
changed = dict()
created = []
skip = hasher()
helpers = dict() # Preserve this between passes # was our
### logprof ends
@@ -1486,16 +1476,17 @@ def order_globs(globs, original_path):
return globs
def ask_the_questions():
def ask_the_questions(log_dict):
for aamode in sorted(log_dict.keys()):
# Describe the type of changes
if aamode == 'PERMITTING':
aaui.UI_Info(_('Complain-mode changes:'))
elif aamode == 'REJECTING':
aaui.UI_Info(_('Enforce-mode changes:'))
elif aamode == 'merge':
pass # aa-mergeprof
else:
# This is so wrong!
fatal_error(_('Invalid mode found: %s') % aamode)
raise AppArmorBug(_('Invalid mode found: %s') % aamode)
for profile in sorted(log_dict[aamode].keys()):
# Update the repo profiles
@@ -1513,16 +1504,83 @@ def ask_the_questions():
for hat in hats:
if not aa[profile].get(hat).get('file'):
# Ignore log events for a non-existing profile or child profile. Such events can occour
# after deleting a profile or hat manually, or when processing a foreign log.
# (Checking for 'file' is a simplified way to check if it's a profile_storage() struct.)
debug_logger.debug("Ignoring events for non-existing profile %s" % combine_name(profile, hat))
continue
if not aa[profile].get(hat, {}).get('file'):
if aamode != 'merge':
# Ignore log events for a non-existing profile or child profile. Such events can occour
# after deleting a profile or hat manually, or when processing a foreign log.
# (Checking for 'file' is a simplified way to check if it's a profile_storage() struct.)
debug_logger.debug("Ignoring events for non-existing profile %s" % combine_name(profile, hat))
continue
ans = ''
while ans not in ['CMD_ADDHAT', 'CMD_ADDSUBPROFILE', 'CMD_DENY']:
q = aaui.PromptQuestion()
q.headers += [_('Profile'), profile]
if log_dict[aamode][profile][hat]['profile']:
q.headers += [_('Requested Subprofile'), hat]
q.functions.append('CMD_ADDSUBPROFILE')
else:
q.headers += [_('Requested Hat'), hat]
q.functions.append('CMD_ADDHAT')
q.functions += ['CMD_DENY', 'CMD_ABORT', 'CMD_FINISHED']
q.default = 'CMD_DENY'
ans = q.promptUser()[0]
if ans == 'CMD_FINISHED':
return
if ans == 'CMD_DENY':
continue # don't ask about individual rules if the user doesn't want the additional subprofile/hat
if log_dict[aamode][profile][hat]['profile']:
aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing subprofile')
aa[profile][hat]['profile'] = True
else:
aa[profile][hat] = profile_storage(profile, hat, 'mergeprof ask_the_questions() - missing hat')
aa[profile][hat]['profile'] = False
#Add the includes from the other profile to the user profile
done = False
options = []
for inc in log_dict[aamode][profile][hat]['include'].keys():
if not inc in aa[profile][hat]['include'].keys():
options.append('#include <%s>' %inc)
default_option = 1
q = aaui.PromptQuestion()
q.options = options
q.selected = default_option - 1
q.headers = [_('File includes'), _('Select the ones you wish to add')]
q.functions = ['CMD_ALLOW', 'CMD_IGNORE_ENTRY', 'CMD_ABORT', 'CMD_FINISHED']
q.default = 'CMD_ALLOW'
while not done and options:
ans, selected = q.promptUser()
if ans == 'CMD_IGNORE_ENTRY':
done = True
elif ans == 'CMD_ALLOW':
selection = options[selected]
inc = re_match_include(selection)
deleted = apparmor.aa.delete_duplicates(aa[profile][hat], inc)
aa[profile][hat]['include'][inc] = True
options.pop(selected)
aaui.UI_Info(_('Adding %s to the file.') % selection)
if deleted:
aaui.UI_Info(_('Deleted %s previous matching profile entries.') % deleted)
elif ans == 'CMD_FINISHED':
return
# check for and ask about conflicting exec modes
ask_conflict_mode(profile, hat, aa[profile][hat], log_dict[aamode][profile][hat])
for ruletype in ruletypes:
for rule_obj in log_dict[aamode][profile][hat][ruletype].rules:
# XXX aa-mergeprof also has this code - if you change it, keep aa-mergeprof in sync!
if is_known_rule(aa[profile][hat], ruletype, rule_obj):
continue
@@ -1655,7 +1713,6 @@ def ask_the_questions():
else:
done = False
# END of code (mostly) shared with aa-mergeprof
def selection_to_rule_obj(rule_obj, selection):
rule_type = type(rule_obj)
@@ -1726,6 +1783,39 @@ def delete_duplicates(profile, incname):
return deleted
def ask_conflict_mode(profile, hat, old_profile, merge_profile):
'''ask user about conflicting exec rules'''
for oldrule in old_profile['file'].rules:
conflictingrules = merge_profile['file'].get_exec_conflict_rules(oldrule)
if conflictingrules.rules:
q = aaui.PromptQuestion()
q.headers = [_('Path'), oldrule.path.regex]
q.headers += [_('Select the appropriate mode'), '']
options = []
options.append(oldrule.get_clean())
for rule in conflictingrules.rules:
options.append(rule.get_clean())
q.options = options
q.functions = ['CMD_ALLOW', 'CMD_ABORT']
done = False
while not done:
ans, selected = q.promptUser()
if ans == 'CMD_ALLOW':
if selected == 0:
pass # just keep the existing rule
elif selected > 0:
# replace existing rule with merged one
old_profile['file'].delete(oldrule)
old_profile['file'].add(conflictingrules.rules[selected - 1])
else:
raise AppArmorException(_('Unknown selection'))
for rule in conflictingrules.rules:
merge_profile['file'].delete(rule) # make sure aa-mergeprof doesn't ask to add conflicting rules later
done = True
def match_includes(profile, rule_type, rule_obj):
newincludes = []
for incname in include.keys():
@@ -1767,11 +1857,9 @@ def set_logfile(filename):
elif os.path.isdir(logfile):
raise AppArmorException(_('%s is a directory. Please specify a file as logfile') % logfile)
def do_logprof_pass(logmark='', passno=0, pid=pid):
def do_logprof_pass(logmark='', passno=0, log_pid=log_pid):
# set up variables for this pass
# t = hasher()
# transitions = hasher()
# seen = hasher() # XXX global?
global log
log = []
global existing_profiles
@@ -1779,9 +1867,7 @@ def do_logprof_pass(logmark='', passno=0, pid=pid):
# aa = hasher()
# profile_changes = hasher()
# prelog = hasher()
# log_dict = hasher()
# changed = dict()
# skip = hasher() # XXX global?
# filelist = hasher()
aaui.UI_Info(_('Reading log entries from %s.') % logfile)
@@ -1799,7 +1885,7 @@ def do_logprof_pass(logmark='', passno=0, pid=pid):
## 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(pid, logfile, existing_profiles, profile_dir, log)
log_reader = apparmor.logparser.ReadLog(log_pid, logfile, existing_profiles, profile_dir, log)
log = log_reader.read_log(logmark)
#read_log(logmark)
@@ -1811,9 +1897,9 @@ def do_logprof_pass(logmark='', passno=0, pid=pid):
for pid in sorted(profile_changes.keys()):
set_process(pid, profile_changes[pid])
collapse_log()
log_dict = collapse_log()
ask_the_questions()
ask_the_questions(log_dict)
if aaui.UI_mode == 'yast':
# To-Do
@@ -2019,6 +2105,7 @@ def set_process(pid, profile):
process.close()
def collapse_log():
log_dict = hasher()
for aamode in prelog.keys():
for profile in prelog[aamode].keys():
for hat in prelog[aamode][profile].keys():
@@ -2099,6 +2186,8 @@ def collapse_log():
if not is_known_rule(aa[profile][hat], 'signal', signal_event):
log_dict[aamode][profile][hat]['signal'].add(signal_event)
return log_dict
def is_skippable_file(path):
"""Returns True if filename matches something to be skipped (rpm or dpkg backup files, hidden files etc.)
The list of skippable files needs to be synced with apparmor initscript and libapparmor _aa_is_blacklisted()
@@ -3652,24 +3741,36 @@ def logger_path():
######Initialisations######
conf = apparmor.config.Config('ini', CONFDIR)
cfg = conf.read_config('logprof.conf')
def init_aa(confdir="/etc/apparmor"):
global CONFDIR
global conf
global cfg
global profile_dir
global extra_profile_dir
global parser
# prevent various failures if logprof.conf doesn't exist
if not cfg.sections():
cfg.add_section('settings')
cfg.add_section('required_hats')
if CONFDIR:
return # config already initialized (and possibly changed afterwards), so don't overwrite the config variables
if cfg['settings'].get('default_owner_prompt', False):
cfg['settings']['default_owner_prompt'] = ''
CONFDIR = confdir
conf = apparmor.config.Config('ini', CONFDIR)
cfg = conf.read_config('logprof.conf')
profile_dir = conf.find_first_dir(cfg['settings'].get('profiledir')) or '/etc/apparmor.d'
if not os.path.isdir(profile_dir):
raise AppArmorException('Can\'t find AppArmor profiles')
# prevent various failures if logprof.conf doesn't exist
if not cfg.sections():
cfg.add_section('settings')
cfg.add_section('required_hats')
extra_profile_dir = conf.find_first_dir(cfg['settings'].get('inactive_profiledir')) or '/usr/share/apparmor/extra-profiles/'
if cfg['settings'].get('default_owner_prompt', False):
cfg['settings']['default_owner_prompt'] = ''
parser = conf.find_first_file(cfg['settings'].get('parser')) or '/sbin/apparmor_parser'
if not os.path.isfile(parser) or not os.access(parser, os.EX_OK):
raise AppArmorException('Can\'t find apparmor_parser')
profile_dir = conf.find_first_dir(cfg['settings'].get('profiledir')) or '/etc/apparmor.d'
if not os.path.isdir(profile_dir):
raise AppArmorException('Can\'t find AppArmor profiles in %s' % (profile_dir))
extra_profile_dir = conf.find_first_dir(cfg['settings'].get('inactive_profiledir')) or '/usr/share/apparmor/extra-profiles/'
parser = conf.find_first_file(cfg['settings'].get('parser')) or '/sbin/apparmor_parser'
if not os.path.isfile(parser) or not os.access(parser, os.EX_OK):
raise AppArmorException('Can\'t find apparmor_parser at %s' % (parser))

View File

@@ -16,6 +16,7 @@ import apparmor.aa as apparmor
class Prof(object):
def __init__(self, filename):
apparmor.init_aa()
self.aa = apparmor.aa
self.filelist = apparmor.filelist
self.include = apparmor.include

View File

@@ -259,14 +259,11 @@ def open_file_read(path):
return orig
def verify_policy(policy):
def verify_policy(policy, exe, base=None, include=None):
'''Verify policy compiles'''
exe = "/sbin/apparmor_parser"
if not os.path.exists(exe):
rc, exe = cmd(['which', 'apparmor_parser'])
if rc != 0:
warn("Could not find apparmor_parser. Skipping verify")
return True
if not exe:
warn("Could not find apparmor_parser. Skipping verify")
return True
fn = ""
# if policy starts with '/' and is one line, assume it is a path
@@ -279,7 +276,14 @@ def verify_policy(policy):
os.write(f, policy)
os.close(f)
rc, out = cmd([exe, '-QTK', fn])
command = [exe, '-QTK']
if base:
command.extend(['-b', base])
if include:
command.extend(['-I', include])
command.append(fn)
rc, out = cmd(command)
os.unlink(fn)
if rc == 0:
return True
@@ -302,6 +306,22 @@ class AppArmorEasyProfile:
if os.path.isfile(self.conffile):
self._get_defaults()
self.parser_path = '/sbin/apparmor_parser'
if opt.parser_path:
self.parser_path = opt.parser_path
elif not os.path.exists(self.parser_path):
rc, self.parser_path = cmd(['which', 'apparmor_parser'])
if rc != 0:
self.parser_path = None
self.parser_base = "/etc/apparmor.d"
if opt.parser_base:
self.parser_base = opt.parser_base
self.parser_include = None
if opt.parser_include:
self.parser_include = opt.parser_include
if opt.templates_dir and os.path.isdir(opt.templates_dir):
self.dirs['templates'] = os.path.abspath(opt.templates_dir)
elif not opt.templates_dir and \
@@ -350,8 +370,6 @@ class AppArmorEasyProfile:
if not 'policygroups' in self.dirs:
raise AppArmorException("Could not find policygroups directory")
self.aa_topdir = "/etc/apparmor.d"
self.binary = binary
if binary:
if not valid_binary_path(binary):
@@ -506,9 +524,15 @@ class AppArmorEasyProfile:
def gen_abstraction_rule(self, abstraction):
'''Generate an abstraction rule'''
p = os.path.join(self.aa_topdir, "abstractions", abstraction)
if not os.path.exists(p):
raise AppArmorException("%s does not exist" % p)
base = os.path.join(self.parser_base, "abstractions", abstraction)
if not os.path.exists(base):
if not self.parser_include:
raise AppArmorException("%s does not exist" % base)
include = os.path.join(self.parser_include, "abstractions", abstraction)
if not os.path.exists(include):
raise AppArmorException("Neither %s nor %s exist" % (base, include))
return "#include <abstractions/%s>" % abstraction
def gen_variable_declaration(self, dec):
@@ -661,7 +685,7 @@ class AppArmorEasyProfile:
if no_verify:
debug("Skipping policy verification")
elif not verify_policy(policy):
elif not verify_policy(policy, self.parser_path, self.parser_base, self.parser_include):
msg("\n" + policy)
raise AppArmorException("Invalid policy")
@@ -804,6 +828,10 @@ def check_for_manifest_arg_append(option, opt_str, value, parser):
def add_parser_policy_args(parser):
'''Add parser arguments'''
parser.add_option("--parser",
dest="parser_path",
help="The path to the profile parser used for verification",
metavar="PATH")
parser.add_option("-a", "--abstractions",
action="callback",
callback=check_for_manifest_arg,
@@ -811,6 +839,14 @@ def add_parser_policy_args(parser):
dest="abstractions",
help="Comma-separated list of abstractions",
metavar="ABSTRACTIONS")
parser.add_option("-b", "--base",
dest="parser_base",
help="Set the base directory for resolving abstractions",
metavar="DIR")
parser.add_option("-I", "--Include",
dest="parser_include",
help="Add a directory to the search path when resolving abstractions",
metavar="DIR")
parser.add_option("--read-path",
action="callback",
callback=check_for_manifest_arg_append,

View File

@@ -243,6 +243,8 @@ class ReadLog:
if e['operation'] == 'change_hat':
if aamode != 'HINT' and aamode != 'PERMITTING':
return None
if e['error_code'] == 1 and e['info'] == 'unconfined can not change_hat':
return None
profile = e['name2']
#hat = None
if '//' in e['name2']:

View File

@@ -37,14 +37,16 @@ RE_ACCESS_KEYWORDS = ( joint_access_keyword + # one of the access_keyword or
RE_FLAG = '(?P<%s>(\S+|"[^"]+"|\(\s*\S+\s*\)|\(\s*"[^"]+"\)\s*))' # string without spaces, or quoted string, optionally wrapped in (...). %s is the match group name
# plaintext version: | * | "* " | ( * ) | ( " * " ) |
# XXX this regex will allow repeated parameters, last one wins
# XXX (the parser will reject such rules)
RE_DBUS_DETAILS = re.compile(
'^' +
'(\s+(?P<access>' + RE_ACCESS_KEYWORDS + '))?' + # optional access keyword(s)
'(\s+(bus\s*=\s*' + RE_FLAG % 'bus' + '))?' + # optional bus= system | session | AARE, (...) optional
'(\s+(path\s*=\s*' + RE_FLAG % 'path' + '))?' + # optional path=AARE, (...) optional
'(\s+(name\s*=\s*' + RE_FLAG % 'name' + '))?' + # optional name=AARE, (...) optional
'(\s+(interface\s*=\s*' + RE_FLAG % 'interface' + '))?' + # optional interface=AARE, (...) optional
'(\s+(member\s*=\s*' + RE_FLAG % 'member' + '))?' + # optional member=AARE, (...) optional
'((\s+(bus\s*=\s*' + RE_FLAG % 'bus' + '))?|' + # optional bus= system | session | AARE, (...) optional
'(\s+(path\s*=\s*' + RE_FLAG % 'path' + '))?|' + # optional path=AARE, (...) optional
'(\s+(name\s*=\s*' + RE_FLAG % 'name' + '))?|' + # optional name=AARE, (...) optional
'(\s+(interface\s*=\s*' + RE_FLAG % 'interface' + '))?|' + # optional interface=AARE, (...) optional
'(\s+(member\s*=\s*' + RE_FLAG % 'member' + '))?|' + # optional member=AARE, (...) optional
'(\s+(peer\s*=\s*\((,|\s)*' + # optional peer=( name=AARE and/or label=AARE ), (...) required
'(' +
'(' + '(,|\s)*' + ')' + # empty peer=()
@@ -57,7 +59,7 @@ RE_DBUS_DETAILS = re.compile(
'|' # or
'(' + 'label\s*=\s*' + RE_PROFILE_NAME % 'peerlabel3' + '(,|\s)+' + 'name\s*=\s*' + RE_PROFILE_NAME % 'peername3' + ')' + # peer label + name (match name peername3/peerlabel3)
')'
'(,|\s)*\)))?'
'(,|\s)*\)))?){0,6}'
'\s*$')

View File

@@ -24,6 +24,8 @@ _ = init_translation()
class aa_tools:
def __init__(self, tool_name, args):
apparmor.init_aa()
self.name = tool_name
self.profiledir = args.dir
self.profiling = args.program

View File

@@ -64,8 +64,8 @@ def get_translated_hotkey(translated, cmsg=''):
msg = 'PromptUser: ' + _('Invalid hotkey for')
# Originally (\S) was used but with translations it would not work :(
if re.search('\((\S+)\)', translated, re.LOCALE):
return re.search('\((\S+)\)', translated, re.LOCALE).groups()[0]
if re.search('\((\S+)\)', translated):
return re.search('\((\S+)\)', translated).groups()[0]
else:
if cmsg:
raise AppArmorException(cmsg)

View File

@@ -23,11 +23,17 @@ include $(COMMONDIR)/Make.rules
ifdef USE_SYSTEM
LD_LIBRARY_PATH=
PYTHONPATH=
CONFDIR=
BASEDIR=
PARSER=
else
# PYTHON_DIST_BUILD_PATH based on libapparmor/swig/python/test/Makefile.am
PYTHON_DIST_BUILD_PATH = ../../libraries/libapparmor/swig/python/build/$$($(PYTHON) -c "import distutils.util; import platform; print(\"lib.%s-%s\" %(distutils.util.get_platform(), platform.python_version()[:3]))")
LD_LIBRARY_PATH=../../libraries/libapparmor/src/.libs/
PYTHONPATH=..:$(PYTHON_DIST_BUILD_PATH)
CONFDIR=$(CURDIR)
BASEDIR=../../profiles/apparmor.d
PARSER=../../parser/apparmor_parser
endif
.PHONY: __libapparmor
@@ -62,10 +68,10 @@ clean:
rm -rf __pycache__/ .coverage htmlcov
check: __libapparmor
export PYTHONPATH=$(PYTHONPATH) ; export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) ; export LC_ALL=C; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(call pyalldo, $(test)))
export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(call pyalldo, $(test)))
.coverage: $(wildcard ../aa-* ../apparmor/*.py test-*.py) __libapparmor
export PYTHONPATH=$(PYTHONPATH) ; export LD_LIBRARY_PATH=$(LD_LIBRARY_PATH); export LC_ALL=C; $(COVERAGE_IGNORE_FAILURES_CMD) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(PYTHON) -m coverage run --branch -p $(test); )
export PYTHONPATH=$(PYTHONPATH) LD_LIBRARY_PATH=$(LD_LIBRARY_PATH) LC_ALL=C __AA_CONFDIR=$(CONFDIR) __AA_BASEDIR=$(BASEDIR) __AA_PARSER=$(PARSER) ; $(COVERAGE_IGNORE_FAILURES_CMD) ; $(foreach test, $(wildcard test-*.py), echo ; echo === $(test) === ; $(PYTHON) -m coverage run --branch -p $(test); )
$(PYTHON) -m coverage combine
coverage: .coverage

View File

@@ -103,6 +103,17 @@ def setup_regex_tests(test_class):
stub_test.__doc__ = "test '%s': %s" % (line, desc)
setattr(test_class, 'test_%d' % (i), stub_test)
def setup_aa(aa):
confdir = os.getenv('__AA_CONFDIR')
try:
if confdir:
aa.init_aa(confdir=confdir)
else:
aa.init_aa()
except AttributeError:
# apparmor.aa module versions <= 2.11 do not have the init_aa() method
pass
def write_file(directory, file, contents):
'''construct path, write contents to it, and return the constructed path'''
path = os.path.join(directory, file)

View File

@@ -10,11 +10,11 @@
# ------------------------------------------------------------------
[settings]
profiledir = /etc/apparmor.d /etc/subdomain.d
inactive_profiledir = /usr/share/doc/apparmor-profiles/extras
profiledir = ../../profiles/apparmor.d
inactive_profiledir = ../../profiles/apparmor/profiles/extra
logfiles = /var/log/audit/audit.log /var/log/syslog /var/log/messages
parser = /sbin/apparmor_parser /sbin/subdomain_parser
parser = ../../parser/apparmor_parser
ldd = /usr/bin/ldd
logger = /bin/logger /usr/bin/logger

View File

@@ -16,7 +16,7 @@ import shutil
import subprocess
import sys
import unittest
from common_test import AATest, setup_all_loops
from common_test import AATest, setup_all_loops, setup_aa
import apparmor.aa as apparmor
from common_test import read_file
@@ -156,6 +156,7 @@ class MinitoolsTest(AATest):
self.assertEqual(exp_content, real_content, 'Failed to cleanup profile properly')
setup_aa(apparmor)
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -18,6 +18,8 @@ import sys
import tempfile
import unittest
import apparmor.easyprof as easyprof
topdir = None
debugging = False
@@ -163,6 +165,23 @@ TEMPLATES_DIR="%s/templates"
self.binary = "/opt/bin/foo"
self.full_args = ['-c', self.conffile, self.binary]
# Check __AA_BASEDIR, which may be set by the Makefile, to see if
# we should use a non-default base directory path to find
# abstraction files
#
# NOTE: Individual tests can append another --base path to the
# args list and override a base path set here
base = os.getenv('__AA_BASEDIR')
if base:
self.full_args.append('--base=%s' % base)
# Check __AA_PARSER, which may be set by the Makefile, to see if
# we should use a non-default apparmor_parser path to verify
# policy
parser = os.getenv('__AA_PARSER')
if parser:
self.full_args.append('--parser=%s' % parser)
if debugging:
self.full_args.append('-d')
@@ -913,6 +932,94 @@ POLICYGROUPS_DIR="%s/templates"
raise
raise Exception ("abstraction '%s' should be invalid" % s)
def _create_tmp_base_dir(self, prefix='', abstractions=[], tunables=[]):
'''Create a temporary base dir layout'''
base_name = 'apparmor.d'
if prefix:
base_name = '%s-%s' % (prefix, base_name)
base_dir = os.path.join(self.tmpdir, base_name)
abstractions_dir = os.path.join(base_dir, 'abstractions')
tunables_dir = os.path.join(base_dir, 'tunables')
os.mkdir(base_dir)
os.mkdir(abstractions_dir)
os.mkdir(tunables_dir)
for f in abstractions:
contents = '''
# Abstraction file for testing
/%s r,
''' % (f)
open(os.path.join(abstractions_dir, f), 'w').write(contents)
for f in tunables:
contents = '''
# Tunable file for testing
@{AA_TEST_%s}=foo
''' % (f)
open(os.path.join(tunables_dir, f), 'w').write(contents)
return base_dir
def test_genpolicy_abstractions_custom_base(self):
'''Test genpolicy (custom base dir)'''
abstraction = "custom-base-dir-test-abstraction"
# The default template #includes the base abstraction and global
# tunable so we need to create placeholders
base = self._create_tmp_base_dir(abstractions=['base', abstraction], tunables=['global'])
args = ['--abstractions=%s' % abstraction, '--base=%s' % base]
p = self._gen_policy(extra_args=args)
search = "#include <abstractions/%s>" % abstraction
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###ABSTRACTIONS###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_abstractions_custom_base_bad(self):
'''Test genpolicy (custom base dir - bad base dirs)'''
abstraction = "custom-base-dir-test-abstraction"
bad = [ None, '/etc/apparmor.d', '/' ]
for base in bad:
try:
args = ['--abstractions=%s' % abstraction]
if base:
args.append('--base=%s' % base)
self._gen_policy(extra_args=args)
except easyprof.AppArmorException:
continue
except Exception:
raise
raise Exception ("abstraction '%s' should be invalid" % abstraction)
def test_genpolicy_abstractions_custom_include(self):
'''Test genpolicy (custom include dir)'''
abstraction = "custom-include-dir-test-abstraction"
# No need to create placeholders for the base abstraction or global
# tunable since we're not adjusting the base directory
include = self._create_tmp_base_dir(abstractions=[abstraction])
args = ['--abstractions=%s' % abstraction, '--Include=%s' % include]
p = self._gen_policy(extra_args=args)
search = "#include <abstractions/%s>" % abstraction
self.assertTrue(search in p, "Could not find '%s' in:\n%s" % (search, p))
inv_s = '###ABSTRACTIONS###'
self.assertFalse(inv_s in p, "Found '%s' in :\n%s" % (inv_s, p))
def test_genpolicy_abstractions_custom_include_bad(self):
'''Test genpolicy (custom include dir - bad include dirs)'''
abstraction = "custom-include-dir-test-abstraction"
bad = [ None, '/etc/apparmor.d', '/' ]
for include in bad:
try:
args = ['--abstractions=%s' % abstraction]
if include:
args.append('--Include=%s' % include)
self._gen_policy(extra_args=args)
except easyprof.AppArmorException:
continue
except Exception:
raise
raise Exception ("abstraction '%s' should be invalid" % abstraction)
def test_genpolicy_profile_name_bad(self):
'''Test genpolicy (profile name - bad values)'''
bad = [
@@ -2568,40 +2675,16 @@ POLICYGROUPS_DIR="%s/templates"
# Main
#
if __name__ == '__main__':
def cleanup(files):
for f in files:
if os.path.exists(f):
os.unlink(f)
absfn = os.path.abspath(sys.argv[0])
topdir = os.path.dirname(os.path.dirname(absfn))
if len(sys.argv) > 1 and (sys.argv[1] == '-d' or sys.argv[1] == '--debug'):
debugging = True
created = []
# Create the necessary files to import aa-easyprof
init = os.path.join(os.path.dirname(absfn), '__init__.py')
if not os.path.exists(init):
open(init, 'a').close()
created.append(init)
symlink = os.path.join(os.path.dirname(absfn), 'easyprof.py')
if not os.path.exists(symlink):
os.symlink(os.path.join(topdir, 'apparmor', 'easyprof.py'), symlink)
created.append(symlink)
created.append(symlink + 'c')
# Now that we have everything we need, import aa-easyprof
import easyprof
# run the tests
suite = unittest.TestSuite()
suite.addTest(unittest.TestLoader().loadTestsFromTestCase(T))
rc = unittest.TextTestRunner(verbosity=2).run(suite)
cleanup(created)
if not rc.wasSuccessful():
sys.exit(1)

View File

@@ -10,7 +10,7 @@
# ------------------------------------------------------------------
import unittest
from common_test import AATest, setup_all_loops
from common_test import AATest, setup_all_loops, setup_aa
from common_test import read_file, write_file
import os
@@ -784,8 +784,8 @@ class AaTest_get_file_perms_2(AATest):
('/dev/null', {'allow': {'all': {'r', 'w', 'k'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/dev/null'} }),
('/foo/bar', {'allow': {'all': {'r', 'w'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/foo/bar'} }), # exec perms not included
('/no/thing', {'allow': {'all': set(), 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': set() }),
('/usr/lib/ispell/', {'allow': {'all': {'r'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/lib/ispell/', '/usr/lib{,32,64}/**'} }), # from abstractions/enchant
('/usr/lib/aspell/*.so', {'allow': {'all': {'m', 'r'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/lib/aspell/*', '/usr/lib/aspell/*.so', '/usr/lib{,32,64}/**'} }), # from abstractions/aspell via abstractions/enchant
('/usr/lib/ispell/', {'allow': {'all': {'r'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/lib/ispell/', '/{usr/,}lib{,32,64}/**'} }), # from abstractions/enchant
('/usr/lib/aspell/*.so', {'allow': {'all': {'m', 'r'}, 'owner': set() }, 'deny': {'all':set(), 'owner': set()}, 'paths': {'/usr/lib/aspell/*', '/usr/lib/aspell/*.so', '/{usr/,}lib{,32,64}/**'} }), # from abstractions/aspell via abstractions/enchant
]
def _run_test(self, params, expected):
@@ -820,8 +820,8 @@ class AaTest_propose_file_rules(AATest):
(['/usr/share/common-licenses/foo/bar', 'w'], ['/usr/share/common*/foo/* rw,', '/usr/share/common-licenses/** rw,', '/usr/share/common-licenses/foo/bar rw,'] ),
(['/dev/null', 'wk'], ['/dev/null rwk,'] ),
(['/foo/bar', 'rw'], ['/foo/bar rw,'] ),
(['/usr/lib/ispell/', 'w'], ['/usr/lib{,32,64}/** rw,', '/usr/lib/ispell/ rw,'] ),
(['/usr/lib/aspell/some.so', 'k'], ['/usr/lib/aspell/* mrk,', '/usr/lib/aspell/*.so mrk,', '/usr/lib{,32,64}/** mrk,', '/usr/lib/aspell/some.so mrk,'] ),
(['/usr/lib/ispell/', 'w'], ['/{usr/,}lib{,32,64}/** rw,', '/usr/lib/ispell/ rw,'] ),
(['/usr/lib/aspell/some.so', 'k'], ['/usr/lib/aspell/* mrk,', '/usr/lib/aspell/*.so mrk,', '/{usr/,}lib{,32,64}/** mrk,', '/usr/lib/aspell/some.so mrk,'] ),
]
def _run_test(self, params, expected):
@@ -855,6 +855,7 @@ class AaTest_propose_file_rules(AATest):
proposals = propose_file_rules(profile, rule_obj)
self.assertEqual(proposals, expected)
setup_aa(apparmor.aa)
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=2)

View File

@@ -24,7 +24,7 @@ class Test(unittest.TestCase):
conf = ini_config.read_config('logprof.conf')
logprof_sections = ['settings', 'repository', 'qualifiers', 'required_hats', 'defaulthat', 'globs']
logprof_sections_options = ['profiledir', 'inactive_profiledir', 'logfiles', 'parser', 'ldd', 'logger', 'default_owner_prompt', 'custom_includes']
logprof_settings_parser = '/sbin/apparmor_parser /sbin/subdomain_parser'
logprof_settings_parser = '../../parser/apparmor_parser'
self.assertEqual(conf.sections(), logprof_sections)
self.assertEqual(conf.options('settings'), logprof_sections_options)

View File

@@ -89,6 +89,10 @@ class DbusTestParse(DbusTest):
('dbus peer=(, label = bar, name = foo ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar,', False)), # XXX peerlabel includes the comma
('dbus peer=( label = bar , name = foo ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)),
('dbus peer=( label = "bar" name = "foo" ),' , exp(False, False, False, '', None , True , None, True, None, True, None, True, None, True, None, True, 'foo', False, 'bar', False)),
('dbus path=/foo/bar bus=session,' , exp(False, False, False, '', None , True , 'session', False, '/foo/bar', False, None, True, None, True, None, True, None, True, None, True)),
('dbus bus=system path=/foo/bar bus=session,' , exp(False, False, False, '', None , True , 'session', False, '/foo/bar', False, None, True, None, True, None, True, None, True, None, True)), # XXX bus= specified twice, last one wins
('dbus send peer=(label="foo") bus=session,' , exp(False, False, False, '', {'send'}, False, 'session', False, None, True, None, True, None, True, None, True, None, True, 'foo', False)),
('dbus bus=1 bus=2 bus=3 bus=4 bus=5 bus=6,' , exp(False, False, False, '', None , True , '6', False, None, True, None, True, None, True, None, True, None, True, None, True)), # XXX bus= specified multiple times, last one wins
]
def _run_test(self, rawrule, expected):
@@ -108,6 +112,8 @@ class DbusTestParseInvalid(DbusTest):
('dbus peer=(label=foo) path=,' , AppArmorException),
('dbus (invalid),' , AppArmorException),
('dbus peer=,' , AppArmorException),
('dbus bus=session bind bus=system,', AppArmorException),
('dbus bus=1 bus=2 bus=3 bus=4 bus=5 bus=6 bus=7,', AppArmorException),
]
def _run_test(self, rawrule, expected):

View File

@@ -10,7 +10,7 @@
# ------------------------------------------------------------------
import unittest
from common_test import AATest, setup_all_loops, read_file
from common_test import AATest, setup_all_loops, setup_aa, read_file
import os
from apparmor.common import open_file_read
@@ -214,7 +214,6 @@ class TestLogToProfile(AATest):
apparmor.aa.log = dict()
apparmor.aa.aa = apparmor.aa.hasher()
apparmor.aa.prelog = apparmor.aa.hasher()
apparmor.aa.log_dict = apparmor.aa.hasher()
profile = parsed_event['profile']
hat = profile
@@ -229,12 +228,12 @@ class TestLogToProfile(AATest):
for root in log:
apparmor.aa.handle_children('', '', root) # interactive for exec events!
apparmor.aa.collapse_log()
log_dict = apparmor.aa.collapse_log()
apparmor.aa.filelist = apparmor.aa.hasher()
apparmor.aa.filelist[profile_dummy_file]['profiles'][profile] = True
new_profile = apparmor.aa.serialize_profile(apparmor.aa.log_dict[aamode][profile], profile, None)
new_profile = apparmor.aa.serialize_profile(log_dict[aamode][profile], profile, None)
expected_profile = read_file('%s.profile' % params)
@@ -268,6 +267,7 @@ print('Testing libapparmor test_multi tests...')
TestLibapparmorTestMulti.tests = find_test_multi('../../libraries/libapparmor/testsuite/test_multi/')
TestLogToProfile.tests = find_test_multi('../../libraries/libapparmor/testsuite/test_multi/')
setup_aa(apparmor.aa)
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=1) # reduced verbosity due to the big number of tests

View File

@@ -11,7 +11,7 @@
import apparmor.aa as aa
import unittest
from common_test import AAParseTest, setup_regex_tests
from common_test import AAParseTest, setup_regex_tests, setup_aa
class BaseAAParseMountTest(AAParseTest):
def setUp(self):
@@ -39,6 +39,7 @@ class AAParseUmountTest(BaseAAParseMountTest):
('unmount /mnt/external,', 'unmount with mount point'),
]
setup_aa(aa)
if __name__ == '__main__':
setup_regex_tests(AAParseMountTest)
setup_regex_tests(AAParseRemountTest)

View File

@@ -10,7 +10,7 @@
# ------------------------------------------------------------------
import unittest
from common_test import AATest, setup_all_loops
from common_test import AATest, setup_all_loops, setup_aa
import apparmor.aa as apparmor
import os
@@ -30,9 +30,6 @@ skip_startswith = (
'generated_x/ambiguous-',
'generated_x/dominate-',
# permissions before path
'generated_perms_leading/',
# 'safe' and 'unsafe' keywords
'generated_perms_safe/',
@@ -52,6 +49,7 @@ exception_not_raised = [
'change_profile/onx_conflict_unsafe1.sd',
'change_profile/onx_conflict_unsafe2.sd',
'dbus/bad_modifier_2.sd',
'dbus/bad_regex_01.sd',
'dbus/bad_regex_02.sd',
'dbus/bad_regex_03.sd',
@@ -154,6 +152,8 @@ exception_not_raised = [
'vars/vars_dbus_bad_01.sd',
'vars/vars_dbus_bad_02.sd',
'vars/vars_dbus_bad_03.sd',
'vars/vars_dbus_bad_04.sd',
'vars/vars_dbus_bad_05.sd',
'vars/vars_dbus_bad_06.sd',
'vars/vars_dbus_bad_07.sd',
'vars/vars_file_evaluation_7.sd',
@@ -256,16 +256,75 @@ syntax_failure = [
'file/ok_5.sd', # Invalid mode UX
'file/ok_2.sd', # Invalid mode RWM
'file/ok_4.sd', # Invalid mode iX
'file/ok_embedded_spaces_4.sd', # \-escaped space
'file/file/ok_embedded_spaces_4.sd', # \-escaped space
'file/ok_quoted_4.sd', # quoted string including \"
'xtrans/simple_ok_pix_1.sd', # Invalid mode pIx
'xtrans/simple_ok_pux_1.sd', # Invalid mode rPux
# dbus regex mismatch
'vars/vars_dbus_4.sd',
'vars/vars_dbus_9.sd',
'vars/vars_dbus_2.sd',
# unexpected uppercase vs. lowercase in *x rules - generated_perms_leading directory
'generated_perms_leading/exact-re-Puxtarget.sd',
'generated_perms_leading/dominate-ownerCuxtarget2.sd',
'generated_perms_leading/ambiguous-Cux.sd',
'generated_perms_leading/dominate-ownerPux.sd',
'generated_perms_leading/exact-re-ownerPux.sd',
'generated_perms_leading/overlap-ownerCuxtarget.sd',
'generated_perms_leading/exact-re-ownerCuxtarget.sd',
'generated_perms_leading/dominate-Puxtarget2.sd',
'generated_perms_leading/dominate-ownerCuxtarget.sd',
'generated_perms_leading/dominate-ownerPuxtarget.sd',
'generated_perms_leading/ambiguous-Pux.sd',
'generated_perms_leading/ambiguous-Cuxtarget2.sd',
'generated_perms_leading/exact-Puxtarget2.sd',
'generated_perms_leading/ambiguous-ownerCux.sd',
'generated_perms_leading/exact-ownerPux.sd',
'generated_perms_leading/ambiguous-ownerPuxtarget.sd',
'generated_perms_leading/exact-re-ownerPuxtarget.sd',
'generated_perms_leading/exact-re-Cuxtarget.sd',
'generated_perms_leading/exact-re-Puxtarget2.sd',
'generated_perms_leading/dominate-Cux.sd',
'generated_perms_leading/exact-re-ownerCuxtarget2.sd',
'generated_perms_leading/ambiguous-ownerCuxtarget.sd',
'generated_perms_leading/exact-re-Cuxtarget2.sd',
'generated_perms_leading/ambiguous-Puxtarget.sd',
'generated_perms_leading/overlap-Puxtarget.sd',
'generated_perms_leading/ambiguous-Puxtarget2.sd',
'generated_perms_leading/overlap-Puxtarget2.sd',
'generated_perms_leading/exact-Puxtarget.sd',
'generated_perms_leading/overlap-ownerPuxtarget.sd',
'generated_perms_leading/exact-ownerCuxtarget.sd',
'generated_perms_leading/exact-re-ownerCux.sd',
'generated_perms_leading/exact-ownerPuxtarget2.sd',
'generated_perms_leading/exact-ownerCux.sd',
'generated_perms_leading/overlap-Cuxtarget2.sd',
'generated_perms_leading/ambiguous-Cuxtarget.sd',
'generated_perms_leading/ambiguous-ownerPuxtarget2.sd',
'generated_perms_leading/dominate-ownerCux.sd',
'generated_perms_leading/exact-Pux.sd',
'generated_perms_leading/exact-Cuxtarget.sd',
'generated_perms_leading/overlap-ownerCuxtarget2.sd',
'generated_perms_leading/overlap-Pux.sd',
'generated_perms_leading/overlap-ownerPux.sd',
'generated_perms_leading/ambiguous-ownerCuxtarget2.sd',
'generated_perms_leading/exact-re-Cux.sd',
'generated_perms_leading/exact-re-Pux.sd',
'generated_perms_leading/overlap-Cuxtarget.sd',
'generated_perms_leading/exact-re-ownerPuxtarget2.sd',
'generated_perms_leading/exact-Cuxtarget2.sd',
'generated_perms_leading/exact-Cux.sd',
'generated_perms_leading/overlap-Cux.sd',
'generated_perms_leading/overlap-ownerCux.sd',
'generated_perms_leading/exact-ownerPuxtarget.sd',
'generated_perms_leading/dominate-Pux.sd',
'generated_perms_leading/exact-ownerCuxtarget2.sd',
'generated_perms_leading/dominate-Puxtarget.sd',
'generated_perms_leading/ambiguous-ownerPux.sd',
'generated_perms_leading/overlap-ownerPuxtarget2.sd',
'generated_perms_leading/dominate-Cuxtarget2.sd',
'generated_perms_leading/dominate-Cuxtarget.sd',
'generated_perms_leading/dominate-ownerPuxtarget2.sd',
# escaping with \
'file/ok_embedded_spaces_4.sd', # \-escaped space
'file/file/ok_embedded_spaces_4.sd', # \-escaped space
'file/ok_quoted_4.sd', # quoted string including \"
# misc
'vars/vars_dbus_8.sd', # Path doesn't start with / or variable: {/@{TLDS}/foo,/com/@{DOMAINS}}
@@ -399,6 +458,7 @@ def find_and_setup_test_profiles(profile_dir):
print('Running %s parser simple_tests...' % len(TestParseParserTests.tests))
setup_aa(apparmor)
find_and_setup_test_profiles('../../parser/tst/simple_tests/')
setup_all_loops(__name__)

View File

@@ -11,7 +11,7 @@
import apparmor.aa as aa
import unittest
from common_test import AAParseTest, setup_regex_tests
from common_test import AAParseTest, setup_regex_tests, setup_aa
class AAParsePivotRootTest(AAParseTest):
def setUp(self):
@@ -24,6 +24,7 @@ class AAParsePivotRootTest(AAParseTest):
('pivot_root /old /new -> /usr/bin/child,', 'pivot_root child rule'),
]
setup_aa(aa)
if __name__ == '__main__':
setup_regex_tests(AAParsePivotRootTest)
unittest.main(verbosity=2)

View File

@@ -11,7 +11,7 @@
import apparmor.aa as aa
import unittest
from common_test import AATest, setup_all_loops
from common_test import AATest, setup_all_loops, setup_aa
from apparmor.common import AppArmorBug, AppArmorException
from apparmor.regex import ( strip_parenthesis, strip_quotes, parse_profile_start_line, re_match_include,
@@ -502,6 +502,7 @@ class TestStripQuotes(AATest):
setup_aa(aa)
setup_all_loops(__name__)
if __name__ == '__main__':
# these two are not converted to a tests[] loop yet

View File

@@ -11,7 +11,7 @@
import apparmor.aa as aa
import unittest
from common_test import AAParseTest, setup_regex_tests
from common_test import AAParseTest, setup_regex_tests, setup_aa
class AAParseUnixTest(AAParseTest):
@@ -34,6 +34,7 @@ class AAParseUnixTest(AAParseTest):
'complex unix rule'),
]
setup_aa(aa)
if __name__ == '__main__':
setup_regex_tests(AAParseUnixTest)
unittest.main(verbosity=2)