Add an APPARMOR_PROFILE user spec option to sudoers

sudoers now supports an APPARMOR_PROFILE option, which can be specified
as e.g.

    alice       ALL=(ALL:ALL)   APPARMOR_PROFILE=foo    ALL

The line above says "user alice can run any command as any user/group,
under confinement by the AppArmor profile 'foo'." Profiles can be
specified in any way that complies with the rules of
aa_change_profile(2). For instance, the sudoers configuration

    alice       ALL=(ALL:ALL)   APPARMOR_PROFILE=unconfined     ALL

allows alice to run any command unconfined (i.e., without an AppArmor
profile), while

    alice       ALL=(ALL:ALL)   APPARMOR_PROFILE=foo//&bar      ALL

tells sudoers that alice can run any command under the stacked AppArmor
profiles 'foo' and 'bar'.

The intention of this option is to give sysadmins on Linux distros
supporting AppArmor better options for fine-grained access control.
Among other things, this option can enforce mandatory access control
(MAC) over the operations that a privileged user is able to perform to
ensure that they cannot privesc past the boundaries of a specified
profile. It can also be used to limit which users are able to get
unconfined system access, by enforcing a default AppArmor profile on all
users and then specifying 'APPARMOR_PROFILE=unconfined' for a privileged
subset of users.
This commit is contained in:
kernelmethod
2022-05-23 13:16:10 -06:00
parent bd25b85a66
commit c20859d55b
18 changed files with 142 additions and 1 deletions

View File

@@ -190,6 +190,9 @@ check_user(int validated, int mode)
#ifdef HAVE_SELINUX
if (user_role == NULL && user_type == NULL)
#endif
#ifdef HAVE_APPARMOR
if (user_apparmor_profile == NULL)
#endif
#ifdef HAVE_PRIV_SET
if (runas_privs == NULL && runas_limitprivs == NULL)
#endif

View File

@@ -553,6 +553,14 @@ print_cmndspec_csv(FILE *fp, struct sudoers_parse_tree *parse_tree,
}
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
if (cs->apparmor_profile != NULL) {
fprintf(fp, "%sapparmor_profile=%s,", need_comma ? "," : "",
cs->apparmor_profile);
need_comma = true;
}
#endif /* HAVE_APPARMOR */
#ifdef HAVE_PRIV_SET
/* Print Solaris privs/limitprivs */
if (cs->privs != NULL || cs->limitprivs != NULL) {
@@ -571,7 +579,7 @@ print_cmndspec_csv(FILE *fp, struct sudoers_parse_tree *parse_tree,
/*
* Merge adjacent commands with matching tags, runas, SELinux
* role/type and Solaris priv settings.
* role/type, AppArmor profiles and Solaris priv settings.
*/
for (;;) {
/* Does the next entry differ only in the command itself? */
@@ -585,6 +593,9 @@ print_cmndspec_csv(FILE *fp, struct sudoers_parse_tree *parse_tree,
#ifdef HAVE_SELINUX
|| cs->role != next->role || cs->type != next->type
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
|| cs->apparmor_profile != next->apparmor_profile
#endif /* HAVE_APPARMOR */
|| cs->runchroot != next->runchroot || cs->runcwd != next->runcwd;
if (!quoted && !last_one) {

View File

@@ -581,6 +581,9 @@ cmndspec_continues(struct cmndspec *cs, struct cmndspec *next)
#ifdef HAVE_SELINUX
&& cs->role == next->role && cs->type == next->type
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
&& cs->apparmor_profile == next->apparmor_profile
#endif /* HAVE_APPARMOR */
&& cs->runchroot == next->runchroot && cs->runcwd == next->runcwd;
return ret;
}
@@ -755,6 +758,16 @@ print_cmndspec_json(struct json_container *jsonc,
}
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
if (cs->apparmor_profile != NULL) {
sudo_json_open_array(jsonc, "AppArmor_Spec");
value.type = JSON_STRING;
value.u.string = cs->apparmor_profile;
sudo_json_add_value(jsonc, "apparmor_profile", &value);
sudo_json_close_array(jsonc);
}
#endif /* HAVE_APPARMOR */
#ifdef HAVE_PRIV_SET
/* Print Solaris privs/limitprivs */
if (cs->privs != NULL || cs->limitprivs != NULL) {

View File

@@ -460,6 +460,18 @@ print_cmndspec_ldif(FILE *fp, struct sudoers_parse_tree *parse_tree,
}
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
/* Print AppArmor profile */
if (cs->apparmor_profile != NULL) {
if (asprintf(&attr_val, "apparmor_profile=%s", cs->apparmor_profile) == -1) {
sudo_fatalx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
}
print_attribute_ldif(fp, "sudoOption", attr_val);
free(attr_val);
}
#endif /* HAVE_APPARMOR */
#ifdef HAVE_PRIV_SET
/* Print Solaris privs/limitprivs */
if (cs->privs != NULL || cs->limitprivs != NULL) {

View File

@@ -976,6 +976,14 @@ cmndspec_equivalent(struct cmndspec *cs1, struct cmndspec *cs2, bool check_negat
debug_return_bool(false);
}
#endif
#ifdef HAVE_APPARMOR
if (cs1->apparmor_profile != NULL && cs2->apparmor_profile != NULL) {
if (strcmp(cs1->apparmor_profile, cs2->apparmor_profile) != 0)
debug_return_bool(false);
} else if (cs1->apparmor_profile != cs2->apparmor_profile) {
debug_return_bool(false);
}
#endif
#ifdef HAVE_PRIV_SET
if (cs1->privs != NULL && cs2->privs != NULL) {
if (strcmp(cs1->privs, cs2->privs) != 0)

View File

@@ -657,6 +657,10 @@ struct sudo_defs_types sudo_defs_table[] = {
"passprompt_regex", T_LIST|T_SPACE|T_BOOL,
N_("List of regular expressions to use when matching a password prompt"),
NULL,
}, {
"apparmor_profile", T_STR,
N_("AppArmor profile to use in the new security context: %s"),
NULL,
}, {
NULL, 0, NULL
}

View File

@@ -306,6 +306,8 @@
#define def_log_passwords (sudo_defs_table[I_LOG_PASSWORDS].sd_un.flag)
#define I_PASSPROMPT_REGEX 152
#define def_passprompt_regex (sudo_defs_table[I_PASSPROMPT_REGEX].sd_un.list)
#define I_APPARMOR_PROFILE 153
#define def_apparmor_profile (sudo_defs_table[I_APPARMOR_PROFILE].sd_un.str)
enum def_tuple {
never,

View File

@@ -475,3 +475,6 @@ log_passwords
passprompt_regex
T_LIST|T_SPACE|T_BOOL
"List of regular expressions to use when matching a password prompt"
apparmor_profile
T_STR
"AppArmor profile to use in the new security context: %s"

View File

@@ -241,6 +241,10 @@ sudoers_format_cmndspec(struct sudo_lbuf *lbuf,
if (cs->type != NULL && FIELD_CHANGED(prev_cs, cs, type))
sudo_lbuf_append(lbuf, "TYPE=%s ", cs->type);
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
if (cs->apparmor_profile != NULL && FIELD_CHANGED(prev_cs, cs, apparmor_profile))
sudo_lbuf_append(lbuf, "APPARMOR_PROFILE=%s ", cs->apparmor_profile);
#endif /* HAVE_APPARMOR */
if (cs->runchroot != NULL && FIELD_CHANGED(prev_cs, cs, runchroot))
sudo_lbuf_append(lbuf, "CHROOT=%s ", cs->runchroot);
if (cs->runcwd != NULL && FIELD_CHANGED(prev_cs, cs, runcwd))

View File

@@ -144,6 +144,7 @@ static void alias_error(const char *name, int errnum);
%token <tok> CWD /* working directory for command */
%token <tok> TYPE /* SELinux type */
%token <tok> ROLE /* SELinux role */
%token <tok> APPARMOR_PROFILE /* AppArmor profile */
%token <tok> PRIVS /* Solaris privileges */
%token <tok> LIMITPRIVS /* Solaris limit privileges */
%token <tok> CMND_TIMEOUT /* command timeout */
@@ -182,6 +183,7 @@ static void alias_error(const char *name, int errnum);
%type <string> chrootspec
%type <string> rolespec
%type <string> typespec
%type <string> apparmor_profilespec
%type <string> privsspec
%type <string> limitprivsspec
%type <string> timeoutspec
@@ -534,6 +536,10 @@ cmndspec : runasspec options cmndtag digcmnd {
cs->type = $2.type;
parser_leak_remove(LEAK_PTR, $2.type);
#endif
#ifdef HAVE_APPARMOR
cs->apparmor_profile = $2.apparmor_profile;
parser_leak_remove(LEAK_PTR, $2.apparmor_profile);
#endif
#ifdef HAVE_PRIV_SET
cs->privs = $2.privs;
parser_leak_remove(LEAK_PTR, $2.privs);
@@ -688,6 +694,11 @@ typespec : TYPE '=' WORD {
}
;
apparmor_profilespec : APPARMOR_PROFILE '=' WORD {
$$ = $3;
}
;
privsspec : PRIVS '=' WORD {
$$ = $3;
}
@@ -783,6 +794,7 @@ reserved_word : ALL { $$ = "ALL"; }
| TYPE { $$ = "TYPE"; }
| PRIVS { $$ = "PRIVS"; }
| LIMITPRIVS { $$ = "LIMITPRIVS"; }
| APPARMOR_PROFILE { $$ = "APPARMOR_PROFILE"; }
;
reserved_alias : reserved_word {
@@ -846,6 +858,13 @@ options : /* empty */ {
parser_leak_remove(LEAK_PTR, $$.type);
free($$.type);
$$.type = $2;
#endif
}
| options apparmor_profilespec {
#ifdef HAVE_APPARMOR
parser_leak_remove(LEAK_PTR, $$.apparmor_profile);
free($$.apparmor_profile);
$$.apparmor_profile = $2;
#endif
}
| options privsspec {

View File

@@ -254,6 +254,24 @@ apply_cmndspec(struct cmndspec *cs)
}
}
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
/* Set AppArmor profile, if specified */
if (cs->apparmor_profile != NULL) {
user_apparmor_profile = strdup(cs->apparmor_profile);
if (user_apparmor_profile == NULL) {
sudo_warnx(U_("%s: %s"), __func__,
U_("unable to allocate memory"));
debug_return_bool(false);
}
} else {
user_apparmor_profile = def_apparmor_profile;
def_apparmor_profile = NULL;
}
if (user_apparmor_profile != NULL) {
sudo_debug_printf(SUDO_DEBUG_INFO|SUDO_DEBUG_LINENO,
"user_apparmor_profile -> %s", user_apparmor_profile);
}
#endif
#ifdef HAVE_PRIV_SET
/* Set Solaris privilege sets */
if (runas_privs == NULL) {
@@ -525,6 +543,10 @@ new_long_entry(struct cmndspec *cs, struct cmndspec *prev_cs)
if (cs->type && (!prev_cs->type || strcmp(cs->type, prev_cs->type) != 0))
debug_return_bool(true);
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
if (cs->apparmor_profile && (!prev_cs->apparmor_profile || strcmp(cs->apparmor_profile, prev_cs->apparmor_profile) != 0))
debug_return_bool(true);
#endif /* HAVE_APPARMOR */
if (cs->runchroot && (!prev_cs->runchroot || strcmp(cs->runchroot, prev_cs->runchroot) != 0))
debug_return_bool(true);
if (cs->runcwd && (!prev_cs->runcwd || strcmp(cs->runcwd, prev_cs->runcwd) != 0))

View File

@@ -149,6 +149,9 @@ struct command_options {
#ifdef HAVE_SELINUX
char *role, *type; /* SELinux role and type */
#endif
#ifdef HAVE_APPARMOR
char *apparmor_profile; /* AppArmor profile */
#endif
#ifdef HAVE_PRIV_SET
char *privs, *limitprivs; /* Solaris privilege sets */
#endif
@@ -233,6 +236,9 @@ struct cmndspec {
#ifdef HAVE_SELINUX
char *role, *type; /* SELinux role and type */
#endif
#ifdef HAVE_APPARMOR
char *apparmor_profile; /* AppArmor profile */
#endif
#ifdef HAVE_PRIV_SET
char *privs, *limitprivs; /* Solaris privilege sets */
#endif

View File

@@ -321,6 +321,16 @@ sudoers_policy_deserialize_info(void *v, struct defaults_list *defaults)
continue;
}
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
if (MATCHES(*cur, "apparmor_profile=")) {
CHECK(*cur, "apparmor_profile=");
free(user_apparmor_profile);
user_apparmor_profile = strdup(*cur + sizeof("apparmor_profile=") - 1);
if (user_apparmor_profile == NULL)
goto oom;
continue;
}
#endif /* HAVE_APPARMOR */
#ifdef HAVE_BSD_AUTH_H
if (MATCHES(*cur, "bsdauth_type=")) {
CHECK(*cur, "bsdauth_type=");
@@ -933,6 +943,12 @@ sudoers_policy_store_result(bool accepted, char *argv[], char *envp[],
goto oom;
}
#endif /* HAVE_SELINUX */
#ifdef HAVE_APPARMOR
if (user_apparmor_profile != NULL) {
if ((command_info[info_len++] = sudo_new_key_val("apparmor_profile", user_apparmor_profile)) == NULL)
goto oom;
}
#endif /* HAVE_APPARMOR */
#ifdef HAVE_PRIV_SET
if (runas_privs != NULL) {
if ((command_info[info_len++] = sudo_new_key_val("runas_privs", runas_privs)) == NULL)

View File

@@ -30,6 +30,7 @@
"runas_limitprivs"
"selinux_role"
"selinux_type"
"apparmor_profile"
"bsdauth_type"
"network_addrs"
"max_groups"

View File

@@ -55,6 +55,7 @@
"NOTAFTER"
"ROLE"
"TYPE"
"APPARMOR_PROFILE"
"PRIVS"
"LIMITPRIVS"
@@ -127,6 +128,7 @@
"env_keep"
"role"
"type"
"apparmor_profile"
"env_file"
"restricted_env_file"
"sudoers_locale"

View File

@@ -1790,6 +1790,9 @@ sudo_user_free(void)
free(user_role);
free(user_type);
#endif
#ifdef HAVE_APPARMOR
free(user_apparmor_profile);
#endif
#ifdef HAVE_PRIV_SET
free(runas_privs);
free(runas_limitprivs);

View File

@@ -111,6 +111,9 @@ struct sudo_user {
char *role;
char *type;
#endif
#ifdef HAVE_APPARMOR
char *apparmor_profile;
#endif
#ifdef HAVE_PRIV_SET
char *privs;
char *limitprivs;
@@ -246,6 +249,7 @@ struct sudo_user {
#define runas_gr (sudo_user._runas_gr)
#define user_role (sudo_user.role)
#define user_type (sudo_user.type)
#define user_apparmor_profile (sudo_user.apparmor_profile)
#define user_closefrom (sudo_user.closefrom)
#define runas_privs (sudo_user.privs)
#define runas_limitprivs (sudo_user.limitprivs)

View File

@@ -679,6 +679,14 @@ ALL {
goto got_alias;
#endif
}
<INITIAL>APPARMOR_PROFILE {
#ifdef HAVE_APPARMOR
LEXTRACE("APPARMOR_PROFILE ");
return APPARMOR_PROFILE;
#else
goto got_alias;
#endif
}
<INITIAL>PRIVS {
#ifdef HAVE_PRIV_SET
LEXTRACE("PRIVS ");