Add an apparmor_profile sudo setting

Define a new sudo setting, `apparmor_profile`, that can be used to pass
in an AppArmor profile that should be used to confine commands. If
apparmor_profile is specified, sudo will execute the command using the
new `apparmor_execve` function, which confines the command under the
provided profile before exec'ing it.
This commit is contained in:
kernelmethod
2022-05-23 13:41:42 -06:00
parent 0b541c2029
commit bd25b85a66
7 changed files with 150 additions and 0 deletions

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

@@ -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

@@ -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

@@ -82,6 +82,7 @@ static struct sudo_settings sudo_settings[] = {
{ "cmnd_cwd" },
{ "askpass" },
{ "intercept_setid" },
{ "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)
@@ -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;
}

View File

@@ -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[]);