2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-03 15:55:46 +00:00

Compare commits

...

53 Commits

Author SHA1 Message Date
Christian Boltz
93768757f5 Merge Fix duplicated 'make' in error message
Fixes: 5993ff21d2 / https://gitlab.com/apparmor/apparmor/-/merge_requests/1779

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1781
Approved-by: Ryan Lee <rlee287@yahoo.com>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2025-09-02 16:08:56 +00:00
Christian Boltz
7255edec09 Fix duplicated 'make' in error message
Fixes: 5993ff21d2 / https://gitlab.com/apparmor/apparmor/-/merge_requests/1779
2025-08-30 13:30:40 +02:00
John Johansen
7757c7130c Merge profiles: curl: switch to user-tmp abstraction
The curl profile allows reading and writing to /tmp/ so instead of
two rules that don't cover all tmp locations, switch to the user-tmp
abstraction to allow access to the various possible tmp locations.

Note: The does reduce the write permission to owner write, instead
of the wider file w /tmp/**,

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

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1773
Approved-by: Maxime Bélair <maxime.belair@canonical.com>
Merged-by: John Johansen <john@jjmx.net>
2025-08-30 01:12:37 +00:00
John Johansen
e58f65d0c1 Merge regression: avoid exposing disconnected_mount_complain's mounts externally
Due to a behavioral change between 6.14 and 6.15, the move_mount that we
set up in disconnected_mount_complain's open_tree test would now persist,
causing issues with test cleanup after that test. Ensure that we remount
all the mounts as private after unsharing the mount namespace to prevent
this from happening.

Signed-off-by: Ryan Lee <ryan.lee@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1779
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: John Johansen <john@jjmx.net>
2025-08-30 01:00:27 +00:00
Christian Boltz
d72cc8f09e Merge test-aa-show-usage: correctly handle disabled profiles
test_show_unconfined_profiles did not exclude disabled profiles from the
expected results, which led to test failures when some profiles were
disabled.

Disabled profiles are now correctly excluded from the results, fixing the
test.

Reported-by: Georgia Garcia <georgia.garcia@canonical.com>
Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1780
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2025-08-29 11:24:59 +00:00
Maxime Bélair
b80179834d test-aa-show-usage: correctly handle disabled profiles
test_show_unconfined_profiles did not exclude disabled profiles from the
expected results, which led to test failures when some profiles were
disabled.

Disabled profiles are now correctly excluded from the results, fixing the
test.

Reported-by: Georgia Garcia <georgia.garcia@canonical.com>
Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-29 10:13:38 +02:00
Ryan Lee
5993ff21d2 regression: avoid exposing disconnected_mount_complain's mounts externally
Due to a behavioral change between 6.14 and 6.15, the move_mount that we
set up in disconnected_mount_complain's open_tree test would now persist,
causing issues with test cleanup after that test. Ensure that we remount
all the mounts as private after unsharing the mount namespace to prevent
this from happening.

Signed-off-by: Ryan Lee <ryan.lee@canonical.com>
2025-08-28 12:21:09 -07:00
John Johansen
0e755d24bb Merge profiles: add authd socket to unix-chkpwd for authd PAM
Fixes: LP: #2120211

Signed-off-by: Ryan Lee <ryan.lee@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1775
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2025-08-19 02:26:14 +00:00
John Johansen
db74dda3c6 Merge profiles: add /run/snapd.socket rule for curl
This ideally is a temporary fix because we do not want to allow all users
of curl to be able to access the snapd socket. However, this will work for
now until we can mediate the accesses better.

Fixes: LP: #2120669

Signed-off-by: Ryan Lee <ryan.lee@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1774
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2025-08-18 23:54:40 +00:00
Ryan Lee
6f5a4219d7 profiles: add authd socket to unix-chkpwd for authd PAM
Fixes: LP: #2120211

Signed-off-by: Ryan Lee <ryan.lee@canonical.com>
2025-08-18 16:31:35 -07:00
Ryan Lee
0e58e3d7fb profiles: add /run/snapd.socket rule for curl
This ideally is a temporary fix because we do not want to allow all users
of curl to be able to access the snapd socket. However, this will work for
now until we can mediate the accesses better.

Fixes: LP: #2120669

Signed-off-by: Ryan Lee <ryan.lee@canonical.com>
2025-08-18 12:15:40 -07:00
John Johansen
e7daccedc6 Merge regression: disconnected_mount_complain dangling fds and alloc fail handling
Signed-off-by: Ryan Lee <ryan.lee@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1767
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: John Johansen <john@jjmx.net>
2025-08-16 06:46:14 +00:00
John Johansen
59e7fdd96a profiles: curl: switch to user-tmp abstraction
The curl profile allows reading and writing to /tmp/ so instead of
two rules that don't cover all tmp locations, switch to the user-tmp
abstraction to allow access to the various possible tmp locations.

Note: The does reduce the write permission to owner write, instead
of the wider file w /tmp/**,

In addition move the @{HOME} permissions to be restricted files owned
by the user.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2025-08-15 23:43:04 -07:00
Maxime Bélair
468f0096ee Merge aa-notify: Improve support for local profiles
This MR contains fixes and improvements for --local profiles in aa-notify

 - aa-notify: Make --local commandline option override use_local_profiles
 - utils: Move get_local_include to ProfileStorage
 - utils: Add tests for get_local_include
 - aa-notify gui: Fix undefined variable when ttkthemes is not installed

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1770
Approved-by: Maxime Bélair <maxime.belair@canonical.com>
Merged-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-15 11:52:26 +00:00
Maxime Bélair
d993dfbb02 aa-notify gui: Fix undefined variable when ttkthemes is not installed
When ttkthemes is not installed, bg_color is undefined. We don't use it
in that case.

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-15 13:51:32 +02:00
Maxime Bélair
ba336533ac utils: Add tests for get_local_include
Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-15 13:51:32 +02:00
Maxime Bélair
0d34f12d7e utils: Move get_local_include to ProfileStorage
Move get_local_include from aa.py to ProfileStorage, a more logical
location.

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-15 13:51:26 +02:00
John Johansen
ebba635fa9 Merge profiles: Allow curl to read tmp, for scripts which might use config/etags/data...
Some system scripts, namely pollinate, pass temporary files as data.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1769
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2025-08-14 17:24:56 +00:00
Christian Boltz
e477ccacfa Merge abstractions/gtk: allow writing vulcan cache
Reported by darix

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1766
Approved-by: Ryan Lee <rlee287@yahoo.com>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2025-08-14 13:49:33 +00:00
Christian Boltz
9c5064529a Merge abstractions/libnuma: add rules for active usage
The current profile is for linking against libnuma. This
update adds the rules needed to get system information
when actually using libnuma functionality.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1768
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2025-08-14 13:06:22 +00:00
Christian Boltz
862d8ec9fc Merge aa-notify: Add --xauthority to set $XAUTHORITY under sudo
Fixes #449

Tkinter (used by aa-notify) needs the $XAUTHORITY envvar to start but on
some systems (e.g. OpenSuse), sudo clears it. This change add a
--xauthority command-line option to set it explicitly, so aa-notify works
under sudo.

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>

Closes #449
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1771
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2025-08-14 11:36:15 +00:00
Maxime Bélair
fbd266c63f aa-notify: Add --xauthority to set $XAUTHORITY under sudo
Fixes #449

Tkinter (used by aa-notify) needs the $XAUTHORITY envvar to start but on
some systems (e.g. OpenSuse), sudo clears it. This change add a
--xauthority command-line option to set it explicitly, so aa-notify works
under sudo.

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-14 11:06:09 +02:00
Maxime Bélair
fcbf8e34ec aa-notify: Make --local commandline option override use_local_profiles
If both the --local commandline option and use_local_profiles
configuration are specified, the commandline now takes precedence.

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-14 10:01:03 +02:00
Simon Poirier
01ab33202a profiles: Allow curl to read tmp, for scripts which might use config/etags/data...
Signed-off-by: Simon Poirier <simon.poirier@canonical.com>
2025-08-13 21:37:53 -04:00
Christian Ehrhardt
24216d79e9 abstractions/libnuma: add rules for active usage
The current profile is for linking against libnuma. This
update adds the rules needed to get system information
when actually using libnuma functionality.

Signed-off-by: Christian Ehrhardt <christian.ehrhardt@canonical.com>
2025-08-13 10:39:49 +02:00
Ryan Lee
bef673f3c6 regression: disconnected_mount_complain dangling fds and alloc fail handling
Signed-off-by: Ryan Lee <ryan.lee@canonical.com>
2025-08-12 15:00:20 -07:00
Christian Boltz
8210308508 abstractions/gtk: allow writing vulcan cache
Reported by darix
2025-08-12 22:08:16 +02:00
John Johansen
a8875460ed Merge utils: Allow writing to profile includes
Allow writing to local profiles

This notably allows aa-notify to write to local profiles instead of the main profile with the new `--local` option. This keeps the base profile clean, avoiding breakages when the system updates profiles.

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1764
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2025-08-12 08:36:56 +00:00
Maxime Bélair
eae49bf8de test-aa-notify: Update help test
Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-11 18:16:53 +02:00
Maxime Bélair
144d782ae8 aa-notify: Update config with use_local_profiles
aa-notify configuration now supports use_local_profiles, and this option
is documented in the manual.

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-11 18:16:53 +02:00
Maxime Bélair
df1a4c8782 aa-notify: Allow writing to local profiles
The new option --local allows user to write new rules to local profiles
instead of system profiles, enabling cleaner profile deployment.

This option support the values (yes, no and auto)

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-11 18:16:53 +02:00
Maxime Bélair
4c30a0ac65 utils: Allow writing to profile includes
This patch allows writing write in include files and save them to disk.
This is particularly helpful for local includes (generally used in
profiles through `include if exists <local/foo>`), and keeps the base
profile clean, avoiding breakages when the system updates profiles.

Signed-off-by: Maxime Bélair <maxime.belair@canonical.com>
2025-08-11 18:16:22 +02:00
Steve Beattie
60ca491f21 Merge fix more parser leaks
Closes #534
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1763
Approved-by: Steve Beattie <steve+gitlab@nxnw.org>
Merged-by: Steve Beattie <steve+gitlab@nxnw.org>
2025-08-06 19:12:04 -07:00
Georgia Garcia
43fa5f88a7 parser: fix cases leaks when new state creation fails
For every item in "cases", a new state is created, but if the creation
of one of them fails, the rest of the items in that list would not be
deleted and would leak. Fix it by continuing to iterate over the items
in the list and delete them, and then re-throw the exception.

$ /usr/bin/valgrind --leak-check=full --error-exitcode=151 ../apparmor_parser -Q -I simple_tests/ -M ./features_files/features.all simple_tests/xtrans/x-conflict.sd

==564911== 208 (48 direct, 160 indirect) bytes in 1 blocks are definitely lost in loss record 15 of 19
==564911==    at 0x4846FA3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==564911==    by 0x179E74: CharNode::follow(Cases&) (expr-tree.h:447)
==564911==    by 0x189F8B: DFA::update_state_transitions(optflags const&, State*) (hfa.cc:376)
==564911==    by 0x18A25B: DFA::process_work_queue(char const*, optflags const&) (hfa.cc:442)
==564911==    by 0x18CB65: DFA::DFA(Node*, optflags const&, bool) (hfa.cc:486)
==564911==    by 0x178263: aare_rules::create_chfa(int*, std::vector<aa_perms, std::allocator<aa_perms> >&, optflags const&, bool, bool) (aare_rules.cc:258)
==564911==    by 0x178A4F: aare_rules::create_dfablob(unsigned long*, int*, std::vector<aa_perms, std::allocator<aa_perms> >&, optflags const&, bool, bool) (aare_rules.cc:359)
==564911==    by 0x14E4E1: process_profile_regex(Profile*) (parser_regex.c:791)
==564911==    by 0x154CDF: process_profile_rules(Profile*) (parser_policy.c:194)
==564911==    by 0x154E0F: post_process_profile(Profile*, int) (parser_policy.c:240)
==564911==    by 0x154F7A: post_process_policy_list (parser_policy.c:257)
==564911==    by 0x154F7A: post_process_policy(int) (parser_policy.c:267)
==564911==    by 0x141B17: process_profile(int, aa_kernel_interface*, char const*, aa_policy_cache*) (parser_main.c:1227)

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/534
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-08-06 19:15:37 -03:00
Georgia Garcia
bb03d9ee08 parser: fix leak on conflicting x modifiers
When the "conflicting x modifiers" exception was thrown, the DFA
object creation would fail, therefore the destructor would not be
called and the states previously allocated would leak.

Unfortunately there's no way to call the destructor if the object was
not created, so I moved the contents of the destructor into a cleanup
helper function to be called in both instances.

$ /usr/bin/valgrind --leak-check=full --error-exitcode=151 ../apparmor_parser -Q -I simple_tests/ -M ./features_files/features.all simple_tests/xtrans/x-conflict.sd

==564911== 592 (112 direct, 480 indirect) bytes in 1 blocks are definitely lost in loss record 16 of 19
==564911==    at 0x4846FA3: operator new(unsigned long) (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==564911==    by 0x189C9A: DFA::add_new_state(optflags const&, std::set<ImportantNode*, std::less<ImportantNode*>, std::allocator<ImportantNode*> >*, std::set<ImportantNode*, std::less<ImportantNode*>, std::allocator<ImportantNode*> >*, State*) (hfa.cc:337)
==564911==    by 0x18CB22: add_new_state (hfa.cc:357)
==564911==    by 0x18CB22: DFA::DFA(Node*, optflags const&, bool) (hfa.cc:473)
==564911==    by 0x178263: aare_rules::create_chfa(int*, std::vector<aa_perms, std::allocator<aa_perms> >&, optflags const&, bool, bool) (aare_rules.cc:258)
==564911==    by 0x178A4F: aare_rules::create_dfablob(unsigned long*, int*, std::vector<aa_perms, std::allocator<aa_perms> >&, optflags const&, bool, bool) (aare_rules.cc:359)
==564911==    by 0x14E4E1: process_profile_regex(Profile*) (parser_regex.c:791)
==564911==    by 0x154CDF: process_profile_rules(Profile*) (parser_policy.c:194)
==564911==    by 0x154E0F: post_process_profile(Profile*, int) (parser_policy.c:240)
==564911==    by 0x154F7A: post_process_policy_list (parser_policy.c:257)
==564911==    by 0x154F7A: post_process_policy(int) (parser_policy.c:267)
==564911==    by 0x141B17: process_profile(int, aa_kernel_interface*, char const*, aa_policy_cache*) (parser_main.c:1227)
==564911==    by 0x135421: main (parser_main.c:1771)

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/534
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-08-06 19:15:37 -03:00
Georgia Garcia
d9866f0a24 parser: fix leaking disconnected paths when merging
Valgrind showed that the disconnected paths variables were leaking
during the merge. That happened because flagvals did not implement a
destructor freeing the variables, so they leaked. flagvals cannot
implement a destructor, because that would make it a non-trivial union
member and parser_yacc.y would not compile. This patch implements a
"clear" function that is supposed to act as the destructor.

$ /usr/bin/valgrind --leak-check=full --error-exitcode=151 ../apparmor_parser -Q -I simple_tests/ -M ./features_files/features.all flags_ok_disconnected_ipc15.sd
...
==3708747== 5 bytes in 1 blocks are definitely lost in loss record 1 of 11
==3708747==    at 0x4846828: malloc (in /usr/libexec/valgrind/vgpreload_memcheck-amd64-linux.so)
==3708747==    by 0x492E35E: strdup (strdup.c:42)
==3708747==    by 0x14C74E: set_disconnected_path (profile.h:188)
==3708747==    by 0x14C74E: flagvals::init(char const*) (profile.h:223)
==3708747==    by 0x14859B: yyparse() (parser_yacc.y:592)
==3708747==    by 0x141A99: process_profile(int, aa_kernel_interface*, char const*, aa_policy_cache*) (parser_main.c:1187)
==3708747==    by 0x135421: main (parser_main.c:1771)
...

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-08-06 19:15:37 -03:00
John Johansen
fedcab2ad0 Merge nss-systemd: Grant access to the GDM user database
GDM 49~beta implements a userdb VarLink service for managing the unix users
running the greeter shell, as well as the gnome-initial-setup users.

```
gdm-launch-environment][1892]: Gdm: GdmSessionWorker: determining if authenticated user (password required:0) is authorized to session
unix_chkpwd[1897]: could not obtain user info (gdm-greeter)
kernel: audit: type=1400 audit(1754399331.488:211): apparmor="DENIED" operation="connect" class="file" profile="unix-chkpwd" name="/run/systemd/userdb/org.gnome.DisplayManager" pid=1897 comm="unix_chkpwd" requested_mask="wr" denied_mask="wr" fsuid=0 ouid=0
gdm-launch-environment][1892]: Gdm: GdmSessionWorker: user is not authorized to log in: Authentication failure
```

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1761
Approved-by: Ryan Lee <rlee287@yahoo.com>
Merged-by: John Johansen <john@jjmx.net>
2025-08-06 06:02:14 +00:00
Alessandro Astone
b6caed3b57 nss-systemd: Grant access to the GDM user database
GDM 49~beta implements a userdb VarLink service for managing the unix users
running the greeter shell, as well as the gnome-initial-setup users.

gdm-launch-environment][1892]: Gdm: GdmSessionWorker: determining if authenticated user (password required:0) is authorized to session
unix_chkpwd[1897]: could not obtain user info (gdm-greeter)
kernel: audit: type=1400 audit(1754399331.488:211): apparmor="DENIED" operation="connect" class="file" profile="unix-chkpwd" name="/run/systemd/userdb/org.gnome.DisplayManager" pid=1897 comm="unix_chkpwd" requested_mask="wr" denied_mask="wr" fsuid=0 ouid=0
gdm-launch-environment][1892]: Gdm: GdmSessionWorker: user is not authorized to log in: Authentication failure

LP: #2119541
2025-08-05 15:51:25 +02:00
John Johansen
ae70dc38f8 Merge parser: drop dead code in mount.cc
perms = 0, therefore perms & something is always false.

Fixes: coverity#320937 and coverity#320937

Also remove nop code from mnt_rule::post_parse_profile(Profile &prof) as discussed in this MR.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1759
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2025-08-05 08:31:10 +00:00
Steve Beattie
51bdbec119 Merge parser misc fixes (memory leaks, restoring ostream format)
Closes #533
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1760
Approved-by: Steve Beattie <steve+gitlab@nxnw.org>
Merged-by: Steve Beattie <steve+gitlab@nxnw.org>
2025-08-04 22:34:01 -07:00
Georgia Garcia
b8dee97ed3 parser: fix leaking name in variable expansion
Fixes: https://gitlab.com/apparmor/apparmor/-/issues/533
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-08-04 18:55:58 -03:00
Georgia Garcia
8b2e2c3358 parser: free leaking cod_entry in case of failure in do_alias
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-08-04 18:55:58 -03:00
Georgia Garcia
3faddfcf46 parser: fix coverity's "not restoring ostream format"
Save the ostream flags and restore them after the std::hex
modification.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-08-04 18:55:58 -03:00
Georgia Garcia
05458768cf parser: constify and pass by reference unchanged value
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-08-04 18:55:58 -03:00
Georgia Garcia
cb0d66d55a parser: fix leaks in deleted variables
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-08-04 18:55:58 -03:00
Christian Boltz
0de9678d4f mount.cc: remove nop code from mnt_rule::post_parse_profile(Profile &prof)
... as discussed in https://gitlab.com/apparmor/apparmor/-/merge_requests/1759#note_2665952086
2025-08-04 19:35:26 +02:00
Christian Boltz
617d3021e8 parser: drop dead code in mount.cc
perms = 0, therefore perms & something is always false.

Fixes: coverity#320937 and coverity#320937
2025-08-04 00:08:26 +02:00
Christian Boltz
63b46dd3d7 Merge utils: fix typo in aa-show-usage man page
Signed-off-by: Ryan Lee <ryan.lee@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1757
Approved-by: Christian Boltz <apparmor@cboltz.de>
Merged-by: Christian Boltz <apparmor@cboltz.de>
2025-08-01 19:56:38 +00:00
Ryan Lee
67382dcf15 utils: fix typo in aa-show-usage man page
Signed-off-by: Ryan Lee <ryan.lee@canonical.com>
2025-08-01 12:20:18 -07:00
Steve Beattie
d61295a249 Merge parser: fix variable expansion
When the variable was being expanded, it needed to be reevaluated to
check if there was still unresolved variables. That allowed for a
weird bug to happen: If the string contained a variable preceded by @,
like in "user@@{uid}" and the variable was resolved to a case where {
is used, like in @{uid}={[0-9],[1-9][0-9]}, then on the second pass,
the parser would try to resolve the following variable
@{[0-9],[1-9][0-9]}, which is incorrect behavior. Fix it by not
including part of the string that was already resolved on the
subsequent passes.

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

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1756
Approved-by: Steve Beattie <steve+gitlab@nxnw.org>
Merged-by: Steve Beattie <steve+gitlab@nxnw.org>
2025-07-31 22:55:32 -07:00
Georgia Garcia
a2f2ca6119 parser: fix variable expansion
When the variable was being expanded, it needed to be reevaluated to
check if there was still unresolved variables. That allowed for a
weird bug to happen: If the string contained a variable preceded by @,
like in "user@@{uid}" and the variable was resolved to a case where {
is used, like in @{uid}={[0-9],[1-9][0-9]}, then on the second pass,
the parser would try to resolve the following variable
@{[0-9],[1-9][0-9]}, which is incorrect behavior. Fix it by not
including part of the string that was already resolved on the
subsequent passes.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2025-07-31 18:04:16 -03:00
John Johansen
61e09c6ffa Merge Fix whitespace in hfa.h
This got broken in 0f36070a54 / https://gitlab.com/apparmor/apparmor/-/merge_requests/1750

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1755
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2025-07-31 19:48:00 +00:00
Christian Boltz
45a7cc1ed0 Fix whitespace in hfa.h
This got broken in 0f36070a54 / https://gitlab.com/apparmor/apparmor/-/merge_requests/1750
2025-07-31 21:28:03 +02:00
28 changed files with 370 additions and 93 deletions

View File

@@ -334,7 +334,16 @@ State *DFA::add_new_state(optflags const &opts, NodeSet *anodes,
ProtoState proto;
proto.init(nnodev, anodev);
State *state = new State(opts, node_map.size(), proto, other, filedfa);
State *state;
try {
state = new State(opts, node_map.size(), proto, other, filedfa);
} catch(int error) {
/* this function is called in the DFA object creation,
* and the exception prevents the destructor from
* being called, so call the helper here */
cleanup();
throw error;
}
pair<NodeMap::iterator,bool> x = node_map.insert(proto, state);
if (x.second == false) {
delete state;
@@ -392,7 +401,17 @@ void DFA::update_state_transitions(optflags const &opts, State *state)
*/
for (Cases::iterator j = cases.begin(); j != cases.end(); j++) {
State *target;
target = add_new_state(opts, j->second, nonmatching);
try {
target = add_new_state(opts, j->second, nonmatching);
} catch (int error) {
/* when add_new_state fails, there could still
* be NodeSets in the rest of cases, so clean
* them up before re-throwing the exception */
for (Cases::iterator k = ++j; k != cases.end(); k++) {
delete k->second;
}
throw error;
}
/* Don't insert transition that the otherwise transition
* already covers
@@ -522,11 +541,7 @@ DFA::DFA(Node *root, optflags const &opts, bool buildfiledfa): root(root), filed
DFA::~DFA()
{
anodes_cache.clear();
nnodes_cache.clear();
for (Partition::iterator i = states.begin(); i != states.end(); i++)
delete *i;
cleanup();
}
State *DFA::match_len(State *state, const char *str, size_t len)

View File

@@ -368,6 +368,15 @@ class DFA {
NodeMap node_map;
std::list<State *> work_queue;
void cleanup(void) {
anodes_cache.clear();
nnodes_cache.clear();
for (Partition::iterator i = states.begin(); i != states.end(); i++) {
delete *i;
}
states.clear();
}
public:
DFA(Node *root, optflags const &flags, bool filedfa);
virtual ~DFA();
@@ -397,7 +406,7 @@ public:
void compute_perms_table_ent(State *state, size_t pos,
std::vector <aa_perms> &perms_table);
void compute_perms_table(std::vector <aa_perms> &perms_table);
void compute_perms_table(std::vector <aa_perms> &perms_table);
unsigned int diffcount;
int oob_range;

View File

@@ -570,6 +570,8 @@ ostream &mnt_rule::dump(ostream &os)
{
prefix_rule_t::dump(os);
std::ios::fmtflags fmt(os.flags());
if (perms & AA_MAY_MOUNT)
os << "mount";
else if (perms & AA_MAY_UMOUNT)
@@ -603,6 +605,7 @@ ostream &mnt_rule::dump(ostream &os)
os << " " << "(0x" << hex << perms << "/0x" << (audit != AUDIT_UNSPECIFIED ? perms : 0) << ")";
os << ",\n";
os.flags(fmt);
return os;
}
@@ -1163,21 +1166,7 @@ fail:
void mnt_rule::post_parse_profile(Profile &prof)
{
if (trans) {
perm32_t perms = 0;
int n = add_entry_to_x_table(&prof, trans);
if (!n) {
PERROR("Profile %s has too many specified profile transitions.\n", prof.name);
exit(1);
}
if (perms & AA_USER_EXEC)
perms |= SHIFT_PERMS(n << 10, AA_USER_SHIFT);
if (perms & AA_OTHER_EXEC)
perms |= SHIFT_PERMS(n << 10, AA_OTHER_SHIFT);
perms = ((perms & ~AA_ALL_EXEC_MODIFIERS) |
(perms & AA_ALL_EXEC_MODIFIERS));
trans = NULL;
/* TODO: pivot_root profile transition */
}
}

View File

@@ -133,8 +133,10 @@ static void process_entries(const void *nodep, VISIT value, int level unused)
if (entry->link_name &&
strncmp((*t)->from, entry->link_name, len) == 0) {
char *n = do_alias(*t, entry->link_name);
if (!n)
if (!n) {
free_cod_entries(dup);
return;
}
if (!dup)
dup = copy_cod_entry(entry);
free(dup->link_name);

View File

@@ -188,24 +188,21 @@ cleanup:
if (prof->attachment) {
tmp = symtab::delete_var(PROFILE_EXEC_VAR);
delete tmp;
if (saved_exec_path) {
if (saved_exec_path)
symtab::add_var(*saved_exec_path);
delete saved_exec_path;
}
}
cleanup_attach:
if (prof->attachment) {
tmp = symtab::delete_var(PROFILE_ATTACH_VAR);
delete tmp;
if (saved_attach_path) {
if (saved_attach_path)
symtab::add_var(*saved_attach_path);
delete saved_attach_path;
}
}
cleanup_name:
tmp = symtab::delete_var(PROFILE_NAME_VARIABLE);
delete tmp;
delete saved_exec_path;
delete saved_attach_path;
out:
return error;
}

View File

@@ -577,6 +577,7 @@ flags: opt_flags TOK_OPENPAREN flagvals TOK_CLOSEPAREN
flagvals: flagvals flagval
{
$1.merge($2);
$2.clear();
$$ = $1;
};

View File

@@ -78,6 +78,7 @@ void ProfileList::dump_profile_names(bool children)
Profile::~Profile()
{
hat_table.clear();
flags.clear();
free_cod_entries(entries);
free_cond_entry_list(xattrs);
@@ -97,10 +98,6 @@ Profile::~Profile()
free(name);
if (attachment)
free(attachment);
if (flags.disconnected_path)
free(flags.disconnected_path);
if (flags.disconnected_ipc)
free(flags.disconnected_ipc);
if (ns)
free(ns);
for (int i = (AA_EXEC_LOCAL >> 10) + 1; i < AA_EXEC_COUNT; i++)

View File

@@ -175,6 +175,12 @@ public:
signal = 0;
error = 0;
}
void clear(void) {
free(disconnected_path);
free(disconnected_ipc);
}
void init(const char *str)
{
init();
@@ -301,7 +307,7 @@ public:
}
// same ignore rhs.disconnect_path
} else {
disconnected_path = rhs.disconnected_path;
disconnected_path = strdup(rhs.disconnected_path);
}
}
if (rhs.disconnected_ipc) {
@@ -311,7 +317,7 @@ public:
}
// same so do nothing
} else {
disconnected_ipc = rhs.disconnected_ipc;
disconnected_ipc = strdup(rhs.disconnected_ipc);
}
}
if (rhs.signal) {

View File

@@ -431,11 +431,14 @@ public:
ostream &dump(ostream &os) override {
class_rule_t::dump(os);
std::ios::fmtflags fmt(os.flags());
if (saved)
os << "(0x" << std::hex << perms << "/orig " << saved << ") ";
else
os << "(0x" << std::hex << perms << ") ";
os.flags(fmt);
return os;
}
@@ -460,7 +463,11 @@ public:
ostream &dump(ostream &os) override {
class_rule_t::dump(os);
std::ios::fmtflags fmt(os.flags());
os << "(0x" << std::hex << perms << ") ";
os.flags(fmt);
return os;
}

View File

@@ -0,0 +1,7 @@
#=DESCRIPTION expansion of alternation after extra, unescaped @
#=EXRESULT PASS
@{uid} = {[0-9],[1-9][0-9]}
/usr/bin/foo {
/sys/fs/cgroup/user.slice/user-@{uid}.slice/user@@{uid}.service/cpu.max r,
}

View File

@@ -189,11 +189,23 @@ static void trim_trailing_slash(std::string& str)
str.clear(); // str is all '/'
}
int copy_value_to_name(const std::string& value, char **name)
{
free(*name);
*name = strdup(value.c_str());
if (!*name) {
errno = ENOMEM;
return -1;
}
return 0;
}
int variable::expand_by_alternation(char **name)
{
std::string expanded_name = "";
bool filter_leading_slash = false;
bool filter_trailing_slash = false;
int ret = 0;
if (!name) {
PERROR("ASSERT: name to be expanded cannot be NULL\n");
@@ -226,8 +238,6 @@ int variable::expand_by_alternation(char **name)
return 1;
}
free(*name);
size_t setsize = ref->expanded.size();
auto i = ref->expanded.begin();
@@ -252,15 +262,19 @@ int variable::expand_by_alternation(char **name)
if (setsize > 1) {
expanded_name += "}";
}
expanded_name = prefix + expanded_name + suffix;
*name = strdup(expanded_name.c_str());
if (!*name) {
errno = ENOMEM;
return -1;
}
/* don't include prefix */
expanded_name = expanded_name + suffix;
ret = copy_value_to_name(expanded_name, name);
if (ret)
return ret;
/* recursive until no variables are found in *name */
return expand_by_alternation(name);
ret = expand_by_alternation(name);
if (ret == 0) {
/* return prefix to name */
expanded_name = prefix + std::string(*name);
ret = copy_value_to_name(expanded_name, name);
}
return ret;
}
int variable::expand_variable()
@@ -293,6 +307,7 @@ int variable::expand_variable()
}
name = variable::process_var(var.c_str());
variable *ref = symtab::lookup_existing_symbol(name);
free(name);
if (!ref) {
PERROR("Failed to find declaration for: %s\n", var.c_str());
rc = 1;
@@ -322,7 +337,6 @@ int variable::expand_variable()
}
out:
free(name);
expanding = false;
return rc;
}

View File

@@ -35,6 +35,10 @@
owner @{HOME}/.gtkrc r,
owner @{HOME}/.gtkrc-2.0 r,
owner @{HOME}/.gtk-bookmarks r,
owner @{HOME}/.cache/gtk-4.0/ rw,
owner @{HOME}/.cache/gtk-4.0/vulkan-pipeline-cache/{,*} rw,
owner @{HOME}/.config/gtkrc r,
owner @{HOME}/.config/gtkrc-2.0 r,
owner @{HOME}/.config/gtk-{3,4}.0/ rw,

View File

@@ -11,12 +11,20 @@
abi <abi/4.0>,
# this abstract profile can be included by applications that are
# dynamically linked to libnuma
# This abstract profile can be included by applications that are
# dynamically linked to libnuma.
# libnuma defines the function num_init() as the .init function
# to be called by the runtime linker (ld) when libnuma is loaded
# even if not any active usage of libnuma takes place
@{sys}/devices/system/cpu/node/ r,
# Actually using libnuma functionality will need a few more
# sysfs entries to gather information about the system
@{sys}/devices/system/cpu/ r,
@{sys}/devices/system/node/node[0-9]*/meminfo r,
@{sys}/devices/system/node/*/cpumap r,
# Include additions to the abstraction
include if exists <abstractions/libnuma.d>

View File

@@ -25,6 +25,7 @@
@{run}/systemd/userdb/io.systemd.Home rw, # systemd-home dirs
@{run}/systemd/userdb/io.systemd.NameServiceSwitch rw, # UNIX/glibc NSS
@{run}/systemd/userdb/io.systemd.Machine rw, # systemd-machined
@{run}/systemd/userdb/org.gnome.DisplayManager rw, # GDM implements a user database for its greeter
@{PROC}/sys/kernel/random/boot_id r,

View File

@@ -17,19 +17,19 @@ profile curl /usr/bin/curl {
include <abstractions/private-files-strict>
include <abstractions/ssl_certs>
#can read/write data and configs from tmp
include <abstractions/user-tmp>
@{exec_path} mr,
# allow reading configuration files from $HOME
priority=1 file r @{HOME}/.curlrc,
priority=1 file r @{HOME}/.config/curlrc,
priority=1 owner file r @{HOME}/.curlrc,
priority=1 owner file r @{HOME}/.config/curlrc,
# allow reading other configuration files/certs from $HOME
# (see --config, --cacert options)
file r @{HOME}/**,
# allow writing output to $HOME, /tmp (see -o option)
file w @{HOME}/**,
file w /tmp/**,
owner file rw @{HOME}/**,
# allows UDP (for DNS), TCP (for http, https, etc), abstract Unix sockets, IPv4, IPv6
network unix stream,
@@ -39,6 +39,10 @@ profile curl /usr/bin/curl {
network inet6 stream,
network inet6 dgram,
# Allow access to the snap socket until we can revisit it with delegation
# or profile refactoring
file rw @{run}/snapd.socket,
# Site-specific additions and overrides. See local/README for details.
include if exists <local/curl>
}

View File

@@ -30,6 +30,9 @@ profile unix-chkpwd /{,usr/}{,s}bin/unix_chkpwd {
/run/host/userdb/*.user r,
/run/host/userdb/*.user-privileged r,
# authd socket for PAM
@{run}/authd.sock rw,
# file_inherit
owner /dev/tty[0-9]* rw,

View File

@@ -57,6 +57,26 @@ int fork_and_execvat(int exec_fd, char* const* argv) {
}
}
int set_up_mount_separation() {
if (unshare(CLONE_NEWNS) == -1) {
perror("FAIL: could not unshare mount namespace");
return -1;
}
/*
* Try to remount / as MS_PRIVATE | MS_REC so that our mounts don't
* last outside of this particular process. In particular, the open_tree
* test's move_mount doesn't persist in 6.14 but persists in 6.15.
* This behavior change was brought about by the upstream commit
* 21107723831e (fs: mount detached mounts onto detached mounts) and its
* semantic change to the behavior of fs/namespace.c:dissolve_on_fput.
*/
if (mount(NULL, "/", NULL, MS_PRIVATE | MS_REC, NULL) == -1) {
perror("FAIL: could not make / mount rprivate");
return -1;
}
return 0;
}
int test_with_old_style_mount() {
// Set up directory fds for future reference
DEBUG_PRINTF("Opening fds for shadowed and shadowing directories\n");
@@ -85,6 +105,7 @@ int test_with_old_style_mount() {
perror("FAIL: could not open executable file in shadowed dir");
close(shadowed_file_fd);
close(shadowing_dirfd);
close(shadowed_dirfd);
return 1;
}
@@ -99,8 +120,7 @@ int test_with_old_style_mount() {
DEBUG_PRINTF("Unshare mount ns and bind mount over shadowed dir\n");
// Call unshare() to step into a new mount namespace
if (unshare(CLONE_NEWNS) == -1) {
perror("FAIL: could not unshare mount namespace");
if (set_up_mount_separation() == -1) {
rc |= 1;
goto cleanup_fds;
}
@@ -137,6 +157,11 @@ int test_with_old_style_mount() {
DEBUG_PRINTF("Read from disconnected file\n");
char *file_contents_buf = calloc(shadowed_file_size+1, sizeof(char));
if (file_contents_buf == NULL) {
perror("FAIL: could not allocate memory to read file");
rc |= 1;
goto cleanup_mount;
}
if (read(shadowed_file_fd, file_contents_buf, shadowed_file_size) == -1) {
perror("FAIL: could not read from file after mount");
rc |= 1;
@@ -164,6 +189,7 @@ int test_with_old_style_mount() {
rc |= 1;
}
cleanup_fds:
close(shadowed_exec_fd);
close(shadowed_file_fd);
close(shadowing_dirfd);
close(shadowed_exec_fd);
@@ -178,8 +204,7 @@ int test_with_old_style_mount() {
int test_with_open_tree_mount() {
DEBUG_PRINTF("Unshare mount ns\n");
// Call unshare() to step into a new mount namespace
if (unshare(CLONE_NEWNS) == -1) {
perror("FAIL: could not unshare mount namespace");
if (set_up_mount_separation() == -1) {
return 1;
}
DEBUG_PRINTF("bind mount shadowed using new mount API\n");
@@ -268,8 +293,7 @@ int test_with_open_tree_mount() {
int test_with_fsmount(const char *source) {
DEBUG_PRINTF("Unshare mount ns\n");
// Call unshare() to step into a new mount namespace
if (unshare(CLONE_NEWNS) == -1) {
perror("FAIL: could not unshare mount namespace");
if (set_up_mount_separation() == -1) {
return 1;
}

View File

@@ -612,7 +612,8 @@ def add_to_profile(rule, profile_name):
return
update_profile_path = update_profile.__file__
command = ['pkexec', '--keep-cwd', update_profile_path, 'add_rule', rule, profile_name]
command = ['pkexec', '--keep-cwd', update_profile_path, 'add_rule', args.local, rule, profile_name]
try:
subprocess.run(command, check=True)
except subprocess.CalledProcessError as e:
@@ -647,7 +648,7 @@ def allow_rules(clean_rules, allow_all=False):
with open(tmp.name, mode='w') as f:
for profile_name, profile_rules in clean_rules.items():
written += f.write(profile_rules.get_writable_rules(template_path))
written += f.write(profile_rules.get_writable_rules(template_path, args.local))
if written > 0:
create_from_file(tmp.name)
@@ -841,6 +842,7 @@ def main():
parser = argparse.ArgumentParser(description=_('Display AppArmor notifications or messages for DENIED entries.'))
parser.add_argument('-p', '--poll', action='store_true', help=_('poll AppArmor logs and display notifications'))
parser.add_argument('--display', type=str, help=_('set the DISPLAY environment variable (might be needed if sudo resets $DISPLAY)'))
parser.add_argument('--xauthority', type=str, help=_('set the XAUTHORITY environment variable (might be needed if sudo resets XAUTHORITY)'))
parser.add_argument('-f', '--file', type=str, help=_('search FILE for AppArmor messages'))
parser.add_argument('-l', '--since-last', action='store_true', help=_('display stats since last login'))
parser.add_argument('-s', '--since-days', type=int, metavar=('NUM'), help=_('show stats for last NUM days (can be used alone or with -p)'))
@@ -849,6 +851,7 @@ def main():
parser.add_argument('-w', '--wait', type=int, metavar=('NUM'), help=_('wait NUM seconds before displaying notifications (with -p)'))
parser.add_argument('-m', '--merge-notifications', action='store_true', help=_('Merge notification for improved readability (with -p)'))
parser.add_argument('-F', '--foreground', action='store_true', help=_('Do not fork to the background'))
parser.add_argument('-L', '--local', nargs='?', const='yes', choices=['yes', 'no', 'auto'], help=_('Add to local profile'))
parser.add_argument('--prompt-filter', type=str, metavar=('PF'), help=_('kind of operations which display a popup prompt'))
parser.add_argument('--debug', action='store_true', help=_('debug mode'))
parser.add_argument('--configdir', type=str, help=argparse.SUPPRESS)
@@ -938,6 +941,7 @@ def main():
- prompt_filter
- maximum_number_notification_profiles
- keys_to_aggregate
- use_local_profiles
- filter.profile,
- filter.operation,
- filter.name,
@@ -960,6 +964,7 @@ def main():
'message_footer',
'maximum_number_notification_profiles',
'keys_to_aggregate',
'use_local_profiles',
'filter.profile',
'filter.operation',
'filter.name',
@@ -1069,6 +1074,19 @@ def main():
else:
keys_to_aggregate = {'operation', 'class', 'name', 'denied', 'target'}
if not args.local:
if 'use_local_profiles' in config['']:
if config['']['use_local_profiles'] in {'auto', 'yes', 'no'}:
args.local = config['']['use_local_profiles']
elif config['']['use_local_profiles'] is None:
args.local = 'yes'
else:
sys.exit(_('ERROR: using an invalid value for use_local_profiles in config {}\nSupported values: {}').format(
config['']['use_local_profiles'], ', '.join({'yes', 'auto', 'no'})
))
else:
args.local = 'auto'
if args.file:
logfile = args.file
elif os.path.isfile('/var/run/auditd.pid') and os.path.isfile('/var/log/audit/audit.log'):
@@ -1086,6 +1104,8 @@ def main():
if args.display:
os.environ['DISPLAY'] = args.display
if args.xauthority:
os.environ['XAUTHORITY'] = args.xauthority
if args.poll:
# Exit immediately if show_notifications is no or any of the options below

View File

@@ -71,6 +71,14 @@ This has no effect when running under sudo.
wait NUM seconds before displaying notifications (for use with -p)
=item -L, --local [{yes,no,auto}]
add rules to a local profiles instead of the real profiles.
This simplify profiles' deployment by keeping local modifications self-contained.
- B<yes>: always use a local profile
- B<no>: never use a local profile
- B<auto>: use a local profile if the main profile already relies on a local profile
=item -v, --verbose
show messages with summaries.
@@ -98,6 +106,9 @@ System-wide configuration for B<aa-notify> is done via
# Binaries for which we ignore userns-related capability denials
ignore_denied_capability="sudo,su"
# Write change to local profiles if enabled to preserve regular profiles and simplify upgrades (yes, no, auto)
use_local_profiles="yes"
# OPTIONAL - kind of operations which display a popup prompt.
prompt_filter="userns"

View File

@@ -72,7 +72,7 @@ Filter by profile name
=item --filter.profile_attach PROFILE_ATTACH
Filter by profile attachement (i.e. by path of the executable to which this profile applies)
Filter by profile attachment (i.e. by path of the executable to which this profile applies)
=item --filter.profile_path PROFILE_PATH

View File

@@ -1703,6 +1703,71 @@ def read_profile(file, is_active_profile, read_error_fatal=False):
extra_profiles.add_profile(filename, profile, attachment, profile_data[profile])
# TODO: Split profiles' creating and saving.
def create_local_profile_if_needed(profile_name):
base_profile = profile_name
while True:
parent = active_profiles[base_profile].data.get('parent')
if parent == '':
break
base_profile = parent
local_include = active_profiles[profile_name].get_local_include()
# Not found: we add a mention of the local profile in the main profile
if not local_include:
local_include = "local/" + profile_name.replace('/', '.')
active_profiles[profile_name]['inc_ie'].add(IncludeRule(local_include, True, True))
write_profile_ui_feedback(base_profile)
inc_file = profile_dir + '/' + local_include
# Create the include if needed
if not include.get(inc_file, {}).get(inc_file, False):
include[inc_file] = dict()
include[inc_file][inc_file] = ProfileStorage(inc_file, inc_file, "create_local_profile_if_needed")
return inc_file
def serialize_include(prof_storage, include_metadata=True):
lines = []
if include_metadata:
lines.append('# Last Modified: %s' % time.asctime())
if prof_storage.get('initial_comment'):
lines.append(prof_storage['initial_comment'].rstrip())
lines.extend(prof_storage.get_rules_clean(0))
return '\n'.join(lines) + '\n'
def write_include_ui_feedback(include_data, incfile, out_dir=None, include_metadata=True):
aaui.UI_Info(_('Writing updated include file %s') % incfile)
write_include(include_data, incfile, out_dir, include_metadata)
def write_include(include_data, incfile, out_dir=None, include_metadata=True):
target_file = incfile if incfile.startswith('/') else os.path.join(profile_dir, incfile)
if out_dir:
target_file = os.path.join(out_dir, os.path.basename(target_file))
include_string = serialize_include(include_data, include_metadata=include_metadata)
with NamedTemporaryFile('w', suffix='~', delete=False, dir=profile_dir + "/local") as tmp:
if os.path.exists(target_file):
shutil.copymode(target_file, tmp.name)
else:
pass # 0o600 (NamedTemporaryFile default)
tmp.write(include_string)
try:
shutil.move(tmp.name, target_file)
except PermissionError:
aaui.UI_Important(_('WARNING: Can\'t write to %s. Please run this script with elevated privileges') % target_file)
def attach_profile_data(profiles, profile_data):
profile_data = merged_to_split(profile_data)
# Make deep copy of data to avoid changes to

View File

@@ -100,12 +100,12 @@ class ProfileRules:
for raw_rule in raw_rules:
self.rules.append(SelectableRule(raw_rule, self.selectable))
def get_writable_rules(self, template_path, allow_all=False):
def get_writable_rules(self, template_path, local='yes', allow_all=False):
out = ''
for rule in self.rules:
if allow_all or rule.selected.get():
if not self.is_userns_profile:
out += 'add_rule\t{}\t{}\n'.format(rule.rule, self.profile_name)
out += 'add_rule\t{}\t{}\t{}\n'.format(local, rule.rule, self.profile_name)
else:
out += 'create_userns\t{}\t{}\t{}\t{}\t{}\n'.format(template_path, self.profile_name, self.bin_path, self.profile_path, 'allow')
return out
@@ -158,17 +158,18 @@ class ShowMoreGUIAggregated(GUI):
self.text_display = tk.Text(self.label_frame, wrap='word', height=40, width=100, yscrollcommand=self.scrollbar.set)
kwargs = {
"height": self.text_display.winfo_reqheight() - 4, # The border are *inside* the canvas but *outside* the textbox. I need to remove 4px (2*the size of the borders) to get the same size
"width": self.text_display.winfo_reqwidth() - 4,
"borderwidth": self.text_display['borderwidth'],
"relief": self.text_display['relief'],
"yscrollcommand": self.scrollbar.set,
}
if ttkthemes:
self.text_display.configure(background=self.bg_color, foreground=self.fg_color)
self.canvas = tk.Canvas(
self.label_frame,
background=self.bg_color,
height=self.text_display.winfo_reqheight() - 4, # The border are *inside* the canvas but *outside* the textbox. I need to remove 4px (2*the size of the borders) to get the same size
width=self.text_display.winfo_reqwidth() - 4,
borderwidth=self.text_display['borderwidth'],
relief=self.text_display['relief'],
yscrollcommand=self.scrollbar.set
)
kwargs['background'] = self.bg_color
self.canvas = tk.Canvas(self.label_frame, **kwargs)
self.inner_frame = ttk.Frame(self.canvas)
self.canvas.create_window((2, 2), window=self.inner_frame, anchor='nw')

View File

@@ -199,6 +199,21 @@ class ProfileStorage:
return data
def get_local_include(self):
inc = None
preferred_inc = self.data['name']
if preferred_inc.startswith('/'):
preferred_inc = preferred_inc[1:]
preferred_inc = 'local/' + preferred_inc.replace('/', '.')
# If a local profile already exists, we use it.
for rule in self.data['inc_ie'].rules:
if rule.path.startswith("local/"):
inc = rule.path
if rule.path == preferred_inc: # Prefer includes that matches the profile name.
break
return inc
@classmethod
def parse(cls, line, file, lineno, profile, hat):
"""parse a profile start line (using parse_profile_startline()) and convert it to an instance of this class"""

View File

@@ -8,8 +8,19 @@ from apparmor import aa
from apparmor.logparser import ReadLog
from apparmor.translations import init_translation
_ = init_translation()
is_aa_inited = False
def init_if_needed():
global is_aa_inited
if not is_aa_inited:
aa.init_aa()
aa.read_profiles()
is_aa_inited = True
def create_userns(template_path, name, bin_path, profile_path, decision):
with open(template_path, 'r') as f:
@@ -27,27 +38,48 @@ def create_userns(template_path, name, bin_path, profile_path, decision):
exit(_('Cannot reload updated profile'))
def add_to_profile(rule, profile_name):
aa.init_aa()
aa.update_profiles()
rule_type, rule_class = ReadLog('', '', '').get_rule_type(rule)
rule_obj = rule_class.create_instance(rule)
if not aa.active_profiles.profile_exists(profile_name):
exit(_('Cannot find {} in profiles').format(profile_name))
aa.active_profiles[profile_name][rule_type].add(rule_obj, cleanup=True)
def add_to_profile(rule_obj, profile_name):
aa.active_profiles[profile_name][rule_obj.rule_name].add(rule_obj, cleanup=True)
# Save changes
aa.write_profile_ui_feedback(profile_name)
def add_to_local_profile(rule_obj, profile_name):
inc_file = aa.create_local_profile_if_needed(profile_name)
aa.include[inc_file][inc_file].data[rule_obj.rule_name].add(rule_obj, cleanup=True)
aa.write_include_ui_feedback(aa.include[inc_file][inc_file], inc_file)
def add_rule(mode, rule, profile_name):
init_if_needed()
if not aa.active_profiles.profile_exists(profile_name):
exit(_('Cannot find {} in profiles').format(profile_name))
rule_type, rule_class = ReadLog('', '', '').get_rule_type(rule)
rule_obj = rule_class.create_instance(rule)
if mode == 'yes':
add_to_local_profile(rule_obj, profile_name)
elif mode == 'no':
add_to_profile(rule_obj, profile_name)
elif mode == 'auto':
if aa.active_profiles[profile_name].get_local_include():
add_to_local_profile(rule_obj, profile_name)
else:
add_to_profile(rule_obj, profile_name)
else:
usage(False)
aa.reload_base(profile_name)
def usage(is_help):
print('This tool is a low level tool - do not use it directly')
print('{} create_userns <template_path> <name> <bin_path> <profile_path> <decision>'.format(sys.argv[0]))
print('{} add_rule <rule> <profile_name>'.format(sys.argv[0]))
print('{} add_rule <mode=yes|no|auto> <rule> <profile_name>'.format(sys.argv[0]))
print('{} from_file <file>'.format(sys.argv[0]))
if is_help:
exit(0)
@@ -76,9 +108,9 @@ def do_command(command, args):
usage(False)
create_userns(args[1], args[2], args[3], args[4], args[5])
elif command == 'add_rule':
if not len(args) == 3:
if not len(args) == 4:
usage(False)
add_to_profile(args[1], args[2])
add_rule(args[1], args[2], args[3])
elif command == 'help':
usage(True)
else:

View File

@@ -20,6 +20,9 @@ interface_theme="ubuntu"
# Binaries for which we ignore userns-related capability denials
ignore_denied_capability="sudo,su"
# OPTIONAL - Write changes to local profiles to preserve regular profiles and simplify upgrades (yes, no, auto)
# use_local_profiles="yes"
# OPTIONAL - kind of operations which display a popup prompt.
# prompt_filter="userns"

View File

@@ -167,8 +167,9 @@ class AANotifyTest(AANotifyBase):
expected_return_code = 0
expected_output_1 = \
'''usage: aa-notify [-h] [-p] [--display DISPLAY] [-f FILE] [-l] [-s NUM] [-v]
[-u USER] [-w NUM] [-m] [-F] [--prompt-filter PF] [--debug]
'''usage: aa-notify [-h] [-p] [--display DISPLAY] [--xauthority XAUTHORITY]
[-f FILE] [-l] [-s NUM] [-v] [-u USER] [-w NUM] [-m] [-F]
[-L [{yes,no,auto}]] [--prompt-filter PF] [--debug]
[--filter.profile PROFILE] [--filter.operation OPERATION]
[--filter.name NAME] [--filter.denied DENIED]
[--filter.family FAMILY] [--filter.socket SOCKET]
@@ -182,6 +183,9 @@ Display AppArmor notifications or messages for DENIED entries.
-p, --poll poll AppArmor logs and display notifications
--display DISPLAY set the DISPLAY environment variable (might be needed if
sudo resets $DISPLAY)
--xauthority XAUTHORITY
set the XAUTHORITY environment variable (might be needed
if sudo resets XAUTHORITY)
-f, --file FILE search FILE for AppArmor messages
-l, --since-last display stats since last login
-s, --since-days NUM show stats for last NUM days (can be used alone or with
@@ -193,6 +197,8 @@ Display AppArmor notifications or messages for DENIED entries.
-m, --merge-notifications
Merge notification for improved readability (with -p)
-F, --foreground Do not fork to the background
-L, --local [{yes,no,auto}]
Add to local profile
--prompt-filter PF kind of operations which display a popup prompt
--debug debug mode
@@ -231,6 +237,11 @@ Filtering options:
), (
', --wait NUM ',
' NUM, --wait NUM',
), (
' -L, --local [{yes,no,auto}]\n'
+ ' Add to local profile',
' -L [{yes,no,auto}], --local [{yes,no,auto}]\n'
+ ' Add to local profile'
)]
for patch in patches:
expected_output_2 = expected_output_2.replace(patch[0], patch[1])

View File

@@ -92,7 +92,16 @@ Filtering options:
if line.startswith(' Profile '):
nb_profile += 1
command = ['grep', '-Er', r'flags=.*unconfined.*\{', '--', aa.profile_dir]
command = ['grep', '-Er', r'flags=.*unconfined.*\{']
# Remove disabled profiles from grep
disable_dir = os.path.join(aa.profile_dir, 'disable')
if os.path.isdir(disable_dir):
for name in os.listdir(disable_dir):
command.append('--exclude=' + name)
command.extend(['--', aa.profile_dir])
result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL, text=True, check=False)
self.assertEqual(
len(result.stdout.splitlines()), nb_profile,

View File

@@ -14,6 +14,7 @@ import unittest
from apparmor.common import AppArmorBug, AppArmorException
from apparmor.profile_storage import ProfileStorage, add_or_remove_flag, split_flags, var_transform
from apparmor.rule.capability import CapabilityRule
from apparmor.rule.include import IncludeRule
from common_test import AATest, setup_all_loops
@@ -313,6 +314,27 @@ class AaTest_var_transform(AATest):
self.assertEqual(var_transform(params), expected)
class AaTest_include(AATest):
tests = (
(('profile foo /foo {', []), None), # No include
(('profile foo /foo {', ['elsewhere/foo']), None), # No include in local/
(('profile foo /foo {', ['local/foo']), "local/foo"), # Single include, we pick it
(('profile foo /foo {', ['local/bar']), "local/bar"), # Single include, we pick it
(('profile x//y /y {', ['local/x..y', 'local/y']), "local/x..y"), # Pick the include that matches the profile nam
(('profile foo /foo {', ['local/bar', 'local/foo', 'local/baz']), "local/foo"), # Pick the include that matches the profile name
(('/usr/bin/xx {', ['local/usr.bin.xx', 'local/xx']), "local/usr.bin.xx"), # Pick the include that matches the profile name
(('profile foo /foo {', ['local/bar', 'local/baz', 'local/qux']), "local/qux"), # No match, pick the last one
)
def _run_test(self, params, expected):
(profile, hat, prof_storage) = ProfileStorage.parse(params[0], 'somefile', 1, None, None)
for inc in params[1]:
prof_storage.data['inc_ie'].add(IncludeRule(inc, True, True))
self.assertEqual(prof_storage.get_local_include(), expected)
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=1)