Merge pull request #148 from kernelmethod/apparmor_support

Add AppArmor support to sudo
This commit is contained in:
Todd C. Miller
2022-05-27 08:26:24 -06:00
committed by GitHub
37 changed files with 488 additions and 4 deletions

View File

@@ -375,6 +375,10 @@ Defaults are listed in brackets after the description.
ldap_sasl_interactive_bind_s() function is present in the
LDAP libraries.
--with-apparmor
Enable support for the AppArmor Linux Security Module (LSM) on
supported systems.
--with-logincap
This adds support for login classes specified in `/etc/login.conf`.
It is enabled by default on BSD/OS, Darwin, FreeBSD, OpenBSD, and

View File

@@ -1178,6 +1178,7 @@ scripts/mkpkg
scripts/pp
scripts/unanon
src/Makefile.in
src/apparmor.c
src/conversation.c
src/copy_file.c
src/edit_open.c

View File

@@ -51,6 +51,9 @@
/* Define to 1 if you use AIX general authentication. */
#undef HAVE_AIXAUTH
/* Define to 1 to enable AppArmor support. */
#undef HAVE_APPARMOR
/* Define to 1 if you have the `arc4random' function. */
#undef HAVE_ARC4RANDOM

View File

@@ -67,6 +67,7 @@ AC_SUBST([BAMAN])
AC_SUBST([LCMAN])
AC_SUBST([PSMAN])
AC_SUBST([SEMAN])
AC_SUBST([AAMAN])
AC_SUBST([devdir])
AC_SUBST([mansectsu])
AC_SUBST([mansectform])
@@ -251,6 +252,7 @@ BAMAN=0
LCMAN=0
PSMAN=0
SEMAN=0
AAMAN=0
LIBINTL=
LIBCRYPTO=
LIBMD=
@@ -1483,6 +1485,19 @@ AC_ARG_WITH(selinux, [AS_HELP_STRING([--with-selinux], [enable SELinux support])
;;
esac], [with_selinux=no])
AC_ARG_WITH(apparmor, [AS_HELP_STRING([--with-apparmor], [enable AppArmor support])],
[case $with_apparmor in
yes) AC_DEFINE(HAVE_APPARMOR)
AAMAN=1
SUDO_OBJS="${SUDO_OBJS} apparmor.o"
AC_CHECK_LIB(apparmor, aa_change_profile,
[SUDO_LIBS="${SUDO_LIBS} -lapparmor"])
;;
no) ;;
*) AC_MSG_ERROR([--with-apparmor does not take an argument.])
esac], [with_apparmor=no])
AC_ARG_ENABLE(sasl,
[AS_HELP_STRING([--enable-sasl], [Enable/disable LDAP SASL support])],
[ case "$enableval" in
@@ -5416,6 +5431,7 @@ AH_TEMPLATE(HAVE_PROJECT_H, [Define to 1 if you have the <project.h> header file
AH_TEMPLATE(HAVE_SECURID, [Define to 1 if you use SecurID for authentication.])
AH_TEMPLATE(HAVE_SELINUX, [Define to 1 to enable SELinux RBAC support.])
AH_TEMPLATE(HAVE_SETKEYCREATECON, [Define to 1 if you have the `setkeycreatecon' function.])
AH_TEMPLATE(HAVE_APPARMOR, [Define to 1 to enable AppArmor support.])
AH_TEMPLATE(HAVE_SHL_LOAD, [Define to 1 if you have the `shl_load' function.])
AH_TEMPLATE(HAVE_SKEY, [Define to 1 if you use S/Key.])
AH_TEMPLATE(HAVE_SKEYACCESS, [Define to 1 if your S/Key library has skeyaccess().])

View File

@@ -5,5 +5,6 @@ RUN DEBIAN_FRONTEND=noninteractive TZ=America/Denver apt-get update && \
build-essential curl dpkg-dev ed libldap2-dev libpam0g-dev \
libsasl2-dev libselinux1-dev libsepol1-dev libssl-dev zlib1g-dev \
libaudit-dev libssl-dev python3-dev libpython3-dev libwolfssl-dev \
libapparmor-dev \
file lsb-release fakeroot pkg-config procps git ssh openssh-client
RUN useradd -ms /bin/bash build

View File

@@ -5,5 +5,6 @@ RUN DEBIAN_FRONTEND=noninteractive TZ=America/Denver apt-get update && \
build-essential curl dpkg-dev ed libldap2-dev libpam0g-dev \
libsasl2-dev libselinux1-dev libsepol1-dev libssl-dev zlib1g-dev \
libaudit-dev libssl-dev python3-dev libpython3-dev libwolfssl-dev \
libapparmor-dev \
file lsb-release fakeroot pkg-config procps git ssh openssh-client
RUN useradd -ms /bin/bash build

View File

@@ -5,5 +5,6 @@ RUN DEBIAN_FRONTEND=noninteractive TZ=America/Denver apt-get update && \
build-essential curl dpkg-dev ed libldap2-dev libpam0g-dev \
libsasl2-dev libselinux1-dev libsepol1-dev libssl-dev zlib1g-dev \
libaudit-dev libssl-dev python3-dev libpython3-dev libwolfssl-dev \
libapparmor-dev \
file lsb-release fakeroot pkg-config procps git ssh openssh-client
RUN useradd -ms /bin/bash build

View File

@@ -5,5 +5,6 @@ RUN DEBIAN_FRONTEND=noninteractive TZ=America/Denver apt-get update && \
build-essential curl dpkg-dev ed libldap2-dev libpam0g-dev \
libsasl2-dev libselinux1-dev libsepol1-dev libssl-dev zlib1g-dev \
libaudit-dev libssl-dev python3-dev libpython3-dev libwolfssl-dev \
libapparmor-dev \
file lsb-release fakeroot pkg-config procps git ssh openssh-client
RUN useradd -ms /bin/bash build

View File

@@ -5,5 +5,6 @@ RUN DEBIAN_FRONTEND=noninteractive TZ=America/Denver apt-get update && \
build-essential curl dpkg-dev ed libldap2-dev libpam0g-dev \
libsasl2-dev libselinux1-dev libsepol1-dev libssl-dev zlib1g-dev \
libaudit-dev libssl-dev python3-dev libpython3-dev libwolfssl-dev \
libapparmor-dev \
file lsb-release fakeroot pkg-config procps git ssh openssh-client
RUN useradd -ms /bin/bash build

View File

@@ -1290,6 +1290,8 @@ Runas_Spec ::= '(' Runas_List? (':' Runas_List)? ')'
SELinux_Spec ::= ('ROLE=role' | 'TYPE=type')
.\}
AppArmor_Spec ::= 'APPARMOR_PROFILE=profile'
.if \n(PS \{\
Solaris_Priv_Spec ::= ('PRIVS=privset' | 'LIMITPRIVS=privset')
@@ -1503,6 +1505,7 @@ Options may consist of
.if \n(SL \{\
SELinux roles and/or types,
.\}
AppArmor profiles,
.if \n(PS \{\
Solaris privileges sets,
.\}
@@ -1533,6 +1536,59 @@ A role or type specified on the command line,
however, will supersede the values in
\fIsudoers\fR.
.\}
.SS "AppArmor_Spec"
On systems supporting AppArmor,
\fIsudoers\fR
file entries may optionally specify an AppArmor profile that should be
used to confine a command.
If an AppArmor profile is specified with the command, it will override
any default values specified in
\fIsudoers\fR.
Appropriate profile transition rules must be defined to support the
profile change specified for a user.
.PP
AppArmor profiles can be specified in any way that complies with the
rules of
aa_change_profile(2).
For instance, in the following
\fIsudoers\fR
entry
.nf
.sp
.RS 0n
alice ALL = (root) APPARMOR_PROFILE=my-profile ALL
.RE
.fi
.PP
the user
\fBalice\fR
may run any command as root under confinement by the profile
\(oqmy-profile\(cq.
You can also stack profiles, or allow a user to run commands unconfined by
any profile. E.g.,
.nf
.sp
.RS 0n
bob ALL = (root) APPARMOR_PROFILE=foo//&bar /usr/bin/vi
cathy ALL = (root) APPARMOR_PROFILE=unconfined /bin/ls
.RE
.fi
.PP
These
\fIsudoers\fR
entries allow user
\fBbob\fR
to run
\fI/usr/bin/vi\fR
as root under the stacked profiles
\(oqfoo\(cq
and
\(oqbar\(cq,
and user
\fBcathy\fR
to run
\fI/bin/ls\fR
without any confinement at all.
.if \n(PS \{\
.SS "Solaris_Priv_Spec"
On Solaris systems,
@@ -4161,6 +4217,19 @@ which does not create a new PAM session.
.PP
\fBStrings\fR:
.TP 18n
apparmor_profile
The default AppArmor profile to transition into when executing the
command.
The default
\fIapparmor_profile\fR
can be overriden for individual
\fIsudoers\fR
entries by specifying the
\fIAPPARMOR_PROFILE\fR
option.
This option is only available when sudo is built with AppArmor
support.
.TP 18n
authfail_message
Message that is displayed after a user fails to authenticate.
The message may include the

View File

@@ -21,6 +21,7 @@
.\" Materiel Command, USAF, under agreement number F39502-99-1-0512.
.\"
.nr SL @SEMAN@
.nr AA @AAMAN@
.nr BA @BAMAN@
.nr LC @LCMAN@
.nr PS @PSMAN@
@@ -1231,13 +1232,23 @@ Runas_Spec ::= '(' Runas_List? (':' Runas_List)? ')'
.el Option_Spec ::= (SELinux_Spec | Date_Spec | Timeout_Spec | Chdir_Spec | Chroot_Spec)
.\}
.el \{\
.ie \n(AA \{\
.ie \n(PS Option_Spec ::= (AppArmor_Spec | Solaris_Priv_Spec | Date_Spec | Timeout_Spec | Chdir_Spec | Chroot_Spec)
.el Option_Spec ::= (AppArmor_Spec | Date_Spec | Timeout_Spec | Chdir_Spec | Chroot_Spec)
.\}
.el \{\
.ie \n(PS Option_Spec ::= (Solaris_Priv_Spec | Date_Spec | Timeout_Spec | Chdir_Spec | Chroot_Spec)
.el Option_Spec ::= (Date_Spec | Timeout_Spec | Chdir_Spec | Chroot_Spec)
.\}
.\}
.if \n(SL \{\
SELinux_Spec ::= ('ROLE=role' | 'TYPE=type')
.\}
.if \n(AA \{\
AppArmor_Spec ::= 'APPARMOR_PROFILE=profile'
.\}
.if \n(PS \{\
Solaris_Priv_Spec ::= ('PRIVS=privset' | 'LIMITPRIVS=privset')
@@ -1427,6 +1438,9 @@ Options may consist of
.if \n(SL \{\
SELinux roles and/or types,
.\}
.if \n(AA \{\
AppArmor profiles,
.\}
.if \n(PS \{\
Solaris privileges sets,
.\}
@@ -1457,6 +1471,55 @@ A role or type specified on the command line,
however, will supersede the values in
.Em sudoers .
.\}
.if \n(AA \{\
.Ss AppArmor_Spec
On systems supporting AppArmor,
.Em sudoers
file entries may optionally specify an AppArmor profile that should be
used to confine a command.
If an AppArmor profile is specified with the command, it will override
any default values specified in
.Em sudoers .
Appropriate profile transition rules must be defined to support the
profile change specified for a user.
.Pp
AppArmor profiles can be specified in any way that complies with the
rules of
.Xr aa_change_profile 2 .
For instance, in the following
.Em sudoers
entry
.Bd -literal
alice ALL = (root) APPARMOR_PROFILE=my-profile ALL
.Ed
.Pp
the user
.Sy alice
may run any command as root under confinement by the profile
.Ql my-profile .
You can also stack profiles, or allow a user to run commands unconfined by
any profile. E.g.,
.Bd -literal
bob ALL = (root) APPARMOR_PROFILE=foo//&bar /usr/bin/vi
cathy ALL = (root) APPARMOR_PROFILE=unconfined /bin/ls
.Ed
.Pp
These
.Em sudoers
entries allow user
.Sy bob
to run
.Pa /usr/bin/vi
as root under the stacked profiles
.Ql foo
and
.Ql bar ,
and user
.Sy cathy
to run
.Pa /bin/ls
without any confinement at all.
.\}
.if \n(PS \{\
.Ss Solaris_Priv_Spec
On Solaris systems,
@@ -3931,6 +3994,20 @@ which does not create a new PAM session.
.Pp
.Sy Strings :
.Bl -tag -width 16n
.if \n(AA \{\
.It apparmor_profile
The default AppArmor profile to transition into when executing the
command.
The default
.Em apparmor_profile
can be overriden for individual
.Em sudoers
entries by specifying the
.Em APPARMOR_PROFILE
option.
This option is only available when sudo is built with AppArmor
support.
.\}
.It authfail_message
Message that is displayed after a user fails to authenticate.
The message may include the

View File

@@ -85,6 +85,7 @@ struct sudo_conf_debug_file_list;
#define SUDO_DEBUG_SELINUX (12<<6) /* selinux */
#define SUDO_DEBUG_UTIL (13<<6) /* utility functions */
#define SUDO_DEBUG_UTMP (14<<6) /* utmp file ops */
#define SUDO_DEBUG_APPARMOR (15<<6) /* AppArmor */
#define SUDO_DEBUG_ALL 0xffff0000 /* all subsystems */
/* Error return for sudo_debug_register(). */

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

@@ -667,6 +667,10 @@ struct sudo_defs_types sudo_defs_table[] = {
"intercept_type", T_TUPLE,
N_("The mechanism used by the intercept and log_subcmds options: %s"),
def_data_intercept_type,
}, {
"apparmor_profile", T_STR,
N_("AppArmor profile to use in the new security context: %s"),
NULL,
}, {
NULL, 0, NULL
}

View File

@@ -306,8 +306,10 @@
#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_INTERCEPT_TYPE 153
#define I_INTERCEPT_TYPE 154
#define def_intercept_type (sudo_defs_table[I_INTERCEPT_TYPE].sd_un.tuple)
#define I_APPARMOR_PROFILE 153
#define def_apparmor_profile (sudo_defs_table[I_APPARMOR_PROFILE].sd_un.str)
enum def_tuple {
never,

View File

@@ -479,3 +479,6 @@ intercept_type
T_TUPLE
"The mechanism used by the intercept and log_subcmds options: %s"
dso trace
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

@@ -328,6 +328,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=");
@@ -957,6 +967,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

@@ -1825,6 +1825,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;
@@ -248,6 +251,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 ");

View File

@@ -115,7 +115,7 @@ sub mkdep {
# Expand some configure bits
$makefile =~ s:\@DEV\@::g;
$makefile =~ s:\@COMMON_OBJS\@:aix.lo event_poll.lo event_select.lo:;
$makefile =~ s:\@SUDO_OBJS\@:intercept.pb-c.o openbsd.o preload.o selinux.o sesh.o solaris.o:;
$makefile =~ s:\@SUDO_OBJS\@:intercept.pb-c.o openbsd.o preload.o apparmor.o selinux.o sesh.o solaris.o:;
$makefile =~ s:\@SUDOERS_OBJS\@:bsm_audit.lo linux_audit.lo ldap.lo ldap_util.lo ldap_conf.lo solaris_audit.lo sssd.lo:;
# XXX - fill in AUTH_OBJS from contents of the auth dir instead
$makefile =~ s:\@AUTH_OBJS\@:afs.lo aix_auth.lo bsdauth.lo dce.lo fwtk.lo getspwuid.lo kerb5.lo pam.lo passwd.lo rfc1938.lo secureware.lo securid5.lo sia.lo:;

View File

@@ -269,6 +269,23 @@ case "$osversion" in
make_opts="${make_opts}${make_opts+ }"'docdir=$(datarootdir)/doc/packages/$(PACKAGE_TARNAME)'
;;
deb*|ubu*)
# AppArmor is enabled by default starting in
# Debian: Debian 10 (Buster)
# Ubuntu: Ubuntu 12.04 (Precise Pangolin)
osmajor=`sed -n -e 's/^VERSION_ID=\"\([0-9]*\).*$/\1/p' /etc/os-release`
case "$osversion" in
deb*)
if [ -z $osmajor ] || [ $osmajor -ge 10 ]; then
with_apparmor=true
fi
;;
ubu*)
if [ -z $osmajor ] || [ $osmajor -ge 14 ]; then
with_apparmor=true
fi
;;
esac
# Encrypted remote I/O log support.
with_openssl=true
# Python plugins
@@ -295,6 +312,9 @@ case "$osversion" in
configure_opts="${configure_opts}${configure_opts+$tab}--with-sssd-lib=/usr/lib/$MULTIARCH"
fi
fi
if [ X"$with_apparmor" = X"true" ]; then
configure_opts="${configure_opts}${configure_opts+$tab}--with-apparmor"
fi
configure_opts="--prefix=/usr
--with-all-insults
--with-pam
@@ -311,7 +331,6 @@ case "$osversion" in
--with-sendmail=/usr/sbin/sendmail
--mandir=/usr/share/man
--libexecdir=/usr/lib
--with-selinux
--with-linux-audit
$configure_opts"
# Use correct libaudit dependency

View File

@@ -378,6 +378,24 @@ sudo_noexec.lo: $(srcdir)/sudo_noexec.c $(incdir)/sudo_compat.h \
$(LIBTOOL) $(LTFLAGS) --mode=compile $(CC) -c $(CPPFLAGS) $(CFLAGS) $(PIE_CFLAGS) $(HARDENING_CFLAGS) $(srcdir)/sudo_noexec.c
# Autogenerated dependencies, do not modify
apparmor.o: $(srcdir)/apparmor.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/apparmor.c
apparmor.i: $(srcdir)/apparmor.c $(incdir)/compat/stdbool.h \
$(incdir)/sudo_compat.h $(incdir)/sudo_conf.h \
$(incdir)/sudo_debug.h $(incdir)/sudo_event.h \
$(incdir)/sudo_fatal.h $(incdir)/sudo_gettext.h \
$(incdir)/sudo_plugin.h $(incdir)/sudo_queue.h \
$(incdir)/sudo_util.h $(srcdir)/sudo.h $(top_builddir)/config.h \
$(top_builddir)/pathnames.h
$(CC) -E -o $@ $(CPPFLAGS) $<
apparmor.plog: apparmor.i
rm -f $@; pvs-studio --cfg $(PVS_CFG) --sourcetree-root $(top_srcdir) --skip-cl-exe yes --source-file $(srcdir)/apparmor.c --i-file $< --output-file $@
check_net_ifs.o: $(srcdir)/regress/net_ifs/check_net_ifs.c \
$(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
$(incdir)/sudo_util.h $(top_builddir)/config.h

111
src/apparmor.c Normal file
View File

@@ -0,0 +1,111 @@
/*
* SPDX-License-Identifier: ISC
*
* Copyright (c) 2022 Will Shand <wss2ec@virginia.edu>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <config.h>
#ifdef HAVE_APPARMOR
# include <stdio.h>
# include <stdlib.h>
# include <sys/apparmor.h>
# include "sudo.h"
# include "sudo_debug.h"
/**
* @brief Check whether AppArmor is enabled.
*
* @return 1 if AppArmor is enabled, 0 otherwise.
*/
int
apparmor_is_enabled(void)
{
int ret;
FILE *fd;
debug_decl(apparmor_is_enabled, SUDO_DEBUG_APPARMOR);
/* Check whether AppArmor is enabled by reading
* /sys/module/apparmor/parameters/enabled
*
* When this file exists and its contents are equal to "Y", AppArmor
* is enabled. This is a little more reliable than using
* aa_is_enabled(2), which performs an additional check on securityfs
* that will fail in settings where securityfs isn't available
* (e.g. inside a container).
*/
fd = fopen("/sys/module/apparmor/parameters/enabled", "r");
if (fd == NULL)
debug_return_int(0);
ret = (fgetc(fd) == 'Y');
fclose(fd);
debug_return_int(ret);
}
/**
* @brief Prepare to transition into a new AppArmor profile.
*
* @param new_profile The AppArmor profile to transition into on the
* next exec.
*
* @return 0 on success, and a nonzero value on failure.
*/
int
apparmor_prepare(const char *new_profile)
{
int ret;
char *mode, *old_profile;
debug_decl(apparmor_prepare, SUDO_DEBUG_APPARMOR);
/* Determine the current AppArmor confinement status */
if ((ret = aa_getcon(&old_profile, &mode)) == -1) {
sudo_warn("%s", U_("failed to determine AppArmor confinement"));
old_profile = NULL;
goto done;
}
/* Tell AppArmor to transition into the new profile on the
* next exec */
if ((ret = aa_change_onexec(new_profile)) != 0) {
sudo_warn(U_("unable to change AppArmor profile to %s"), new_profile);
goto done;
}
if (mode == NULL)
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: changing AppArmor profile: %s -> %s", __func__,
old_profile, new_profile ? new_profile : "?"
);
else
sudo_debug_printf(SUDO_DEBUG_INFO,
"%s: changing AppArmor profile: %s (%s) -> %s", __func__,
old_profile, mode, new_profile ? new_profile : "?"
);
done:
/* The profile string returned by aa_getcon must be free'd, while the
* mode string must _not_ be free'd */
if (old_profile != NULL)
free(old_profile);
debug_return_int(ret);
}
#endif /* HAVE_APPARMOR */

View File

@@ -83,6 +83,7 @@ static struct sudo_settings sudo_settings[] = {
{ "askpass" },
{ "intercept_setid" },
{ "intercept_ptrace" },
{ "apparmor_profile" },
{ NULL }
};

View File

@@ -689,6 +689,9 @@ command_info_to_details(char * const info[], struct command_details *details)
for (i = 0; info[i] != NULL; i++) {
sudo_debug_printf(SUDO_DEBUG_INFO, " %d: %s", i, info[i]);
switch (info[i][0]) {
case 'a':
SET_STRING("apparmor_profile=", apparmor_profile);
break;
case 'c':
SET_STRING("chroot=", chroot)
SET_STRING("command=", command)
@@ -895,6 +898,15 @@ command_info_to_details(char * const info[], struct command_details *details)
exit(EXIT_FAILURE);
}
#endif
#ifdef HAVE_APPARMOR
if (details->apparmor_profile != NULL && apparmor_is_enabled()) {
i = apparmor_prepare(details->apparmor_profile);
if (i != 0)
exit(EXIT_FAILURE);
}
#endif
debug_return;
}

View File

@@ -104,6 +104,7 @@
#define ARG_ASKPASS 25
#define ARG_INTERCEPT_SETID 26
#define ARG_INTERCEPT_PTRACE 27
#define ARG_APPARMOR_PROFILE 28
/*
* Flags for tgetpass()
@@ -199,6 +200,7 @@ struct command_details {
const char *chroot;
const char *selinux_role;
const char *selinux_type;
const char *apparmor_profile;
const char *utmp_user;
const char *tty;
char **argv;
@@ -286,6 +288,10 @@ int selinux_setexeccon(void);
void selinux_execve(int fd, const char *path, char *const argv[],
char *envp[], int flags);
/* apparmor.c */
int apparmor_is_enabled(void);
int apparmor_prepare(const char* new_profile);
/* solaris.c */
void set_project(struct passwd *);
int os_init_solaris(int argc, char *argv[], char *envp[]);