diff --git a/.gitignore b/.gitignore index 42eda4c47..97ad9ad4f 100644 --- a/.gitignore +++ b/.gitignore @@ -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 diff --git a/parser/Makefile b/parser/Makefile index add64b7e6..d8a7e7809 100644 --- a/parser/Makefile +++ b/parser/Makefile @@ -102,12 +102,12 @@ SRCS = parser_common.c parser_include.c parser_interface.c parser_lex.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 bignum.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 \ @@ -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 $@ diff --git a/parser/all_rule.cc b/parser/all_rule.cc new file mode 100644 index 000000000..0f20c6359 --- /dev/null +++ b/parser/all_rule.cc @@ -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 +#include +#include +#include + + + +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 +} diff --git a/parser/all_rule.h b/parser/all_rule.h new file mode 100644 index 000000000..53cc23e3e --- /dev/null +++ b/parser/all_rule.h @@ -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 */ diff --git a/parser/apparmor.d.pod b/parser/apparmor.d.pod index f351d71f2..b6bb3ef39 100644 --- a/parser/apparmor.d.pod +++ b/parser/apparmor.d.pod @@ -125,7 +125,7 @@ B = [ ( I | I ',' | I ) B = ( I | I ) [ '\r' ] '\n' -B = ( I | I | I | I | I | I | I | I | I | I | I | I | I) +B = ( I | I | I | I | I | I | I | I | I | I | I | I | I | I) B = ( I | I | I ) @@ -346,6 +346,8 @@ B = ( 'safe' | 'unsafe' ) B = I +B = 'all' + =back All resources and programs need a full path. There may be any number of @@ -1604,6 +1606,26 @@ Not all kernels support B mode and the parser will downgrade rules to B mode in that situation. If no exec mode is specified, the default is B 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 diff --git a/parser/io_uring.cc b/parser/io_uring.cc index d7715b69d..ec3af65bf 100644 --- a/parser/io_uring.cc +++ b/parser/io_uring.cc @@ -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" diff --git a/parser/io_uring.h b/parser/io_uring.h index d363c5443..7299e16b0 100644 --- a/parser/io_uring.h +++ b/parser/io_uring.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 diff --git a/parser/parser.h b/parser/parser.h index d69db3ba0..b3c19eea3 100644 --- a/parser/parser.h +++ b/parser/parser.h @@ -461,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); diff --git a/parser/parser_lex.l b/parser/parser_lex.l index b00743338..de7141d33 100644 --- a/parser/parser_lex.l +++ b/parser/parser_lex.l @@ -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++; diff --git a/parser/parser_misc.c b/parser/parser_misc.c index ed80d068f..d66c990d7 100644 --- a/parser/parser_misc.c +++ b/parser/parser_misc.c @@ -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) { diff --git a/parser/parser_yacc.y b/parser/parser_yacc.y index 1238e5b59..f16195927 100644 --- a/parser/parser_yacc.y +++ b/parser/parser_yacc.y @@ -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 @@ -188,6 +187,7 @@ bool check_x_qualifier(struct cod_entry *entry, const char *&errror); #include "mqueue.h" #include "io_uring.h" #include "network.h" + #include "all_rule.h" } %union { @@ -207,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; @@ -303,6 +304,7 @@ bool check_x_qualifier(struct cod_entry *entry, const char *&errror); %type io_uring_perms %type opt_io_uring_perm %type io_uring_rule +%type all_rule %% @@ -656,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); @@ -677,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 @@ -722,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 { @@ -1511,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 {} @@ -1773,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); -} diff --git a/parser/profile.cc b/parser/profile.cc index cf8110ef2..95c4e0756 100644 --- a/parser/profile.cc +++ b/parser/profile.cc @@ -347,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"), diff --git a/parser/rule.h b/parser/rule.h index def3190ff..05dbed13d 100644 --- a/parser/rule.h +++ b/parser/rule.h @@ -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); diff --git a/parser/tst/simple_tests/all/bad_01.sd b/parser/tst/simple_tests/all/bad_01.sd new file mode 100644 index 000000000..9502657eb --- /dev/null +++ b/parser/tst/simple_tests/all/bad_01.sd @@ -0,0 +1,8 @@ +# +#=Description basic ptrace all rule +#=EXRESULT FAIL +# +/usr/bin/foo { + all read readby trace tracedby , + + } diff --git a/parser/tst/simple_tests/all/bad_02.sd b/parser/tst/simple_tests/all/bad_02.sd new file mode 100644 index 000000000..d8fc8cdf0 --- /dev/null +++ b/parser/tst/simple_tests/all/bad_02.sd @@ -0,0 +1,8 @@ +# +#=Description basic ptrace all rule +#=EXRESULT FAIL +# +/usr/bin/foo { + owner all, + + } diff --git a/parser/tst/simple_tests/all/ok_01.sd b/parser/tst/simple_tests/all/ok_01.sd new file mode 100644 index 000000000..188fb5f6f --- /dev/null +++ b/parser/tst/simple_tests/all/ok_01.sd @@ -0,0 +1,8 @@ +# +#=Description basic all rule +#=EXRESULT PASS +# +/usr/bin/foo { + all, + + } diff --git a/parser/tst/simple_tests/all/ok_02.sd b/parser/tst/simple_tests/all/ok_02.sd new file mode 100644 index 000000000..cc82e3ae6 --- /dev/null +++ b/parser/tst/simple_tests/all/ok_02.sd @@ -0,0 +1,8 @@ +# +#=Description basic all rule +#=EXRESULT PASS +# +/usr/bin/foo { + audit all, + + } diff --git a/parser/tst/simple_tests/all/ok_03.sd b/parser/tst/simple_tests/all/ok_03.sd new file mode 100644 index 000000000..eadc3eaf4 --- /dev/null +++ b/parser/tst/simple_tests/all/ok_03.sd @@ -0,0 +1,8 @@ +# +#=Description basic all rule +#=EXRESULT PASS +# +/usr/bin/foo { + allow all, + + } diff --git a/parser/tst/simple_tests/all/ok_04.sd b/parser/tst/simple_tests/all/ok_04.sd new file mode 100644 index 000000000..8458b3727 --- /dev/null +++ b/parser/tst/simple_tests/all/ok_04.sd @@ -0,0 +1,8 @@ +# +#=Description basic all rule +#=EXRESULT PASS +# +/usr/bin/foo { + deny all, + + } diff --git a/parser/tst/simple_tests/all/ok_05.sd b/parser/tst/simple_tests/all/ok_05.sd new file mode 100644 index 000000000..12d65cf4e --- /dev/null +++ b/parser/tst/simple_tests/all/ok_05.sd @@ -0,0 +1,8 @@ +# +#=Description basic all rule +#=EXRESULT PASS +# +/usr/bin/foo { + audit deny all, + + } diff --git a/parser/tst/simple_tests/all/ok_06.sd b/parser/tst/simple_tests/all/ok_06.sd new file mode 100644 index 000000000..6016dc246 --- /dev/null +++ b/parser/tst/simple_tests/all/ok_06.sd @@ -0,0 +1,8 @@ +# +#=Description basic all rule +#=EXRESULT PASS +# +/usr/bin/foo { + audit allow all, + + } diff --git a/utils/test/test-parser-simple-tests.py b/utils/test/test-parser-simple-tests.py index 33eec798c..c177c1527 100644 --- a/utils/test/test-parser-simple-tests.py +++ b/utils/test/test-parser-simple-tests.py @@ -263,6 +263,14 @@ unknown_line = ( 'file/ok_other_2.sd', 'file/ok_other_3.sd', + # 'all' keyword + 'all/ok_01.sd', + 'all/ok_02.sd', + 'all/ok_03.sd', + 'all/ok_04.sd', + 'all/ok_05.sd', + 'all/ok_06.sd', + # 'unsafe' keyword 'file/file/front_perms_ok_1.sd', 'file/front_perms_ok_1.sd',