diff --git a/.gitignore b/.gitignore index b294eef29..f0ee4dfbc 100644 --- a/.gitignore +++ b/.gitignore @@ -58,6 +58,7 @@ parser/profile.o parser/ptrace.o parser/rule.o parser/signal.o +parser/userns.o parser/*.7 parser/*.5 parser/*.8 diff --git a/parser/Makefile b/parser/Makefile index 15f9d975d..39bf38d3d 100644 --- a/parser/Makefile +++ b/parser/Makefile @@ -101,10 +101,10 @@ SRCS = parser_common.c parser_include.c parser_interface.c parser_lex.c \ parser_yacc.c parser_regex.c parser_variable.c parser_policy.c \ parser_alias.c common_optarg.c lib.c network.c \ mount.cc dbus.cc profile.cc rule.cc signal.cc ptrace.cc \ - af_rule.cc af_unix.cc policy_cache.c default_features.c + af_rule.cc af_unix.cc policy_cache.c default_features.c userns.cc HDRS = parser.h parser_include.h immunix.h mount.h dbus.h lib.h profile.h \ rule.h common_optarg.h signal.h ptrace.h network.h af_rule.h af_unix.h \ - policy_cache.h file_cache.h + policy_cache.h file_cache.h userns.h TOOLS = apparmor_parser OBJECTS = $(patsubst %.cc, %.o, $(SRCS:.c=.o)) @@ -301,6 +301,9 @@ profile.o: profile.cc profile.h parser.h network.h rule.o: rule.cc rule.h policydb.h $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< +userns.o: userns.cc userns.h parser.h parser_yacc.h rule.h $(APPARMOR_H) + $(CXX) $(EXTRA_CFLAGS) -c -o $@ $< + parser_version.h: Makefile @echo \#define PARSER_VERSION \"$(VERSION)\" > .ver @mv -f .ver $@ diff --git a/parser/parser.h b/parser/parser.h index 4ab4c5c72..992e3d833 100644 --- a/parser/parser.h +++ b/parser/parser.h @@ -344,6 +344,7 @@ extern int features_supports_ptrace; extern int features_supports_unix; extern int features_supports_stacking; extern int features_supports_domain_xattr; +extern int features_supports_userns; extern int kernel_supports_oob; extern int conf_verbose; extern int conf_quiet; diff --git a/parser/parser_common.c b/parser/parser_common.c index 398df43d7..db3d7b357 100644 --- a/parser/parser_common.c +++ b/parser/parser_common.c @@ -78,6 +78,7 @@ int features_supports_signal = 0; /* kernel supports signal rules */ int features_supports_ptrace = 0; /* kernel supports ptrace rules */ int features_supports_stacking = 0; /* kernel supports stacking */ int features_supports_domain_xattr = 0; /* x attachment cond */ +int features_supports_userns = 0; /* kernel supports user namespace */ int kernel_supports_oob = 0; /* out of band transitions */ int conf_verbose = 0; int conf_quiet = 0; diff --git a/parser/parser_lex.l b/parser/parser_lex.l index e02575dae..7798d8833 100644 --- a/parser/parser_lex.l +++ b/parser/parser_lex.l @@ -327,6 +327,7 @@ GT > %x INCLUDE %x INCLUDE_EXISTS %x ABI_MODE +%x USERNS_MODE %% @@ -339,7 +340,7 @@ GT > } %} -{ +{ {WS}+ { DUMP_PREPROCESS; /* Ignoring whitespace */ } } @@ -556,7 +557,6 @@ GT > } { - create { RETURN_TOKEN(TOK_CREATE); } listen { RETURN_TOKEN(TOK_LISTEN); } accept { RETURN_TOKEN(TOK_ACCEPT); } connect { RETURN_TOKEN(TOK_CONNECT); } @@ -567,6 +567,10 @@ GT > shutdown { RETURN_TOKEN(TOK_SHUTDOWN); } } +{ + create { RETURN_TOKEN(TOK_CREATE); } +} + { bind { RETURN_TOKEN(TOK_BIND); } } @@ -724,13 +728,16 @@ include/{WS} { case TOK_ABI: state = ABI_MODE; break; + case TOK_USERNS: + state = USERNS_MODE; + break; default: /* nothing */ break; } PUSH_AND_RETURN(state, token); } -{ +{ {END_OF_RULE} { if (YY_START != INITIAL) POP_NODUMP(); @@ -738,14 +745,14 @@ include/{WS} { } } -{ +{ \r?\n { DUMP_PREPROCESS; current_lineno++; } } -{ +{ (.|\n) { DUMP_PREPROCESS; /* Something we didn't expect */ @@ -780,4 +787,5 @@ unordered_map state_names = { STATE_TABLE_ENT(INCLUDE), STATE_TABLE_ENT(INCLUDE_EXISTS), STATE_TABLE_ENT(ABI_MODE), + STATE_TABLE_ENT(USERNS_MODE), }; diff --git a/parser/parser_main.c b/parser/parser_main.c index e4f9be8a7..9d9d70e37 100644 --- a/parser/parser_main.c +++ b/parser/parser_main.c @@ -941,6 +941,9 @@ void set_supported_features() features_supports_domain_xattr = features_intersect(kernel_features, policy_features, "domain/attach_conditions/xattr"); + features_supports_userns = features_intersect(kernel_features, + policy_features, + "namespaces/mask/userns_create"); } static bool do_print_cache_dir(aa_features *features, int dirfd, const char *path) diff --git a/parser/parser_misc.c b/parser/parser_misc.c index 6b6b334b5..3b7aee75e 100644 --- a/parser/parser_misc.c +++ b/parser/parser_misc.c @@ -120,6 +120,7 @@ static struct keyword_table keyword_table[] = { {"tracedby", TOK_TRACEDBY}, {"readby", TOK_READBY}, {"abi", TOK_ABI}, + {"userns", TOK_USERNS}, /* terminate */ {NULL, 0} diff --git a/parser/parser_regex.c b/parser/parser_regex.c index fc6035931..9c614157a 100644 --- a/parser/parser_regex.c +++ b/parser/parser_regex.c @@ -935,6 +935,7 @@ static const char *mediates_ptrace = CLASS_STR(AA_CLASS_PTRACE); static const char *mediates_extended_net = CLASS_STR(AA_CLASS_NET); static const char *mediates_netv8 = CLASS_STR(AA_CLASS_NETV8); static const char *mediates_net_unix = CLASS_SUB_STR(AA_CLASS_NET, AF_UNIX); +static const char *mediates_ns = CLASS_STR(AA_CLASS_NS); int process_profile_policydb(Profile *prof) { @@ -977,6 +978,9 @@ int process_profile_policydb(Profile *prof) (!prof->policy.rules->add_rule(mediates_extended_net, 0, AA_MAY_READ, 0, dfaflags) || !prof->policy.rules->add_rule(mediates_net_unix, 0, AA_MAY_READ, 0, dfaflags))) goto out; + if (features_supports_userns && + !prof->policy.rules->add_rule(mediates_ns, 0, AA_MAY_READ, 0, dfaflags)) + goto out; if (prof->policy.rules->rule_count > 0) { int xmatch_len = 0; diff --git a/parser/parser_yacc.y b/parser/parser_yacc.y index f317556e4..1ea38363b 100644 --- a/parser/parser_yacc.y +++ b/parser/parser_yacc.y @@ -142,6 +142,7 @@ void add_local_entry(Profile *prof); %token TOK_TRACEDBY %token TOK_READBY %token TOK_ABI +%token TOK_USERNS /* rlimits */ %token TOK_RLIMIT @@ -177,6 +178,7 @@ void add_local_entry(Profile *prof); #include "signal.h" #include "ptrace.h" #include "af_unix.h" + #include "userns.h" } %union { @@ -193,6 +195,7 @@ void add_local_entry(Profile *prof); signal_rule *signal_entry; ptrace_rule *ptrace_entry; unix_rule *unix_entry; + userns_rule *userns_entry; flagvals flags; int fmode; @@ -272,6 +275,10 @@ void add_local_entry(Profile *prof); %type opt_named_transition %type opt_exec_mode %type opt_file +%type userns_perm +%type userns_perms +%type opt_userns_perm +%type userns_rule %% @@ -858,6 +865,22 @@ rules: rules opt_prefix unix_rule $$ = $1; } +rules: rules opt_prefix userns_rule + { + if ($2.owner) + yyerror(_("owner prefix not allowed on userns rules")); + if ($2.deny && $2.audit) { + $3->deny = 1; + } else if ($2.deny) { + $3->deny = 1; + $3->audit = $3->mode; + } else if ($2.audit) { + $3->audit = $3->mode; + } + $1->rule_ents.push_back($3); + $$ = $1; + } + rules: rules opt_prefix change_profile { PDEBUG("matched: rules change_profile\n"); @@ -1542,6 +1565,32 @@ ptrace_rule: TOK_PTRACE opt_ptrace_perm opt_conds TOK_END_OF_RULE $$ = ent; } +userns_perm: TOK_VALUE + { + if (strcmp($1, "create") == 0) + $$ = AA_USERNS_CREATE; + else + $$ = 0; + + if ($1) + free($1); + } + | TOK_CREATE { $$ = AA_USERNS_CREATE; } + +userns_perms: { /* nothing */ $$ = 0; } + | userns_perms userns_perm { $$ = $1 | $2; } + | userns_perms TOK_COMMA userns_perm { $$ = $1 | $3; } + +opt_userns_perm: { /* nothing */ $$ = 0; } + | userns_perm { $$ = $1; } + | TOK_OPENPAREN userns_perms TOK_CLOSEPAREN { $$ = $2; } + +userns_rule: TOK_USERNS opt_userns_perm opt_conds TOK_END_OF_RULE + { + userns_rule *ent = new userns_rule($2, $3); + $$ = ent; + } + hat_start: TOK_CARET {} | TOK_HAT {} diff --git a/parser/policydb.h b/parser/policydb.h index 53b80090c..4d7420d51 100644 --- a/parser/policydb.h +++ b/parser/policydb.h @@ -34,6 +34,7 @@ #define AA_CLASS_SIGNAL 10 #define AA_CLASS_NETV8 14 #define AA_CLASS_LABEL 16 +#define AA_CLASS_NS 21 /* defined in libapparmor's apparmor.h #define AA_CLASS_DBUS 32 */ #define AA_CLASS_X 33 diff --git a/parser/userns.cc b/parser/userns.cc new file mode 100644 index 000000000..a83d05904 --- /dev/null +++ b/parser/userns.cc @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2022 + * 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. + */ + +#include "parser.h" +#include "profile.h" +#include "userns.h" + +#include +#include +#include +#include + +void userns_rule::move_conditionals(struct cond_entry *conds) +{ + struct cond_entry *cond_ent; + + list_for_each(conds, cond_ent) { + /* for now disallow keyword 'in' (list) */ + if (!cond_ent->eq) + yyerror("keyword \"in\" is not allowed in userns rules\n"); + + /* no valid conditionals atm */ + yyerror("invalid userns rule conditional \"%s\"\n", + cond_ent->name); + } +} + +userns_rule::userns_rule(int mode_p, struct cond_entry *conds): + audit(0), deny(0) +{ + if (mode_p) { + if (mode_p & ~AA_VALID_USERNS_PERMS) + yyerror("mode contains invalid permissions for userns\n"); + mode = mode_p; + + } else { + /* default to all perms */ + mode = AA_VALID_USERNS_PERMS; + } + + move_conditionals(conds); + free_cond_list(conds); +} + +ostream &userns_rule::dump(ostream &os) +{ + if (audit) + os << "audit "; + if (deny) + os << "deny "; + + os << "userns "; + + if (mode != AA_VALID_USERNS_PERMS) { + if (mode & AA_USERNS_CREATE) + os << "create "; + } + + os << ",\n"; + + return os; +} + + +int userns_rule::expand_variables(void) +{ + return 0; +} + +void userns_rule::warn_once(const char *name) +{ + rule_t::warn_once(name, "userns rules not enforced"); +} + +int userns_rule::gen_policy_re(Profile &prof) +{ + std::ostringstream buffer; + std::string buf; + + if (!features_supports_userns) { + warn_once(prof.name); + return RULE_NOT_SUPPORTED; + } + + buffer << "\\x" << std::setfill('0') << std::setw(2) << std::hex << AA_CLASS_NS; + buf = buffer.str(); + if (mode & AA_VALID_USERNS_PERMS) { + if (!prof.policy.rules->add_rule(buf.c_str(), deny, mode, audit, + dfaflags)) + goto fail; + } + + return RULE_OK; + +fail: + return RULE_ERROR; +} diff --git a/parser/userns.h b/parser/userns.h new file mode 100644 index 000000000..9337da285 --- /dev/null +++ b/parser/userns.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) 2022 + * 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_USERNS_H +#define __AA_USERNS_H + +#include "parser.h" + +#define AA_USERNS_CREATE 8 +#define AA_VALID_USERNS_PERMS (AA_USERNS_CREATE) + +class userns_rule: public rule_t { + void move_conditionals(struct cond_entry *conds); +public: + int mode; + int audit; + int deny; + + userns_rule(int mode, struct cond_entry *conds); + virtual ~userns_rule() + { + }; + + virtual ostream &dump(ostream &os); + virtual int expand_variables(void); + virtual int gen_policy_re(Profile &prof); + virtual void post_process(Profile &prof unused) { }; + +protected: + virtual void warn_once(const char *name) override; +}; + +#endif /* __AA_USERNS_H */