diff --git a/MANIFEST b/MANIFEST index ccd3aa01e..2fb53d929 100644 --- a/MANIFEST +++ b/MANIFEST @@ -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 diff --git a/include/sudo_debug.h b/include/sudo_debug.h index b598aefd0..399602b8f 100644 --- a/include/sudo_debug.h +++ b/include/sudo_debug.h @@ -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(). */ diff --git a/src/Makefile.in b/src/Makefile.in index f6ff40532..9882923ba 100644 --- a/src/Makefile.in +++ b/src/Makefile.in @@ -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 diff --git a/src/apparmor.c b/src/apparmor.c new file mode 100644 index 000000000..5f36f3064 --- /dev/null +++ b/src/apparmor.c @@ -0,0 +1,111 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2022 Will Shand + * + * 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 + +#ifdef HAVE_APPARMOR + +# include +# include +# include + +# 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 */ diff --git a/src/parse_args.c b/src/parse_args.c index 9cc320bd7..5c2a62aed 100644 --- a/src/parse_args.c +++ b/src/parse_args.c @@ -82,6 +82,7 @@ static struct sudo_settings sudo_settings[] = { { "cmnd_cwd" }, { "askpass" }, { "intercept_setid" }, + { "apparmor_profile" }, { NULL } }; diff --git a/src/sudo.c b/src/sudo.c index c039cab49..fcf2492d4 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -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) @@ -897,6 +900,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; } diff --git a/src/sudo.h b/src/sudo.h index 2e088de72..2719eb698 100644 --- a/src/sudo.h +++ b/src/sudo.h @@ -103,6 +103,7 @@ #define ARG_CWD 24 #define ARG_ASKPASS 25 #define ARG_INTERCEPT_SETID 26 +#define ARG_APPARMOR_PROFILE 27 /* * Flags for tgetpass() @@ -198,6 +199,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; @@ -285,6 +287,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[]);