mirror of
https://gitlab.com/apparmor/apparmor
synced 2025-09-01 14:55:10 +00:00
Compare commits
48 Commits
v4.0.0-alp
...
v4.0.0-alp
Author | SHA1 | Date | |
---|---|---|---|
|
884adcc58f | ||
|
f5be84acdc | ||
|
5d940b2a47 | ||
|
583d116871 | ||
|
e361644d5a | ||
|
4e758a838d | ||
|
197d00d21a | ||
|
a9c5388f69 | ||
|
75ca0e7919 | ||
|
fb5f59024c | ||
|
a71ac76e6d | ||
|
2be9c431ca | ||
|
05de4b82e7 | ||
|
820f1fb5f2 | ||
|
65905b0c55 | ||
|
1945ecbf19 | ||
|
11976c42e3 | ||
|
327588f019 | ||
|
84e22b4cca | ||
|
248625ae00 | ||
|
9ab72ffc7c | ||
|
9be09aa909 | ||
|
2bf35277a0 | ||
|
9db134223c | ||
|
ef56e60e06 | ||
|
96965c3da2 | ||
|
a9494f5523 | ||
|
57985480ca | ||
|
f10467556c | ||
|
30707be87f | ||
|
f61fd42061 | ||
|
847ab59e1c | ||
|
427b58a4b6 | ||
|
aa20721be1 | ||
|
5957aa49f5 | ||
|
e133a9fc68 | ||
|
32307601a0 | ||
|
5b139521aa | ||
|
2d7bd40606 | ||
|
24806f6f61 | ||
|
fbe68f0078 | ||
|
74265e8ded | ||
|
41df2ca366 | ||
|
adf19138d5 | ||
|
1758b66c9d | ||
|
dd9b7b358f | ||
|
b45c10d4de | ||
|
c175e414c8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -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
|
||||
|
@@ -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".]
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -1 +1 @@
|
||||
4.0.0~alpha2
|
||||
4.0.0~alpha3
|
||||
|
@@ -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 ;\
|
||||
|
@@ -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
|
||||
|
@@ -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
130
parser/all_rule.cc
Normal 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
70
parser/all_rule.h
Normal 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 */
|
@@ -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
235
parser/bignum.h
Normal 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 */
|
@@ -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"
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
};
|
109
parser/network.h
109
parser/network.h
@@ -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 */
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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++;
|
||||
|
@@ -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)
|
||||
|
@@ -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)
|
||||
{
|
||||
|
@@ -33,6 +33,7 @@
|
||||
#include "parser.h"
|
||||
#include "profile.h"
|
||||
#include "parser_yacc.h"
|
||||
#include "network.h"
|
||||
|
||||
/* #define DEBUG */
|
||||
#ifdef DEBUG
|
||||
|
@@ -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 != ®ex_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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
123
parser/profile.h
123
parser/profile.h
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
8
parser/tst/simple_tests/all/bad_01.sd
Normal file
8
parser/tst/simple_tests/all/bad_01.sd
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
#=Description basic ptrace all rule
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
/usr/bin/foo {
|
||||
all read readby trace tracedby ,
|
||||
|
||||
}
|
8
parser/tst/simple_tests/all/bad_02.sd
Normal file
8
parser/tst/simple_tests/all/bad_02.sd
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
#=Description basic ptrace all rule
|
||||
#=EXRESULT FAIL
|
||||
#
|
||||
/usr/bin/foo {
|
||||
owner all,
|
||||
|
||||
}
|
8
parser/tst/simple_tests/all/ok_01.sd
Normal file
8
parser/tst/simple_tests/all/ok_01.sd
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
#=Description basic all rule
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
all,
|
||||
|
||||
}
|
8
parser/tst/simple_tests/all/ok_02.sd
Normal file
8
parser/tst/simple_tests/all/ok_02.sd
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
#=Description basic all rule
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
audit all,
|
||||
|
||||
}
|
8
parser/tst/simple_tests/all/ok_03.sd
Normal file
8
parser/tst/simple_tests/all/ok_03.sd
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
#=Description basic all rule
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
allow all,
|
||||
|
||||
}
|
8
parser/tst/simple_tests/all/ok_04.sd
Normal file
8
parser/tst/simple_tests/all/ok_04.sd
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
#=Description basic all rule
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
deny all,
|
||||
|
||||
}
|
8
parser/tst/simple_tests/all/ok_05.sd
Normal file
8
parser/tst/simple_tests/all/ok_05.sd
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
#=Description basic all rule
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
audit deny all,
|
||||
|
||||
}
|
8
parser/tst/simple_tests/all/ok_06.sd
Normal file
8
parser/tst/simple_tests/all/ok_06.sd
Normal file
@@ -0,0 +1,8 @@
|
||||
#
|
||||
#=Description basic all rule
|
||||
#=EXRESULT PASS
|
||||
#
|
||||
/usr/bin/foo {
|
||||
audit allow all,
|
||||
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_bad64.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_bad64.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_bad65.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_bad65.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_bad66.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_bad66.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_bad67.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_bad67.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_bad68.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_bad68.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_bad69.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_bad69.sd
Normal 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,
|
||||
}
|
12
parser/tst/simple_tests/profile/flags/flags_ok29.sd
Normal file
12
parser/tst/simple_tests/profile/flags/flags_ok29.sd
Normal 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,
|
||||
}
|
12
parser/tst/simple_tests/profile/flags/flags_ok30.sd
Normal file
12
parser/tst/simple_tests/profile/flags/flags_ok30.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok31.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok31.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok32.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok32.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok33.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok33.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok34.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok34.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok35.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok35.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok36.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok36.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok37.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok37.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok38.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok38.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok39.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok39.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok40.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok40.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok41.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok41.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok42.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok42.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok43.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok43.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok44.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok44.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok45.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok45.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok46.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok46.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok47.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok47.sd
Normal 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,
|
||||
}
|
10
parser/tst/simple_tests/profile/flags/flags_ok48.sd
Normal file
10
parser/tst/simple_tests/profile/flags/flags_ok48.sd
Normal 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,
|
||||
}
|
12
parser/tst/simple_tests/profile/flags/flags_ok49.sd
Normal file
12
parser/tst/simple_tests/profile/flags/flags_ok49.sd
Normal 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,
|
||||
}
|
12
parser/tst/simple_tests/profile/flags/flags_ok50.sd
Normal file
12
parser/tst/simple_tests/profile/flags/flags_ok50.sd
Normal 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,
|
||||
}
|
@@ -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
|
||||
|
||||
|
@@ -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,
|
||||
#
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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',
|
||||
|
@@ -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'):
|
||||
|
@@ -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},
|
||||
|
@@ -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)
|
||||
|
83
utils/apparmor/rule/all.py
Normal file
83
utils/apparmor/rule/all.py
Normal 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
321
utils/test/test-all.py
Normal 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)
|
@@ -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:
|
||||
|
@@ -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',
|
||||
|
@@ -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)
|
||||
|
Reference in New Issue
Block a user