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

Compare commits

...

62 Commits

Author SHA1 Message Date
John Johansen
2fc80487f7 Prepare for AppArmor 4.0 beta3 release
- update version file

Signed-off-by: John Johansen <john.johansen@canonical.com>
2024-03-17 00:55:14 -07:00
John Johansen
c87969b37c Merge profiles: Add more unconfined profiles
This adds the remaining set of unconfined profiles, from the set listed
in https://bugs.launchpad.net/ubuntu/+source/pageedit/+bug/2046844

Fixes: https://bugs.launchpad.net/ubuntu/+source/pageedit/+bug/2046844
Signed-off-by: John Johansen <john.johansen@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1186
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-03-17 07:27:49 +00:00
John Johansen
b68bb18860 Merge network: several fixes
This patchset includes several fixes for the inet mediation.

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/374
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1183
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-03-17 07:22:24 +00:00
John Johansen
c47789340a Merge add unconfined profiles for geary, loupe and firefox dev versions
These applications need to use user namespaces, hence it needs an
unconfined profile when user namespaces are restricted from unconfined
like other applications in MR #1123

https://gitlab.com/apparmor/apparmor/-/merge_requests/1123

In addition this serves as a handle to uniquely identify them instead
of unconfined to peers in policy.

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

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1185
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-03-17 07:18:03 +00:00
John Johansen
e23a3eeba5 profiles: Add more unconfined profiles
This adds the remaining set of unconfined profiles, from the set listed
in https://bugs.launchpad.net/ubuntu/+source/pageedit/+bug/2046844

Fixes: https://bugs.launchpad.net/ubuntu/+source/pageedit/+bug/2046844
Signed-off-by: John Johansen <john.johansen@canonical.com>
2024-03-17 00:16:37 -07:00
Georgia Garcia
d0fadc48cf tests: add missing rules to the inet/inet6 mediation tests
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 18:24:19 -03:00
Georgia Garcia
aec3f3b22c add unconfined profiles for geary, loupe and firefox dev versions
These applications need to use user namespaces, hence it needs an
unconfined profile when user namespaces are restricted from unconfined
like other applications in MR #1123

https://gitlab.com/apparmor/apparmor/-/merge_requests/1123

In addition this serves as a handle to uniquely identify them instead
of unconfined to peers in policy.

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

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 17:44:23 -03:00
Georgia Garcia
101651c88f parser: fix af_inet feature from network to network_v8
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 16:38:10 -03:00
Georgia Garcia
efc2ec5fdd parser: fix cmp function for network rules
The network cmp function was missing the new attributes added, causing
rules to be dropped.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 16:38:10 -03:00
Georgia Garcia
b01b9895e7 parser: add ability to specify anonymous ip
If anonymous ip is specified, then the port will match anything.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 16:38:10 -03:00
Georgia Garcia
a0a0c88d9e parser: generate all ip options when ip is not specified
When the ip is not specified, then we should generate rules for ip
types: anonymous, ipv4 and ipv6. And that's the case for both local
and peer when considering recv and send permissions.

std::ostringstream does not have a copy constructor, that's why in
several places one can see streaming the string of one stream into
another.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 10:33:33 -03:00
Georgia Garcia
63676459c4 parser: encode create permission separately from the others
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 10:33:33 -03:00
Georgia Garcia
9ed04cb01e parser: introduce network label attribute
We want to be able to determine label in the future and build the
policy dfa based on its presence or not.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 10:33:33 -03:00
Georgia Garcia
2a885872a3 parser: ip size encoding should be an enum, not the ip size
According to the protocol expected by the kernel, the field
representing the ip size should be an enum instead of the actual ip
size. This is more future-proof.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 10:33:33 -03:00
Georgia Garcia
989501428e parser: initial steps into encoding protocol properly
Before the inet patches, protocol was not handled, so the information
was ignored. This patch introduces the ability to start mediating
protocol.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-15 10:33:33 -03:00
John Johansen
25f21a0758 Merge libraries/libapparmor: fix syntax in configure
We're not trying to execute a command in EXTRA_WARNINGS, so don't try to spawn
a subshell for it which gives:
```
./configure: 14770: EXTRA_WARNINGS: not found
checking whether C compiler accepts -flto-partition=none... yes
```

We can either use ${} or just $ (style). Use $ to be consistent with other
uses in the file.

Signed-off-by: Sam James <sam@gentoo.org>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1184
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-03-15 04:05:15 +00:00
Sam James
022af9c528 libraries/libapparmor: fix syntax in configure
We're not trying to execute a command in EXTRA_WARNINGS, so don't try to spawn
a subshell for it which gives:
```
./configure: 14770: EXTRA_WARNINGS: not found
checking whether C compiler accepts -flto-partition=none... yes
```

We can either use ${} or just $ (style). Use $ to be consistent with other
uses in the file.

Signed-off-by: Sam James <sam@gentoo.org>
2024-03-15 02:36:12 +00:00
John Johansen
9a1838016c Merge Allow pam_unix to execute unix_chkpwd
Latest pam_unix always runs /usr/sbin/unix_chkpwd instead of reading
/etc/shadow itsself. Add exec permissions to abstraction/authentication.

It also needs to read /proc/@{pid}/loginuid

Also cleanup the now-superfluous rules from the smbd profile.

Fixes: https://bugzilla.opensuse.org/show_bug.cgi?id=1219139

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1181
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-03-14 21:09:59 +00:00
Christian Boltz
f4c19acfba Merge MountRule: Fix _is_covered_localvars
If `fstype==None`, `_is_covered_localvars` would trigger an exception.

This is fixed and a new testcase is added.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1182
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2024-03-14 12:46:01 +00:00
Maxime Bélair
dac9d08764 MountRule: Fix _is_covered_localvars 2024-03-14 12:46:01 +00:00
Christian Boltz
243162ca29 Allow pam_unix to execute unix_chkpwd
Latest pam_unix always runs /usr/sbin/unix_chkpwd instead of reading
/etc/shadow itsself. Add exec permissions to abstraction/authentication.

It also needs to read /proc/@{pid}/loginuid

Also cleanup the now-superfluous rules from the smbd profile.

Fixes: https://bugzilla.opensuse.org/show_bug.cgi?id=1219139
2024-03-13 23:13:19 +01:00
Georgia Garcia
ae978c1953 Merge Fix test-aa-notify on openSUSE Tumbleweed (new 'last')
The new 2037-proof `last` on openSUSE Tumbleweed doesn't support the
`-1` option.

Remove it, and cut off the output manually.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1180
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-12 18:54:35 +00:00
Christian Boltz
d19db55a37 Fix test-aa-notify on openSUSE Tumbleweed (new 'last')
The new 2037-proof `last` on openSUSE Tumbleweed doesn't support the
`-1` option.

Remove it, and cut off the output manually.
2024-03-12 19:37:29 +01:00
Christian Boltz
e3d381cf91 Merge Clean superfluous openssl abstraction includes
With abstractions/openssl now being included from abstraction/base
(via the indirection of abstractions/crypto) anything already
including abstraction/base can stop including abstractions/openssl
directly.

This is a follow up to 3d1dedfa as suggested by @cboltz

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1179
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2024-03-12 18:12:12 +00:00
Christian Ehrhardt
aa69d9adc9 Clean superfluous openssl abstraction includes
With abstractions/openssl now being included from abstraction/base
(via the indirection of abstractions/crypto) anything already
including abstraction/base can stop including abstractions/openssl
directly.
2024-03-12 14:54:01 +01:00
John Johansen
3d1dedfa7e Merge abstractions/crypto: allow read of more common crypto configuration files
Administrators might want to define global limits (e.g. disabling
a particular feature) via configuration files, but to make that work
all confined software needs to be allowed to read those files or
otherwise the risk is to silently fall back to internal defaults.

This adds the paths usually used by gnutls and openssl to improve these kind of use cases.

Fixes: https://bugs.launchpad.net/ubuntu/+source/libvirt/+bug/2056739
Fixes: https://bugs.launchpad.net/ubuntu/+source/chrony/+bug/2056747
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1178
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-03-12 11:27:59 +00:00
Christian Ehrhardt
f27b1ef93a abstractions/crypto: allow read of openssl config
Administrators might want to define global limits (e.g. disabling
a particular feature) via configuration files, but to make that work
all confined software needs to be allowed to read those files or
otherwise the risk is to silently fall back to internal defaults.

This adds the abstraction already defined for openssl to
abstraction/crypto as it is about cryptography, but also because
abstraction/base includes abstraction/crypto and therefore it will
be allowed in general.
2024-03-12 08:57:12 +01:00
Christian Ehrhardt
18d6a917f8 abstractions/crypto: allow read of gnutls config
Administrators might want to define global limits (e.g. disabling
a particular feature) via configuration files, but to make that work
all confined software needs to be allowed to read those files or
otherwise the risk is to silently fall back to internal defaults.

This adds the paths usually used by gnutls to abstraction/crypto
as it is about cryptography, but also because abstraction/base
includes abstraction/crypto and therefore it will be allowed
in general.
2024-03-12 08:54:36 +01:00
Christian Boltz
d1d39d176e Merge parser(Makefile): don't ship /var in downstream packages
Should help with building /var free images downstream.

See below: https://gitlab.com/apparmor/apparmor/-/merge_requests/1167#note_1798547092

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1167
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2024-03-08 12:53:32 +00:00
Max Gautier
2d654477f2 parser(Makefile): dont install /var/lib/apparmor
This directory is not used anymore.
This help downstream build of /var free images.

Links: https://0pointer.net/blog/fitting-everything-together.html
2024-03-08 10:18:16 +01:00
John Johansen
66dc2cc7d0 Merge Minor improvements for MountRule to fix make check failure
Minor improvements for MountRule
- Adding support for regex in fstype
- add resctrl filesystem
- Adding support for source beginning by '{' 

This MR allows to support edge cases for MountRule e.g. source = {,/usr}/lib{,32,64,x32}/modules/ or fstype = fuse.*

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/370
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1176
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-03-07 19:27:03 +00:00
Maxime Bélair
021c3248f9 Minor improvements for MountRule 2024-03-07 19:27:03 +00:00
John Johansen
353ba896d4 Prepare for AppArmor 4.0 beta2 release
- update version file

Signed-off-by: John Johansen <john.johansen@canonical.com>
2024-03-06 17:06:45 -08:00
John Johansen
c13007f7fc Merge libapparmor: check if AX_CHECK_COMPILE_FLAG is available
The error message when autoconf-archive is not installed is not very
intuitive:

```
./configure: line 14422: EXTRA_WARNINGS: command not found
./configure: line 14423: syntax error near unexpected token `-flto-partition=none,'
./configure: line 14423: `AX_CHECK_COMPILE_FLAG(-flto-partition=none, , , -Werror)'
```

So, check if AX_CHECK_COMPILE_FLAG is defined and if not, complain
that autoconf-archive is missing.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1174
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2024-03-06 21:21:54 +00:00
John Johansen
88a420853e Merge parser: fix policy generation for non-af_inet rules
The layout for AF_INET and AF_INET6 rules were being applied to all
families, which causes failures in their mediation.

Fixes: ddefe11a ("parser: add fine grained conditionals to network rule")
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1175
Merged-by: John Johansen <john@jjmx.net>
2024-03-06 21:19:21 +00:00
Georgia Garcia
2db41acd1b parser: fix generic perms in network rules
The permission for network rules when the inet mediation was not
available, or for when the family was not af_inet or af_inet6 was
being generated as one that would allow anything. Make them specific
using perms.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-06 10:33:54 -03:00
Georgia Garcia
a10d9044b8 parser: fix policy generation for non-af_inet rules
The layout for AF_INET and AF_INET6 rules were being applied to all
families, which causes failures in their mediation.

Fixes: ddefe11a ("parser: add fine grained conditionals to network rule")
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-06 10:07:17 -03:00
Christian Boltz
b53441a689 Merge Update ancient paths in apparmor and apparmor.d manpage
- replace example calls of /etc/init.d/apparmor with apparmor.service
- drop /etc/init.d/apparmor in filelist
- replace /var/lib/apparmor/ with /var/cache/apparmor/

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1171
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2024-03-06 12:24:13 +00:00
Christian Boltz
7452f34279 Update ancient paths in apparmor and apparmor.d manpage
- replace example calls of /etc/init.d/apparmor with apparmor.service
- drop /etc/init.d/apparmor in filelist
- replace /var/lib/apparmor/ with /var/cache/apparmor/
2024-03-05 22:59:18 +01:00
Georgia Garcia
aedb8a5b00 libapparmor: check if AX_CHECK_COMPILE_FLAG is available
The error message when autoconf-archive is not installed is not very
intuitive:

./configure: line 14422: EXTRA_WARNINGS: command not found
./configure: line 14423: syntax error near unexpected token `-flto-partition=none,'
./configure: line 14423: `AX_CHECK_COMPILE_FLAG(-flto-partition=none, , , -Werror)'

So, check if AX_CHECK_COMPILE_FLAG is defined and if not, complain
that autoconf-archive is missing.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-04 10:27:34 -03:00
Christian Boltz
6695944c2c Merge utils: fix coding style in mount
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1173
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2024-03-04 13:05:41 +00:00
Georgia Garcia
01090dcf1b utils: fix coding style in mount
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-04 09:24:58 -03:00
Georgia Garcia
3ea2bfec56 Merge Small fixes in MountRule
- Removed unnecessary variable source_is_path in mount rules
- Changed a string to a r-string to avoid an 'invalid escape sequence \s' warning

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1172
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-04 11:53:51 +00:00
Georgia Garcia
3d1a867c0a Merge Update mailinglist and homepage in changehat READMEs
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1170
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-04 11:53:10 +00:00
Georgia Garcia
dfb02cbd93 Merge MountRule: check for unknown fstype and options keywords, and fix issues uncovered by that
* **MountRule: sync flags_keywords with parser code**

    ... based on /mount.cc mnt_opts_table

    Several keywords and aliases were missing in flags_keywords:
    - B
    - M
    - make-private
    - make-rprivate
    - make-rshared
    - make-rslave
    - make-runbindable
    - make-shared
    - make-slave
    - make-unbindable
    - r
    - R
    - read-only
    - w

    Also sort the keywords in the same order as in mount.cc.

    Note: AARE handling is still a TODO.

    After that, update the list of known parsing failures:
    - several valid profiles are now correctly parsed
    - some `"make-*" mount opt and an invalid src` bad profiles are no
      longer detected as being invalid

* **test-mount.py: fix MountRule instance creation**

    If fstype or options is a str, it has to be exactly one keyword, because
    \__init__() / check_and_split_list() won't parse a str.

    Our "normal" code already honors this, and only hands over fstype and
    options as sets or a single-keyword str.

    However, a few tests (wrongly) handed over a str that would need further
    parsing. Adjust the tests to no longer do this.

* **MountRule: check for unknown fstype and options**

    ... now that the previous commits fixed issues that ended up as unknown
    keywords.

    Also add mount/ok_12.sd as known-failing test. It uses fstype=AARE which
    MountRule doesn't support (yet?).

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1169
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-04 11:52:53 +00:00
Georgia Garcia
90f056c1c6 Merge Several MountRule fixes and improvements
* Fix writing 'mount {options,fstype} in ...' rules

We need spaces around the 'in' keyword.

Also add some tests for this.

* Make error check more readable

* MountRule: make get_clean() more readable

... by getting rid of two mostly-identical, big return statements.

Also add tests for bare umound and remount rules to ensure full test
coverage.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1168
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Georgia Garcia <georgia.garcia@canonical.com>
2024-03-04 11:52:33 +00:00
Maxime Bélair
0daf3e8c9b Changing string to r-string to avoid warning 2024-03-04 09:02:24 +01:00
Maxime Bélair
a86c1bd45a Remove unnecessary variable source_is_path in mount rules 2024-03-04 09:00:58 +01:00
Christian Boltz
8f4073ecd9 MountRule: check for unknown fstype and options
... now that the previous commits fixed issues that ended up as unknown
keywords.

Also add mount/ok_12.sd as known-failing test. It uses fstype=AARE which
MountRule doesn't support (yet?).
2024-03-03 21:30:49 +01:00
Christian Boltz
440be71c12 Update mailinglist and homepage in changehat READMEs 2024-03-03 18:12:06 +01:00
Christian Boltz
8d21f01924 test-mount.py: fix MountRule instance creation
If fstype or options is a str, it has to be exactly one keyword, because
__init__() / check_and_split_list() won't parse a str.

Our "normal" code already honors this, and only hands over fstype and
options as sets or a single-keyword str.

However, a few tests (wrongly) handed over a str that would need further
parsing. Adjust the tests to no longer do this.
2024-03-03 15:52:14 +01:00
Christian Boltz
4e546291a5 MountRule: sync flags_keywords with parser code
... based on /mount.cc mnt_opts_table

Several keywords and aliases were missing in flags_keywords:
- B
- M
- make-private
- make-rprivate
- make-rshared
- make-rslave
- make-runbindable
- make-shared
- make-slave
- make-unbindable
- r
- R
- read-only
- w

Also sort the keywords in the same order as in mount.cc.

Note: AARE handling is still a TODO.

After that, update the list of known parsing failures:
- several valid profiles are now correctly parsed
- some `"make-*" mount opt and an invalid src` bad profiles are no
  longer detected as being invalid
2024-03-03 15:37:59 +01:00
Christian Boltz
8c026077d6 MountRule: make get_clean() more readable
... by getting rid of two mostly-identical, big return statements.

Also add tests for bare umound and remount rules to ensure full test
coverage.
2024-03-03 13:09:43 +01:00
Christian Boltz
5e4c4a0cb3 Make error check more readable 2024-03-03 12:53:49 +01:00
Christian Boltz
9c27a7c435 Fix writing 'mount {options,fstype} in ...' rules
We need spaces around the 'in' keyword.

Also add some tests for this.
2024-03-03 12:49:57 +01:00
Christian Boltz
a367c07437 Merge Add useful error message in test-mount.py
If /proc/filesystems contains a filesystem that is not listed in
MountRule valid_fs, print a useful error message that says what exactly
is going on, instead of only saying "False is not True".

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1166
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2024-03-01 21:30:33 +00:00
Christian Boltz
2200013088 Merge Cleanup old handling of mount rules
Now that we have MountRule and MountRuleset, drop the old "just store
the whole rule" code for mount rules.

Also drop some old tests that used that "store the whole mount rule"
code, and adjust the regex_matches tests to import the regex directly
from apparmor.regex.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1165
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2024-03-01 21:29:48 +00:00
Christian Boltz
d5afc33c40 Merge MountRule: Fix typo in 'btrfs', and add '9p' filesystem
The `9p` filesystem is available during the build in build.opensuse.org.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1164
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2024-03-01 21:27:44 +00:00
Christian Boltz
517e7c96c8 MountRule: add '9p' filesystem
This filesystem is available during the build in build.opensuse.org
2024-03-01 21:36:26 +01:00
Christian Boltz
a7cd59819e Add useful error message in test-mount.py
If /proc/filesystems contains a filesystem that is not listed in
MountRule valid_fs, print a useful error message that says what exactly
is going on, instead of only saying "False is not True".
2024-03-01 20:34:11 +01:00
Christian Boltz
e7f5ee3271 MountRule: Fix typo in 'btrfs' 2024-03-01 19:49:21 +01:00
Christian Boltz
da75b1c8d8 Cleanup old handling of mount rules
Now that we have MountRule and MountRuleset, drop the old "just store
the whole rule" code for mount rules.

Also drop some old tests that used that "store the whole mount rule"
code, and adjust the regex_matches tests to import the regex directly
from apparmor.regex.
2024-03-01 19:46:02 +01:00
59 changed files with 703 additions and 412 deletions

View File

@@ -67,10 +67,10 @@ to syslog.
References
----------
Project webpage:
http://developer.novell.com/wiki/index.php/Novell_AppArmor
https://apparmor.net/
To provide feedback or ask questions please contact the
apparmor-dev@forge.novell.com mail list. This is the development list
apparmor@lists.ubuntu.com mail list. This is the development list
for the AppArmor team.
See also: change_hat(3), and the Linux-PAM online documentation at

View File

@@ -188,10 +188,9 @@ parent context.
8. Feedback/Resources
-----------------
To provide feedback or ask questions please contact the
apparmor-dev@forge.novell.com mail list. This is the development list for the
AppArmor team.
Project webpage:
https://apparmor.net/
To provide feedback or ask questions please contact the
apparmor@lists.ubuntu.com mail list. This is the development list
for the AppArmor team.

View File

@@ -188,10 +188,9 @@ parent context.
8. Feedback/Resources
-----------------
To provide feedback or ask questions please contact the
apparmor-dev@forge.novell.com mail list. This is the development list for the
AppArmor team.
Project webpage:
https://apparmor.net/
To provide feedback or ask questions please contact the
apparmor@lists.ubuntu.com mail list. This is the development list
for the AppArmor team.

View File

@@ -1 +1 @@
4.0.0~beta1
4.0.0~beta3

View File

@@ -92,7 +92,8 @@ if test "$ac_cv_prog_cc_c99" = "no"; then
AC_MSG_ERROR([C99 mode is required to build libapparmor])
fi
EXTRA_CFLAGS="-Wall $(EXTRA_WARNINGS) -fPIC"
m4_ifndef([AX_CHECK_COMPILE_FLAG], [AC_MSG_ERROR(['autoconf-archive' missing])])
EXTRA_CFLAGS="-Wall $EXTRA_WARNINGS -fPIC"
AX_CHECK_COMPILE_FLAG([-flto-partition=none], , , [-Werror])
AS_VAR_IF([ax_cv_check_cflags__Werror__flto_partition_none], [yes],
[EXTRA_CFLAGS="$EXTRA_CFLAGS -flto-partition=none"]

View File

@@ -440,7 +440,6 @@ install-arch: $(INSTALLDEPS)
install-indep: indep
install -m 755 -d $(INSTALL_CONFDIR)
install -m 644 parser.conf $(INSTALL_CONFDIR)
install -m 755 -d ${DESTDIR}/var/lib/apparmor
install -m 755 -d $(APPARMOR_BIN_PREFIX)
install -m 755 rc.apparmor.functions $(APPARMOR_BIN_PREFIX)
install -m 755 profile-load $(APPARMOR_BIN_PREFIX)

View File

@@ -2037,8 +2037,6 @@ An example AppArmor profile:
=over 4
=item F</etc/init.d/boot.apparmor>
=item F</etc/apparmor.d/>
=back

View File

@@ -36,12 +36,11 @@ of resources. AppArmor's unique security model is to bind access control
attributes to programs rather than to users.
AppArmor confinement is provided via I<profiles> loaded into the kernel
via apparmor_parser(8), typically through the F</etc/init.d/apparmor>
SysV initscript, which is used like this:
via apparmor_parser(8), typically through the F<apparmor.service>
systemd unit, which is used like this:
# /etc/init.d/apparmor start
# /etc/init.d/apparmor stop
# /etc/init.d/apparmor restart
# systemctl start apparmor
# systemctl reload apparmor
AppArmor can operate in two modes: I<enforcement>, and I<complain or learning>:
@@ -273,11 +272,9 @@ Else, if auditd is running, see auditd(8) and auditd.conf(5).
=over 4
=item F</etc/init.d/apparmor>
=item F</etc/apparmor.d/>
=item F</var/lib/apparmor/>
=item F</var/cache/apparmor/>
=item F</var/log/audit/audit.log>

View File

@@ -234,6 +234,7 @@ struct mnt_keyword_table {
unsigned int clear;
};
// keep in sync with utils/apparmor/rule/mount.py flags_keywords
static struct mnt_keyword_table mnt_opts_table[] = {
{"ro", MS_RDONLY, 0},
{"r", MS_RDONLY, 0},

View File

@@ -252,6 +252,19 @@ const char *net_find_af_name(unsigned int af)
return NULL;
}
const char *net_find_protocol_name(unsigned int protocol)
{
size_t i;
for (i = 0; i < sizeof(network_mappings) / sizeof(*network_mappings); i++) {
if (network_mappings[i].protocol == protocol) {
return network_mappings[i].protocol_name;
}
}
return NULL;
}
const struct network_tuple *net_find_mapping(const struct network_tuple *map,
const char *family,
const char *type,
@@ -347,6 +360,10 @@ bool network_rule::parse_port(ip_conds &entry)
bool network_rule::parse_address(ip_conds &entry)
{
if (strcmp(entry.sip, "anon") == 0) {
entry.is_anonymous = true;
return true;
}
entry.is_ip = true;
return parse_ip(entry.sip, &entry.ip);
}
@@ -374,23 +391,24 @@ void network_rule::move_conditionals(struct cond_entry *conds, ip_conds &ip_cond
}
}
void network_rule::set_netperm(unsigned int family, unsigned int type)
void network_rule::set_netperm(unsigned int family, unsigned int type, unsigned int protocol)
{
if (type > SOCK_PACKET) {
/* setting mask instead of a bit */
network_perms[family] |= type;
network_perms[family].first |= type;
} else
network_perms[family] |= 1 << type;
network_perms[family].first |= 1 << type;
network_perms[family].second |= protocol;
}
network_rule::network_rule(perms_t perms_p, struct cond_entry *conds,
struct cond_entry *peer_conds):
dedup_perms_rule_t(AA_CLASS_NETV8)
dedup_perms_rule_t(AA_CLASS_NETV8), label(NULL)
{
size_t family_index;
for (family_index = AF_UNSPEC; family_index < get_af_max(); family_index++) {
network_map[family_index].push_back({ family_index, 0xFFFFFFFF, 0xFFFFFFFF });
set_netperm(family_index, 0xFFFFFFFF);
set_netperm(family_index, 0xFFFFFFFF, 0xFFFFFFFF);
}
move_conditionals(conds, local);
@@ -412,18 +430,18 @@ network_rule::network_rule(perms_t perms_p, struct cond_entry *conds,
network_rule::network_rule(perms_t perms_p, const char *family, const char *type,
const char *protocol, struct cond_entry *conds,
struct cond_entry *peer_conds):
dedup_perms_rule_t(AA_CLASS_NETV8)
dedup_perms_rule_t(AA_CLASS_NETV8), label(NULL)
{
const struct network_tuple *mapping = NULL;
while ((mapping = net_find_mapping(mapping, family, type, protocol))) {
network_map[mapping->family].push_back({ mapping->family, mapping->type, mapping->protocol });
set_netperm(mapping->family, mapping->type);
set_netperm(mapping->family, mapping->type, mapping->protocol);
}
if (type == NULL && network_map.empty()) {
while ((mapping = net_find_mapping(mapping, type, family, protocol))) {
network_map[mapping->family].push_back({ mapping->family, mapping->type, mapping->protocol });
set_netperm(mapping->family, mapping->type);
set_netperm(mapping->family, mapping->type, mapping->protocol);
}
}
@@ -447,10 +465,10 @@ network_rule::network_rule(perms_t perms_p, const char *family, const char *type
}
network_rule::network_rule(perms_t perms_p, unsigned int family, unsigned int type):
dedup_perms_rule_t(AA_CLASS_NETV8)
dedup_perms_rule_t(AA_CLASS_NETV8), label(NULL)
{
network_map[family].push_back({ family, type, 0xFFFFFFFF });
set_netperm(family, type);
set_netperm(family, type, 0xFFFFFFFF);
if (perms_p) {
perms = perms_p;
@@ -479,7 +497,8 @@ ostream &network_rule::dump(ostream &os)
for (const auto& perm : network_perms) {
unsigned int family = perm.first;
unsigned int type = perm.second;
unsigned int type = perm.second.first;
unsigned int protocol = perm.second.second;
const char *family_name = net_find_af_name(family);
if (family_name)
@@ -507,6 +526,12 @@ ostream &network_rule::dump(ostream &os)
os << " #" << std::hex << (type & mask);
printf(" }");
const char *protocol_name = net_find_protocol_name(protocol);
if (protocol_name)
os << " " << protocol_name;
else
os << " #" << protocol;
}
os << ",\n";
@@ -531,14 +556,14 @@ std::string gen_ip_cond(const struct ip_address ip)
int i;
if (ip.family == AF_INET) {
/* add a byte containing the size of the following ip */
oss << "\\x04";
oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << IPV4_SIZE;
u8 *byte = (u8 *) &ip.address.address_v4; /* in network byte order */
for (i = 0; i < 4; i++)
oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned int>(byte[i]);
} else {
/* add a byte containing the size of the following ip */
oss << "\\x10";
oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << IPV6_SIZE;
for (i = 0; i < 16; ++i)
oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << static_cast<unsigned int>(ip.address.address_v6[i]);
}
@@ -557,48 +582,114 @@ std::string gen_port_cond(uint16_t port)
return oss.str();
}
void network_rule::gen_ip_conds(std::ostringstream &oss, ip_conds entry, bool is_peer, bool is_cmd)
std::list<std::ostringstream> gen_all_ip_options(std::ostringstream &oss) {
std::list<std::ostringstream> all_streams;
std::ostringstream anon, ipv4, ipv6;
int i;
anon << oss.str();
ipv4 << oss.str();
ipv6 << oss.str();
anon << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ANON_SIZE;
/* add a byte containing the size of the following ip */
ipv4 << "\\x" << std::setfill('0') << std::setw(2) << std::hex << IPV4_SIZE;
for (i = 0; i < 4; i++)
ipv4 << ".";
/* add a byte containing the size of the following ip */
ipv6 << "\\x" << std::setfill('0') << std::setw(2) << std::hex << IPV6_SIZE;
for (i = 0; i < 16; ++i)
ipv6 << ".";
all_streams.push_back(std::move(anon));
all_streams.push_back(std::move(ipv4));
all_streams.push_back(std::move(ipv6));
return all_streams;
}
std::list<std::ostringstream> copy_streams_list(std::list<std::ostringstream> &streams)
{
/* encode protocol */
if (!is_cmd) {
if (entry.is_ip) {
oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((entry.ip.family & 0xff00) >> 8);
oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (entry.ip.family & 0xff);
std::list<std::ostringstream> streams_copy;
for (auto &oss : streams) {
std::ostringstream oss_copy(oss.str());
streams_copy.push_back(std::move(oss_copy));
}
return streams_copy;
}
bool network_rule::gen_ip_conds(Profile &prof, std::list<std::ostringstream> &streams, ip_conds &entry, bool is_peer, bool is_cmd)
{
std::string buf;
perms_t cond_perms;
std::list<std::ostringstream> ip_streams;
for (auto &oss : streams) {
if (entry.is_port && !(entry.is_ip && entry.is_anonymous)) {
/* encode port type (privileged - 1, remote - 2, unprivileged - 0) */
if (!is_peer && perms & AA_NET_BIND && entry.port < IPPORT_RESERVED)
oss << "\\x01";
else if (is_peer)
oss << "\\x02";
else
oss << "\\x00";
oss << gen_port_cond(entry.port);
} else {
oss << "..";
/* port type + port number */
oss << "...";
}
}
if (entry.is_port) {
/* encode port type (privileged - 1, remote - 2, unprivileged - 0) */
if (!is_peer && perms & AA_NET_BIND && entry.port < IPPORT_RESERVED)
oss << "\\x01";
else if (is_peer)
oss << "\\x02";
else
oss << "\\x00";
ip_streams = std::move(streams);
streams.clear();
oss << gen_port_cond(entry.port);
} else {
/* port type + port number */
if (!is_cmd)
oss << ".";
oss << "..";
for (auto &oss : ip_streams) {
if (entry.is_ip) {
oss << gen_ip_cond(entry.ip);
streams.push_back(std::move(oss));
} else if (entry.is_anonymous) {
oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ANON_SIZE;
streams.push_back(std::move(oss));
} else {
streams.splice(streams.end(), gen_all_ip_options(oss));
}
}
if (entry.is_ip) {
oss << gen_ip_cond(entry.ip);
} else {
/* encode 0 to indicate there's no ip (ip size) */
oss << "\\x00";
}
cond_perms = map_perms(perms);
if (!is_cmd && (label || is_peer))
cond_perms = (AA_CONT_MATCH << 1);
oss << "\\-x01"; /* oob separator */
oss << default_match_pattern; /* label - not used for now */
oss << "\\x00"; /* null transition */
for (auto &oss : streams) {
oss << "\\x00"; /* null transition */
buf = oss.str();
/* AA_CONT_MATCH mapping (cond_perms) only applies to perms, not audit */
if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, cond_perms,
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0,
parseopts))
return false;
if (label || is_peer) {
if (!is_peer)
cond_perms = map_perms(perms);
oss << default_match_pattern; /* label - not used for now */
oss << "\\x00"; /* null transition */
buf = oss.str();
if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, cond_perms,
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0,
parseopts))
return false;
}
}
return true;
}
bool network_rule::gen_net_rule(Profile &prof, u16 family, unsigned int type_mask) {
bool network_rule::gen_net_rule(Profile &prof, u16 family, unsigned int type_mask, unsigned int protocol) {
std::ostringstream buffer;
std::string buf;
@@ -612,58 +703,96 @@ bool network_rule::gen_net_rule(Profile &prof, u16 family, unsigned int type_mas
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (type_mask & 0xff);
}
if (!features_supports_inet) {
buf = buffer.str();
if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(AA_VALID_NET_PERMS),
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(AA_VALID_NET_PERMS) : 0,
parseopts))
return false;
return true;
}
if (perms & AA_PEER_NET_PERMS) {
gen_ip_conds(buffer, peer, true, false);
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_ADDR;
gen_ip_conds(buffer, local, false, true);
if (!features_supports_inet || (family != AF_INET && family != AF_INET6)) {
buf = buffer.str();
if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(perms),
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0,
parseopts))
return false;
return true;
}
if ((perms & AA_NET_LISTEN) || (perms & AA_NET_OPT)) {
gen_ip_conds(buffer, local, false, false);
if (perms & AA_NET_LISTEN) {
std::ostringstream cmd_buffer;
cmd_buffer << buffer.str();
cmd_buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_LISTEN;
/* length of queue allowed - not used for now */
cmd_buffer << "..";
buf = cmd_buffer.str();
if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(perms),
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0,
parseopts))
return false;
buf = buffer.str();
/* create perms need to be generated excluding the rest of the perms */
if (perms & AA_NET_CREATE) {
if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(perms & AA_NET_CREATE) | (AA_CONT_MATCH << 1),
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms & AA_NET_CREATE) : 0,
parseopts))
return false;
}
/* encode protocol */
if (protocol > 0xffff) {
buffer << "..";
} else {
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((protocol & 0xff00) >> 8);
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (protocol & 0xff);
}
if (perms & AA_PEER_NET_PERMS) {
std::list<std::ostringstream> streams;
std::ostringstream cmd_buffer;
cmd_buffer << buffer.str();
streams.push_back(std::move(cmd_buffer));
if (!gen_ip_conds(prof, streams, peer, true, false))
return false;
for (auto &oss : streams) {
oss << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_ADDR;
}
if (perms & AA_NET_OPT) {
std::ostringstream cmd_buffer;
cmd_buffer << buffer.str();
cmd_buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_OPT;
/* level - not used for now */
cmd_buffer << "..";
/* socket mapping - not used for now */
cmd_buffer << "..";
buf = cmd_buffer.str();
if (!gen_ip_conds(prof, streams, local, false, true))
return false;
}
std::list<std::ostringstream> streams;
std::ostringstream common_buffer;
common_buffer << buffer.str();
streams.push_back(std::move(common_buffer));
if (!gen_ip_conds(prof, streams, local, false, false))
return false;
if (perms & AA_NET_LISTEN) {
std::list<std::ostringstream> cmd_streams;
cmd_streams = copy_streams_list(streams);
for (auto &cmd_buffer : streams) {
std::ostringstream listen_buffer;
listen_buffer << cmd_buffer.str();
listen_buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_LISTEN;
/* length of queue allowed - not used for now */
listen_buffer << "..";
buf = listen_buffer.str();
if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(perms),
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0,
parseopts))
return false;
}
}
if (perms & AA_NET_OPT) {
std::list<std::ostringstream> cmd_streams;
cmd_streams = copy_streams_list(streams);
for (auto &cmd_buffer : streams) {
std::ostringstream opt_buffer;
opt_buffer << cmd_buffer.str();
opt_buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << CMD_OPT;
/* level - not used for now */
opt_buffer << "..";
/* socket mapping - not used for now */
opt_buffer << "..";
buf = opt_buffer.str();
if (!prof.policy.rules->add_rule(buf.c_str(), rule_mode == RULE_DENY, map_perms(perms),
dedup_perms_rule_t::audit == AUDIT_FORCE ? map_perms(perms) : 0,
parseopts))
return false;
}
}
return true;
}
@@ -679,17 +808,18 @@ int network_rule::gen_policy_re(Profile &prof)
for (const auto& perm : network_perms) {
unsigned int family = perm.first;
unsigned int type = perm.second;
unsigned int type = perm.second.first;
unsigned int protocol = perm.second.second;
if (type > 0xffff) {
if (!gen_net_rule(prof, family, type))
if (!gen_net_rule(prof, family, type, protocol))
goto fail;
} else {
int t;
/* generate rules for types that are set */
for (t = 0; t < 16; t++) {
if (type & (1 << t)) {
if (!gen_net_rule(prof, family, t))
if (!gen_net_rule(prof, family, t, protocol))
goto fail;
}
}
@@ -760,13 +890,27 @@ void network_rule::update_compat_net(void)
}
}
static int cmp_network_map(std::unordered_map<unsigned int, perms_t> lhs,
std::unordered_map<unsigned int, perms_t> rhs)
static int cmp_ip_conds(ip_conds const &lhs, ip_conds const &rhs)
{
int res = null_strcmp(lhs.sip, rhs.sip);
if (res)
return res;
res = null_strcmp(lhs.sport, rhs.sport);
if (res)
return res;
return lhs.is_anonymous - rhs.is_anonymous;
}
static int cmp_network_map(std::unordered_map<unsigned int, std::pair<unsigned int, unsigned int>> lhs,
std::unordered_map<unsigned int, std::pair<unsigned int, unsigned int>> rhs)
{
int res;
size_t family_index;
for (family_index = AF_UNSPEC; family_index < get_af_max(); family_index++) {
res = lhs[family_index] - rhs[family_index];
res = lhs[family_index].first - rhs[family_index].first;
if (res)
return res;
res = lhs[family_index].second - rhs[family_index].second;
if (res)
return res;
}
@@ -779,5 +923,14 @@ int network_rule::cmp(rule_t const &rhs) const
if (res)
return res;
network_rule const &nrhs = rule_cast<network_rule const &>(rhs);
return cmp_network_map(network_perms, nrhs.network_perms);
res = cmp_network_map(network_perms, nrhs.network_perms);
if (res)
return res;
res = cmp_ip_conds(local, nrhs.local);
if (res)
return res;
res = cmp_ip_conds(peer, nrhs.peer);
if (res)
return res;
return null_strcmp(label, nrhs.label);
};

View File

@@ -26,6 +26,7 @@
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <list>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
@@ -79,6 +80,10 @@
#define CMD_LISTEN 2
#define CMD_OPT 4
#define ANON_SIZE 0
#define IPV4_SIZE 1
#define IPV6_SIZE 2
struct network_tuple {
const char *family_name;
unsigned int family;
@@ -127,6 +132,8 @@ public:
uint16_t port;
struct ip_address ip;
bool is_anonymous = false;
void free_conds() {
if (sip)
free(sip);
@@ -139,10 +146,11 @@ class network_rule: public dedup_perms_rule_t {
void move_conditionals(struct cond_entry *conds, ip_conds &ip_cond);
public:
std::unordered_map<unsigned int, std::vector<struct aa_network_entry>> network_map;
std::unordered_map<unsigned int, perms_t> network_perms;
std::unordered_map<unsigned int, std::pair<unsigned int, unsigned int>> network_perms;
ip_conds peer;
ip_conds local;
char *label;
bool has_local_conds(void) { return local.sip || local.sport; }
bool has_peer_conds(void) { return peer.sip || peer.sport; }
@@ -178,9 +186,9 @@ public:
}
};
void gen_ip_conds(std::ostringstream &oss, ip_conds entry, bool is_peer, bool is_cmd);
bool gen_net_rule(Profile &prof, u16 family, unsigned int type_mask);
void set_netperm(unsigned int family, unsigned int type);
bool gen_ip_conds(Profile &prof, std::list<std::ostringstream> &streams, ip_conds &entry, bool is_peer, bool is_cmd);
bool gen_net_rule(Profile &prof, u16 family, unsigned int type_mask, unsigned int protocol);
void set_netperm(unsigned int family, unsigned int type, unsigned int protocol);
void update_compat_net(void);
bool parse_address(ip_conds &entry);
bool parse_port(ip_conds &entry);

View File

@@ -921,7 +921,7 @@ void set_supported_features()
"network_v8");
features_supports_inet = features_intersect(kernel_features,
policy_features,
"network/af_inet");
"network_v8/af_inet");
features_supports_unix = features_intersect(kernel_features,
policy_features,
"network/af_unix");

View File

@@ -31,6 +31,10 @@
/{usr/,}lib/@{multiarch}/security/pam_*.so mr,
/{usr/,}lib/@{multiarch}/security/ r,
# pam_unix
owner /proc/@{pid}/loginuid r,
/{,usr/}{,s}bin/unix_chkpwd Px,
# gssapi
@{etc_ro}/gss/mech r,
@{etc_ro}/gss/mech.d/ r,

View File

@@ -13,6 +13,9 @@
abi <abi/4.0>,
# Global config of openssl
include <abstractions/openssl>
@{etc_ro}/gcrypt/hwf.deny r,
@{etc_ro}/gcrypt/random.conf r,
@{PROC}/sys/crypto/fips_enabled r,
@@ -24,4 +27,8 @@
/etc/crypto-policies/*/*.txt r,
/usr/share/crypto-policies/*/*.txt r,
# Global gnutls config
@{etc_ro}/gnutls/config r,
@{etc_ro}/gnutls/pkcs11.conf r,
include if exists <abstractions/crypto.d>

View File

@@ -50,7 +50,6 @@
include <abstractions/kde-icon-cache-write>
include <abstractions/kde>
include <abstractions/nameservice> # for IceProcessMessages () from libICE.so (called by libQtCore.so)
include <abstractions/openssl>
include <abstractions/qt5>
include <abstractions/recent-documents-write>
include <abstractions/X>

View File

@@ -4,7 +4,7 @@
abi <abi/4.0>,
include <tunables/global>
profile firefox /usr/lib/firefox{,-esr}/firefox{,-esr} flags=(unconfined) {
profile firefox /usr/lib/firefox{,-esr,-beta,-devedition,-nightly}/firefox{,-esr,-bin} flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.

12
profiles/apparmor.d/geary Normal file
View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile geary /usr/bin/geary flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/geary>
}

View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile goldendict /usr/bin/goldendict flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/goldendict>
}

View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile kchmviewer /usr/bin/kchmviewer flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/kchmviewer>
}

12
profiles/apparmor.d/loupe Normal file
View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile loupe /usr/bin/loupe flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/loupe>
}

View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile notepadqq /{{usr/bin,etc/alternatives}/notepadqq,usr/lib/notepadqq/notepadqq.sh} flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/notepadqq>
}

View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile pageedit /usr/bin/pageedit flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/pageedit>
}

View File

@@ -11,8 +11,6 @@ profile php-fpm /usr/sbin/php-fpm* flags=(attach_disconnected) {
include <abstractions/nameservice>
# common php files and support files that php needs
include <abstractions/php>
# read openssl configuration
include <abstractions/openssl>
# read the system certificates
include <abstractions/ssl_certs>

View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile privacybrowser /usr/bin/privacybrowser flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/privacybrowser>
}

View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile qmapshack /usr/bin/qmapshack flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/qmapshack>
}

View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile qutebrowser /usr/bin/qutebrowser flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/qutebrowser>
}

View File

@@ -0,0 +1,12 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
profile rssguard /usr/bin/rssguard flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/rssguard>
}

View File

@@ -6,7 +6,6 @@ profile samba-bgqd /usr/lib*/samba/{,samba/}samba-bgqd {
include <abstractions/base>
include <abstractions/cups-client>
include <abstractions/nameservice>
include <abstractions/openssl>
include <abstractions/samba>
signal receive set=term peer=smbd,

View File

@@ -22,7 +22,6 @@ profile syslog-ng /{usr/,}{bin,sbin}/syslog-ng {
include <abstractions/consoles>
include <abstractions/nameservice>
include <abstractions/mysql>
include <abstractions/openssl>
include <abstractions/python>
include <abstractions/hosts_access>

13
profiles/apparmor.d/scide Normal file
View File

@@ -0,0 +1,13 @@
# This profile allows everything and only exists to give the
# application a name instead of having the label "unconfined"
abi <abi/4.0>,
include <tunables/global>
#supercollider-ide
profile scide /usr/bin/scide flags=(unconfined) {
userns,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/scide>
}

View File

@@ -0,0 +1,35 @@
# apparmor.d - Full set of apparmor profiles
# Copyright (C) 2019-2021 Mikhail Morfikov
# SPDX-License-Identifier: GPL-2.0-only
# The apparmor.d project comes with several variables and abstractions
# that are not part of upstream AppArmor yet. Therefore this profile was
# adopted to use abstractions and variables that are available.
# Copyright (C) Christian Boltz 2024
abi <abi/4.0>,
include <tunables/global>
profile unix-chkpwd /{,usr/}{,s}bin/unix_chkpwd {
include <abstractions/base>
include <abstractions/nameservice>
# To write records to the kernel auditing log.
capability audit_write,
network netlink raw,
/{,usr/}{,s}bin/unix_chkpwd mr,
/etc/shadow r,
# systemd userdb, used in nspawn
/run/host/userdb/*.user r,
/run/host/userdb/*.user-privileged r,
# file_inherit
owner /dev/tty[0-9]* rw,
include if exists <local/unix-chkpwd>
}

View File

@@ -19,7 +19,6 @@ profile dovecot-auth /usr/lib*/dovecot/auth {
include <abstractions/base>
include <abstractions/mysql>
include <abstractions/nameservice>
include <abstractions/openssl>
include <abstractions/wutmp>
include <abstractions/dovecot-common>

View File

@@ -17,7 +17,6 @@ profile dovecot-dict /usr/lib*/dovecot/dict {
include <abstractions/base>
include <abstractions/mysql>
include <abstractions/nameservice>
include <abstractions/openssl>
include <abstractions/dovecot-common>
capability setuid,

View File

@@ -17,7 +17,6 @@ include <tunables/global>
profile dovecot-imap-login /usr/lib*/dovecot/imap-login {
include <abstractions/base>
include <abstractions/dovecot-common>
include <abstractions/openssl>
capability setuid,
capability sys_chroot,

View File

@@ -18,7 +18,6 @@ profile dovecot-lmtp /usr/lib*/dovecot/lmtp {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/dovecot-common>
include <abstractions/openssl>
include <abstractions/ssl_certs>
include <abstractions/ssl_keys>

View File

@@ -19,7 +19,6 @@ include <tunables/global>
profile dovecot-managesieve-login /usr/lib*/dovecot/managesieve-login {
include <abstractions/base>
include <abstractions/dovecot-common>
include <abstractions/openssl>
capability setuid,
capability sys_chroot,

View File

@@ -17,7 +17,6 @@ include <tunables/global>
profile dovecot-pop3-login /usr/lib*/dovecot/pop3-login {
include <abstractions/base>
include <abstractions/dovecot-common>
include <abstractions/openssl>
capability setuid,
capability sys_chroot,

View File

@@ -16,7 +16,6 @@ include <tunables/ntpd>
profile ntpd /usr/{bin,sbin}/{,open}ntpd flags=(attach_disconnected) {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/openssl>
include <abstractions/ssl_certs>
include <abstractions/xad>

View File

@@ -8,7 +8,6 @@ profile smbd /usr/{bin,sbin}/smbd {
include <abstractions/consoles>
include <abstractions/cups-client>
include <abstractions/nameservice>
include <abstractions/openssl>
include <abstractions/samba>
include <abstractions/user-tmp>
include <abstractions/wutmp>
@@ -50,7 +49,6 @@ profile smbd /usr/{bin,sbin}/smbd {
/usr/share/samba/** r,
/usr/{bin,sbin}/smbd mr,
/usr/{bin,sbin}/smbldap-useradd Px,
/usr/sbin/unix_chkpwd Px,
/var/cache/samba/** rwk,
/var/{cache,lib}/samba/printing/printers.tdb mrw,
/var/lib/nscd/netgroup r,
@@ -63,8 +61,6 @@ profile smbd /usr/{bin,sbin}/smbd {
@{run}/samba/ncalrpc/** rw,
/var/spool/samba/** rw,
owner /proc/@{pid}/loginuid r,
@{HOMEDIRS}/** lrwk,
/var/lib/samba/usershares/{,**} lrwk,

View File

@@ -17,7 +17,6 @@ include <tunables/global>
profile postfix-proxymap /usr/lib/postfix/{bin/,sbin/,}proxymap {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/openssl>
include <abstractions/postfix-common>
/etc/my.cnf r,

View File

@@ -18,7 +18,6 @@ profile postfix-smtp /usr/lib/postfix/{bin/,sbin/,}smtp {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/postfix-common>
include <abstractions/openssl>
capability dac_override,
capability dac_read_search,

View File

@@ -18,7 +18,6 @@ profile postfix-smtpd /usr/lib/postfix/{bin/,sbin/,}smtpd {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/postfix-common>
include <abstractions/openssl>
include <abstractions/ssl_certs>
include <abstractions/ssl_keys>

View File

@@ -17,7 +17,6 @@ include <tunables/global>
profile postfix-tlsmgr /usr/lib/postfix/{bin/,sbin/,}tlsmgr {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/openssl>
include <abstractions/postfix-common>
/usr/lib/postfix/{bin/,sbin/,}tlsmgr mrix,

View File

@@ -26,7 +26,6 @@ include <tunables/global>
profile dhclient /{usr/,}sbin/dhclient {
include <abstractions/base>
include <abstractions/bash>
include <abstractions/openssl>
include <abstractions/nameservice>
capability net_raw,

View File

@@ -17,7 +17,6 @@ include <tunables/global>
include <abstractions/base>
include <abstractions/consoles>
include <abstractions/nameservice>
include <abstractions/openssl>
capability setgid,
capability setuid,

View File

@@ -13,7 +13,6 @@ include <tunables/global>
profile clamd /usr/sbin/clamd {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/openssl>
capability setgid,
capability setuid,

View File

@@ -13,7 +13,6 @@ include <tunables/global>
profile haproxy /usr/sbin/haproxy {
include <abstractions/base>
include <abstractions/nameservice>
include <abstractions/openssl>
capability net_admin,
capability net_bind_service,
capability setgid,

View File

@@ -20,7 +20,6 @@ include <tunables/global>
include <abstractions/kerberosclient>
include <abstractions/nameservice>
include <abstractions/perl>
include <abstractions/openssl>
capability kill,
capability net_bind_service,

View File

@@ -17,7 +17,6 @@ include <tunables/global>
include <abstractions/nameservice>
include <abstractions/authentication>
include <abstractions/user-mail>
include <abstractions/openssl>
/dev/urandom r,
/tmp/* rwl,

View File

@@ -17,7 +17,6 @@ include <tunables/global>
include <abstractions/nameservice>
include <abstractions/authentication>
include <abstractions/user-mail>
include <abstractions/openssl>
/dev/urandom r ,
/tmp/.* rwl ,

View File

@@ -17,7 +17,6 @@ include <tunables/global>
include <abstractions/nameservice>
include <abstractions/authentication>
include <abstractions/user-mail>
include <abstractions/openssl>
/dev/urandom r ,
/tmp/.* rwl ,

View File

@@ -97,16 +97,20 @@ do_tests "ipv4 udp no conds" pass pass $bind_ipv4 $bind_port $remote_ipv4 $remot
generate_profile="genprofile network $sender:px -- image=$sender network"
do_tests "ipv4 tcp no conds" pass pass $bind_ipv4 $bind_port $remote_ipv4 $remote_port tcp "$generate_profile"
generate_profile="genprofile network;ip=$bind_ipv4;port=$bind_port;peer=(ip=$remote_ipv4,port=$remote_port) $sender:px -- image=$sender network;ip=$remote_ipv4;port=$remote_port;peer=(ip=$bind_ipv4,port=$bind_port)"
setsockopt_rules="network;(setopt,getopt);ip=0.0.0.0;port=0"
rcv_rules="network;ip=$bind_ipv4;peer=(ip=anon)"
snd_rules="network;ip=$remote_ipv4;peer=(ip=anon)"
generate_profile="genprofile network;ip=$bind_ipv4;port=$bind_port;peer=(ip=$remote_ipv4,port=$remote_port) $setsockopt_rules $rcv_rules $sender:px -- image=$sender network;ip=$remote_ipv4;port=$remote_port;peer=(ip=$bind_ipv4,port=$bind_port) $setsockopt_rules $snd_rules"
do_tests "ipv4 udp generic perms" pass pass $bind_ipv4 $bind_port $remote_ipv4 $remote_port udp "$generate_profile"
generate_profile="genprofile network;ip=$bind_ipv4;port=$bind_port;peer=(ip=$remote_ipv4,port=$remote_port) $sender:px -- image=$sender network;ip=$remote_ipv4;port=$remote_port;peer=(ip=$bind_ipv4,port=$bind_port)"
generate_profile="genprofile network;ip=$bind_ipv4;port=$bind_port;peer=(ip=$remote_ipv4,port=$remote_port) $setsockopt_rules $rcv_rules $sender:px -- image=$sender network;ip=$remote_ipv4;port=$remote_port;peer=(ip=$bind_ipv4,port=$bind_port) $setsockopt_rules $snd_rules"
do_tests "ipv4 tcp generic perms" pass pass $bind_ipv4 $bind_port $remote_ipv4 $remote_port tcp "$generate_profile"
generate_profile="genprofile network;(connect,receive,send);ip=$bind_ipv4;port=$bind_port;peer=(ip=$remote_ipv4,port=$remote_port) $sender:px -- image=$sender network;ip=$remote_ipv4;port=$remote_port;peer=(ip=$bind_ipv4,port=$bind_port)"
generate_profile="genprofile network;(connect,receive,send);ip=$bind_ipv4;port=$bind_port;peer=(ip=$remote_ipv4,port=$remote_port) $setsockopt_rules $rcv_rules $sender:px -- image=$sender network;ip=$remote_ipv4;port=$remote_port;peer=(ip=$bind_ipv4,port=$bind_port) $setsockopt_rules $snd_rules"
do_tests "ipv4 udp specific perms" pass pass $bind_ipv4 $bind_port $remote_ipv4 $remote_port udp "$generate_profile"
generate_profile="genprofile network;(connect,receive,send);ip=$bind_ipv4;port=$bind_port;peer=(ip=$remote_ipv4,port=$remote_port) $sender:px -- image=$sender network;ip=$remote_ipv4;port=$remote_port;peer=(ip=$bind_ipv4,port=$bind_port)"
generate_profile="genprofile network;(connect,receive,send);ip=$bind_ipv4;port=$bind_port;peer=(ip=$remote_ipv4,port=$remote_port) $setsockopt_rules $rcv_rules $sender:px -- image=$sender network;ip=$remote_ipv4;port=$remote_port;peer=(ip=$bind_ipv4,port=$bind_port) $setsockopt_rules $snd_rules"
do_tests "ipv4 tcp specific perms" pass pass $bind_ipv4 $bind_port $remote_ipv4 $remote_port tcp "$generate_profile"
removeprofile
@@ -122,10 +126,14 @@ do_tests "ipv6 udp no conds" pass pass $bind_ipv6 $bind_port $remote_ipv6 $remot
generate_profile="genprofile network $sender:px -- image=$sender network"
do_tests "ipv6 tcp no conds" pass pass $bind_ipv6 $bind_port $remote_ipv6 $remote_port tcp "$generate_profile"
generate_profile="genprofile network;ip=$bind_ipv6;port=$bind_port;peer=(ip=$remote_ipv6,port=$remote_port) $sender:px -- image=$sender network;ip=$remote_ipv6;port=$remote_port;peer=(ip=$bind_ipv6,port=$bind_port)"
setsockopt_rules="network;(setopt,getopt);ip=::0;port=0"
rcv_rules="network;ip=$bind_ipv6;peer=(ip=anon)"
snd_rules="network;ip=$remote_ipv6;peer=(ip=anon)"
generate_profile="genprofile network;ip=$bind_ipv6;port=$bind_port;peer=(ip=$remote_ipv6,port=$remote_port) $setsockopt_rules $rcv_rules $sender:px -- image=$sender network;ip=$remote_ipv6;port=$remote_port;peer=(ip=$bind_ipv6,port=$bind_port) $setsockopt_rules $snd_rules"
do_tests "ipv6 udp generic perms" pass pass $bind_ipv6 $bind_port $remote_ipv6 $remote_port udp "$generate_profile"
generate_profile="genprofile network;ip=$bind_ipv6;port=$bind_port;peer=(ip=$remote_ipv6,port=$remote_port) $sender:px -- image=$sender network;ip=$remote_ipv6;port=$remote_port;peer=(ip=$bind_ipv6,port=$bind_port)"
generate_profile="genprofile network;ip=$bind_ipv6;port=$bind_port;peer=(ip=$remote_ipv6,port=$remote_port) $setsockopt_rules $rcv_rules $sender:px -- image=$sender network;ip=$remote_ipv6;port=$remote_port;peer=(ip=$bind_ipv6,port=$bind_port) $setsockopt_rules $snd_rules"
do_tests "ipv6 tcp generic perms" pass pass $bind_ipv6 $bind_port $remote_ipv6 $remote_port tcp "$generate_profile"

View File

@@ -39,7 +39,7 @@ from apparmor.profile_storage import ProfileStorage, add_or_remove_flag, ruletyp
from apparmor.regex import (
RE_HAS_COMMENT_SPLIT, RE_PROFILE_CHANGE_HAT, RE_PROFILE_CONDITIONAL,
RE_PROFILE_CONDITIONAL_BOOLEAN, RE_PROFILE_CONDITIONAL_VARIABLE, RE_PROFILE_END,
RE_PROFILE_HAT_DEF, RE_PROFILE_MOUNT, RE_PROFILE_PIVOT_ROOT, RE_PROFILE_START,
RE_PROFILE_HAT_DEF, RE_PROFILE_PIVOT_ROOT, RE_PROFILE_START,
RE_PROFILE_UNIX, RE_RULE_HAS_COMMA, parse_profile_start_line, re_match_include)
from apparmor.rule.abi import AbiRule
from apparmor.rule.capability import CapabilityRule
@@ -1995,29 +1995,6 @@ def parse_profile_data(data, file, do_include, in_preamble):
# Conditional Boolean defined
pass
elif RE_PROFILE_MOUNT.search(line):
matches = RE_PROFILE_MOUNT.search(line).groups()
if not profile:
raise AppArmorException(_('Syntax Error: Unexpected mount entry found in file: %(file)s line: %(line)s')
% {'file': file, 'line': lineno + 1})
audit = False
if matches[0]:
audit = True
allow = 'allow'
if matches[1] and matches[1].strip() == 'deny':
allow = 'deny'
mount = matches[2]
mount_rule = parse_mount_rule(mount)
mount_rule.audit = audit
mount_rule.deny = (allow == 'deny')
mount_rules = profile_data[profname][allow].get('mount', [])
mount_rules.append(mount_rule)
profile_data[profname][allow]['mount'] = mount_rules
elif RE_PROFILE_PIVOT_ROOT.search(line):
matches = RE_PROFILE_PIVOT_ROOT.search(line).groups()
@@ -2202,11 +2179,6 @@ def split_to_merged(profile_data):
return merged
def parse_mount_rule(line):
# XXX Do real parsing here
return aarules.Raw_Mount_Rule(line)
def parse_pivot_root_rule(line):
# XXX Do real parsing here
return aarules.Raw_Pivot_Root_Rule(line)

View File

@@ -16,7 +16,7 @@ import re
from apparmor.common import AppArmorBug, AppArmorException
from apparmor.regex import RE_PROFILE_MOUNT, RE_PROFILE_PATH_OR_VAR, strip_parenthesis
#from apparmor.rule import AARE
from apparmor.rule import AARE
from apparmor.rule import BaseRule, BaseRuleset, parse_modifiers, logprof_value_or_all, check_and_split_list
from apparmor.translations import init_translation
@@ -25,7 +25,7 @@ _ = init_translation()
# TODO :
# - match correctly AARE on every field
# - Find the actual list of supported filesystems. This one comes from /proc/filesystems
# - Find the actual list of supported filesystems. This one comes from /proc/filesystems. We also blindly accept fuse.*
# - Support path that begin by { (e.g. {,/usr}/lib/...) This syntax is not a valid AARE but is used by usr.lib.snapd.snap-confine.real in Ubuntu and will currently raise an error in genprof if these lines are not modified.
# - Apparmor remount logs are displayed as mount (with remount flag). Profiles generated with aa-genprof are therefore mount rules. It could be interesting to make them remount rules.
@@ -33,40 +33,52 @@ valid_fs = [
'sysfs', 'tmpfs', 'bdevfs', 'procfs', 'cgroup', 'cgroup2', 'cpuset', 'devtmpfs', 'configfs', 'debugfs', 'tracefs',
'securityfs', 'sockfs', 'bpf', 'npipefs', 'ramfs', 'hugetlbfs', 'devpts', 'ext3', 'ext2', 'ext4', 'squashfs',
'vfat', 'ecryptfs', 'fuseblk', 'fuse', 'fusectl', 'efivarfs', 'mqueue', 'store', 'autofs', 'binfmt_misc', 'overlay',
'none', 'bdev', 'proc', 'pipefs', 'pstore', 'brtfs', 'xfs',
'none', 'bdev', 'proc', 'pipefs', 'pstore', 'btrfs', 'xfs', '9p', 'resctrl', 'zfs', 'iso9660', 'udf', 'ntfs3',
'nfs', 'cifs',
]
flags_keywords = [
'ro', 'rw', 'nosuid', 'suid', 'nodev', 'dev', 'noexec', 'exec', 'sync', 'async', 'remount', 'mand', 'nomand',
'dirsync', 'noatime', 'atime', 'nodiratime', 'diratime', 'bind', 'rbind', 'move', 'verbose', 'silent', 'loud',
'acl', 'noacl', 'unbindable', 'runbindable', 'private', 'rprivate', 'slave', 'rslave', 'shared', 'rshared',
'relatime', 'norelatime', 'iversion', 'noiversion', 'strictatime', 'nostrictatime', 'lazytime', 'nolazytime',
'nouser', 'user', 'symfollow', 'nosymfollow', '([A-Za-z0-9]|AARE)', # TODO: handle AARE
# keep in sync with parser/mount.cc mnt_opts_table!
'ro', 'r', 'read-only', 'rw', 'w', 'suid', 'nosuid', 'dev', 'nodev', 'exec', 'noexec', 'sync', 'async', 'remount',
'mand', 'nomand', 'dirsync', 'symfollow', 'nosymfollow', 'atime', 'noatime', 'diratime', 'nodiratime', 'bind', 'B',
'move', 'M', 'rbind', 'R', 'verbose', 'silent', 'loud', 'acl', 'noacl', 'unbindable', 'make-unbindable', 'runbindable',
'make-runbindable', 'private', 'make-private', 'rprivate', 'make-rprivate', 'slave', 'make-slave', 'rslave', 'make-rslave',
'shared', 'make-shared', 'rshared', 'make-rshared', 'relatime', 'norelatime', 'iversion', 'noiversion', 'strictatime',
'nostrictatime', 'lazytime', 'nolazytime', 'user', 'nouser',
'([A-Za-z0-9])',
]
join_valid_flags = '|'.join(flags_keywords)
join_valid_fs = '|'.join(valid_fs)
sep = r"\s*[\s,]\s*"
sep = r'\s*[\s,]\s*'
fs_type_pattern = r"\b(?P<fstype_or_vfstype>fstype|vfstype)\b\s*(?P<fstype_equals_or_in>=|in)\s*"\
r"(?P<fstype>\(\s*(" + join_valid_fs + ")(" + sep + "(" + join_valid_fs + "))*\s*\)|"\
r"\{\s*(" + join_valid_fs + ")(" + sep + "(" + join_valid_fs + r"))*\s*\}|(\s*" + join_valid_fs + "))"\
# We aim to be a bit more restrictive than \S+ used in regex.py
FS_AARE = r'([][".*@{}\w^-]+)'
fs_type_pattern = r'\b(?P<fstype_or_vfstype>fstype|vfstype)\b\s*(?P<fstype_equals_or_in>=|in)\s*'\
r'(?P<fstype>\(\s*(' + FS_AARE + r')(' + sep + r'(' + FS_AARE + r'))*\s*\)|'\
r'\{\s*(' + FS_AARE + r')(' + sep + r'(' + FS_AARE + r'))*\s*\}|(\s*' + FS_AARE + r'))'\
option_pattern = r"\s*(\boption(s?)\b\s*(?P<options_equals_or_in>=|in)\s*"\
r"(?P<options>\(\s*(" + join_valid_flags + ")(" + sep + "(" + join_valid_flags + r"))*\s*\)|" \
"(\s*" + join_valid_flags + ")"\
"))?"
mount_condition_pattern = rf"({fs_type_pattern})?\s*({option_pattern})?"
option_pattern = r'\s*(\boption(s?)\b\s*(?P<options_equals_or_in>=|in)\s*'\
r'(?P<options>\(\s*(' + join_valid_flags + r')(' + sep + r'(' + join_valid_flags + r'))*\s*\)|' \
r'(\s*' + join_valid_flags + r')'\
r'))?'
mount_condition_pattern = rf'({fs_type_pattern})?\s*({option_pattern})?'
# Source can either be
# - A path : /foo
# - A globbed Path : {,/usr}/lib{,32,64,x32}/modules/
# - A filesystem : sysfs (sudo mount -t tmpfs tmpfs /tmp/bar)
# - Any label : mntlabel (sudo mount -t tmpfs mntlabel /tmp/bar)
source_fileglob_pattern = r"(\s*" + (RE_PROFILE_PATH_OR_VAR % "source_file")[:-1] + "|" + r"\w+" + "))"
dest_fileglob_pattern = r"(\s*" + RE_PROFILE_PATH_OR_VAR % "dest_file" + ')'
# Thus we cannot use directly RE_PROFILE_PATH_OR_VAR
source_fileglob_pattern = r'(\s*(?P<source_file>([/{]\S*|"[/{][^"]*"|@{\S+}\S*|"@{\S+}[^"]*")|\w+))'
dest_fileglob_pattern = r'(\s*' + RE_PROFILE_PATH_OR_VAR % 'dest_file' + r')'
RE_MOUNT_DETAILS = re.compile(r'^\s*' + mount_condition_pattern + rf'(\s+{source_fileglob_pattern})?' + rf'(\s+->\s+{dest_fileglob_pattern})?\s*' + r'$')
RE_UMOUNT_DETAILS = re.compile(r'^\s*' + mount_condition_pattern + rf'(\s+{dest_fileglob_pattern})?\s*' + r'$')
RE_MOUNT_DETAILS = re.compile(r"^\s*" + mount_condition_pattern + rf"(\s+{source_fileglob_pattern})?" + rf"(\s+->\s+{dest_fileglob_pattern})?\s*" +"$")
RE_UMOUNT_DETAILS = re.compile(r"^\s*" + mount_condition_pattern + rf"(\s+{dest_fileglob_pattern})?\s*" +"$")
class MountRule(BaseRule):
'''Class to handle and store a single mount rule'''
@@ -91,22 +103,37 @@ class MountRule(BaseRule):
self.operation = operation
self.fstype, self.all_fstype, unknown_items = check_and_split_list(fstype[1] if fstype != self.ALL else fstype, valid_fs, self.ALL, type(self).__name__, 'fstype')
if unknown_items:
for it in unknown_items:
# Several filesystems use fuse internally and are referred as fuse.<software_name> (e.g. fuse.jmtpfs, fuse.s3fs, fuse.obexfs).
# Since this list seems to evolve too fast for a fixed list to work in practice, we just accept fuse.*
# See https://github.com/libfuse/libfuse/wiki/Filesystems and, https://doc.ubuntu-fr.org/fuse
if it.startswith('fuse.') and len(it) > 5:
continue
it = AARE(it, is_path=False)
found = False
for fs in valid_fs:
if self._is_covered_aare(it, self.all_fstype, AARE(fs, False), self.all_fstype, 'fstype'):
found = True
break
if not found:
raise AppArmorException(_('Passed unknown fstype keyword to %s: %s') % (type(self).__name__, ' '.join(unknown_items)))
self.is_fstype_equal = fstype[0] if not self.all_fstype else None
self.options, self.all_options, unknown_items = check_and_split_list(options[1] if options != self.ALL else options, flags_keywords, self.ALL, type(self).__name__, 'options')
if unknown_items:
raise AppArmorException(_('Passed unknown options keyword to %s: %s') % (type(self).__name__, ' '.join(unknown_items)))
self.is_options_equal = options[0] if not self.all_options else None
if source != self.ALL and source[0].isalpha():
self.source = source
self.all_source = False
self.source_is_path = False
else:
self.source_is_path = True
self.source, self.all_source = self._aare_or_all(source, 'source', is_path=self.source_is_path, log_event=log_event)
self.source, self.all_source = self._aare_or_all(source, 'source', is_path=False, log_event=log_event)
if not self.all_fstype and self.is_fstype_equal != "=" and self.is_fstype_equal != "in":
if not self.all_fstype and self.is_fstype_equal not in ('=', 'in'):
raise AppArmorBug(f'Invalid is_fstype_equal : {self.is_fstype_equal}')
if not self.all_options and self.is_options_equal != "=" and self.is_options_equal != "in":
if not self.all_options and self.is_options_equal not in ('=', 'in'):
raise AppArmorBug(f'Invalid is_options_equal : {self.is_options_equal}')
if self.operation != 'mount' and not self.all_source:
raise AppArmorException(f'Operation {self.operation} cannot have a source')
@@ -119,7 +146,6 @@ class MountRule(BaseRule):
self.can_glob = not self.all_source and not self.all_dest and not self.all_options
@classmethod
def _create_instance(cls, raw_rule, matches):
'''parse raw_rule and return instance of this class'''
@@ -132,7 +158,7 @@ class MountRule(BaseRule):
if matches.group('details'):
rule_details = matches.group('details')
if operation == "mount":
if operation == 'mount':
parsed = RE_MOUNT_DETAILS.search(rule_details)
else:
parsed = RE_UMOUNT_DETAILS.search(rule_details)
@@ -152,7 +178,7 @@ class MountRule(BaseRule):
is_options_equal = r['options_equals_or_in']
options = strip_parenthesis(r['options']).replace(',', ' ').split()
else:
is_options_equal =None
is_options_equal = None
options = cls.ALL
if operation == 'mount' and r['source_file'] is not None: # Umount cannot have a source
@@ -168,38 +194,42 @@ class MountRule(BaseRule):
else:
is_fstype_equal = None
is_options_equal = None
fstype = cls.ALL
fstype = cls.ALL
options = cls.ALL
source = cls.ALL
dest = cls.ALL
source = cls.ALL
dest = cls.ALL
return cls(operation=operation, fstype=(is_fstype_equal, fstype), options=(is_options_equal, options), source=source, dest=dest, audit=audit, deny=deny, allow_keyword=allow_keyword, comment=comment)
def get_clean(self, depth=0):
space = ' ' * depth
fstype = ' fstype%s(%s)' % (self.is_fstype_equal, ', '.join(sorted(self.fstype))) if not self.all_fstype else ''
options = ' options%s(%s)' % (self.is_options_equal, ', '.join(sorted(self.options))) if not self.all_options else ''
fstype = ' fstype%s(%s)' % (wrap_in_with_spaces(self.is_fstype_equal), ', '.join(sorted(self.fstype))) if not self.all_fstype else ''
options = ' options%s(%s)' % (wrap_in_with_spaces(self.is_options_equal), ', '.join(sorted(self.options))) if not self.all_options else ''
source = ''
dest = ''
if self.operation == 'mount':
return ('%s%s%s%s%s%s%s,%s' % ( self.modifiers_str(),
space,
self.operation,
fstype,
options,
" " + str(self.source.regex) if not self.all_source else '',
" -> " + str(self.dest.regex) if not self.all_dest else '',
self.comment,
))
if not self.all_source:
source = ' ' + str(self.source.regex)
if not self.all_dest:
dest = ' -> ' + str(self.dest.regex)
else:
return ('%s%s%s%s%s%s,%s' % ( self.modifiers_str(),
space,
self.operation,
fstype,
options,
" " + str(self.dest.regex) if not self.all_dest else '',
self.comment,
))
if not self.all_dest:
dest = ' ' + str(self.dest.regex)
return ('%s%s%s%s%s%s%s,%s' % (self.modifiers_str(),
space,
self.operation,
fstype,
options,
source,
dest,
self.comment,
))
def _is_covered_localvars(self, other_rule):
if self.operation != other_rule.operation:
@@ -208,16 +238,18 @@ class MountRule(BaseRule):
return False
if self.is_options_equal != other_rule.is_options_equal:
return False
if not self._is_covered_list(self.fstype, self.all_fstype, other_rule.fstype, other_rule.all_fstype, 'fstype'):
return False
for o_it in other_rule.fstype or []:
found = False
for s_it in self.fstype or []:
if self._is_covered_aare(AARE(s_it, False), self.all_fstype, AARE(o_it, False), other_rule.all_fstype, 'fstype'):
found = True
if not found:
return False
if not self._is_covered_list(self.options, self.all_options, other_rule.options, other_rule.all_options, 'options'):
return False
if not self.source_is_path and not other_rule.source_is_path:
if self.source != other_rule.source:
return False
elif self.source_is_path != other_rule.source_is_path:
return False
elif not self._is_covered_aare(self.source, self.all_source, other_rule.source, other_rule.all_source, 'source'):
if not self._is_covered_aare(self.source, self.all_source, other_rule.source, other_rule.all_source, 'source'):
return False
if not self._is_covered_aare(self.dest, self.all_dest, other_rule.dest, other_rule.all_dest, 'dest'):
return False
@@ -233,12 +265,7 @@ class MountRule(BaseRule):
return False
if self.fstype != rule_obj.fstype or self.options != rule_obj.options:
return False
if not self.source_is_path and not rule_obj.source_is_path:
if self.source != rule_obj.source:
return False
elif self.source_is_path != rule_obj.source_is_path:
return False
elif not self._is_equal_aare(self.source, self.all_source, rule_obj.source, rule_obj.all_source, 'source'):
if not self._is_equal_aare(self.source, self.all_source, rule_obj.source, rule_obj.all_source, 'source'):
return False
if not self._is_equal_aare(self.dest, self.all_dest, rule_obj.dest, rule_obj.all_dest, 'dest'):
return False
@@ -256,8 +283,8 @@ class MountRule(BaseRule):
elif not self.all_source and type(self.source) is not str:
self.source = self.source.glob_path()
if self.source.is_equal('/**/'):
self.all_source= True
self.source=self.ALL
self.all_source = True
self.source = self.ALL
else:
self.options = self.ALL
@@ -273,15 +300,22 @@ class MountRule(BaseRule):
return (
_('Operation'), operation,
_('Fstype'), (self.is_fstype_equal, fstype) if fstype != 'ALL' else fstype ,
_('Options'), (self.is_options_equal, options) if options != 'ALL' else options ,
_('Fstype'), (self.is_fstype_equal, fstype) if fstype != 'ALL' else fstype,
_('Options'), (self.is_options_equal, options) if options != 'ALL' else options,
_('Source'), source,
_('Destination'), dest,
)
class MountRuleset(BaseRuleset):
'''Class to handle and store a collection of Mount rules'''
def wrap_in_with_spaces(value):
''' wrap 'in' keyword in spaces, and leave everything else unchanged '''
if value == 'in':
value = ' in '
return value

View File

@@ -140,7 +140,8 @@ Feb 4 13:40:38 XPS-13-9370 kernel: [128552.880347] audit: type=1400 audit({epoc
if 'SUDO_USER' in os.environ:
username = os.environ.get('SUDO_USER')
return_code, output = cmd(['last', '-1', username, '--time-format', 'iso'])
return_code, output = cmd(['last', username, '--time-format', 'iso'])
output = output.split('\n')[0] # the first line is enough
# example of output:
# ubuntu tty7 :0 2024-01-05T14:29:11-03:00 gone - no logout
if output.startswith(username):

View File

@@ -28,36 +28,36 @@ _ = init_translation()
class MountTestParse(AATest):
tests = (
# Rule Operation Filesystem Options Source Destination Audit Deny Allow Comment
('mount fstype=bpf options=rw bpf -> /sys/fs/bpf/,', MountRule('mount', ('=',('bpf')), ('=',('rw')), "bpf", "/sys/fs/bpf/", False, False, False, '' )),
('mount fstype=bpf options=(rw) random_label -> /sys/fs/bpf/,', MountRule('mount', ('=',('bpf')), ('=',('rw')), "random_label", "/sys/fs/bpf/", False, False, False, '' )),
('mount,', MountRule('mount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('mount fstype=(ext3, ext4),', MountRule('mount', ('=',('ext3', 'ext4')), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('mount bpf,', MountRule('mount', MountRule.ALL, MountRule.ALL, "bpf", MountRule.ALL, False, False, False, '' )),
('mount none,', MountRule('mount', MountRule.ALL, MountRule.ALL, "none", MountRule.ALL, False, False, False, '' )),
('mount fstype=(ext3, ext4) options=(ro),', MountRule('mount', ('=',('ext3', 'ext4')), ('=',('ro')), MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('mount @{mntpnt},', MountRule('mount', MountRule.ALL, MountRule.ALL, "@{mntpnt}", MountRule.ALL, False, False, False, '' )),
('mount /a,', MountRule('mount', MountRule.ALL, MountRule.ALL, "/a", MountRule.ALL, False, False, False, '' )),
('mount fstype=(ext3, ext4) /a -> /b,', MountRule('mount', ('=',('ext3', 'ext4')), MountRule.ALL, "/a", "/b", False, False, False, '' )),
('mount fstype=(ext3, ext4) options=(ro, rbind) /a -> /b,', MountRule('mount', ('=',('ext3', 'ext4')), ('=',('ro', 'rbind')), "/a", "/b", False, False, False, '' )),
('mount fstype=(ext3, ext4) options=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('=',('ext3', 'ext4')), ('=',('ro', 'rbind')), "/a", "/b", False, False, False, ' #cmt')),
('mount fstype=(ext3, ext4) options in (ro, rbind) /a -> /b,', MountRule('mount', ('=',('ext3', 'ext4')), ('in',('ro', 'rbind')), "/a", "/b", False, False, False, '' )),
('mount fstype in (ext3, ext4) options=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('in',('ext3', 'ext4')),('=',('ro', 'rbind')), "/a", "/b", False, False, False, ' #cmt')),
('mount fstype in (ext3, ext4) option in (ro, rbind) /a, #cmt', MountRule('mount', ('in',('ext3', 'ext4')),('in',('ro', 'rbind')), "/a", MountRule.ALL, False, False, False, ' #cmt')),
('mount fstype=(ext3, ext4) option=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('=',('ext3', 'ext4')), ('=', ('ro', 'rbind')), "/a", "/b", False, False, False, ' #cmt')),
('mount options=(rw, rbind) /usr/lib{,32,64,x32}/modules/ -> /tmp/snap.rootfs_*{,/usr}/lib/modules/,',
MountRule('mount', MountRule.ALL, ('=',('rw', 'rbind')), "/usr/lib{,32,64,x32}/modules/",
"/tmp/snap.rootfs_*{,/usr}/lib/modules/",
False, False, False, '' )),
('umount,', MountRule('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('umount fstype=ext3,', MountRule('umount', ('=',('ext3')), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('umount /a,', MountRule('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, "/a", False, False, False, '' )),
('remount,', MountRule('remount',MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('remount fstype=ext4,', MountRule('remount',('=',('ext4')), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('remount /b,', MountRule('remount',MountRule.ALL, MountRule.ALL, MountRule.ALL, "/b", False, False, False, '' )),
# Rule Operation Filesystem Options Source Destination Audit Deny Allow Comment
('mount fstype=bpf options=rw bpf -> /sys/fs/bpf/,', MountRule('mount', ('=', ('bpf')), ('=', ('rw')), 'bpf', '/sys/fs/bpf/', False, False, False, '' )),
('mount fstype=fuse.obex* options=rw bpf -> /sys/fs/bpf/,', MountRule('mount', ('=', ('fuse.obex*')), ('=', ('rw')), 'bpf', '/sys/fs/bpf/', False, False, False, '' )),
('mount fstype=fuse.* options=rw bpf -> /sys/fs/bpf/,', MountRule('mount', ('=', ('fuse.*')), ('=', ('rw')), 'bpf', '/sys/fs/bpf/', False, False, False, '' )),
('mount fstype=bpf options=(rw) random_label -> /sys/fs/bpf/,', MountRule('mount', ('=', ("bpf")), ('=', ('rw')), 'random_label', '/sys/fs/bpf/', False, False, False, '' )),
('mount,', MountRule('mount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('mount fstype=(ext3, ext4),', MountRule('mount', ('=', ('ext3', 'ext4')), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('mount bpf,', MountRule('mount', MountRule.ALL, MountRule.ALL, 'bpf', MountRule.ALL, False, False, False, '' )),
('mount none,', MountRule('mount', MountRule.ALL, MountRule.ALL, 'none', MountRule.ALL, False, False, False, '' )),
('mount fstype=(ext3, ext4) options=(ro),', MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('mount @{mntpnt},', MountRule('mount', MountRule.ALL, MountRule.ALL, '@{mntpnt}', MountRule.ALL, False, False, False, '' )),
('mount /a,', MountRule('mount', MountRule.ALL, MountRule.ALL, '/a', MountRule.ALL, False, False, False, '' )),
('mount fstype=(ext3, ext4) /a -> /b,', MountRule('mount', ('=', ('ext3', 'ext4')), MountRule.ALL, '/a', '/b', False, False, False, '' )),
('mount fstype=(ext3, ext4) options=(ro, rbind) /a -> /b,', MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro', 'rbind')), '/a', '/b', False, False, False, '' )),
('mount fstype=(ext3, ext4) options=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro', 'rbind')), '/a', '/b', False, False, False, ' #cmt')),
('mount fstype=(ext3, ext4) options in (ro, rbind) /a -> /b,', MountRule('mount', ('=', ('ext3', 'ext4')), ('in', ('ro', 'rbind')), '/a', '/b', False, False, False, '' )),
('mount fstype in (ext3, ext4) options=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('in', ('ext3', 'ext4')), ('=', ('ro', 'rbind')), '/a', '/b', False, False, False, ' #cmt')),
('mount fstype in (ext3, ext4) option in (ro, rbind) /a, #cmt', MountRule('mount', ('in', ('ext3', 'ext4')), ('in', ('ro', 'rbind')), '/a', MountRule.ALL, False, False, False, ' #cmt')),
('mount fstype=(ext3, ext4) option=(ro, rbind) /a -> /b, #cmt', MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro', 'rbind')), '/a', '/b', False, False, False, ' #cmt')),
('mount options=(rw, rbind) {,/usr}/lib{,32,64,x32}/modules/ -> /tmp/snap.rootfs_*{,/usr}/lib/modules/,',
MountRule('mount', MountRule.ALL, ('=', ('rw', 'rbind')), '{,/usr}/lib{,32,64,x32}/modules/',
'/tmp/snap.rootfs_*{,/usr}/lib/modules/',
False, False, False, '' )),
('umount,', MountRule('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('umount fstype=ext3,', MountRule('umount', ('=', ('ext3')), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('umount /a,', MountRule('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/a', False, False, False, '' )),
('remount,', MountRule('remount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('remount fstype=ext4,', MountRule('remount', ('=', ('ext4')), MountRule.ALL, MountRule.ALL, MountRule.ALL, False, False, False, '' )),
('remount /b,', MountRule('remount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/b', False, False, False, '' )),
)
def _run_test(self, rawrule, expected):
@@ -69,12 +69,12 @@ class MountTestParse(AATest):
class MountTestParseInvalid(AATest):
tests = (
('mount fstype=,', AppArmorException),
('mount fstype=(foo),', AppArmorException),
('mount fstype=(),', AppArmorException),
('mount options=(),', AppArmorException),
('mount option=(invalid),', AppArmorException),
('mount option=(ext3ext4),',AppArmorException),
('mount fstype=,', AppArmorException),
('mount fstype=(foo),', AppArmorException),
('mount fstype=(),', AppArmorException),
('mount options=(),', AppArmorException),
('mount option=(invalid),', AppArmorException),
('mount option=(ext3ext4),', AppArmorException),
)
def _run_test(self, rawrule, expected):
@@ -88,60 +88,69 @@ class MountTestParseInvalid(AATest):
def test_diff_non_mountrule(self):
exp = namedtuple('exp', ('audit', 'deny'))
obj = MountRule('mount',("=", '(ext4)'), MountRule.ALL, MountRule.ALL, MountRule.ALL)
obj = MountRule('mount', ('=', 'ext4'), MountRule.ALL, MountRule.ALL, MountRule.ALL)
with self.assertRaises(AppArmorBug):
obj.is_equal(exp(False, False), False)
def test_diff_invalid_fstype_equals_or_in(self):
with self.assertRaises(AppArmorBug):
MountRule('mount', ('ext3', '(ext4)'), MountRule.ALL, MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
MountRule('mount', ('ext3', 'ext4'), MountRule.ALL, MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
def test_diff_invalid_fstype_keyword(self):
with self.assertRaises(AppArmorException):
MountRule('mount', ('=', 'invalidfs'), MountRule.ALL, MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
def test_diff_invalid_options_equals_or_in(self):
with self.assertRaises(AppArmorBug):
MountRule('mount', MountRule.ALL, ('rbind', '(rw)'), MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
MountRule('mount', MountRule.ALL, ('rbind', 'rw'), MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
def test_diff_invalid_options_keyword(self):
with self.assertRaises(AppArmorException):
MountRule('mount', MountRule.ALL, ('=', 'invalid'), MountRule.ALL, MountRule.ALL) # fstype[0] should be '=' or 'in'
def test_diff_fstype(self):
obj1 = MountRule('mount',("=", '(ext4)'), MountRule.ALL, MountRule.ALL, MountRule.ALL)
obj2 = MountRule('mount',MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL)
obj1 = MountRule('mount', ('=', 'ext4'), MountRule.ALL, MountRule.ALL, MountRule.ALL)
obj2 = MountRule('mount', MountRule.ALL, MountRule.ALL, MountRule.ALL, MountRule.ALL)
self.assertFalse(obj1.is_equal(obj2, False))
def test_diff_source(self):
obj1 = MountRule('mount',MountRule.ALL, MountRule.ALL, "/foo", MountRule.ALL)
obj2 = MountRule('mount',MountRule.ALL, MountRule.ALL, "/bar", MountRule.ALL)
obj1 = MountRule('mount', MountRule.ALL, MountRule.ALL, '/foo', MountRule.ALL)
obj2 = MountRule('mount', MountRule.ALL, MountRule.ALL, '/bar', MountRule.ALL)
self.assertFalse(obj1.is_equal(obj2, False))
def test_invalid_umount_with_source(self):
with self.assertRaises(AppArmorException):
MountRule('umount', MountRule.ALL, MountRule.ALL, "/foo", MountRule.ALL) # Umount and remount shall not have a source
MountRule('umount', MountRule.ALL, MountRule.ALL, '/foo', MountRule.ALL) # Umount and remount shall not have a source
def test_invalid_remount_with_source(self):
with self.assertRaises(AppArmorException):
MountRule('remount', MountRule.ALL, MountRule.ALL, "/foo", MountRule.ALL)
MountRule('remount', MountRule.ALL, MountRule.ALL, '/foo', MountRule.ALL)
class MountTestFilesystems(AATest):
def test_fs(self):
with open("/proc/filesystems") as f:
with open('/proc/filesystems') as f:
for line in f:
self.assertTrue(line.split()[-1] in valid_fs)
fs_name = line.split()[-1]
self.assertTrue(fs_name in valid_fs, '/proc/filesystems contains %s which is not listed in MountRule valid_fs' % fs_name)
class MountTestGlob(AATest):
def test_glob(self):
globList = [(
"mount options=(bind, rw) /home/user/Downloads/ -> /mnt/a/,",
"mount options=(bind, rw) /home/user/Downloads/,",
"mount options=(bind, rw) /home/user/*/,",
"mount options=(bind, rw) /home/**/,",
"mount options=(bind, rw),",
"mount,",
"mount,",
'mount options=(bind, rw) /home/user/Downloads/ -> /mnt/a/,',
'mount options=(bind, rw) /home/user/Downloads/,',
'mount options=(bind, rw) /home/user/*/,',
'mount options=(bind, rw) /home/**/,',
'mount options=(bind, rw),',
'mount,',
'mount,',
)]
for globs in globList:
for i in range(len(globs)-1):
for i in range(len(globs) - 1):
rule = MountRule.create_instance(globs[i])
rule.glob()
self.assertEqual(rule.get_clean(), globs[i+1])
self.assertEqual(rule.get_clean(), globs[i + 1])
class MountTestClean(AATest):
@@ -152,7 +161,13 @@ class MountTestClean(AATest):
(' mount fstype = ( sysfs , procfs ) , ', 'mount fstype=(procfs, sysfs),'),
(' mount options = ( rw ) , ', 'mount options=(rw),'),
(' mount options = ( rw , noatime ) , ', 'mount options=(noatime, rw),'),
(' mount fstype in ( sysfs ) , ', 'mount fstype in (sysfs),'),
(' mount fstype in ( sysfs , procfs ) , ', 'mount fstype in (procfs, sysfs),'),
(' mount options in ( rw ) , ', 'mount options in (rw),'),
(' mount options in ( rw , noatime ) , ', 'mount options in (noatime, rw),'),
(' umount , ', 'umount,'),
(' umount /foo , ', 'umount /foo,'),
(' remount , ', 'remount,'),
(' remount /foo , ', 'remount /foo,'),
)
@@ -165,11 +180,12 @@ class MountTestClean(AATest):
self.assertEqual(expected, clean, 'unexpected clean rule')
self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule')
class MountLogprofHeaderTest(AATest):
tests = (
('mount,', [_('Operation'), _('mount'), _('Fstype'), _('ALL'), _('Options'), _('ALL'), _('Source'), _('ALL'), _('Destination'), _('ALL')]),
('mount options=(ro, nosuid) /a,', [_('Operation'), _('mount'), _('Fstype'), _('ALL'), _('Options'), ("=", _('nosuid ro')),_('Source'), _('/a'), _('Destination'), _('ALL')]),
('mount fstype=(ext3, ext4) options=(ro, nosuid) /a -> /b,', [_('Operation'), _('mount'), _('Fstype'), ("=", _('ext3 ext4')),_('Options'), ("=", _('nosuid ro')),_('Source'), _('/a'), _('Destination'), _('/b')])
('mount,', [_('Operation'), _('mount'), _('Fstype'), _('ALL'), _('Options'), _('ALL'), _('Source'), _('ALL'), _('Destination'), _('ALL')]),
('mount options=(ro, nosuid) /a,', [_('Operation'), _('mount'), _('Fstype'), _('ALL'), _('Options'), ('=', _('nosuid ro')), _('Source'), _('/a'), _('Destination'), _('ALL')]),
('mount fstype=(ext3, ext4) options=(ro, nosuid) /a -> /b,', [_('Operation'), _('mount'), _('Fstype'), ('=', _('ext3 ext4')), _('Options'), ('=', _('nosuid ro')), _('Source'), _('/a'), _('Destination'), _('/b')])
)
def _run_test(self, params, expected):
@@ -179,45 +195,59 @@ class MountLogprofHeaderTest(AATest):
class MountIsCoveredTest(AATest):
def test_is_covered(self):
obj = MountRule("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "/foo/b*", "/b*")
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b*', '/b*')
tests = [
("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "/foo/b", "/bar"),
("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "/foo/bar", "/b")
('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b', '/bar'),
('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/bar', '/b')
]
for test in tests:
self.assertTrue(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_covered_fs_source(self):
obj = MountRule("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "tmpfs", MountRule.ALL)
self.assertTrue(obj.is_covered(MountRule("mount", ("=", ('ext3')), ("=", ('ro')), "tmpfs", MountRule.ALL)))
self.assertFalse(obj.is_equal(MountRule("mount", ("=", ('ext3')), ("=", ('ro')), "tmpfs", MountRule.ALL)))
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), 'tmpfs', MountRule.ALL)
self.assertTrue(obj.is_covered(MountRule('mount', ('=', ('ext3')), ('=', ('ro')), 'tmpfs', MountRule.ALL)))
self.assertFalse(obj.is_equal(MountRule('mount', ('=', ('ext3')), ('=', ('ro')), 'tmpfs', MountRule.ALL)))
def test_is_covered_regex(self):
obj = MountRule('mount', ('=', ('sys*', 'fuse.*')), ('=', ('ro')), 'tmpfs', MountRule.ALL)
tests = [
('mount', ('=', ('sysfs', 'fuse.s3fs')), ('=', ('ro')), 'tmpfs', MountRule.ALL),
('mount', ('=', ('sysfs', 'fuse.jmtpfs', 'fuse.s3fs', 'fuse.obexfs', 'fuse.obexautofs', 'fuse.fuseiso')), ('=', ('ro')), 'tmpfs', MountRule.ALL)
]
for test in tests:
self.assertTrue(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_notcovered(self):
obj = MountRule("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "/foo/b*", "/b*")
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b*', '/b*')
tests = [
("mount", ("in", ('ext3', 'ext4')), ("=", ('ro')), "/foo/bar", "/bar" ),
("mount", ("=", ('procfs, ext4')), ("=", ('ro')), "/foo/bar", "/bar" ),
("mount", ("=", ('ext3')), ("=", ('rw')), "/foo/bar", "/bar" ),
("mount", ("=", ('ext3', 'ext4')), MountRule.ALL, "/foo/b*", "/bar" ),
("mount", MountRule.ALL, ("=", ('ro')), "/foo/b*", "/bar" ),
("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "/invalid/bar", "/bar" ),
("umount", MountRule.ALL, MountRule.ALL, MountRule.ALL, "/bar" ),
("remount", MountRule.ALL, MountRule.ALL, MountRule.ALL, "/bar" ),
("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "tmpfs", "/bar" ),
("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "/foo/b*", "/invalid" ),
('mount', ('in', ('ext3', 'ext4')), ('=', ('ro')), '/foo/bar', '/bar' ),
('mount', ('=', ('procfs', 'ext4')), ('=', ('ro')), '/foo/bar', '/bar' ),
('mount', ('=', ('ext3')), ('=', ('rw')), '/foo/bar', '/bar' ),
('mount', ('=', ('ext3', 'ext4')), MountRule.ALL, '/foo/b*', '/bar' ),
('mount', MountRule.ALL, ('=', ('ro')), '/foo/b*', '/bar' ),
('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/invalid/bar', '/bar' ),
('umount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/bar' ),
('remount', MountRule.ALL, MountRule.ALL, MountRule.ALL, '/bar' ),
('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), 'tmpfs', '/bar' ),
('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), '/foo/b*', '/invalid'),
]
for test in tests:
self.assertFalse(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_not_covered_fs_source(self):
obj = MountRule("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "tmpfs", MountRule.ALL)
test = ("mount", ("=", ('ext3', 'ext4')), ("=", ('ro')), "procfs", MountRule.ALL)
obj = MountRule('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), 'tmpfs', MountRule.ALL)
test = ('mount', ('=', ('ext3', 'ext4')), ('=', ('ro')), 'procfs', MountRule.ALL)
self.assertFalse(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
def test_is_not_covered_fs_options(self):
obj = MountRule('mount', MountRule.ALL, ('=', ('ro')), 'tmpfs', MountRule.ALL)
test = ('mount', MountRule.ALL, ('=', ('rw')), 'procfs', MountRule.ALL)
self.assertFalse(obj.is_covered(MountRule(*test)))
self.assertFalse(obj.is_equal(MountRule(*test)))
setup_all_loops(__name__)

View File

@@ -1,53 +0,0 @@
#! /usr/bin/python3
# ------------------------------------------------------------------
#
# Copyright (C) 2014 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License published by the Free Software Foundation.
#
# ------------------------------------------------------------------
import unittest
import apparmor.aa as aa
from common_test import AAParseTest, setup_aa, setup_regex_tests
class BaseAAParseMountTest(AAParseTest):
def setUp(self):
self.parse_function = aa.parse_mount_rule
class AAParseMountTest(BaseAAParseMountTest):
tests = (
('mount,', 'mount base keyword rule'),
('mount -o ro,', 'mount ro rule'),
('mount -o rw /dev/sdb1 -> /mnt/external,', 'mount rw with mount point'),
)
class AAParseRemountTest(BaseAAParseMountTest):
tests = (
('remount,', 'remount base keyword rule'),
('remount -o ro,', 'remount ro rule'),
('remount -o ro /,', 'remount ro with mountpoint'),
)
class AAParseUmountTest(BaseAAParseMountTest):
tests = (
('umount,', 'umount base keyword rule'),
('umount /mnt/external,', 'umount with mount point'),
('unmount,', 'unmount base keyword rule'),
('unmount /mnt/external,', 'unmount with mount point'),
)
setup_aa(aa)
if __name__ == '__main__':
setup_regex_tests(AAParseMountTest)
setup_regex_tests(AAParseRemountTest)
setup_regex_tests(AAParseUmountTest)
unittest.main(verbosity=1)

View File

@@ -85,6 +85,16 @@ exception_not_raised = (
'mount/bad_1.sd',
'mount/bad_2.sd',
# not checked/detected: "make-*" mount opt and an invalid src
'mount/bad_opt_17.sd',
'mount/bad_opt_18.sd',
'mount/bad_opt_19.sd',
'mount/bad_opt_20.sd',
'mount/bad_opt_21.sd',
'mount/bad_opt_22.sd',
'mount/bad_opt_23.sd',
'mount/bad_opt_24.sd',
'profile/flags/flags_bad10.sd',
'profile/flags/flags_bad11.sd',
'profile/flags/flags_bad12.sd',
@@ -314,15 +324,8 @@ unknown_line = (
'bare_include_tests/ok_85.sd',
'bare_include_tests/ok_86.sd',
# option = make-${valid-option} (e.g. make-private) is not supported
'mount/ok_opt_48.sd',
'mount/ok_opt_49.sd',
'mount/ok_opt_50.sd',
'mount/ok_opt_51.sd',
'mount/ok_opt_52.sd',
'mount/ok_opt_53.sd',
'mount/ok_opt_54.sd',
'mount/ok_opt_55.sd',
# mount with fstype using AARE
'mount/ok_12.sd',
# Mount with flags in {remount, [r]unbindable, [r]shared, [r]private, [r]slave} does not support a source
'mount/ok_opt_68.sd',
@@ -334,15 +337,7 @@ unknown_line = (
'mount/ok_opt_74.sd',
'mount/ok_opt_75.sd',
# option = make-${valid-option} (e.g. make-private) is not supported
'mount/ok_opt_76.sd',
'mount/ok_opt_77.sd',
'mount/ok_opt_78.sd',
'mount/ok_opt_79.sd',
'mount/ok_opt_80.sd',
'mount/ok_opt_81.sd',
'mount/ok_opt_82.sd',
'mount/ok_opt_83.sd',
# options=slave with /** src (first rule in the test causes exception)
'mount/ok_opt_84.sd',
# According to spec mount should be in the form fstype=... options=... and NOT in the form options=... fstype=...
@@ -351,8 +346,6 @@ unknown_line = (
'mount/ok_opt_combo_1.sd',
'mount/ok_opt_combo_4.sd',
# Invalid keyword: read-only --> Should be ro
'mount/ok_opt_3.sd',
# Options should be comma separated
'mount/in_4.sd', # also order option then fstype is invalid
)

View File

@@ -14,7 +14,7 @@ import unittest
import apparmor.aa as aa
from apparmor.common import AppArmorBug, AppArmorException
from apparmor.regex import (
RE_PROFILE_CAP, RE_PROFILE_DBUS, RE_PROFILE_PTRACE, RE_PROFILE_SIGNAL,
RE_PROFILE_CAP, RE_PROFILE_DBUS, RE_PROFILE_MOUNT, RE_PROFILE_PTRACE, RE_PROFILE_SIGNAL,
RE_PROFILE_START, parse_profile_start_line, re_match_include,
re_match_include_parse, strip_parenthesis, strip_quotes)
from common_test import AATest, setup_aa, setup_all_loops
@@ -248,7 +248,7 @@ class AARegexMount(AARegexTest):
"""Tests for RE_PROFILE_MOUNT"""
def AASetup(self):
self.regex = aa.RE_PROFILE_MOUNT
self.regex = RE_PROFILE_MOUNT
tests = (
(' mount,', (None, None, 'mount,', 'mount', None, None)),