2
0
mirror of https://gitlab.com/apparmor/apparmor synced 2025-09-01 14:55:10 +00:00

Compare commits

..

48 Commits

Author SHA1 Message Date
John Johansen
884adcc58f repare for AppArmor 4.0 alpha 3 release
- update version file

Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-09-18 03:51:29 -07:00
John Johansen
f5be84acdc Merge Utils: add support for the 'all,' rule
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1105
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-09-12 00:39:42 +00:00
John Johansen
5d940b2a47 Merge apparmor.vim: add support for the 'all' rule
... and update the bugreporting info in the header

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1106
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-09-12 00:37:44 +00:00
Christian Boltz
583d116871 apparmor.vim: add support for the 'all' rule
... and update the bugreporting info in the header
2023-09-10 18:38:48 +02:00
Christian Boltz
e361644d5a Utils: add support for the 'all,' rule 2023-09-10 18:07:41 +02:00
John Johansen
4e758a838d Merge parser: add support for a generic all rule type
Extend the policy syntax to have a rule that allows specifying all
permissions for all rule types.

  allow all,

This is useful for making blacklist based policy, but can also be
useful when combined with other rule prefixes, eg. to add audit
to all rules.

  audit access all,

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

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1103
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-09-07 09:09:13 +00:00
John Johansen
197d00d21a parser: add support for a generic all rule type
Extend the policy syntax to have a rule that allows specifying all
permissions for all rule types.

  allow all,

This is useful for making blacklist based policy, but can also be
useful when combined with other rule prefixes, eg. to add audit
to all rules.

  audit access all,

Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-09-07 01:30:15 -07:00
John Johansen
a9c5388f69 Merge parser: refactor network rules and prepare for fine grained network rules
This lays the ground work for fine grained network mediation. The switch to using the rules class brings the advantages of shared infrastructure and is needed for tracking all the different combinations possible with finer control.

In addition range generation and support for large numbers (needed for ipv6) are added. This patchset does not make policy visible changes to network rules.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1104
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-09-07 07:22:21 +00:00
Georgia Garcia
75ca0e7919 parser: track leading zeros required for ipv6 range regex generator
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-09-07 00:13:52 -07:00
Georgia Garcia
fb5f59024c parser: add uppercase hex on regex range generator
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-09-07 00:13:42 -07:00
Georgia Garcia
a71ac76e6d parser: add regex generator for range
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-09-07 00:13:29 -07:00
Georgia Garcia
2be9c431ca parser: add opt_cond in preparation to finer grained network mediation
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-09-07 00:13:15 -07:00
Georgia Garcia
05de4b82e7 parser: implement dedup of network rules
Since network rules don't use the "perms" attribute, it is using the
dedup class in which duplicate rules are removed.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-09-07 00:13:04 -07:00
Georgia Garcia
820f1fb5f2 parser: refactor network to use rule class as its base.
There is one significant difference in the encoding of the network
rules. Before this change, when the parser was encoding a "network,"
rule, it would generate an entry for every family and every
type/protocol. After this patch the parser should generate an entry
for every family, but the type/protocol is changed to .. in the pcre
syntax. There should be no difference in behavior.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-09-07 00:12:51 -07:00
John Johansen
65905b0c55 Merge aa-status: separate error messages from the regular output
using dfprintf for error messages subjects them to the other
output controls and can cause them to be surpressed when they
shouldn't.

Instead use a dedicated error function and add a quiet flag to
allow silencing errors.

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

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1102
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-09-07 06:01:08 +00:00
John Johansen
1945ecbf19 aa-status: separate error messages from the regular output
using dfprintf for error messages subjects them to the other
output controls and can cause them to be surpressed when they
shouldn't.

Instead use a dedicated error function and add a quiet flag to
allow silencing errors.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-08-28 15:52:44 -07:00
John Johansen
11976c42e3 Merge binutils/aa_status.c: quiet verbose outputs when --json is specified
By default aa-status outputs with --verbose enabled - if --json is also
specified then aa-status would currently output in its first line "apparmor
module is loaded.":

aa-status --json | head -n1
apparmor module is loaded.

And only after this the actual json output would follow. This then results in
failures to parse this JSON output:

aa-status --json | jq .
parse error: Invalid numeric literal at line 1, column 9

This in turn then breaks tools / tests which expect the output of aa-status
--json to be purely json - e.g:
https://salsa.debian.org/apparmor-team/apparmor-profiles-extra/-/blob/debian/unstable/debian/tests/policy-is-loaded#L12

So ensure dprintf() etc do not output when --json is specified to restrict the
output of aa-status to pure JSON.

Signed-off-by: Alex Murray <alex.murray@canonical.com>

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1097
Approved-by: Seth Arnold <seth.arnold@gmail.com>
Merged-by: John Johansen <john@jjmx.net>
2023-08-28 22:07:17 +00:00
John Johansen
327588f019 Merge parser: fix coverity scan 553075
coverity is reporting an overrun of the profile_mode_table

217     		if (merge_profile_mode(mode, rhs.mode) == MODE_CONFLICT)
>>>     CID 322989:    (OVERRUN)
>>>     Overrunning array "profile_mode_table" of 6 8-byte elements at element index 6 (byte offset 55) using index "this->mode" (which evaluates to 6).

this is because it is being indexed by the profile_mode enum which can
go up to a 6th entry. The code tests for MODE_CONFLICT before using
the table so it shouldn't trigger a bug today, but play it safe for
the future and also get rid of the coverity scan error by adding a
"conflict" entry to the mode_table.

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

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1098
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: John Johansen <john@jjmx.net>
2023-08-28 22:05:45 +00:00
John Johansen
84e22b4cca Merge Docs: apparmor.d.pod document io_uring and userns rules
Documentation for io_uring and userns rules is missing from the
apparmor.d man page. Provide some basic documentation for them.

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/349
Signed-off-by: John Johansen <john.johansen@canonical.com>

Closes #349
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1099
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: John Johansen <john@jjmx.net>
2023-08-28 22:05:20 +00:00
John Johansen
248625ae00 Merge apparmor.d.pod: rename SIGNALS to SIGNAL
SIGNAL makes more sense because it's about a single signal.

Besides that, a9494f5523 introduced a (at
that point broken) usage of SIGNAL which becomes valid with this commit.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1100
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-08-28 22:05:04 +00:00
John Johansen
9ab72ffc7c Merge Install systemd unit on fedora/redhat systems
... instead of trying the old rc.apparmor.redhat initscript, which we no
longer ship since 2019519e34.

Also remove a superfluous level of indirection (rhel4 -> redhat).

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/350

Closes #350
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1101
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-08-28 22:04:53 +00:00
Christian Boltz
9be09aa909 Install systemd unit on fedora/redhat systems
... instead of trying the old rc.apparmor.redhat initscript, which we no
longer ship since 2019519e34.

Also remove a superfluous level of indirection (rhel4 -> redhat).

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/350
2023-08-28 18:46:20 +02:00
Christian Boltz
2bf35277a0 apparmor.d.pod: rename SIGNALS to SIGNAL
SIGNAL makes more sense because it's about a single signal.

Besides that, a9494f5523 introduced a (at
that point broken) usage of SIGNAL which becomes valid with this commit.
2023-08-28 18:33:03 +02:00
John Johansen
9db134223c Docs: apparmor.d.pod document io_uring and userns rules
Documentation for io_uring and userns rules is missing from the
apparmor.d man page. Provide some basic documentation for them.

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/349
Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-08-27 01:06:01 -07:00
John Johansen
ef56e60e06 parser: fix coverity scan 553075
coverity is reporting an overrun of the profile_mode_table

217     		if (merge_profile_mode(mode, rhs.mode) == MODE_CONFLICT)
>>>     CID 322989:    (OVERRUN)
>>>     Overrunning array "profile_mode_table" of 6 8-byte elements at element index 6 (byte offset 55) using index "this->mode" (which evaluates to 6).

this is because it is being indexed by the profile_mode enum which can
go up to a 6th entry. The code tests for MODE_CONFLICT before using
the table so it shouldn't trigger a bug today, but play it safe for
the future and also get rid of the coverity scan error by adding a
"conflict" entry to the mode_table.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-08-25 23:49:32 -07:00
John Johansen
96965c3da2 Merge Add support for new profile flags
This adds two new profile flags
* `interruptible` which can be used with prompt
* `kill.signal=XXX` which can be used to set the signal used by kill mode or the kill rule prefix

In addition it adds a few cleanups and fixes around profile flag handling

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1096
Approved-by: Georgia Garcia <georgia.garcia@canonical.com>
Merged-by: John Johansen <john@jjmx.net>
2023-08-26 05:05:42 +00:00
John Johansen
a9494f5523 parser: add kill.signal=XXX flag support
Add a flag that allows setting the signal used to kill the process.
This should not be normally used but can be very useful when
debugging applications, interaction with apparmor.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-08-25 10:16:51 -07:00
John Johansen
57985480ca parser: Fixup flags merge for disconnected_path
When merging flags with a disconnected_path specified, there is no
check for a conflict and we can just throw away (and leak) one of
the paths.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-08-25 07:19:01 -07:00
John Johansen
f10467556c parser: move flag init and merge to flagvals class
We have a basic flagvals class it makes sense to move the parser
code into it, to help make management easier going forward.

Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-08-25 07:19:01 -07:00
John Johansen
30707be87f parser: add interruptible flag
Allow indicating that prompt upcalls to userspace can be interrupted

Signed-off-by: John Johansen <john.johansen@canonical.com>
2023-08-25 07:18:50 -07:00
Alex Murray
f61fd42061 binutils/aa_status.c: quiet verbose outputs when --json is specified
By default aa-status outputs with --verbose enabled - if --json is also
specified then aa-status would currently output in its first line "apparmor
module is loaded.":

aa-status --json | head -n1
apparmor module is loaded.

And only after this the actual json output would follow. This then results in
failures to parse this JSON output:

aa-status --json | jq .
parse error: Invalid numeric literal at line 1, column 9

This in turn then breaks tools / tests which expect the output of aa-status
--json to be purely json - e.g:
https://salsa.debian.org/apparmor-team/apparmor-profiles-extra/-/blob/debian/unstable/debian/tests/policy-is-loaded#L12

So ensure dprintf() etc do not output when --json is specified to restrict the
output of aa-status to pure JSON.

Signed-off-by: Alex Murray <alex.murray@canonical.com>
2023-08-25 09:23:04 +09:30
John Johansen
847ab59e1c Merge collapse_log(): Attach null-* events to correct target profile
ask_exec() and ask_addhat() set
hashlog[aamode][full_profile]['final_name'].

While this was used to get profile and hat split, it was not used as key
for log_dict. This resulted in entries like
log_dict['PERMITTING']['foo//null-/usr/bin/cat']
which are obviously wrong.

Use final_name as log_dict key so that we end up with (assuming child
exec was selected)
log_dict['PERMITTING']['foo///usr/bin/cat']

This fixes a regression introduced in 3.1. Due to other changes in collapse_log() done in master, picking this into 3.1 isn't that easy. I'll submit a separate patch for 3.1.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1091
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-08-24 01:12:09 +00:00
John Johansen
427b58a4b6 Merge Ignore ´//null-` peers in signal and ptrace events
Ideally we'd update them to the chosen exec target - but until this is
implemented, it doesn't make sense to ask about adding a //null-* peer
to a profile.

I propose this patch for all branches.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1090
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-08-24 01:11:02 +00:00
John Johansen
aa20721be1 Merge Don't create local/* profile sniplets by default
... and document how to create them if you still want them.

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/337

Closes #337
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1089
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-08-23 23:26:54 +00:00
Georgia Garcia
5957aa49f5 Merge fix test specifying path on attach disconnected
MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1094
Approved-by: John Johansen <john@jjmx.net>
Merged-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-08-22 12:49:54 +00:00
Georgia Garcia
e133a9fc68 tests: remove superfluous attach_disconnected flag
Merge request https://gitlab.com/apparmor/apparmor/merge_requests/1084
makes it so attach_disconnected.path implies attach_disconnected, so
remove superfluous flag from tests.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-08-22 09:40:17 -03:00
Georgia Garcia
32307601a0 tests: fix test specifying path on attach disconnected
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-08-21 17:36:48 -03:00
Georgia Garcia
5b139521aa tests: replace individual socket permission to socket and put_old/socket
Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-08-21 17:33:33 -03:00
John Johansen
2d7bd40606 Merge tests: fix userns setns opening pipe order
setns tests part of the userns could fail if the parent process opened
the child pipe to write it was done before the child opened the pipe
with read permissions.

From the fifo(7) man page:

A process can open a FIFO in nonblocking mode.  In this case, opening
for read‐only succeeds even if no one has opened on the write side yet
and opening for write‐only fails with ENXIO (no such device or
address) unless the other end has already been opened.

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

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1093
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-08-21 16:13:28 +00:00
Georgia Garcia
24806f6f61 tests: fix userns setns opening pipe order
setns tests part of the userns could fail if the parent process opened
the child pipe to write it was done before the child opened the pipe
with read permissions.

From the fifo(7) man page:

A process can open a FIFO in nonblocking mode.  In this case, opening
for read‐only succeeds even if no one has opened on the write side yet
and opening for write‐only fails with ENXIO (no such device or
address) unless the other end has already been opened.

Signed-off-by: Georgia Garcia <georgia.garcia@canonical.com>
2023-08-21 12:04:51 -03:00
Christian Boltz
fbe68f0078 collapse_log(): use final_name
Now that we have `final_name` as shortcut for
`hashlog[aamode][full_profile]['final_name']`, update the code that used
the long version to make it more readable.
2023-08-20 15:50:20 +02:00
Christian Boltz
74265e8ded collapse_log(): Attach null-* events to correct target profile
ask_exec() and ask_addhat() set
hashlog[aamode][full_profile]['final_name'].

While this was used to get profile and hat split, it was not used as key
for log_dict. This resulted in entries like
log_dict['PERMITTING']['foo//null-/usr/bin/cat']
which are obviously wrong.

Use final_name as log_dict key so that we end up with (assuming child
exec was selected)
log_dict['PERMITTING']['foo///usr/bin/cat']
2023-08-20 15:49:59 +02:00
Christian Boltz
41df2ca366 Ignore ´//null-` peers in signal and ptrace events
Ideally we'd update them to the chosen exec target - but until this is
implemented, it doesn't make sense to ask about adding a //null-* peer
to a profile.
2023-08-20 11:53:08 +02:00
Christian Boltz
adf19138d5 Don't create local/* profile sniplets by default
... and document how to create them if you still want them.

Fixes: https://gitlab.com/apparmor/apparmor/-/issues/337
2023-08-20 11:49:10 +02:00
John Johansen
1758b66c9d Merge Increase timeout in test-logprof.py
On (terribly, but real-world) slow buid hosts, running test-logprof.py
fails with a timeout. Increase the timeout so that even those build
hosts get enough time to finish the aa-logprof tests.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1087
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-08-17 11:04:14 +00:00
Christian Boltz
dd9b7b358f Increase timeout in test-logprof.py
On (terribly, but real-world) slow buid hosts, running test-logprof.py
fails with a timeout. Increase the timeout so that even those build
hosts get enough time to finish the aa-logprof tests.
2023-08-15 20:49:08 +02:00
John Johansen
b45c10d4de Merge Fix compability with Python < 3.9
str.removeprefix() was introduced in Python 3.9. Replace it with
backwards-compatible code.

MR: https://gitlab.com/apparmor/apparmor/-/merge_requests/1085
Approved-by: John Johansen <john@jjmx.net>
Merged-by: John Johansen <john@jjmx.net>
2023-08-15 13:04:06 +00:00
Christian Boltz
c175e414c8 Fix compability with Python < 3.9
str.removeprefix() was introduced in Python 3.9. Replace it with
backwards-compatible code.
2023-08-15 12:40:39 +02:00
80 changed files with 2187 additions and 499 deletions

1
.gitignore vendored
View File

@@ -29,6 +29,7 @@ parser/parser_yacc.h
parser/pod2htm*.tmp
parser/af_rule.o
parser/af_unix.o
parser/all_rule.o
parser/common_optarg.o
parser/dbus.o
parser/default_features.o

View File

@@ -181,6 +181,9 @@ $ make check # depends on the parser having been built first
$ make install
```
Note that the empty local/* profile sniplets no longer get created by default.
If you want them, run `make local` before running `make check`.
[Note that for the parser, binutils, and utils, if you only wish to build/use
some of the locale languages, you can override the default by passing
the LANGS arguments to make; e.g. make all install "LANGS=en_US fr".]

View File

@@ -115,6 +115,7 @@ static void free_processes(struct process *processes, size_t n) {
#define SHOW_PROCESSES 2
static int verbose = 1;
static bool quiet = false;
int opt_show = SHOW_PROFILES | SHOW_PROCESSES;
bool opt_json = false;
bool opt_pretty = false;
@@ -127,15 +128,21 @@ const char *opt_exe = ".*";
const char *profile_statuses[] = {"enforce", "complain", "prompt", "kill", "unconfined"};
const char *process_statuses[] = {"enforce", "complain", "prompt", "kill", "unconfined", "mixed"};
#define dprintf(...) \
#define eprintf(...) \
do { \
if (!quiet) \
fprintf(stderr, __VA_ARGS__); \
} while (0)
#define dprintf(...) \
do { \
if (verbose) \
if (verbose && !opt_json) \
printf(__VA_ARGS__); \
} while (0)
#define dfprintf(...) \
do { \
if (verbose) \
if (verbose && !opt_json) \
fprintf(__VA_ARGS__); \
} while (0)
@@ -149,14 +156,14 @@ static int open_profiles(FILE **fp)
ret = stat("/sys/module/apparmor", &st);
if (ret != 0) {
dfprintf(stderr, "apparmor not present.\n");
eprintf("apparmor not present.\n");
return AA_EXIT_DISABLED;
}
dprintf("apparmor module is loaded.\n");
ret = aa_find_mountpoint(&apparmorfs);
if (ret == -1) {
dfprintf(stderr, "apparmor filesystem is not mounted.\n");
eprintf("apparmor filesystem is not mounted.\n");
return AA_EXIT_NO_CONTROL;
}
@@ -169,9 +176,9 @@ static int open_profiles(FILE **fp)
*fp = fopen(apparmor_profiles, "r");
if (*fp == NULL) {
if (errno == EACCES) {
dfprintf(stderr, "You do not have enough privilege to read the profile set.\n");
eprintf("You do not have enough privilege to read the profile set.\n");
} else {
dfprintf(stderr, "Could not open %s: %s", apparmor_profiles, strerror(errno));
eprintf("Could not open %s: %s", apparmor_profiles, strerror(errno));
}
return AA_EXIT_NO_PERM;
}
@@ -201,7 +208,7 @@ static int get_profiles(FILE *fp, struct profile **profiles, size_t *n) {
char *tmpname = aa_splitcon(line, &status);
if (!tmpname) {
dfprintf(stderr, "Error: failed profile name split of '%s'.\n", line);
eprintf("Error: failed profile name split of '%s'.\n", line);
// skip this entry and keep processing
// else would be AA_EXIT_INTERNAL_ERROR;
continue;
@@ -344,7 +351,7 @@ static int get_processes(struct profile *profiles,
continue;
} else if (rc == -1 ||
asprintf(&exe, "/proc/%s/exe", entry->d_name) == -1) {
fprintf(stderr, "ERROR: Failed to allocate memory\n");
eprintf("ERROR: Failed to allocate memory\n");
ret = AA_EXIT_INTERNAL_ERROR;
goto exit;
} else if (mode) {
@@ -367,7 +374,7 @@ static int get_processes(struct profile *profiles,
// ensure enough space for NUL terminator
real_exe = calloc(PATH_MAX + 1, sizeof(char));
if (real_exe == NULL) {
fprintf(stderr, "ERROR: Failed to allocate memory\n");
eprintf("ERROR: Failed to allocate memory\n");
ret = AA_EXIT_INTERNAL_ERROR;
goto exit;
}
@@ -575,7 +582,7 @@ static int detailed_profiles(FILE *outf, filters_t *filters, bool json,
*/
subfilters.mode = &mode_filter;
if (regcomp(&mode_filter, profile_statuses[i], REG_NOSUB) != 0) {
dfprintf(stderr, "Error: failed to compile sub filter '%s'\n",
eprintf("Error: failed to compile sub filter '%s'\n",
profile_statuses[i]);
return AA_EXIT_INTERNAL_ERROR;
}
@@ -641,7 +648,7 @@ static int detailed_processes(FILE *outf, filters_t *filters, bool json,
*/
subfilters.mode = &mode_filter;
if (regcomp(&mode_filter, process_statuses[i], REG_NOSUB) != 0) {
dfprintf(stderr, "Error: failed to compile sub filter '%s'\n",
eprintf("Error: failed to compile sub filter '%s'\n",
profile_statuses[i]);
return AA_EXIT_INTERNAL_ERROR;
}
@@ -765,6 +772,7 @@ static int print_usage(const char *command, bool error)
" --json displays multiple data points in machine-readable JSON format\n"
" --pretty-json same data as --json, formatted for human consumption as well\n"
" --verbose (default) displays data points about loaded policy set\n"
" --quiet don't output error messages\n"
" -h [(legacy|filter)] this message, or info on the specified option\n"
" --help[=(legacy|filter)] this message, or info on the specified option\n",
command);
@@ -792,6 +800,7 @@ static int print_usage(const char *command, bool error)
#define ARG_EXE 143
#define ARG_PROMPT 144
#define ARG_VERBOSE 'v'
#define ARG_QUIET 'q'
#define ARG_HELP 'h'
static int parse_args(int argc, char **argv)
@@ -809,6 +818,7 @@ static int parse_args(int argc, char **argv)
{"json", no_argument, 0, ARG_JSON},
{"pretty-json", no_argument, 0, ARG_PRETTY},
{"verbose", no_argument, 0, ARG_VERBOSE},
{"quiet", no_argument, 0, ARG_QUIET},
{"help", 2, 0, ARG_HELP},
{"count", no_argument, 0, ARG_COUNT},
{"show", 1, 0, ARG_SHOW},
@@ -830,6 +840,9 @@ static int parse_args(int argc, char **argv)
/* default opt_mode */
/* default opt_show */
break;
case ARG_QUIET:
quiet = true;
break;
case ARG_HELP:
if (!optarg) {
print_usage(argv[0], false);
@@ -838,7 +851,7 @@ static int parse_args(int argc, char **argv)
} else if (strcmp(optarg, "filters") == 0) {
usage_filters();
} else {
dfprintf(stderr, "Error: Invalid --help option '%s'.\n", optarg);
eprintf("Error: Invalid --help option '%s'.\n", optarg);
print_usage(argv[0], true);
break;
}
@@ -906,7 +919,7 @@ static int parse_args(int argc, char **argv)
} else if (strcmp(optarg, "processes") == 0) {
opt_show = SHOW_PROCESSES;
} else {
dfprintf(stderr, "Error: Invalid --show option '%s'.\n", optarg);
eprintf("Error: Invalid --show option '%s'.\n", optarg);
print_usage(argv[0], true);
break;
}
@@ -928,7 +941,7 @@ static int parse_args(int argc, char **argv)
break;
default:
dfprintf(stderr, "Error: Invalid command.\n");
eprintf("Error: Invalid command.\n");
print_usage(argv[0], true);
break;
}
@@ -953,7 +966,7 @@ int main(int argc, char **argv)
if (argc > 1) {
int pos = parse_args(argc, argv);
if (pos < argc) {
dfprintf(stderr, "Error: Unknown options.\n");
eprintf("Error: Unknown options.\n");
print_usage(progname, true);
}
} else {
@@ -965,24 +978,24 @@ int main(int argc, char **argv)
init_filters(&filters, &filter_set);
if (regcomp(filters.mode, opt_mode, REG_NOSUB) != 0) {
dfprintf(stderr, "Error: failed to compile mode filter '%s'\n",
eprintf("Error: failed to compile mode filter '%s'\n",
opt_mode);
return AA_EXIT_INTERNAL_ERROR;
}
if (regcomp(filters.profile, opt_profiles, REG_NOSUB) != 0) {
dfprintf(stderr, "Error: failed to compile profiles filter '%s'\n",
eprintf("Error: failed to compile profiles filter '%s'\n",
opt_profiles);
ret = AA_EXIT_INTERNAL_ERROR;
goto out;
}
if (regcomp(filters.pid, opt_pid, REG_NOSUB) != 0) {
dfprintf(stderr, "Error: failed to compile ps filter '%s'\n",
eprintf("Error: failed to compile ps filter '%s'\n",
opt_pid);
ret = AA_EXIT_INTERNAL_ERROR;
goto out;
}
if (regcomp(filters.exe, opt_exe, REG_NOSUB) != 0) {
dfprintf(stderr, "Error: failed to compile exe filter '%s'\n",
eprintf("Error: failed to compile exe filter '%s'\n",
opt_exe);
ret = AA_EXIT_INTERNAL_ERROR;
goto out;
@@ -997,7 +1010,7 @@ int main(int argc, char **argv)
outf_save = outf;
outf = open_memstream(&buffer, &buffer_size);
if (!outf) {
dfprintf(stderr, "Failed to open memstream: %m\n");
eprintf("Failed to open memstream: %m\n");
return AA_EXIT_INTERNAL_ERROR;
}
}
@@ -1008,7 +1021,7 @@ int main(int argc, char **argv)
*/
ret = get_profiles(fp, &profiles, &nprofiles);
if (ret != 0) {
dfprintf(stderr, "Failed to get profiles: %d....\n", ret);
eprintf("Failed to get profiles: %d....\n", ret);
goto out;
}
@@ -1032,7 +1045,7 @@ int main(int argc, char **argv)
ret = get_processes(profiles, nprofiles, &processes, &nprocesses);
if (ret != 0) {
dfprintf(stderr, "Failed to get processes: %d....\n", ret);
eprintf("Failed to get processes: %d....\n", ret);
} else if (opt_count) {
ret = simple_filtered_process_count(outf, &filters,
processes, nprocesses);
@@ -1058,14 +1071,14 @@ int main(int argc, char **argv)
outf = outf_save;
json = cJSON_Parse(buffer);
if (!json) {
dfprintf(stderr, "Failed to parse json output");
eprintf("Failed to parse json output");
ret = AA_EXIT_INTERNAL_ERROR;
goto out;
}
pretty = cJSON_Print(json);
if (!pretty) {
dfprintf(stderr, "Failed to print pretty json");
eprintf("Failed to print pretty json");
ret = AA_EXIT_INTERNAL_ERROR;
goto out;
}

View File

@@ -1 +1 @@
4.0.0~alpha2
4.0.0~alpha3

View File

@@ -99,15 +99,15 @@ EXTRA_CFLAGS+=-DPACKAGE=\"${NAME}\" -DLOCALEDIR=\"${LOCALEDIR}\"
SRCS = parser_common.c parser_include.c parser_interface.c parser_lex.c \
parser_main.c parser_misc.c parser_merge.c parser_symtab.c \
parser_yacc.c parser_regex.c parser_variable.c parser_policy.c \
parser_alias.c common_optarg.c lib.c network.c \
parser_alias.c common_optarg.c lib.c network.cc \
mount.cc dbus.cc profile.cc rule.cc signal.cc ptrace.cc \
af_rule.cc af_unix.cc policy_cache.c default_features.c userns.cc \
mqueue.cc io_uring.cc
mqueue.cc io_uring.cc all_rule.cc
STATIC_HDRS = af_rule.h af_unix.h capability.h common_optarg.h dbus.h \
file_cache.h immunix.h lib.h mount.h network.h parser.h \
parser_include.h parser_version.h policy_cache.h policydb.h \
profile.h ptrace.h rule.h signal.h userns.h mqueue.h io_uring.h \
common_flags.h
common_flags.h bignum.h all_rule.h
SPECIAL_HDRS = parser_yacc.h unit_test.h base_cap_names.h
GENERATED_HDRS = af_names.h generated_af_names.h \
@@ -295,7 +295,7 @@ signal.o: signal.cc $(HDRS)
ptrace.o: ptrace.cc $(HDRS)
$(CXX) $(EXTRA_CFLAGS) -c -o $@ $<
network.o: network.c $(HDRS)
network.o: network.cc $(HDRS)
$(CXX) $(EXTRA_CFLAGS) -c -o $@ $<
default_features.o: default_features.c $(HDRS)
@@ -322,6 +322,9 @@ mqueue.o: mqueue.cc $(HDRS)
io_uring.o: io_uring.cc $(HDRS)
$(CXX) $(EXTRA_CFLAGS) -c -o $@ $<
all_rule.o: all_rule.cc $(HDRS)
$(CXX) $(EXTRA_CFLAGS) -c -o $@ $<
parser_version.h: Makefile
@echo \#define PARSER_VERSION \"$(VERSION)\" > .ver
@mv -f .ver $@
@@ -373,13 +376,8 @@ tests: apparmor_parser ${TESTS}
$(AAREOBJECT): FORCE
$(MAKE) -C $(AAREDIR) CFLAGS="$(EXTRA_CXXFLAGS)"
.PHONY: install-rhel4
install-rhel4: install-redhat
.PHONY: install-redhat
install-redhat:
install -m 755 -d $(DESTDIR)/etc/init.d
install -m 755 rc.apparmor.$(subst install-,,$@) $(DESTDIR)/etc/init.d/apparmor
install-redhat: install-systemd
.PHONY: install-suse
install-suse: install-systemd
@@ -410,9 +408,9 @@ DISTRO=$(shell if [ -f /etc/slackware-version ] ; then \
if [ "$$(rpm --eval '0%{?suse_version}')" != "0" ] ; then \
echo suse ;\
elif [ "$$(rpm --eval '%{_host_vendor}')" = redhat ] ; then \
echo rhel4 ;\
echo redhat ;\
elif [ "$$(rpm --eval '0%{?fedora}')" != "0" ] ; then \
echo rhel4 ;\
echo redhat ;\
else \
echo unknown ;\
fi ;\

View File

@@ -108,6 +108,9 @@ unix_rule::unix_rule(unsigned int type_p, audit_t audit_p, rule_mode_t rule_mode
perms = AA_VALID_NET_PERMS;
audit = audit_p;
rule_mode = rule_mode_p;
/* if this constructor is used, then there's already a
* downgraded network_rule in profile */
downgrade = false;
}
unix_rule::unix_rule(perms_t perms_p, struct cond_entry *conds,
@@ -190,7 +193,7 @@ static void writeu16(std::ostringstream &o, int v)
void unix_rule::downgrade_rule(Profile &prof) {
perms_t mask = (perms_t) -1;
if (!prof.net.allow && !prof.alloc_net_table())
if (!prof.net.allow && !prof.net.alloc_net_table())
yyerror(_("Memory allocation error."));
if (sock_type_n != -1)
mask = 1 << sock_type_n;
@@ -198,6 +201,11 @@ void unix_rule::downgrade_rule(Profile &prof) {
prof.net.allow[AF_UNIX] |= mask;
if (audit == AUDIT_FORCE)
prof.net.audit[AF_UNIX] |= mask;
const char *error;
network_rule *netv8 = new network_rule(AF_UNIX, sock_type_n);
if(!netv8->add_prefix({audit, rule_mode, owner}, error))
yyerror(error);
prof.rule_ents.push_back(netv8);
} else {
/* deny rules have to be dropped because the downgrade makes
* the rule less specific meaning it will make the profile more
@@ -317,7 +325,8 @@ int unix_rule::gen_policy_re(Profile &prof)
* older kernels and be enforced to the best of the old network
* rules ability
*/
downgrade_rule(prof);
if (downgrade)
downgrade_rule(prof);
if (!features_supports_unix) {
if (features_supports_network || features_supports_networkv8) {
/* only warn if we are building against a kernel

View File

@@ -36,6 +36,7 @@ class unix_rule: public af_rule {
public:
char *addr;
char *peer_addr;
bool downgrade = true;
unix_rule(unsigned int type_p, audit_t audit_p, rule_mode_t rule_mode_p);
unix_rule(perms_t perms, struct cond_entry *conds,

130
parser/all_rule.cc Normal file
View File

@@ -0,0 +1,130 @@
/*
* Copyright (c) 2023
* Canonical Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact Canonical Ltd.
*/
#include "profile.h"
#include "all_rule.h"
#include "af_unix.h"
#include "dbus.h"
#include "io_uring.h"
#include "mqueue.h"
#include "ptrace.h"
#include "signal.h"
#include "userns.h"
#include "mount.h"
#include "parser.h"
#include <iomanip>
#include <string>
#include <iostream>
#include <sstream>
void all_rule::add_implied_rules(Profile &prof)
{
prefix_rule_t *rule;
const prefixes *prefix = this;
rule = new unix_rule(0, audit, rule_mode);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new dbus_rule(0, NULL, NULL);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new io_uring_rule(0, NULL, NULL);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new mqueue_rule(0, NULL);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new ptrace_rule(0, NULL);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new signal_rule(0, NULL);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new userns_rule(0, NULL);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new mnt_rule(NULL, NULL, NULL, NULL, 0);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new mnt_rule(NULL, NULL, NULL, NULL, AA_DUMMY_REMOUNT);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new mnt_rule(NULL, NULL, NULL, NULL, AA_MAY_UMOUNT);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new mnt_rule(NULL, NULL, NULL, NULL, AA_MAY_PIVOTROOT);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
rule = new network_rule(NULL);
(void) rule->add_prefix(*prefix);
prof.rule_ents.push_back(rule);
/* rules that have not been converted to use rule.h */
//file
{
const char *error;
struct cod_entry *entry;
char *path = strdup("/{**,}");
int perms = ((AA_BASE_PERMS & ~AA_EXEC_TYPE) |
(AA_MAY_EXEC));
if (rule_mode != RULE_DENY)
perms |= AA_EXEC_INHERIT;
/* duplicate to other permission set */
perms |= perms << AA_OTHER_SHIFT;
if (!path)
yyerror(_("Memory allocation error."));
entry = new_entry(path, perms, NULL);
if (!entry_add_prefix(entry, *prefix, error)) {
yyerror(_("%s"), error);
}
add_entry_to_policy(&prof, entry);
}
// caps
{
if (prefix->owner)
yyerror(_("owner prefix not allowed on capability rules"));
if (rule_mode == RULE_DENY && audit == AUDIT_FORCE) {
prof.caps.deny |= 0xffffffffffffffff;
} else if (rule_mode == RULE_DENY) {
prof.caps.deny |= 0xffffffffffffffff;
prof.caps.quiet |= 0xffffffffffffffff;
} else {
prof.caps.allow |= 0xffffffffffffffff;
if (audit != AUDIT_UNSPECIFIED)
prof.caps.audit |= 0xffffffffffffffff;
}
}
// TODO: rlimit
}

70
parser/all_rule.h Normal file
View File

@@ -0,0 +1,70 @@
/*
* Copyright (c) 2023
* Canonical Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact Canonical Ltd.
*/
#ifndef __AA_ALL_H
#define __AA_ALL_H
#include "rule.h"
#define AA_IO_URING_OVERRIDE_CREDS AA_MAY_APPEND
#define AA_IO_URING_SQPOLL AA_MAY_CREATE
#define AA_VALID_IO_URING_PERMS (AA_IO_URING_OVERRIDE_CREDS | \
AA_IO_URING_SQPOLL)
class all_rule: public prefix_rule_t {
void move_conditionals(struct cond_entry *conds);
public:
char *label;
all_rule(void): prefix_rule_t(RULE_TYPE_ALL) { }
virtual bool valid_prefix(const prefixes &p, const char *&error) {
if (p.owner) {
error = _("owner prefix not allowed on all rules");
return false;
}
return true;
};
int expand_variables(void)
{
return 0;
}
virtual ostream &dump(ostream &os) {
prefix_rule_t::dump(os);
os << "all";
return os;
}
virtual bool is_mergeable(void) { return true; }
virtual int cmp(rule_t const &rhs) const
{
return prefix_rule_t::cmp(rhs);
};
virtual void add_implied_rules(Profile &prof);
virtual int gen_policy_re(Profile &prof unused) { return RULE_OK; };
protected:
virtual void warn_once(const char *name unused, const char *msg unused) { };
virtual void warn_once(const char *name unused) { };
};
#endif /* __AA_ALL_H */

View File

@@ -115,7 +115,7 @@ B<PROFILE FLAG CONDS> = [ 'flags=' ] '(' comma or white space separated list of
B<PROFILE FLAGS> = I<PROFILE MODE> | I<AUDIT_MODE> | 'mediate_deleted'
| 'attach_disconnected' | 'attach_disconneced.path='I<ABS PATH> | 'chroot_relative'
| 'debug'
| 'debug' | 'interruptible' | 'kill.signal='I<SIGNAL>
B<PROFILE MODE> = 'enforce' | 'complain' | 'kill' | 'unconfined' | 'prompt'
@@ -125,7 +125,7 @@ B<RULES> = [ ( I<LINE RULES> | I<COMMA RULES> ',' | I<BLOCK RULES> )
B<LINE RULES> = ( I<COMMENT> | I<INCLUDE> ) [ '\r' ] '\n'
B<COMMA RULES> = ( I<CAPABILITY RULE> | I<NETWORK RULE> | I<MOUNT RULE> | I<PIVOT ROOT RULE> | I<UNIX RULE> | I<FILE RULE> | I<LINK RULE> | I<CHANGE_PROFILE RULE> | I<RLIMIT RULE> | I<DBUS RULE> | I<MQUEUE RULE> )
B<COMMA RULES> = ( I<CAPABILITY RULE> | I<NETWORK RULE> | I<MOUNT RULE> | I<PIVOT ROOT RULE> | I<UNIX RULE> | I<FILE RULE> | I<LINK RULE> | I<CHANGE_PROFILE RULE> | I<RLIMIT RULE> | I<DBUS RULE> | I<MQUEUE RULE> | I<IO_URING RULE> | I<USERNS RULE> | I<ALL RULE>)
B<BLOCK RULES> = ( I<SUBPROFILE> | I<HAT> | I<QUALIFIER BLOCK> )
@@ -192,6 +192,16 @@ B<MQUEUE LABEL> = 'label' '=' '(' '"' I<AARE> '"' | I<AARE> ')'
B<MQUEUE NAME> = I<AARE>
B<USERNS RULE> = [ I<QUALIFIERS> ] 'userns' [ I<USERNS ACCESS PERMISSIONS> ]
B<USERNS ACCESS PERMISSIONS> = ( 'create' )
B<IO_URING RULE> = [ I<QUALIFIERS> ] 'io_uring' [ I<IO_URING ACCESS PERMISSIONS> [ I<IO_URING LABEL> ]
B<IO_URING ACCESS PERMISSIONS> = ( 'sqpoll' | 'override_creds' )
B<IO_URING LABEL> = 'label' '=' '(' '"' I<AARE> '"' | I<AARE> ')'
B<PIVOT ROOT RULE> = [ I<QUALIFIERS> ] pivot_root [ oldroot=I<OLD PUT FILEGLOB> ] [ I<NEW ROOT FILEGLOB> ] [ '-E<gt>' I<PROFILE NAME> ]
B<SOURCE FILEGLOB> = I<FILEGLOB>
@@ -220,9 +230,9 @@ B<SIGNAL ACCESS> = ( 'r' | 'w' | 'rw' | 'read' | 'write' | 'send' | 'receive' )
B<SIGNAL SET> = 'set' '=' '(' I<SIGNAL LIST> ')'
B<SIGNAL LIST> = Comma or space separated list of I<SIGNALS>
B<SIGNAL LIST> = Comma or space separated list of I<SIGNAL>s
B<SIGNALS> = ( 'hup' | 'int' | 'quit' | 'ill' | 'trap' | 'abrt' | 'bus' | 'fpe' | 'kill' | 'usr1' | 'segv' | 'usr2' | 'pipe' | 'alrm' | 'term' | 'stkflt' | 'chld' | 'cont' | 'stop' | 'stp' | 'ttin' | 'ttou' | 'urg' | 'xcpu' | 'xfsz' | 'vtalrm' | 'prof' | 'winch' | 'io' | 'pwr' | 'sys' | 'emt' | 'exists' | 'rtmin+0' ... 'rtmin+32' )
B<SIGNAL> = ( 'hup' | 'int' | 'quit' | 'ill' | 'trap' | 'abrt' | 'bus' | 'fpe' | 'kill' | 'usr1' | 'segv' | 'usr2' | 'pipe' | 'alrm' | 'term' | 'stkflt' | 'chld' | 'cont' | 'stop' | 'stp' | 'ttin' | 'ttou' | 'urg' | 'xcpu' | 'xfsz' | 'vtalrm' | 'prof' | 'winch' | 'io' | 'pwr' | 'sys' | 'emt' | 'exists' | 'rtmin+0' ... 'rtmin+32' )
B<SIGNAL PEER> = 'peer' '=' I<AARE>
@@ -336,6 +346,8 @@ B<EXEC_MODE> = ( 'safe' | 'unsafe' )
B<EXEC COND> = I<FILEGLOB>
B<ALL RULE> = 'all'
=back
All resources and programs need a full path. There may be any number of
@@ -506,6 +518,11 @@ flags to control what messages will be output. Its effect is kernel
dependent, and it should never appear in policy except when trying
to debug kernel or policy problems.
=item B<interruptible> Enables interrupts for prompt upcall to userspace.
=item B<kill.signal>=I<SIGNAL> This changes the signal that will be
sent by AppArmor when in kill mode or a kill rule has been violated.
=back
=head2 Access Modes
@@ -1133,6 +1150,89 @@ Example AppArmor Message Queue rules:
# Allow create permission for a SYSV queue of label foo
mqueue create label=foo 123,
=head2 User Namespace Rules
User namespaces are part of many sandboxing and containerization
solutions. They provide a way for a non-system root process to be
root within the container. Unfortunately this opens up attack surface
in the kernel and has been part of several exploit chains. As such
AppArmor can be used to restrict the creation of user namespaces to
select processes.
User namespace permission are implied when a rule does not explicitly
state an access list. The rule becomes more restrictive as further
information is specified.
Note: user namespace creation may be restricted so that it is not
available to unprivieged unconfined processes. If this is the case any
process trying to create user namespaces will require a profile that
allows the necessary permissions.
=over 4
=item B<create>
Allow creation of user namespaces.
=back
Example userns rules:
=over 4
# Allow all userns perms
userns,
# Allow creation of a userns
userns create,
=back
=head2 IO_URing Rules
AppArmor supports mediation of the new Linux high speed IO interface.
There is limited mediation at this time to just a few permissions at
the moment.
IO Uring permission are implied when a rule does not explicitly state
an access list. The rule becomes more restrictive as further
information is specified.
Note: io_uring access may be restricted so that it is not available to
unprivileged unconfined processes. If this is the case any process
trying to use io_uring will require a profile that allows the
necessary io_uring permissions.
=over 4
=item B<sqpoll>
All the task confined by the profile to spawn a io_uring polling
thread.
=item B<override_creds>
Grants the task confined by the profile to override (change) its
credentials to the specified label, when executing an io_uring
operation.
=back
Example IO_URING rules:
=over 4
# Allow io_uring operations
io_ring,
# Allow creation of a polling thread
io_uring sqpoll,
# Allow task to override credentials during io_uring operation
io_uring override_creds label=new_creds,
=back
=head2 Pivot Root Rules
AppArmor mediates changing of the root filesystem through the pivot_root(2)
@@ -1506,6 +1606,26 @@ Not all kernels support B<safe> mode and the parser will downgrade rules to
B<unsafe> mode in that situation. If no exec mode is specified, the default is
B<safe> mode in kernels that support it.
=head2 all rule
The all rule is used to add a generic rule for all supported rule types.
This is useful when policy wants to define a black list instead of
white list, but can also be useful to add an access qualifier to all
rules.
Eg. Black list
allow all,
# begin blacklist
deny file,
deny unix,
Eg. Adding audit qualifier
audit access all,
=head2 rlimit rules
AppArmor can set and control the resource limits associated with a

235
parser/bignum.h Normal file
View File

@@ -0,0 +1,235 @@
/*
* Copyright (c) 2023
* Canonical Ltd. (All rights reserved)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact Novell, Inc. or Canonical
* Ltd.
*/
#ifndef __AA_BIGNUM_H
#define __AA_BIGNUM_H
#include <iostream>
#include <vector>
#include <sstream>
#include <algorithm>
#include <string>
class bignum
{
public:
std::vector<uint8_t> data;
uint64_t sad = 543;
uint8_t base;
bool negative = false;
bignum () {}
bignum (unsigned long val) {
if (val == 0)
data.push_back(val);
else {
while(val > 0) {
data.push_back(val % 10);
val /= 10;
}
}
base = 10;
}
bignum (const char *val) {
while (*val) {
data.push_back(*val - 48);
val++;
}
std::reverse(data.begin(), data.end());
base = 10;
}
bignum (const uint8_t val[16]) {
size_t i;
bool flag = true;
for (i = 0; i < 16; i++) {
if (flag && (val[i] & 0xF0) >> 4 != 0)
flag = false;
if (!flag)
data.push_back((val[i] & 0xF0) >> 4);
if (flag && (val[i] & 0x0F) != 0)
flag = false;
if (!flag)
data.push_back(val[i] & 0x0F);
}
std::reverse(data.begin(), data.end());
base = 16;
}
bignum operator+(const bignum &brhs) const {
bignum b1 = this->size() < brhs.size() ? *this : brhs;
bignum b2 = this->size() < brhs.size() ? brhs : *this;
bignum result;
result.base = this->base;
uint8_t carryover = 0;
uint8_t sum;
size_t i;
for (i = 0; i < b1.size(); i++) {
sum = b1[i] + b2[i] + carryover;
if (sum > base - 1)
carryover = 1;
else
carryover = 0;
result.data.push_back(sum % base);
}
for (; i < b2.size(); i++) {
sum = b2[i] + carryover;
if (sum > base - 1)
carryover = 1;
else
carryover = 0;
result.data.push_back(sum % base);
}
if (carryover != 0)
result.data.push_back(carryover);
return result;
}
bignum operator-(const bignum &brhs) const {
bignum b1 = this->size() < brhs.size() ? *this : brhs;
bignum b2 = this->size() < brhs.size() ? brhs : *this;
bignum result;
result.negative = *this < brhs;
result.base = this->base;
int8_t borrow = 0;
int8_t sub;
size_t i;
for (i = 0; i < b1.size(); i++) {
sub = b2[i] - b1[i] - borrow;
if (sub < 0) {
sub += base;
borrow = 1;
} else
borrow = 0;
result.data.push_back(sub);
}
for (; i < b2.size(); i++) {
sub = b2[i] - borrow;
if (sub < 0) {
sub += base;
borrow = 1;
} else
borrow = 0;
result.data.push_back(sub);
}
if (borrow) {
int8_t tmp = result.data[result.size() - 1] -= base;
tmp *= -1;
result.data[result.size() - 1] = tmp;
}
while (result.size() > 1 && result.data[result.size() - 1] == 0)
result.data.pop_back();
return result;
}
bool operator>=(const bignum &rhs) const {
return cmp_bignum(this->data, rhs.data) >= 0;
}
bool operator<=(const bignum &rhs) const {
return cmp_bignum(this->data, rhs.data) <= 0;
}
bool operator>(const bignum &rhs) const {
return cmp_bignum(this->data, rhs.data) > 0;
}
bool operator<(const bignum &rhs) const {
return cmp_bignum(this->data, rhs.data) < 0;
}
int operator[](int index) const {
return this->data[index];
}
friend std::ostream &operator<<(std::ostream &os, bignum &bn);
size_t size() const {
return data.size();
}
/*
returns:
- 0, if the lhs and rhs are equal;
- a negative value if lhs is less than rhs;
- a positive value if lhs is greater than rhs.
*/
int cmp_bignum(std::vector<uint8_t> lhs, std::vector<uint8_t> rhs) const
{
if (lhs.size() > rhs.size())
return 1;
else if (lhs.size() < rhs.size())
return -1;
else {
/* assumes the digits are stored in reverse order */
std::reverse(lhs.begin(), lhs.end());
std::reverse(rhs.begin(), rhs.end());
for (size_t i = 0; i < lhs.size(); i++) {
if (lhs[i] > rhs[i])
return 1;
if (lhs[i] < rhs[i])
return -1;
}
}
return 0;
}
static bignum lower_bound_regex(bignum val)
{
/* single digit numbers reduce to 0 */
if (val.size() == 1) {
val.data[0] = 0;
return val;
}
for (auto& j : val.data) {
uint8_t tmp = j;
j = 0;
if (tmp != val.base - 1) {
break;
}
if (&j == &val.data[val.size()-2]) {
val.data[val.size()-1] = 1;
break;
}
}
return val;
}
static bignum upper_bound_regex(bignum val)
{
for (auto& j : val.data) {
uint8_t tmp = j;
j = val.base - 1;
if (tmp != 0) {
break;
}
}
return val;
}
};
inline std::ostream &operator<<(std::ostream &os, bignum &bn)
{
std::stringstream ss;
bignum tmp = bn;
std::reverse(tmp.data.begin(), tmp.data.end());
for (auto i : tmp.data)
ss << std::hex << (int) i;
os << ss.str();
return os;
};
#endif /* __AA_BIGNUM_H */

View File

@@ -12,7 +12,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact or Canonical Ltd.
* along with this program; if not, contact Canonical Ltd.
*/
#include "common_optarg.h"

View File

@@ -12,7 +12,7 @@
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, contact or Canonical Ltd.
* along with this program; if not, contact Canonical Ltd.
*/
#ifndef __AA_IO_URING_H

View File

@@ -16,10 +16,6 @@
* Ltd.
*/
#include <stdlib.h>
#include <string.h>
#include <sys/apparmor.h>
#include <iomanip>
#include <string>
#include <sstream>
@@ -28,9 +24,9 @@
#include "lib.h"
#include "parser.h"
#include "profile.h"
#include "parser_yacc.h"
#include "network.h"
#define ALL_TYPES 0x43e
int parse_net_perms(const char *str_mode, perms_t *mode, int fail)
{
@@ -119,7 +115,7 @@ static struct network_tuple network_mappings[] = {
/* FIXME: af_names.h is missing AF_LLC, AF_TIPC */
/* mapped types */
{"inet", AF_INET, "raw", SOCK_RAW,
"tcp", 1 << RAW_TCP},
"tcp", 1 << RAW_TCP},
{"inet", AF_INET, "raw", SOCK_RAW,
"udp", 1 << RAW_UDP},
{"inet", AF_INET, "raw", SOCK_RAW,
@@ -239,21 +235,21 @@ size_t get_af_max() {
return af_max;
}
struct aa_network_entry *new_network_ent(unsigned int family,
unsigned int type,
unsigned int protocol)
{
struct aa_network_entry *new_entry;
new_entry = (struct aa_network_entry *) calloc(1, sizeof(struct aa_network_entry));
if (new_entry) {
new_entry->family = family;
new_entry->type = type;
new_entry->protocol = protocol;
new_entry->next = NULL;
}
return new_entry;
}
const char *net_find_af_name(unsigned int af)
{
size_t i;
if (af < 0 || af > get_af_max())
return NULL;
for (i = 0; i < sizeof(network_mappings) / sizeof(*network_mappings); i++) {
if (network_mappings[i].family == af)
return network_mappings[i].family_name;
}
return NULL;
}
const struct network_tuple *net_find_mapping(const struct network_tuple *map,
const char *family,
@@ -302,95 +298,270 @@ const struct network_tuple *net_find_mapping(const struct network_tuple *map,
return NULL;
}
struct aa_network_entry *network_entry(const char *family, const char *type,
const char *protocol)
void network_rule::move_conditionals(struct cond_entry *conds)
{
struct aa_network_entry *new_entry, *entry = NULL;
const struct network_tuple *mapping = NULL;
struct cond_entry *cond_ent;
while ((mapping = net_find_mapping(mapping, family, type, protocol))) {
new_entry = new_network_ent(mapping->family, mapping->type,
mapping->protocol);
if (!new_entry)
yyerror(_("Memory allocation error."));
new_entry->next = entry;
entry = new_entry;
list_for_each(conds, cond_ent) {
/* for now disallow keyword 'in' (list) */
if (!cond_ent->eq)
yyerror("keyword \"in\" is not allowed in network rules\n");
/* no valid conditionals atm */
yyerror("invalid network rule conditional \"%s\"\n",
cond_ent->name);
}
return entry;
};
#define ALL_TYPES 0x43e
const char *net_find_af_name(unsigned int af)
{
size_t i;
if (af < 0 || af > get_af_max())
return NULL;
for (i = 0; i < sizeof(network_mappings) / sizeof(*network_mappings); i++) {
if (network_mappings[i].family == af)
return network_mappings[i].family_name;
}
return NULL;
}
void __debug_network(unsigned int *array, const char *name)
void network_rule::set_netperm(unsigned int family, unsigned int type)
{
if (type > SOCK_PACKET) {
/* setting mask instead of a bit */
network_perms[family] |= type;
} else
network_perms[family] |= 1 << type;
}
network_rule::network_rule(struct cond_entry *conds):
dedup_perms_rule_t(AA_CLASS_NETV8)
{
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);
}
move_conditionals(conds);
free_cond_list(conds);
}
network_rule::network_rule(const char *family, const char *type,
const char *protocol, struct cond_entry *conds):
dedup_perms_rule_t(AA_CLASS_NETV8)
{
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);
}
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);
}
}
if (network_map.empty())
yyerror(_("Invalid network entry."));
move_conditionals(conds);
free_cond_list(conds);
}
network_rule::network_rule(unsigned int family, unsigned int type):
dedup_perms_rule_t(AA_CLASS_NETV8)
{
network_map[family].push_back({ family, type, 0xFFFFFFFF });
set_netperm(family, type);
}
ostream &network_rule::dump(ostream &os)
{
class_rule_t::dump(os);
unsigned int count = sizeof(sock_types)/sizeof(sock_types[0]);
unsigned int mask = ~((1 << count) -1);
unsigned int i, j;
int none = 1;
size_t af_max = get_af_max();
for (i = AF_UNSPEC; i < af_max; i++)
if (array[i]) {
none = 0;
break;
}
if (none)
return;
printf("%s: ", name);
unsigned int j;
/* This can only be set by an unqualified network rule */
if (array[AF_UNSPEC]) {
printf("<all>\n");
return;
if (network_map.find(AF_UNSPEC) != network_map.end()) {
os << ",\n";
return os;
}
for (i = 0; i < af_max; i++) {
if (array[i]) {
const char *fam = net_find_af_name(i);
if (fam)
printf("%s ", fam);
else
printf("#%u ", i);
for (const auto& perm : network_perms) {
unsigned int family = perm.first;
unsigned int type = perm.second;
/* All types/protocols */
if (array[i] == 0xffffffff || array[i] == ALL_TYPES)
continue;
const char *family_name = net_find_af_name(family);
if (family_name)
os << " " << family_name;
else
os << " #" << family;
printf("{ ");
/* All types/protocols */
if (type == 0xffffffff || type == ALL_TYPES)
continue;
for (j = 0; j < count; j++) {
const char *type;
if (array[i] & (1 << j)) {
type = sock_types[j].name;
if (type)
printf("%s ", type);
else
printf("#%u ", j);
printf(" {");
for (j = 0; j < count; j++) {
const char *type_name;
if (type & (1 << j)) {
type_name = sock_types[j].name;
if (type_name)
os << " " << type_name;
else
os << " #" << j;
}
}
if (type & mask)
os << " #" << std::hex << (type & mask);
printf(" }");
}
os << ",\n";
return os;
}
int network_rule::expand_variables(void)
{
return 0;
}
void network_rule::warn_once(const char *name)
{
rule_t::warn_once(name, "network rules not enforced");
}
bool network_rule::gen_net_rule(Profile &prof, u16 family, unsigned int type_mask) {
std::ostringstream buffer;
std::string buf;
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_NETV8;
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((family & 0xff00) >> 8);
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (family & 0xff);
if (type_mask > 0xffff) {
buffer << "..";
} else {
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((type_mask & 0xff00) >> 8);
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (type_mask & 0xff);
}
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;
}
int network_rule::gen_policy_re(Profile &prof)
{
std::ostringstream buffer;
std::string buf;
if (!features_supports_networkv8) {
warn_once(prof.name);
return RULE_NOT_SUPPORTED;
}
for (const auto& perm : network_perms) {
unsigned int family = perm.first;
unsigned int type = perm.second;
if (type > 0xffff) {
if (!gen_net_rule(prof, family, type))
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))
goto fail;
}
}
if (array[i] & mask)
printf("#%x ", array[i] & mask);
}
printf("} ");
}
return RULE_OK;
fail:
return RULE_ERROR;
}
/* initialize static members */
unsigned int *network_rule::allow = NULL;
unsigned int *network_rule::audit = NULL;
unsigned int *network_rule::deny = NULL;
unsigned int *network_rule::quiet = NULL;
bool network_rule::alloc_net_table()
{
if (allow)
return true;
allow = (unsigned int *) calloc(get_af_max(), sizeof(unsigned int));
audit = (unsigned int *) calloc(get_af_max(), sizeof(unsigned int));
deny = (unsigned int *) calloc(get_af_max(), sizeof(unsigned int));
quiet = (unsigned int *) calloc(get_af_max(), sizeof(unsigned int));
if (!allow || !audit || !deny || !quiet)
return false;
return true;
}
/* update is required because at the point of the creation of the
* network_rule object, we don't have owner, rule_mode, or audit
* set.
*/
void network_rule::update_compat_net(void)
{
if (!alloc_net_table())
yyerror(_("Memory allocation error."));
for (auto& nm: network_map) {
for (auto& entry : nm.second) {
if (entry.type > SOCK_PACKET) {
/* setting mask instead of a bit */
if (rule_mode == RULE_DENY) {
deny[entry.family] |= entry.type;
if (dedup_perms_rule_t::audit != AUDIT_FORCE)
quiet[entry.family] |= entry.type;
} else {
allow[entry.family] |= entry.type;
if (dedup_perms_rule_t::audit == AUDIT_FORCE)
audit[entry.family] |= entry.type;
}
} else {
if (rule_mode == RULE_DENY) {
deny[entry.family] |= 1 << entry.type;
if (dedup_perms_rule_t::audit != AUDIT_FORCE)
quiet[entry.family] |= 1 << entry.type;
} else {
allow[entry.family] |= 1 << entry.type;
if (dedup_perms_rule_t::audit == AUDIT_FORCE)
audit[entry.family] |= 1 << entry.type;
}
}
}
}
printf("\n");
}
static int cmp_network_map(std::unordered_map<unsigned int, perms_t> lhs,
std::unordered_map<unsigned int, perms_t> 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];
if (res)
return res;
}
return 0;
}
int network_rule::cmp(rule_t const &rhs) const
{
int res = dedup_perms_rule_t::cmp(rhs);
if (res)
return res;
network_rule const &nrhs = rule_cast<network_rule const &>(rhs);
return cmp_network_map(network_perms, nrhs.network_perms);
};

View File

@@ -29,6 +29,8 @@
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <unordered_map>
#include <vector>
#include "parser.h"
#include "rule.h"
@@ -82,13 +84,10 @@ struct network_tuple {
unsigned int protocol;
};
/* supported AF protocols */
struct aa_network_entry {
unsigned int family;
long unsigned int family;
unsigned int type;
unsigned int protocol;
struct aa_network_entry *next;
};
static inline uint32_t map_perms(uint32_t mask)
@@ -99,45 +98,75 @@ static inline uint32_t map_perms(uint32_t mask)
((mask & (AA_NET_SETOPT | AA_NET_GETOPT)) >> 5); /* 5 + (AA_OTHER_SHIFT - 24) */
};
int parse_net_perms(const char *str_mode, perms_t *perms, int fail);
extern struct aa_network_entry *new_network_ent(unsigned int family,
unsigned int type,
unsigned int protocol);
extern struct aa_network_entry *network_entry(const char *family,
const char *type,
const char *protocol);
extern size_t get_af_max(void);
void __debug_network(unsigned int *array, const char *name);
struct network {
unsigned int *allow; /* array of type masks
* indexed by AF_FAMILY */
unsigned int *audit;
unsigned int *deny;
unsigned int *quiet;
network(void) { allow = audit = deny = quiet = NULL; }
void dump(void) {
if (allow)
__debug_network(allow, "Network");
if (audit)
__debug_network(audit, "Audit Net");
if (deny)
__debug_network(deny, "Deny Net");
if (quiet)
__debug_network(quiet, "Quiet Net");
}
};
size_t get_af_max();
int net_find_type_val(const char *type);
const char *net_find_type_name(int type);
const char *net_find_af_name(unsigned int af);
const struct network_tuple *net_find_mapping(const struct network_tuple *map,
const char *family,
const char *type,
const char *protocol);
class network_rule: public dedup_perms_rule_t {
void move_conditionals(struct cond_entry *conds);
public:
std::unordered_map<unsigned int, std::vector<struct aa_network_entry>> network_map;
std::unordered_map<unsigned int, perms_t> network_perms;
/* empty constructor used only for the profile to access
* static elements to maintain compatibility with
* AA_CLASS_NET */
network_rule(): dedup_perms_rule_t(AA_CLASS_NETV8) { }
network_rule(struct cond_entry *conds);
network_rule(const char *family, const char *type,
const char *protocol, struct cond_entry *conds);
network_rule(unsigned int family, unsigned int type);
virtual ~network_rule()
{
if (allow) {
free(allow);
allow = NULL;
}
if (audit) {
free(audit);
audit = NULL;
}
if (deny) {
free(deny);
deny = NULL;
}
if (quiet) {
free(quiet);
quiet = NULL;
}
};
bool gen_net_rule(Profile &prof, u16 family, unsigned int type_mask);
void set_netperm(unsigned int family, unsigned int type);
void update_compat_net(void);
virtual bool valid_prefix(const prefixes &p, const char *&error) {
if (p.owner) {
error = _("owner prefix not allowed on network rules");
return false;
}
return true;
};
virtual ostream &dump(ostream &os);
virtual int expand_variables(void);
virtual int gen_policy_re(Profile &prof);
virtual bool is_mergeable(void) { return true; }
virtual int cmp(rule_t const &rhs) const;
/* array of type masks indexed by AF_FAMILY */
/* allow, audit, deny and quiet are used for compatibility with AA_CLASS_NET */
static unsigned int *allow;
static unsigned int *audit;
static unsigned int *deny;
static unsigned int *quiet;
bool alloc_net_table(void);
protected:
virtual void warn_once(const char *name) override;
};
#endif /* __AA_NETWORK_H */

View File

@@ -37,6 +37,7 @@
#include "libapparmor_re/apparmor_re.h"
#include "libapparmor_re/aare_rules.h"
#include "rule.h"
#include "bignum.h"
#include <string>
@@ -353,6 +354,8 @@ extern int features_supports_userns;
extern int features_supports_posix_mqueue;
extern int features_supports_sysv_mqueue;
extern int features_supports_io_uring;
extern int features_supports_flag_interruptible;
extern int features_supports_flag_signal;
extern int kernel_supports_oob;
extern int conf_verbose;
extern int conf_quiet;
@@ -409,6 +412,7 @@ extern pattern_t convert_aaregex_to_pcre(const char *aare, int anchor, int glob,
extern int build_list_val_expr(std::string& buffer, struct value_list *list);
extern int convert_entry(std::string& buffer, char *entry);
extern int clear_and_convert_entry(std::string& buffer, char *entry);
extern int convert_range(std::string& buffer, bignum start, bignum end);
extern int process_regex(Profile *prof);
extern int post_process_entry(struct cod_entry *entry);
@@ -457,6 +461,9 @@ extern bool strcomp (const char *lhs, const char *rhs);
extern struct cod_entry *copy_cod_entry(struct cod_entry *cod);
extern void free_cod_entries(struct cod_entry *list);
void debug_cod_entries(struct cod_entry *list);
bool check_x_qualifier(struct cod_entry *entry, const char *&error);
bool entry_add_prefix(struct cod_entry *entry, const prefixes &p, const char *&error);
#define SECONDS_P_MS (1000LL * 1000LL)
long long convert_time_units(long long value, long long base, const char *units);

View File

@@ -82,6 +82,8 @@ int features_supports_userns = 0; /* kernel supports user namespace */
int features_supports_posix_mqueue = 0; /* kernel supports mqueue rules */
int features_supports_sysv_mqueue = 0; /* kernel supports mqueue rules */
int features_supports_io_uring = 0; /* kernel supports io_uring rules */
int features_supports_flag_interruptible = 0;
int features_supports_flag_signal = 0;
int kernel_supports_oob = 0; /* out of band transitions */
int conf_verbose = 0;
int conf_quiet = 0;

View File

@@ -426,6 +426,10 @@ void sd_serialize_profile(std::ostringstream &buf, Profile *profile,
"disconnected");
}
if (profile->flags.signal && features_supports_flag_signal) {
sd_write_name(buf, "kill");
sd_write_uint32(buf, profile->flags.signal);
}
sd_write_struct(buf, "flags");
/* used to be flags.debug, but that's no longer supported */
sd_write_uint32(buf, profile->flags.flags);

View File

@@ -378,7 +378,7 @@ GT >
yyterminate();
}
<INITIAL,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,MQUEUE_MODE>{
<INITIAL,MOUNT_MODE,DBUS_MODE,SIGNAL_MODE,PTRACE_MODE,UNIX_MODE,MQUEUE_MODE,NETWORK_MODE>{
(peer|xattrs)/{WS}*={WS}*\( {
/* we match to the = in the lexer so that we can switch scanner
* state. By the time the parser see the = it may be too late
@@ -658,6 +658,10 @@ include/{WS} {
yy_push_state(INCLUDE);
}
all/({WS}|[^[:alnum:]_]) {
RETURN_TOKEN(TOK_ALL);
}
#.*\r?\n { /* normal comment */
DUMP_AND_DEBUG("comment(%d): %s\n", current_lineno, yytext);
current_lineno++;

View File

@@ -951,6 +951,12 @@ void set_supported_features()
features_supports_io_uring = features_intersect(kernel_features,
policy_features,
"io_uring");
features_supports_flag_interruptible = features_intersect(kernel_features,
policy_features,
"policy/profile/interruptible");
features_supports_flag_signal = features_intersect(kernel_features,
policy_features,
"policy/profile/kill.signal");
}
static bool do_print_cache_dir(aa_features *features, int dirfd, const char *path)

View File

@@ -129,6 +129,7 @@ static struct keyword_table keyword_table[] = {
{"io_uring", TOK_IO_URING},
{"override_creds", TOK_OVERRIDE_CREDS},
{"sqpoll", TOK_SQPOLL},
{"all", TOK_ALL},
/* terminate */
{NULL, 0}
@@ -1086,6 +1087,47 @@ void debug_cod_entries(struct cod_entry *list)
}
}
bool check_x_qualifier(struct cod_entry *entry, const char *&error)
{
if (entry->perms & AA_EXEC_BITS) {
if ((entry->rule_mode == RULE_DENY) &&
(entry->perms & ALL_AA_EXEC_TYPE)) {
error = _("Invalid perms, in deny rules 'x' must not be preceded by exec qualifier 'i', 'p', or 'u'");
return false;
} else if ((entry->rule_mode != RULE_DENY) &&
!(entry->perms & ALL_AA_EXEC_TYPE)) {
error = _("Invalid perms, 'x' must be preceded by exec qualifier 'i', 'p', or 'u'");
return false;
}
}
return true;
}
// cod_entry version of ->add_prefix here just as file rules aren't converted yet
bool entry_add_prefix(struct cod_entry *entry, const prefixes &p, const char *&error)
{
/* modifiers aren't correctly stored for cod_entries yet so
* we can't conflict on them easily. Leave that until conversion
* to rule_t
*/
/* apply rule mode */
entry->rule_mode = p.rule_mode;
/* apply owner/other */
if (p.owner == 1)
entry->perms &= (AA_USER_PERMS | AA_SHARED_PERMS);
else if (p.owner == 2)
entry->perms &= (AA_OTHER_PERMS | AA_SHARED_PERMS);
/* implied audit modifier */
if (p.audit == AUDIT_FORCE && (entry->rule_mode != RULE_DENY))
entry->audit = AUDIT_FORCE;
else if (p.audit != AUDIT_FORCE && (entry->rule_mode == RULE_DENY))
entry->audit = AUDIT_FORCE;
return check_x_qualifier(entry, error);
}
// these need to move to stl
int ordered_cmp_value_list(value_list *lhs, value_list *rhs)
{

View File

@@ -33,6 +33,7 @@
#include "parser.h"
#include "profile.h"
#include "parser_yacc.h"
#include "network.h"
/* #define DEBUG */
#ifdef DEBUG

View File

@@ -845,6 +845,116 @@ int clear_and_convert_entry(std::string& buffer, char *entry)
return convert_entry(buffer, entry);
}
static std::vector<std::pair<bignum, bignum>> regex_range_generator(bignum start, bignum end)
{
std::vector<std::pair<bignum, bignum>> forward;
std::vector<std::pair<bignum, bignum>> reverse;
bignum next, prev;
while (start <= end) {
next = bignum::upper_bound_regex(start);
if (next > end)
break;
forward.emplace_back(start, next);
start = next + 1;
}
while (!end.negative && end >= start) {
prev = bignum::lower_bound_regex(end);
if (prev < start || prev.negative)
break;
reverse.emplace_back(prev, end);
end = prev - 1;
}
if (!end.negative && start <= end) {
forward.emplace_back(start, end);
}
forward.insert(forward.end(), reverse.rbegin(), reverse.rend());
return forward;
}
static std::string generate_regex_range(bignum start, bignum end)
{
std::ostringstream result;
std::vector<std::pair<bignum, bignum>> regex_range;
int j;
regex_range = regex_range_generator(start, end);
for (auto &i: regex_range) {
bignum sstart = i.first;
bignum send = i.second;
if (sstart.base == 16) {
for (j = (size_t) sstart.size(); j < 32; j++)
result << '0';
}
for (j = sstart.size() - 1; j >= 0; j--) {
result << std::nouppercase;
if (sstart[j] == send[j]) {
if (sstart[j] >= 10)
result << '[';
result << std::hex << sstart[j];
if (sstart[j] >= 10)
result << std::uppercase << std::hex << sstart[j] << ']';
} else {
if (sstart[j] < 10 && send[j] >= 10) {
result << '[';
result << std::hex << sstart[j];
if (sstart[j] < 9) {
result << '-';
result << '9';
}
if (send[j] > 10) {
result << 'a';
result << '-';
}
result << std::hex << send[j];
if (send[j] > 10) {
result << 'A';
result << '-';
}
result << std::uppercase << std::hex << send[j];
result << ']';
} else {
result << '[';
result << std::hex << sstart[j];
result << '-';
result << std::hex << send[j];
if (sstart[j] >= 10) {
result << std::uppercase << std::hex << sstart[j];
result << '-';
result << std::uppercase << std::hex << send[j];
}
result << ']';
}
}
}
if (&i != &regex_range.back())
result << ",";
}
return result.str();
}
int convert_range(std::string& buffer, bignum start, bignum end)
{
pattern_t ptype;
int pos;
std::string regex_range = generate_regex_range(start, end);
if (!regex_range.empty()) {
ptype = convert_aaregex_to_pcre(regex_range.c_str(), 0, glob_default, buffer, &pos);
if (ptype == ePatternInvalid)
return FALSE;
} else {
buffer.append(default_match_pattern);
}
return TRUE;
}
int post_process_policydb_ents(Profile *prof)
{
for (RuleList::iterator i = prof->rule_ents.begin(); i != prof->rule_ents.end(); i++) {
@@ -857,80 +967,6 @@ int post_process_policydb_ents(Profile *prof)
return TRUE;
}
static bool gen_net_rule(Profile *prof, u16 family, unsigned int type_mask,
bool audit, bool deny) {
std::ostringstream buffer;
std::string buf;
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_NETV8;
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((family & 0xff00) >> 8);
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (family & 0xff);
if (type_mask > 0xffff) {
buffer << "..";
} else {
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << ((type_mask & 0xff00) >> 8);
buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << (type_mask & 0xff);
}
buf = buffer.str();
if (!prof->policy.rules->add_rule(buf.c_str(), deny, map_perms(AA_VALID_NET_PERMS),
audit ? map_perms(AA_VALID_NET_PERMS) : 0,
parseopts))
return false;
return true;
}
static bool gen_af_rules(Profile *prof, u16 family, unsigned int type_mask,
unsigned int audit_mask, bool deny)
{
if (type_mask > 0xffff && audit_mask > 0xffff) {
/* instead of generating multiple rules wild card type */
return gen_net_rule(prof, family, type_mask, audit_mask, deny);
} else {
int t;
/* generate rules for types that are set */
for (t = 0; t < 16; t++) {
if (type_mask & (1 << t)) {
if (!gen_net_rule(prof, family, t,
audit_mask & (1 << t),
deny))
return false;
}
}
}
return true;
}
bool post_process_policydb_net(Profile *prof)
{
u16 af;
/* no network rules defined so we don't have generate them */
if (!prof->net.allow)
return true;
/* generate rules if the af has something set */
for (af = AF_UNSPEC; af < get_af_max(); af++) {
if (prof->net.allow[af] ||
prof->net.deny[af] ||
prof->net.audit[af] ||
prof->net.quiet[af]) {
if (!gen_af_rules(prof, af, prof->net.allow[af],
prof->net.audit[af],
false))
return false;
if (!gen_af_rules(prof, af, prof->net.deny[af],
prof->net.quiet[af],
true))
return false;
}
}
return true;
}
#define MAKE_STR(X) #X
#define CLASS_STR(X) "\\d" MAKE_STR(X)
#define MAKE_SUB_STR(X) "\\000" MAKE_STR(X)
@@ -959,9 +995,6 @@ int process_profile_policydb(Profile *prof)
if (!post_process_policydb_ents(prof))
goto out;
/* TODO: move to network class */
if (features_supports_networkv8 && !post_process_policydb_net(prof))
goto out;
/* insert entries to show indicate what compiler/policy expects
* to be supported

View File

@@ -70,8 +70,6 @@ mnt_rule *do_mnt_rule(struct cond_entry *src_conds, char *src,
mnt_rule *do_pivot_rule(struct cond_entry *old, char *root,
char *transition);
static void abi_features(char *filename, bool search);
bool add_prefix(struct cod_entry *entry, const prefixes &p, const char *&error);
bool check_x_qualifier(struct cod_entry *entry, const char *&errror);
%}
@@ -149,6 +147,7 @@ bool check_x_qualifier(struct cod_entry *entry, const char *&errror);
%token TOK_IO_URING
%token TOK_OVERRIDE_CREDS
%token TOK_SQPOLL
%token TOK_ALL
/* rlimits */
%token TOK_RLIMIT
@@ -187,13 +186,15 @@ bool check_x_qualifier(struct cod_entry *entry, const char *&errror);
#include "userns.h"
#include "mqueue.h"
#include "io_uring.h"
#include "network.h"
#include "all_rule.h"
}
%union {
char *id;
char *flag_id;
char *mode;
struct aa_network_entry *network_entry;
network_rule *network_entry;
Profile *prof;
struct cod_net_entry *net_entry;
struct cod_entry *user_entry;
@@ -206,6 +207,7 @@ bool check_x_qualifier(struct cod_entry *entry, const char *&errror);
userns_rule *userns_entry;
mqueue_rule *mqueue_entry;
io_uring_rule *io_uring_entry;
all_rule *all_entry;
prefix_rule_t *prefix_entry;
flagvals flags;
@@ -302,6 +304,7 @@ bool check_x_qualifier(struct cod_entry *entry, const char *&errror);
%type <fperms> io_uring_perms
%type <fperms> opt_io_uring_perm
%type <io_uring_entry> io_uring_rule
%type <all_entry> all_rule
%%
@@ -575,8 +578,9 @@ valuelist: valuelist TOK_VALUE
}
flags: { /* nothing */
flagvals fv = { 0, MODE_UNSPECIFIED, 0, 0, NULL };
flagvals fv;
fv.init();
$$ = fv;
};
@@ -596,27 +600,7 @@ flags: opt_flags TOK_OPENPAREN flagvals TOK_CLOSEPAREN
flagvals: flagvals flagval
{
if (merge_profile_mode($1.mode, $2.mode) == MODE_CONFLICT)
yyerror(_("Profile flag '%s' conflicts with '%s'"),
profile_mode_table[$1.mode],
profile_mode_table[$2.mode]);
$1.mode = merge_profile_mode($1.mode, $2.mode);
$1.audit = $1.audit || $2.audit;
$1.path = $1.path | $2.path;
if (($1.path & (PATH_CHROOT_REL | PATH_NS_REL)) ==
(PATH_CHROOT_REL | PATH_NS_REL))
yyerror(_("Profile flag chroot_relative conflicts with namespace_relative"));
if (($1.path & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED)) ==
(PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))
yyerror(_("Profile flag mediate_deleted conflicts with delegate_deleted"));
if (($1.path & (PATH_ATTACH | PATH_NO_ATTACH)) ==
(PATH_ATTACH | PATH_NO_ATTACH))
yyerror(_("Profile flag attach_disconnected conflicts with no_attach_disconnected"));
if (($1.path & (PATH_CHROOT_NSATTACH | PATH_CHROOT_NO_ATTACH)) ==
(PATH_CHROOT_NSATTACH | PATH_CHROOT_NO_ATTACH))
yyerror(_("Profile flag chroot_attach conflicts with chroot_no_attach"));
$1.merge($2);
$$ = $1;
};
@@ -627,39 +611,9 @@ flagvals: flagval
flagval: TOK_VALUE
{
flagvals fv = { 0, MODE_UNSPECIFIED, 0, 0, NULL };
enum profile_mode mode;
flagvals fv;
if (strcmp($1, "debug") == 0) {
/* DEBUG2 is left for internal compiler use atm */
fv.flags |= FLAG_DEBUG1;
} else if ((mode = str_to_mode($1))) {
fv.mode = mode;
} else if (strcmp($1, "audit") == 0) {
fv.audit = 1;
} else if (strcmp($1, "chroot_relative") == 0) {
fv.path |= PATH_CHROOT_REL;
} else if (strcmp($1, "namespace_relative") == 0) {
fv.path |= PATH_NS_REL;
} else if (strcmp($1, "mediate_deleted") == 0) {
fv.path |= PATH_MEDIATE_DELETED;
} else if (strcmp($1, "delegate_deleted") == 0) {
fv.path |= PATH_DELEGATE_DELETED;
} else if (strcmp($1, "attach_disconnected") == 0) {
fv.path |= PATH_ATTACH;
} else if (strcmp($1, "no_attach_disconnected") == 0) {
fv.path |= PATH_NO_ATTACH;
} else if (strcmp($1, "chroot_attach") == 0) {
fv.path |= PATH_CHROOT_NSATTACH;
} else if (strcmp($1, "chroot_no_attach") == 0) {
fv.path |= PATH_CHROOT_NO_ATTACH;
} else if (strncmp($1, "attach_disconnected.path=", 25) == 0) {
/* TODO: make this a proper parse */
fv.path |= PATH_ATTACH;
fv.disconnected_path = strdup($1 + 25);
} else {
yyerror(_("Invalid profile flag: %s."), $1);
}
fv.init($1);
free($1);
$$ = fv;
};
@@ -704,7 +658,7 @@ rules: rules opt_prefix rule
PDEBUG("rules rule: (%s)\n", $3->name);
if (!$3)
yyerror(_("Assert: `rule' returned NULL."));
if (!add_prefix($3, $2, error)) {
if (!entry_add_prefix($3, $2, error)) {
yyerror(_("%s"), error);
}
add_entry_to_policy($1, $3);
@@ -725,7 +679,7 @@ rules: rules opt_prefix block
list_for_each_safe($3->entries, entry, tmp) {
const char *error;
entry->next = NULL;
if (!add_prefix(entry, $2, error)) {
if (!entry_add_prefix(entry, $2, error)) {
yyerror(_("%s"), error);
}
/* transfer rule for now, TODO keep block and just
@@ -742,51 +696,23 @@ rules: rules opt_prefix block
rules: rules opt_prefix network_rule
{
struct aa_network_entry *entry, *tmp;
const char *error;
if (!$3->add_prefix($2, error))
yyerror(error);
/* class members need to be updated after prefix is added */
$3->update_compat_net();
PDEBUG("Matched: network rule\n");
if ($2.owner)
yyerror(_("owner prefix not allowed"));
if (!$3)
yyerror(_("Assert: `network_rule' return invalid protocol."));
if (!$1->alloc_net_table())
yyerror(_("Memory allocation error."));
list_for_each_safe($3, entry, tmp) {
/* map to extended mediation, let rule backend do
* downgrade if needed
*/
if (entry->family == AF_UNIX) {
unix_rule *rule = new unix_rule(entry->type, $2.audit, $2.rule_mode);
auto nm_af_unix = $3->network_map.find(AF_UNIX);
if (nm_af_unix != $3->network_map.end()) {
for (auto& entry : nm_af_unix->second) {
unix_rule *rule = new unix_rule(entry.type,
$2.audit, $2.rule_mode);
if (!rule)
yyerror(_("Memory allocation error."));
$1->rule_ents.push_back(rule);
}
if (entry->type > SOCK_PACKET) {
/* setting mask instead of a bit */
if ($2.rule_mode == RULE_DENY) {
$1->net.deny[entry->family] |= entry->type;
if ($2.audit != AUDIT_FORCE)
$1->net.quiet[entry->family] |= entry->type;
} else {
$1->net.allow[entry->family] |= entry->type;
if ($2.audit == AUDIT_FORCE)
$1->net.audit[entry->family] |= entry->type;
}
} else {
if ($2.rule_mode == RULE_DENY) {
$1->net.deny[entry->family] |= 1 << entry->type;
if ($2.audit != AUDIT_FORCE)
$1->net.quiet[entry->family] |= 1 << entry->type;
} else {
$1->net.allow[entry->family] |= 1 << entry->type;
if ($2.audit == AUDIT_FORCE)
$1->net.audit[entry->family] |= 1 << entry->type;
}
}
free(entry);
}
$1->rule_ents.push_back($3);
$$ = $1;
}
@@ -798,6 +724,7 @@ prefix_rule : mnt_rule { $$ = $1; }
| userns_rule { $$ = $1; }
| mqueue_rule { $$ = $1; }
| io_uring_rule { $$ = $1; }
| all_rule { $$ = $1; }
rules: rules opt_prefix prefix_rule
{
@@ -1156,40 +1083,22 @@ link_rule: TOK_LINK opt_subset_flag id_or_var TOK_ARROW id_or_var TOK_END_OF_RUL
$$ = entry;
};
network_rule: TOK_NETWORK TOK_END_OF_RULE
network_rule: TOK_NETWORK opt_conds TOK_END_OF_RULE
{
size_t family;
struct aa_network_entry *new_entry, *entry = NULL;
for (family = AF_UNSPEC; family < get_af_max(); family++) {
new_entry = new_network_ent(family, 0xffffffff,
0xffffffff);
if (!new_entry)
yyerror(_("Memory allocation error."));
new_entry->next = entry;
entry = new_entry;
}
network_rule *entry = new network_rule($2);
$$ = entry;
}
network_rule: TOK_NETWORK TOK_ID TOK_END_OF_RULE
network_rule: TOK_NETWORK TOK_ID opt_conds TOK_END_OF_RULE
{
struct aa_network_entry *entry;
entry = network_entry($2, NULL, NULL);
if (!entry)
/* test for short circuiting of family */
entry = network_entry(NULL, $2, NULL);
if (!entry)
yyerror(_("Invalid network entry."));
network_rule *entry = new network_rule($2, NULL, NULL, $3);
free($2);
$$ = entry;
}
network_rule: TOK_NETWORK TOK_ID TOK_ID TOK_END_OF_RULE
network_rule: TOK_NETWORK TOK_ID TOK_ID opt_conds TOK_END_OF_RULE
{
struct aa_network_entry *entry;
entry = network_entry($2, $3, NULL);
if (!entry)
yyerror(_("Invalid network entry."));
network_rule *entry = new network_rule($2, $3, NULL, $4);
free($2);
free($3);
$$ = entry;
@@ -1605,6 +1514,14 @@ io_uring_rule: TOK_IO_URING opt_io_uring_perm opt_conds opt_cond_list TOK_END_OF
$$ = ent;
}
all_rule: TOK_ALL TOK_END_OF_RULE
{
all_rule *ent = new all_rule();
if (!ent)
yyerror(_("Memory allocation error."));
$$ = ent;
}
hat_start: TOK_CARET {}
| TOK_HAT {}
@@ -1867,43 +1784,3 @@ static void abi_features(char *filename, bool search)
};
bool check_x_qualifier(struct cod_entry *entry, const char *&error)
{
if (entry->perms & AA_EXEC_BITS) {
if ((entry->rule_mode == RULE_DENY) &&
(entry->perms & ALL_AA_EXEC_TYPE)) {
error = _("Invalid perms, in deny rules 'x' must not be preceded by exec qualifier 'i', 'p', or 'u'");
return false;
} else if ((entry->rule_mode != RULE_DENY) &&
!(entry->perms & ALL_AA_EXEC_TYPE)) {
error = _("Invalid perms, 'x' must be preceded by exec qualifier 'i', 'p', or 'u'");
return false;
}
}
return true;
}
// cod_entry version of ->add_prefix here just as file rules aren't converted yet
bool add_prefix(struct cod_entry *entry, const prefixes &p, const char *&error)
{
/* modifiers aren't correctly stored for cod_entries yet so
* we can't conflict on them easily. Leave that until conversion
* to rule_t
*/
/* apply rule mode */
entry->rule_mode = p.rule_mode;
/* apply owner/other */
if (p.owner == 1)
entry->perms &= (AA_USER_PERMS | AA_SHARED_PERMS);
else if (p.owner == 2)
entry->perms &= (AA_OTHER_PERMS | AA_SHARED_PERMS);
/* implied audit modifier */
if (p.audit == AUDIT_FORCE && (entry->rule_mode != RULE_DENY))
entry->audit = AUDIT_FORCE;
else if (p.audit != AUDIT_FORCE && (entry->rule_mode == RULE_DENY))
entry->audit = AUDIT_FORCE;
return check_x_qualifier(entry, error);
}

View File

@@ -27,7 +27,8 @@ const char *profile_mode_table[] = {
"complain",
"kill",
"unconfined",
"prompt"
"prompt",
"conflict" /* should not ever be displayed */
};
bool deref_profileptr_lt::operator()(Profile * const &lhs, Profile * const &rhs) const
@@ -71,20 +72,6 @@ void ProfileList::dump_profile_names(bool children)
}
}
bool Profile::alloc_net_table()
{
if (net.allow)
return true;
net.allow = (unsigned int *) calloc(get_af_max(), sizeof(unsigned int));
net.audit = (unsigned int *) calloc(get_af_max(), sizeof(unsigned int));
net.deny = (unsigned int *) calloc(get_af_max(), sizeof(unsigned int));
net.quiet = (unsigned int *) calloc(get_af_max(), sizeof(unsigned int));
if (!net.allow || !net.audit || !net.deny || !net.quiet)
return false;
return true;
}
Profile::~Profile()
{
hat_table.clear();
@@ -114,14 +101,6 @@ Profile::~Profile()
for (int i = (AA_EXEC_LOCAL >> 10) + 1; i < AA_EXEC_COUNT; i++)
if (exec_table[i])
free(exec_table[i]);
if (net.allow)
free(net.allow);
if (net.audit)
free(net.audit);
if (net.deny)
free(net.deny);
if (net.quiet)
free(net.quiet);
}
static bool comp (rule_t *lhs, rule_t *rhs)
@@ -347,6 +326,19 @@ static int profile_add_hat_rules(Profile *prof)
void Profile::post_parse_profile(void)
{
/* semantic check stuff that can't be done in parse, like flags */
if (flags.flags & FLAG_INTERRUPTIBLE) {
if (!features_supports_flag_interruptible) {
warn_once(name, "flag interruptible not supported. Ignoring");
/* TODO: don't clear in parse data, only at encode */
flags.flags &= ~FLAG_INTERRUPTIBLE;
}
}
if (flags.signal) {
if (!features_supports_flag_signal) {
warn_once(name, "kill.signal not supported. Ignoring");
}
}
post_process_file_entries(this);
post_process_rule_entries(this);
}
@@ -355,6 +347,12 @@ void Profile::add_implied_rules(void)
{
int error;
for (RuleList::iterator i = rule_ents.begin(); i != rule_ents.end(); i++) {
if ((*i)->skip())
continue;
(*i)->add_implied_rules(*this);
}
error = profile_add_hat_rules(this);
if (error) {
PERROR(_("ERROR adding hat access rule for profile %s\n"),
@@ -363,3 +361,9 @@ void Profile::add_implied_rules(void)
}
}
/* do we want to warn once/profile or just once per compile?? */
void Profile::warn_once(const char *name, const char *msg)
{
common_warn_once(name, msg, &warned_name);
}

View File

@@ -23,6 +23,7 @@
#include "rule.h"
#include "libapparmor_re/aare_rules.h"
#include "network.h"
#include "signal.h"
class Profile;
@@ -114,7 +115,9 @@ static inline enum profile_mode str_to_mode(const char *str)
#define FLAG_HAT 1
#define FLAG_DEBUG1 2
#define FLAG_DEBUG2 4
#define FLAG_INTERRUPTIBLE 8
/* sigh, used in parse union so needs trivial constructors. */
class flagvals {
public:
int flags;
@@ -122,6 +125,61 @@ public:
int audit;
int path;
char *disconnected_path;
int signal;
// stupid not constructor constructors
void init(void)
{
flags = 0;
mode = MODE_UNSPECIFIED;
audit = 0;
path = 0;
disconnected_path = NULL;
signal = 0;
}
void init(const char *str)
{
init();
enum profile_mode pmode = str_to_mode(str);
if (strcmp(str, "debug") == 0) {
/* DEBUG2 is left for internal compiler use atm */
flags |= FLAG_DEBUG1;
} else if (pmode) {
mode = pmode;
} else if (strcmp(str, "audit") == 0) {
audit = 1;
} else if (strcmp(str, "chroot_relative") == 0) {
path |= PATH_CHROOT_REL;
} else if (strcmp(str, "namespace_relative") == 0) {
path |= PATH_NS_REL;
} else if (strcmp(str, "mediate_deleted") == 0) {
path |= PATH_MEDIATE_DELETED;
} else if (strcmp(str, "delegate_deleted") == 0) {
path |= PATH_DELEGATE_DELETED;
} else if (strcmp(str, "attach_disconnected") == 0) {
path |= PATH_ATTACH;
} else if (strcmp(str, "no_attach_disconnected") == 0) {
path |= PATH_NO_ATTACH;
} else if (strcmp(str, "chroot_attach") == 0) {
path |= PATH_CHROOT_NSATTACH;
} else if (strcmp(str, "chroot_no_attach") == 0) {
path |= PATH_CHROOT_NO_ATTACH;
} else if (strncmp(str, "attach_disconnected.path=", 25) == 0) {
/* TODO: make this a proper parse */
path |= PATH_ATTACH;
disconnected_path = strdup(str + 25);
} else if (strncmp(str, "kill.signal=", 12) == 0) {
/* TODO: make this a proper parse */
signal = find_signal_mapping(str + 12);
if (signal == -1)
yyerror("unknown signal specified for kill.signal=\'%s\'\n", str + 12);
} else if (strcmp(str, "interruptible") == 0) {
flags |= FLAG_INTERRUPTIBLE;
} else {
yyerror(_("Invalid profile flag: %s."), str);
}
}
ostream &dump(ostream &os)
{
@@ -135,6 +193,8 @@ public:
if (disconnected_path)
os << ", attach_disconnected.path=" << disconnected_path;
if (signal)
os << ", kill.signal=" << signal;
os << "\n";
return os;
@@ -148,6 +208,58 @@ public:
#endif
}
/* warning for now disconnected_path is just passed on (not copied),
* or leaked on error. It is not freed here, It is freed when the
* profile destroys it self.
*/
void merge(const flagvals &rhs)
{
if (merge_profile_mode(mode, rhs.mode) == MODE_CONFLICT)
yyerror(_("Profile flag '%s' conflicts with '%s'"),
profile_mode_table[mode],
profile_mode_table[rhs.mode]);
mode = merge_profile_mode(mode, rhs.mode);
audit = audit || rhs.audit;
path = path | rhs.path;
if ((path & (PATH_CHROOT_REL | PATH_NS_REL)) ==
(PATH_CHROOT_REL | PATH_NS_REL))
yyerror(_("Profile flag chroot_relative conflicts with namespace_relative"));
if ((path & (PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED)) ==
(PATH_MEDIATE_DELETED | PATH_DELEGATE_DELETED))
yyerror(_("Profile flag mediate_deleted conflicts with delegate_deleted"));
if ((path & (PATH_ATTACH | PATH_NO_ATTACH)) ==
(PATH_ATTACH | PATH_NO_ATTACH))
yyerror(_("Profile flag attach_disconnected conflicts with no_attach_disconnected"));
if ((path & (PATH_CHROOT_NSATTACH | PATH_CHROOT_NO_ATTACH)) ==
(PATH_CHROOT_NSATTACH | PATH_CHROOT_NO_ATTACH))
yyerror(_("Profile flag chroot_attach conflicts with chroot_no_attach"));
if (rhs.disconnected_path) {
if (disconnected_path) {
if (strcmp(disconnected_path, rhs.disconnected_path) != 0) {
yyerror(_("Profile flag attach_disconnected set to conflicting values: '%s' and '%s'"), disconnected_path, rhs.disconnected_path);
}
// same ignore rhs.disconnect_path
} else {
disconnected_path = rhs.disconnected_path;
}
}
if (rhs.signal) {
if (signal) {
if (signal != rhs.signal) {
yyerror(_("Profile flag kill.signal set to conflicting values: '%d' and '%d'"), signal, rhs.signal);
}
// same so do nothing
} else {
signal = rhs.signal;
}
}
/* if we move to dupping disconnected_path will need to have
* an assignment and copy constructor and a destructor
*/
}
};
struct capabilities {
@@ -199,7 +311,7 @@ public:
flagvals flags;
struct capabilities caps;
struct network net;
network_rule net;
struct aa_rlimits rlimits;
@@ -225,7 +337,7 @@ public:
parent = NULL;
flags = { 0, MODE_UNSPECIFIED, 0, 0, NULL };
flags.init();
rlimits = {0, {}};
std::fill(exec_table, exec_table + AA_EXEC_COUNT, (char *)NULL);
@@ -272,7 +384,6 @@ public:
flags.dump(cerr);
caps.dump();
net.dump();
if (entries)
debug_cod_entries(entries);
@@ -285,8 +396,6 @@ public:
hat_table.dump();
}
bool alloc_net_table();
std::string hname(void)
{
if (!parent)
@@ -319,6 +428,10 @@ public:
void post_parse_profile(void);
void add_implied_rules(void);
protected:
const char *warned_name = NULL;
virtual void warn_once(const char *name, const char *msg);
};

View File

@@ -35,8 +35,9 @@ class Profile;
#define RULE_TYPE_RULE 0
#define RULE_TYPE_PREFIX 1
#define RULE_TYPE_PERMS 2
#define RULE_TYPE_ALL 3
// RULE_TYPE_CLASS needs to be last because various class follow it
#define RULE_TYPE_CLASS 3
#define RULE_TYPE_CLASS 4
// rule_cast should only be used after a comparison of rule_type to ensure
// that it is valid. Change to dynamic_cast for debugging
@@ -289,6 +290,10 @@ public:
return true;
}
virtual bool add_prefix(const prefixes &p) {
const char *err;
return add_prefix(p, err);
}
int cmp(prefixes const &rhs) const {
return prefixes::cmp(rhs);

View File

@@ -121,7 +121,7 @@ int parse_signal_perms(const char *str_perms, perms_t *perms, int fail)
return parse_X_perms("signal", AA_VALID_SIGNAL_PERMS, str_perms, perms, fail);
}
static int find_signal_mapping(const char *sig)
int find_signal_mapping(const char *sig)
{
if (strncmp("rtmin+", sig, 6) == 0) {
char *end;

View File

@@ -31,6 +31,7 @@
typedef set<int> Signals;
int find_signal_mapping(const char *sig);
int parse_signal_perms(const char *str_perms, perms_t *perms, int fail);
class signal_rule: public perms_rule_t {

View File

@@ -0,0 +1,8 @@
#
#=Description basic ptrace all rule
#=EXRESULT FAIL
#
/usr/bin/foo {
all read readby trace tracedby ,
}

View File

@@ -0,0 +1,8 @@
#
#=Description basic ptrace all rule
#=EXRESULT FAIL
#
/usr/bin/foo {
owner all,
}

View File

@@ -0,0 +1,8 @@
#
#=Description basic all rule
#=EXRESULT PASS
#
/usr/bin/foo {
all,
}

View File

@@ -0,0 +1,8 @@
#
#=Description basic all rule
#=EXRESULT PASS
#
/usr/bin/foo {
audit all,
}

View File

@@ -0,0 +1,8 @@
#
#=Description basic all rule
#=EXRESULT PASS
#
/usr/bin/foo {
allow all,
}

View File

@@ -0,0 +1,8 @@
#
#=Description basic all rule
#=EXRESULT PASS
#
/usr/bin/foo {
deny all,
}

View File

@@ -0,0 +1,8 @@
#
#=Description basic all rule
#=EXRESULT PASS
#
/usr/bin/foo {
audit deny all,
}

View File

@@ -0,0 +1,8 @@
#
#=Description basic all rule
#=EXRESULT PASS
#
/usr/bin/foo {
audit allow all,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure conflicting mode flags cause an error
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(enforce, kill, interruptible) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure conflicting mode flags cause an error
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(complain, kill, interruptible) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure conflicting mode flags cause an error
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(enforce, complain, kill, unconfined, interruptible) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure bad signal value
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=0) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure bad signal value
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=foo) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure bad signal value
#=EXRESULT FAIL
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=hup.) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,12 @@
#
#=DESCRIPTION validate some uses of the profile flags.
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(interruptible) {
#include <includes/base>
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,12 @@
#
#=DESCRIPTION validate some uses of the profile flags.
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(interruptible audit) {
#include <includes/base>
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(enforce, interruptible) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(complain, interruptible) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill, interruptible) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(interruptible, enforce) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(interruptible, complain) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(interruptible, kill) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(interruptible, unconfined) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(enforce, interruptible) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION ensure flag does not conflict with other mdes, and flags
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(prompt, interruptible) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure conflicting mode flags cause an error
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(prompt, kill.signal=hup) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure signal.kill works with different flags and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(enforce, kill.signal=kill) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure kill.signal works with different flags and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=int, unconfined) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure kill.signal works with different modes and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=quit, kill) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure kill.signal works with different modes and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=hup, complain) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure kill.signal works with different modes and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=ill, enforce) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure kill.signal works with different modes and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill, kill.signal=trap) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure kill.signal works with different modes and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(complain, kill.signal=bus) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,10 @@
#
#=DESCRIPTION Ensure kill.signal works with different flags and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(enforce, kill.signal=usr1) {
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,12 @@
#
#=DESCRIPTION Ensure kill.signals works with different flags and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=stop audit) {
#include <includes/base>
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -0,0 +1,12 @@
#
#=DESCRIPTION Ensure kill.signal works with different flags and signals
#=EXRESULT PASS
# vim:syntax=subdomain
# Last Modified: Sun Apr 17 19:44:44 2005
#
/does/not/exist flags=(kill.signal=emt) {
#include <includes/base>
/usr/X11R6/lib/lib*so* r,
/does/not/exist r,
}

View File

@@ -20,7 +20,7 @@
# Makefile for LSM-based AppArmor profiles
NAME=apparmor-profiles
all: local docs
all: docs
COMMONDIR=../common/
include $(COMMONDIR)/Make.rules
@@ -86,7 +86,7 @@ local:
done
.PHONY: install
install: local
install:
install -m 755 -d ${PROFILES_DEST}
install -m 755 -d ${PROFILES_DEST}/disable
for dir in ${SUBDIRS} ; do \
@@ -122,7 +122,7 @@ CHECK_ABSTRACTIONS=$(shell find ${ABSTRACTIONS_SOURCE} -type f -print)
check: check-parser check-logprof check-abstractions.d check-tunables.d check-extras
.PHONY: check-parser
check-parser: test-dependencies local
check-parser: test-dependencies
@echo "*** Checking profiles from ${PROFILES_SOURCE} and ${EXTRAS_SOURCE} against apparmor_parser"
$(Q)for profile in ${CHECK_PROFILES} ; do \
[ -n "${VERBOSE}" ] && echo "Testing $${profile}" ; \
@@ -138,7 +138,7 @@ check-parser: test-dependencies local
done
.PHONY: check-logprof
check-logprof: test-dependencies local
check-logprof: test-dependencies
@echo "*** Checking profiles from ${PROFILES_SOURCE} against logprof"
$(Q)${LOGPROF} -d ${PROFILES_SOURCE} -f /dev/null || exit 1

View File

@@ -13,9 +13,12 @@
#
# For example, if the shipped /etc/apparmor.d/usr.sbin.smbd profile has:
# include <local/usr.sbin.smbd>
# or
# include if exists <local/usr.sbin.smbd>
#
# then an administrator can adjust /etc/apparmor.d/local/usr.sbin.smbd to
# contain any additional paths to be allowed, such as:
# then an administrator can adjust /etc/apparmor.d/local/usr.sbin.smbd
# (create the file if it doesn't exist yet) to contain any additional paths
# to be allowed, such as:
#
# /var/exports/** lrwk,
#

View File

@@ -96,7 +96,8 @@ do_test()
# Needed for clone(CLONE_NEWNS) and pivot_root()
cap=capability:sys_admin
file_perm="$file:rw /put_old/$file:rw"
file_perm="$file:rw $put_old/$file:rw"
socket_perm="$socket:rw $put_old/$socket:rw"
create_dir="$new_root:w $put_old:w"
# Ensure everything works as expected when unconfined
@@ -104,22 +105,24 @@ do_test "attach_disconnected" pass $file $att_dis_client $socket $loop_device $n
# TODO: adding attach_disconnected.path to a replaced unconfined
genprofile $file_perm unix:create $socket:rw $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket:rw $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:attach_disconnected
genprofile $file_perm unix:create $socket_perm $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket_perm $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:attach_disconnected
do_test "attach_disconnected" pass $file $att_dis_client $socket $loop_device $new_root $put_old
genprofile $file_perm unix:create $socket:rw $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket:rw $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:attach_disconnected flag:attach_disconnected.path=/foo/
genprofile $file_perm unix:create $socket_perm $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket_perm $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:attach_disconnected.path=/foo/
do_test "attach_disconnected.path rule at /" fail $file $att_dis_client $socket $loop_device $new_root $put_old
do_test "attach_disconnected.path" pass "/foo/$file" $att_dis_client $socket $loop_device $new_root $put_old
genprofile $file_perm unix:create $socket_perm $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket_perm $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:attach_disconnected.path=$put_old
genprofile $file_perm unix:create $socket:rw $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket:rw $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:no_attach_disconnected
do_test "attach_disconnected.path" pass $file $att_dis_client $socket $loop_device $new_root $put_old
genprofile $file_perm unix:create $socket_perm $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket_perm $create_dir $cap "pivot_root:ALL" "mount:ALL" flag:no_attach_disconnected
do_test "no_attach_disconnected" fail $file $att_dis_client $socket $loop_device $new_root $put_old
# Ensure default is no_attach_disconnected - no flags set
genprofile $file_perm unix:create $socket:rw $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket:rw $create_dir $cap "pivot_root:ALL" "mount:ALL"
genprofile $file_perm unix:create $socket_perm $att_dis_client:px -- image=$att_dis_client $file_perm unix:create $socket_perm $create_dir $cap "pivot_root:ALL" "mount:ALL"
do_test "no_attach_disconnected" fail $file $att_dis_client $socket $loop_device $new_root $put_old

View File

@@ -58,6 +58,7 @@ int userns_setns(char *client, char *pipename)
{
int userns, exit_status, ret;
char *parentpipe = NULL, *childpipe = NULL;
int parentpipefd;
if (get_pipes(pipename, &parentpipe, &childpipe) == -1) {
fprintf(stderr, "FAIL - failed to allocate pipes\n");
@@ -81,7 +82,14 @@ int userns_setns(char *client, char *pipename)
goto out;
}
if (read_from_pipe(parentpipe) == -1) { // wait for child to unshare
parentpipefd = open_read_pipe(parentpipe);
if (parentpipefd == -1) {
fprintf(stderr, "FAIL - couldn't open parent pipe\n");
ret = EXIT_FAILURE;
goto out;
}
if (read_from_pipe(parentpipefd) == -1) { // wait for child to unshare
fprintf(stderr, "FAIL - parent could not read from pipe\n");
ret = EXIT_FAILURE;
goto out;

View File

@@ -15,16 +15,26 @@ int get_pipes(const char *pipename, char **parentpipe, char **childpipe)
return 0;
}
int read_from_pipe(char *pipename)
int open_read_pipe(char *pipename)
{
int fd, ret;
int fd;
fd = open(pipename, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
perror("FAIL - open read pipe");
return EXIT_FAILURE;
}
return fd;
}
int read_from_pipe(int fd)
{
int ret;
char buf;
fd_set set;
struct timeval timeout;
fd = open(pipename, O_RDONLY | O_NONBLOCK);
if (fd == -1) {
perror("FAIL - open read pipe");
fprintf(stderr, "FAIL - invalid read fd\n");
return EXIT_FAILURE;
}
@@ -59,7 +69,7 @@ int write_to_pipe(char *pipename)
fd = open(pipename, O_WRONLY | O_NONBLOCK);
if (fd == -1) {
perror("FAIL - open write pipe");
fprintf(stderr, "FAIL - open write pipe %s - %m\n", pipename);
return EXIT_FAILURE;
}
close(fd);

View File

@@ -13,6 +13,7 @@ int main(int argc, char *argv[])
int ret;
char *pipename = "/tmp/userns_pipe";
char *parentpipe = NULL, *childpipe = NULL;
int childpipefd;
if (argc > 1)
pipename = argv[1];
@@ -26,6 +27,13 @@ int main(int argc, char *argv[])
if (mkfifo(childpipe, 0666) == -1)
perror("FAIL - setns child mkfifo");
childpipefd = open_read_pipe(childpipe);
if (childpipefd == -1) {
fprintf(stderr, "FAIL - couldn't open child pipe\n");
ret = EXIT_FAILURE;
goto out;
}
if (unshare(CLONE_NEWUSER) == -1) {
perror("FAIL - unshare");
ret = EXIT_FAILURE;
@@ -37,7 +45,7 @@ int main(int argc, char *argv[])
ret = EXIT_FAILURE;
goto out;
}
if (read_from_pipe(childpipe) == -1) { // wait for parent tell child can finish
if (read_from_pipe(childpipefd) == -1) { // wait for parent tell child can finish
fprintf(stderr, "FAIL - child could not read from pipe\n");
ret = EXIT_FAILURE;
goto out;

View File

@@ -1641,16 +1641,18 @@ def collapse_log(hashlog, ignore_null_profiles=True):
log_dict[aamode] = {}
for full_profile in hashlog[aamode].keys():
if hashlog[aamode][full_profile]['final_name'] == '':
final_name = hashlog[aamode][full_profile]['final_name']
if final_name == '':
continue # user chose "deny" or "unconfined" for this target, therefore ignore log events
if '//null-' in hashlog[aamode][full_profile]['final_name'] and ignore_null_profiles:
if '//null-' in final_name and ignore_null_profiles:
# ignore null-* profiles (probably nested childs)
# otherwise we'd accidentally create a null-* hat in the profile which is worse
# XXX drop this once we support nested childs
continue
profile, hat = split_name(hashlog[aamode][full_profile]['final_name']) # XXX limited to two levels to avoid an Exception on nested child profiles or nested null-*
profile, hat = split_name(final_name) # XXX limited to two levels to avoid an Exception on nested child profiles or nested null-*
# TODO: support nested child profiles
# used to avoid to accidentally initialize aa[profile][hat] or calling is_known_rule() on events for a non-existing profile
@@ -1658,9 +1660,9 @@ def collapse_log(hashlog, ignore_null_profiles=True):
if aa.get(profile) and aa[profile].get(hat):
hat_exists = True
if not log_dict[aamode].get(full_profile):
if not log_dict[aamode].get(final_name):
# with execs in ix mode, we already have ProfileStorage initialized and should keep the content it already has
log_dict[aamode][full_profile] = ProfileStorage(profile, hat, 'collapse_log()')
log_dict[aamode][final_name] = ProfileStorage(profile, hat, 'collapse_log()')
for path in hashlog[aamode][full_profile]['path'].keys():
for owner in hashlog[aamode][full_profile]['path'][path]:
@@ -1673,18 +1675,18 @@ def collapse_log(hashlog, ignore_null_profiles=True):
file_event = FileRule(path, mode, None, FileRule.ALL, owner=owner, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'file', file_event):
log_dict[aamode][full_profile]['file'].add(file_event)
log_dict[aamode][final_name]['file'].add(file_event)
# TODO: check for existing rules with this path, and merge them into one rule
for cap in hashlog[aamode][full_profile]['capability'].keys():
cap_event = CapabilityRule(cap, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'capability', cap_event):
log_dict[aamode][full_profile]['capability'].add(cap_event)
log_dict[aamode][final_name]['capability'].add(cap_event)
for cp in hashlog[aamode][full_profile]['change_profile'].keys():
cp_event = ChangeProfileRule(None, ChangeProfileRule.ALL, cp, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'change_profile', cp_event):
log_dict[aamode][full_profile]['change_profile'].add(cp_event)
log_dict[aamode][final_name]['change_profile'].add(cp_event)
dbus = hashlog[aamode][full_profile]['dbus']
for access in dbus: # noqa: E271
@@ -1707,35 +1709,41 @@ def collapse_log(hashlog, ignore_null_profiles=True):
raise AppArmorBug('unexpected dbus access: {}'.format(access))
if not hat_exists or not is_known_rule(aa[profile][hat], 'dbus', dbus_event):
log_dict[aamode][full_profile]['dbus'].add(dbus_event)
log_dict[aamode][final_name]['dbus'].add(dbus_event)
nd = hashlog[aamode][full_profile]['network']
for family in nd.keys():
for sock_type in nd[family].keys():
net_event = NetworkRule(family, sock_type, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'network', net_event):
log_dict[aamode][full_profile]['network'].add(net_event)
log_dict[aamode][final_name]['network'].add(net_event)
ptrace = hashlog[aamode][full_profile]['ptrace']
for peer in ptrace.keys():
if '//null-' in peer:
continue # ignore null-* peers
for access in ptrace[peer].keys():
ptrace_event = PtraceRule(access, peer, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'ptrace', ptrace_event):
log_dict[aamode][full_profile]['ptrace'].add(ptrace_event)
log_dict[aamode][final_name]['ptrace'].add(ptrace_event)
sig = hashlog[aamode][full_profile]['signal']
for peer in sig.keys():
if '//null-' in peer:
continue # ignore null-* peers
for access in sig[peer].keys():
for signal in sig[peer][access].keys():
signal_event = SignalRule(access, signal, peer, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'signal', signal_event):
log_dict[aamode][full_profile]['signal'].add(signal_event)
log_dict[aamode][final_name]['signal'].add(signal_event)
userns = hashlog[aamode][full_profile]['userns']
for access in userns.keys():
userns_event = UserNamespaceRule(access)
if not hat_exists or not is_known_rule(aa[profile][hat], 'userns', userns_event):
log_dict[aamode][full_profile]['userns'].add(userns_event)
log_dict[aamode][final_name]['userns'].add(userns_event)
mqueue = hashlog[aamode][full_profile]['mqueue']
for access in mqueue.keys():
@@ -1743,7 +1751,7 @@ def collapse_log(hashlog, ignore_null_profiles=True):
for mqueue_name in mqueue[access][mqueue_type]:
mqueue_event = MessageQueueRule(access, mqueue_type, MessageQueueRule.ALL, mqueue_name, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'mqueue', mqueue_event):
log_dict[aamode][full_profile]['mqueue'].add(mqueue_event)
log_dict[aamode][final_name]['mqueue'].add(mqueue_event)
io_uring = hashlog[aamode][full_profile]['io_uring']
for access in io_uring.keys():
@@ -1752,7 +1760,7 @@ def collapse_log(hashlog, ignore_null_profiles=True):
label = IOUringRule.ALL
io_uring_event = IOUringRule(access, label, log_event=True)
if not hat_exists or not is_known_rule(aa[profile][hat], 'io_uring', io_uring_event):
log_dict[aamode][full_profile]['io_uring'].add(io_uring_event)
log_dict[aamode][final_name]['io_uring'].add(io_uring_event)
return log_dict
@@ -2117,6 +2125,7 @@ def match_line_against_rule_classes(line, profile, file, lineno, in_preamble):
for rule_name in (
'abi',
'all',
'alias',
'boolean',
'variable',

View File

@@ -191,8 +191,8 @@ class ReadLog:
return
elif e['class'] and e['class'] == 'namespace':
if e['denied_mask'].startswith('userns'):
self.hashlog[aamode][full_profile]['userns'][e['denied_mask'].removeprefix('userns_')] = True
if e['denied_mask'].startswith('userns_'):
self.hashlog[aamode][full_profile]['userns'][ e['denied_mask'][7:] ] = True # [7:] removes the 'userns_' prefix
return
elif e['class'] and e['class'].endswith('mqueue'):

View File

@@ -18,6 +18,7 @@ from apparmor.common import AppArmorBug, AppArmorException
from apparmor.regex import parse_profile_start_line
from apparmor.rule import quote_if_needed
from apparmor.rule.abi import AbiRule, AbiRuleset
from apparmor.rule.all import AllRule, AllRuleset
from apparmor.rule.capability import CapabilityRule, CapabilityRuleset
from apparmor.rule.change_profile import ChangeProfileRule, ChangeProfileRuleset
from apparmor.rule.dbus import DbusRule, DbusRuleset
@@ -37,6 +38,7 @@ _ = init_translation()
ruletypes = {
'abi': {'rule': AbiRule, 'ruleset': AbiRuleset},
'inc_ie': {'rule': IncludeRule, 'ruleset': IncludeRuleset},
'all': {'rule': AllRule, 'ruleset': AllRuleset},
'capability': {'rule': CapabilityRule, 'ruleset': CapabilityRuleset},
'change_profile': {'rule': ChangeProfileRule, 'ruleset': ChangeProfileRuleset},
'dbus': {'rule': DbusRule, 'ruleset': DbusRuleset},

View File

@@ -34,6 +34,7 @@ RE_XATTRS = r'(\s+xattrs\s*=\s*\((?P<xattrs>([^)=]+(=[^)=]+)?\s?)+)\)\s*)?'
RE_FLAGS = r'(\s+(flags\s*=\s*)?\((?P<flags>[^)]+)\))?'
RE_PROFILE_END = re.compile(r'^\s*\}' + RE_EOL)
RE_PROFILE_ALL = re.compile(RE_AUDIT_DENY + r'all' + RE_COMMA_EOL)
RE_PROFILE_CAP = re.compile(RE_AUDIT_DENY + r'capability(?P<capability>(\s+\S+)+)?' + RE_COMMA_EOL)
RE_PROFILE_ALIAS = re.compile(r'^\s*alias\s+(?P<orig_path>"??.+?"??)\s+->\s*(?P<target>"??.+?"??)' + RE_COMMA_EOL)
RE_PROFILE_RLIMIT = re.compile(r'^\s*set\s+rlimit\s+(?P<rlimit>[a-z]+)\s*<=\s*(?P<value>[^ ]+(\s+[a-zA-Z]+)?)' + RE_COMMA_EOL)

View File

@@ -0,0 +1,83 @@
# ----------------------------------------------------------------------
# Copyright (C) 2023 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# ----------------------------------------------------------------------
from apparmor.regex import RE_PROFILE_ALL
from apparmor.rule import BaseRule, BaseRuleset, parse_modifiers
from apparmor.translations import init_translation
_ = init_translation()
class AllRule(BaseRule):
"""Class to handle and store a single all rule"""
# This class doesn't have any localvars, therefore it doesn't need 'ALL'
can_glob = False
rule_name = 'all'
_match_re = RE_PROFILE_ALL
def __init__(self, audit=False, deny=False, allow_keyword=False,
comment='', log_event=None):
super().__init__(audit=audit, deny=deny, allow_keyword=allow_keyword,
comment=comment, log_event=log_event)
# no localvars -> nothing more to do
@classmethod
def _create_instance(cls, raw_rule, matches):
"""parse raw_rule and return instance of this class"""
audit, deny, allow_keyword, comment = parse_modifiers(matches)
return cls(audit=audit, deny=deny,
allow_keyword=allow_keyword,
comment=comment)
def get_clean(self, depth=0):
"""return rule (in clean/default formatting)"""
space = ' ' * depth
return ('%s%sall,%s' % (space, self.modifiers_str(), self.comment))
def _is_covered_localvars(self, other_rule):
"""check if other_rule is covered by this rule object"""
# no localvars, so there can't be a difference
return True
def _is_equal_localvars(self, rule_obj, strict):
"""compare if rule-specific variables are equal"""
# no localvars, so there can't be a difference
return True
def severity(self, sev_db):
# allowing _everything_ is the worst thing you could do, therefore hardcode highest severity
severity = 10
return severity
def _logprof_header_localvars(self):
return _('All'), _('Allow everything')
class AllRuleset(BaseRuleset):
"""Class to handle and store a collection of all rules"""
def get_glob(self, path_or_rule):
# There's nothing to glob in all rules
raise NotImplementedError

321
utils/test/test-all.py Normal file
View File

@@ -0,0 +1,321 @@
#!/usr/bin/python3
# ----------------------------------------------------------------------
# Copyright (C) 2023 Christian Boltz <apparmor@cboltz.de>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of version 2 of the GNU General Public
# License as published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# ----------------------------------------------------------------------
import unittest
from collections import namedtuple
import apparmor.severity as severity
from apparmor.common import AppArmorBug, AppArmorException
from apparmor.rule.all import AllRule, AllRuleset
from apparmor.translations import init_translation
from common_test import AATest, setup_all_loops
_ = init_translation()
exp = namedtuple(
'exp', ('audit', 'allow_keyword', 'deny', 'comment',
# no localvars
))
# --- tests for single AllRule --- #
class AllTest(AATest):
def _compare_obj(self, obj, expected):
self.assertEqual(expected.allow_keyword, obj.allow_keyword)
self.assertEqual(expected.audit, obj.audit)
self.assertEqual(expected.deny, obj.deny)
self.assertEqual(expected.comment, obj.comment)
class AllTestParse(AllTest):
tests = (
# rawrule audit allow deny comment
('all,', exp(False, False, False, '', )),
('deny all, # comment', exp(False, False, True, ' # comment', )),
('audit allow all,', exp(True, True, False, '', )),
('audit allow all,', exp(True, True, False, '', )),
)
def _run_test(self, rawrule, expected):
self.assertTrue(AllRule.match(rawrule))
obj = AllRule.create_instance(rawrule)
self.assertEqual(rawrule.strip(), obj.raw_rule)
self._compare_obj(obj, expected)
class AllTestParseInvalid(AllTest):
tests = (
('all -> ,', AppArmorException),
('owner all,', AppArmorException),
('all foo ,', AppArmorException),
)
def _run_test(self, rawrule, expected):
self.assertFalse(AllRule.match(rawrule))
with self.assertRaises(expected):
AllRule.create_instance(rawrule)
# we won't ever support converting a log event to an 'all,' rule
# class AllTestParseFromLog(AllTest):
class AllFromInit(AllTest):
tests = (
# AllRule object audit allow deny comment
(AllRule(deny=True), exp(False, False, True, '', )),
(AllRule(), exp(False, False, False, '', )),
)
def _run_test(self, obj, expected):
self._compare_obj(obj, expected)
# no localvars -> no way to hand over invalid values, or to miss a required parameter
# class InvalidAllInit(AATest):
class InvalidAllTest(AATest):
def _check_invalid_rawrule(self, rawrule):
obj = None
self.assertFalse(AllRule.match(rawrule))
with self.assertRaises(AppArmorException):
obj = AllRule.create_instance(rawrule)
self.assertIsNone(obj, 'AllRule handed back an object unexpectedly')
def test_invalid_net_missing_comma(self):
self._check_invalid_rawrule('all') # missing comma
def test_invalid_net_non_AllRule(self):
self._check_invalid_rawrule('dbus,') # not a all rule
# no localvars, therefore we can't break anything inside the class variables
# def test_empty_all_data_1(self):
class WriteAllTestAATest(AATest):
tests = (
# raw rule clean rule
(' all , # foo ', 'all, # foo'),
(' audit all ,', 'audit all,'),
(' deny all ,# foo bar', 'deny all, # foo bar'),
(' allow all ,# foo bar', 'allow all, # foo bar'),
(' allow all ,', 'allow all,'),
)
def _run_test(self, rawrule, expected):
self.assertTrue(AllRule.match(rawrule))
obj = AllRule.create_instance(rawrule)
clean = obj.get_clean()
raw = obj.get_raw()
self.assertEqual(expected.strip(), clean, 'unexpected clean rule')
self.assertEqual(rawrule.strip(), raw, 'unexpected raw rule')
def test_write_manually(self):
obj = AllRule(allow_keyword=True)
expected = ' allow all,'
self.assertEqual(expected, obj.get_clean(2), 'unexpected clean rule')
self.assertEqual(expected, obj.get_raw(2), 'unexpected raw rule')
class AllCoveredTest(AATest):
def _run_test(self, param, expected):
obj = AllRule.create_instance(self.rule)
check_obj = AllRule.create_instance(param)
self.assertTrue(AllRule.match(param))
self.assertEqual(obj.is_equal(check_obj), expected[0], 'Mismatch in is_equal, expected {}'.format(expected[0]))
self.assertEqual(obj.is_equal(check_obj, True), expected[1], 'Mismatch in is_equal/strict, expected {}'.format(expected[1]))
self.assertEqual(obj.is_covered(check_obj), expected[2], 'Mismatch in is_covered, expected {}'.format(expected[2]))
self.assertEqual(obj.is_covered(check_obj, True, True), expected[3], 'Mismatch in is_covered/exact, expected {}'.format(expected[3]))
class AllCoveredTest_01(AllCoveredTest):
rule = 'all,'
tests = (
# rule equal strict equal covered covered exact
(' all,', (True, True, True, True)),
(' allow all,', (True, False, True, True)),
('audit all,', (False, False, False, False)),
('audit deny all,', (False, False, False, False)),
(' deny all,', (False, False, False, False)),
)
class AllCoveredTest_02(AllCoveredTest):
rule = 'audit all,'
tests = (
# rule equal strict equal covered covered exact
( 'all,', (False, False, True, False)),
('audit all,', (True, True, True, True)),
)
class AllCoveredTest_03(AllCoveredTest):
rule = 'deny all,'
tests = (
# rule equal strict equal covered covered exact
( 'deny all,', (True, True, True, True)),
('audit deny all,', (False, False, False, False)),
( 'all,', (False, False, False, False)), # XXX should covered be true here?
)
class AllCoveredTest_Invalid(AATest):
def test_invalid_is_covered(self):
raw_rule = 'all,'
class SomeOtherClass(AllRule):
pass
obj = AllRule.create_instance(raw_rule)
testobj = SomeOtherClass.create_instance(raw_rule) # different type
with self.assertRaises(AppArmorBug):
obj.is_covered(testobj)
def test_invalid_is_equal(self):
raw_rule = 'all,'
class SomeOtherClass(AllRule):
pass
obj = AllRule.create_instance(raw_rule)
testobj = SomeOtherClass.create_instance(raw_rule) # different type
with self.assertRaises(AppArmorBug):
obj.is_equal(testobj)
class AllSeverityTest(AATest):
tests = (
('all,', 10),
)
def _run_test(self, params, expected):
sev_db = severity.Severity('../severity.db', 'unknown')
obj = AllRule.create_instance(params)
rank = obj.severity(sev_db)
self.assertEqual(rank, expected)
class AllLogprofHeaderTest(AATest):
tests = (
('all,', [ 'All', _('Allow everything'), ]),
('deny all,', [_('Qualifier'), 'deny', 'All', _('Allow everything'), ]),
('allow all,', [_('Qualifier'), 'allow', 'All', _('Allow everything'), ]),
('audit deny all,', [_('Qualifier'), 'audit deny', 'All', _('Allow everything'), ]),
)
def _run_test(self, params, expected):
obj = AllRule.create_instance(params)
self.assertEqual(obj.logprof_header(), expected)
# --- tests for AllRuleset --- #
class AllRulesTest(AATest):
def test_empty_ruleset(self):
ruleset = AllRuleset()
ruleset_2 = AllRuleset()
self.assertEqual([], ruleset.get_raw(2))
self.assertEqual([], ruleset.get_clean(2))
self.assertEqual([], ruleset_2.get_raw(2))
self.assertEqual([], ruleset_2.get_clean(2))
def test_ruleset_1(self):
ruleset = AllRuleset()
rules = (
'all,',
'all,',
)
expected_raw = [
'all,',
'all,',
'',
]
expected_clean = [
'all,',
'all,',
'',
]
for rule in rules:
ruleset.add(AllRule.create_instance(rule))
self.assertEqual(expected_raw, ruleset.get_raw())
self.assertEqual(expected_clean, ruleset.get_clean())
def test_ruleset_2(self):
ruleset = AllRuleset()
rules = (
'all,',
'allow all,',
'deny all, # example comment',
)
expected_raw = [
' all,',
' allow all,',
' deny all, # example comment',
'',
]
expected_clean = [
' deny all, # example comment',
'',
' all,',
' allow all,',
'',
]
for rule in rules:
ruleset.add(AllRule.create_instance(rule))
self.assertEqual(expected_raw, ruleset.get_raw(1))
self.assertEqual(expected_clean, ruleset.get_clean(1))
class AllGlobTestAATest(AATest):
def setUp(self):
self.ruleset = AllRuleset()
def test_glob(self):
with self.assertRaises(NotImplementedError):
# get_glob is not available for all rules
self.ruleset.get_glob('all,')
def test_glob_ext(self):
with self.assertRaises(NotImplementedError):
# get_glob_ext is not available for all rules
self.ruleset.get_glob_ext('all,')
class AllDeleteTestAATest(AATest):
pass
setup_all_loops(__name__)
if __name__ == '__main__':
unittest.main(verbosity=1)

View File

@@ -65,7 +65,7 @@ class TestLogprof(AATest):
self.process.stdin.close()
self.process.stdout.close()
self.process.terminate()
self.process.wait(timeout=0.2)
self.process.wait(timeout=0.3)
def _run_test(self, params, expected):
auditlog = './logprof/%s.auditlog' % params
@@ -96,7 +96,7 @@ class TestLogprof(AATest):
raise Exception('Unknown line in json log %s: %s' % (jsonlog, line))
# give logprof some time to write the updated profile and terminate
self.process.wait(timeout=0.2)
self.process.wait(timeout=0.3)
self.assertEqual(self.process.returncode, 0)
for file in expected:

View File

@@ -164,6 +164,12 @@ exception_not_raised = (
'profile/flags/flags_bad54.sd',
'profile/flags/flags_bad55.sd',
'profile/flags/flags_bad56.sd',
'profile/flags/flags_bad64.sd',
'profile/flags/flags_bad65.sd',
'profile/flags/flags_bad66.sd',
'profile/flags/flags_bad67.sd',
'profile/flags/flags_bad68.sd',
'profile/flags/flags_bad69.sd',
'profile/flags/flags_bad_disconnected_path1.sd',
'profile/flags/flags_bad_disconnected_path2.sd',
'profile/flags/flags_bad_disconnected_path3.sd',

View File

@@ -1,30 +1,25 @@
" ----------------------------------------------------------------------
" Copyright (c) 2005 Novell, Inc. All Rights Reserved.
" Copyright (c) 2006-2012 Christian Boltz. All Rights Reserved.
"
" Copyright (c) 2006-2023 Christian Boltz. All Rights Reserved.
"
" This program is free software; you can redistribute it and/or
" modify it under the terms of version 2 of the GNU General Public
" License as published by the Free Software Foundation.
"
"
" This program is distributed in the hope that it will be useful,
" but WITHOUT ANY WARRANTY; without even the implied warranty of
" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
" GNU General Public License for more details.
"
"
" You should have received a copy of the GNU General Public License
" along with this program; if not, contact Novell, Inc.
"
" To contact Novell about this file by physical or electronic mail,
" you may find current contact information at www.novell.com.
"
" To contact Christian Boltz about this file by physical or electronic
" mail, you may find current contact information at www.cboltz.de/en/kontakt.
"
" If you want to report a bug via bugzilla.novell.com, please assign it
" to suse-beta[AT]cboltz.de (replace [AT] with @).
" If you want to report a bug for apparmor.vim, please do so at
" - https://gitlab.com/apparmor/apparmor/ or
" - https://bugzilla.opensuse.org (assign it to suse-beta[AT]cboltz.de)
" ----------------------------------------------------------------------
"
" stick this file into ~/.vim/syntax/ and add these commands into your .vimrc
" stick this file into ~/.vim/syntax/ and add these commands into your .vimrc
" to have vim automagically use this syntax file for these directories:
"
" autocmd BufNewFile,BufRead /etc/apparmor.d/* set syntax=apparmor
@@ -49,6 +44,7 @@ syntax case match
" hi sdComment2 ctermfg=darkblue
hi sdGlob ctermfg=darkmagenta
hi sdAlias ctermfg=darkmagenta
hi sdAll ctermfg=darkred ctermbg=yellow
hi sdEntryWriteExec ctermfg=black ctermbg=yellow
hi sdEntryUX ctermfg=darkred cterm=underline
hi sdEntryUXe ctermfg=darkred
@@ -117,12 +113,19 @@ syn match sdAlias /\v^\s*alias\s+@@FILENAME@@\s+-\>\s+@@FILENAME@@@@EOL@@/ conta
" syn match sdComment /#.*/
syn cluster sdEntry contains=sdEntryWriteExec,sdEntryR,sdEntryW,sdEntryIX,sdEntryPX,sdEntryPXe,sdEntryUX,sdEntryUXe,sdEntryM,sdCap,sdSetCap,sdExtHat,sdRLimit,sdNetwork,sdNetworkDanger,sdEntryChangeProfile
" List of all (supported) rules inside a profile.
" XXX When adding support for a new rule type, also add it here. XXX
" XXX Otherwise it will be highlighted as an error. XXX
syn cluster sdEntry contains=sdAll,sdEntryWriteExec,sdEntryR,sdEntryW,sdEntryIX,sdEntryPX,sdEntryPXe,sdEntryUX,sdEntryUXe,sdEntryM,sdCap,sdSetCap,sdExtHat,sdRLimit,sdNetwork,sdNetworkDanger,sdEntryChangeProfile
" TODO: support audit and deny keywords for all rules (not only for files)
" TODO: highlight audit and deny keywords everywhere
" 'all' rule
syn match sdAll /\v^\s*@@auditdeny@@all@@EOL@@/ contains=sdComment nextgroup=@sdEntry,sdComment,sdError,sdInclude
" Capability line
" normal capabilities - really keep this list? syn match sdCap should be enough... (difference: sdCapKey words would loose underlining)