From eef65bf3ee6f73afa4a5de23ae3a794a279f30c0 Mon Sep 17 00:00:00 2001 From: Michael Scherer Date: Thu, 20 Feb 2014 16:19:44 +0100 Subject: core: Add AppArmor profile switching This permit to switch to a specific apparmor profile when starting a daemon. This will result in a non operation if apparmor is disabled. It also add a new build requirement on libapparmor for using this feature. --- Makefile.am | 2 ++ configure.ac | 16 +++++++++++ man/systemd.exec.xml | 13 +++++++++ src/core/build.h | 8 +++++- src/core/dbus-execute.c | 19 +++++++++++++ src/core/execute.c | 23 +++++++++++++++ src/core/execute.h | 3 ++ src/core/load-fragment-gperf.gperf.m4 | 5 +++- src/core/load-fragment.c | 53 +++++++++++++++++++++++++++++++++-- src/core/load-fragment.h | 1 + src/shared/exit-status.c | 3 ++ src/shared/exit-status.h | 3 +- 12 files changed, 144 insertions(+), 5 deletions(-) diff --git a/Makefile.am b/Makefile.am index 56cdb85684..6f8d3b5a29 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1020,6 +1020,7 @@ libsystemd_core_la_CFLAGS = \ $(AUDIT_CFLAGS) \ $(CAP_CFLAGS) \ $(KMOD_CFLAGS) \ + $(APPARMOR_CFLAGS) \ $(SECCOMP_CFLAGS) \ -pthread @@ -1035,6 +1036,7 @@ libsystemd_core_la_LIBADD = \ $(AUDIT_LIBS) \ $(CAP_LIBS) \ $(KMOD_LIBS) \ + $(APPARMOR_LIBS) \ $(SECCOMP_LIBS) if HAVE_SECCOMP diff --git a/configure.ac b/configure.ac index c03a0e26b8..80c14493e6 100644 --- a/configure.ac +++ b/configure.ac @@ -385,6 +385,21 @@ if test "x$enable_selinux" != "xno"; then fi AM_CONDITIONAL(HAVE_SELINUX, [test "$have_selinux" = "yes"]) +have_apparmor=no +AC_ARG_ENABLE(apparmor, AS_HELP_STRING([--disable-apparmor], [Disable optional AppArmor support])) +if test "x$enable_apparmor" != "xno"; then + PKG_CHECK_MODULES([APPARMOR], [libapparmor], + [AC_DEFINE(HAVE_APPARMOR, 1, [Define if AppArmor is available]) + have_apparmor=yes + M4_DEFINES="$M4_DEFINES -DHAVE_APPARMOR"], + [have_apparmor=no]) + if test "x$have_apparmor" = xno -a "x$enable_apparmor" = xyes; then + AC_MSG_ERROR([*** AppArmor support requested but libraries not found]) + fi +fi +AM_CONDITIONAL(HAVE_APPARMOR, [test "$have_apparmor" = "yes"]) + + AC_ARG_WITH(debug-shell, AS_HELP_STRING([--with-debug-shell=PATH], [Path to debug shell binary]), @@ -1110,6 +1125,7 @@ AC_MSG_RESULT([ PAM: ${have_pam} AUDIT: ${have_audit} IMA: ${have_ima} + AppArmor: ${have_apparmor} SELinux: ${have_selinux} SECCOMP: ${have_seccomp} SMACK: ${have_smack} diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml index 7dbe05d265..19839937c7 100644 --- a/man/systemd.exec.xml +++ b/man/systemd.exec.xml @@ -967,6 +967,19 @@ for details. + + AppArmorProfile= + + Take a profile name as argument. + The process executed by the unit will switch to + this profile when started. Profiles must already + be loaded in the kernel, or the unit will fail. + This result in a non operation if AppArmor is not + enabled. If prefixed by -, all errors + will be ignored. + + + IgnoreSIGPIPE= diff --git a/src/core/build.h b/src/core/build.h index c8117edff3..3d7cd3ea39 100644 --- a/src/core/build.h +++ b/src/core/build.h @@ -45,6 +45,12 @@ #define _SELINUX_FEATURE_ "-SELINUX" #endif +#ifdef HAVE_APPARMOR +#define _APPARMOR_FEATURE_ "+APPARMOR" +#else +#define _APPARMOR_FEATURE_ "-APPARMOR" +#endif + #ifdef HAVE_IMA #define _IMA_FEATURE_ "+IMA" #else @@ -87,4 +93,4 @@ #define _SECCOMP_FEATURE_ "-SECCOMP" #endif -#define SYSTEMD_FEATURES _PAM_FEATURE_ " " _LIBWRAP_FEATURE_ " " _AUDIT_FEATURE_ " " _SELINUX_FEATURE_ " " _IMA_FEATURE_ " " _SYSVINIT_FEATURE_ " " _LIBCRYPTSETUP_FEATURE_ " " _GCRYPT_FEATURE_ " " _ACL_FEATURE_ " " _XZ_FEATURE_ " " _SECCOMP_FEATURE_ +#define SYSTEMD_FEATURES _PAM_FEATURE_ " " _LIBWRAP_FEATURE_ " " _AUDIT_FEATURE_ " " _SELINUX_FEATURE_ " " _IMA_FEATURE_ " " _SYSVINIT_FEATURE_ " " _LIBCRYPTSETUP_FEATURE_ " " _GCRYPT_FEATURE_ " " _ACL_FEATURE_ " " _XZ_FEATURE_ " " _SECCOMP_FEATURE_ " " _APPARMOR_FEATURE_ diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 41dbbab904..935c62bdf2 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -482,6 +482,24 @@ static int property_get_selinux_context( return sd_bus_message_append(reply, "(bs)", c->selinux_context_ignore, c->selinux_context); } +static int property_get_apparmor_profile( + sd_bus *bus, + const char *path, + const char *interface, + const char *property, + sd_bus_message *reply, + void *userdata, + sd_bus_error *error) { + + ExecContext *c = userdata; + + assert(bus); + assert(reply); + assert(c); + + return sd_bus_message_append(reply, "(bs)", c->apparmor_profile_ignore, c->apparmor_profile); +} + static int property_get_personality( sd_bus *bus, const char *path, @@ -560,6 +578,7 @@ const sd_bus_vtable bus_exec_vtable[] = { SD_BUS_PROPERTY("SameProcessGroup", "b", bus_property_get_bool, offsetof(ExecContext, same_pgrp), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("UtmpIdentifier", "s", NULL, offsetof(ExecContext, utmp_id), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SELinuxContext", "(bs)", property_get_selinux_context, 0, SD_BUS_VTABLE_PROPERTY_CONST), + SD_BUS_PROPERTY("AppArmorProfile", "(bs)", property_get_apparmor_profile, 0, SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("IgnoreSIGPIPE", "b", bus_property_get_bool, offsetof(ExecContext, ignore_sigpipe), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("NoNewPrivileges", "b", bus_property_get_bool, offsetof(ExecContext, no_new_privileges), SD_BUS_VTABLE_PROPERTY_CONST), SD_BUS_PROPERTY("SystemCallFilter", "(bas)", property_get_syscall_filter, 0, SD_BUS_VTABLE_PROPERTY_CONST), diff --git a/src/core/execute.c b/src/core/execute.c index f8b7521ff9..a328fc265f 100644 --- a/src/core/execute.c +++ b/src/core/execute.c @@ -55,6 +55,10 @@ #include #endif +#ifdef HAVE_APPARMOR +#include +#endif + #include "execute.h" #include "strv.h" #include "macro.h" @@ -77,6 +81,7 @@ #include "async.h" #include "selinux-util.h" #include "errno-list.h" +#include "apparmor-util.h" #ifdef HAVE_SECCOMP #include "seccomp-util.h" @@ -1597,6 +1602,16 @@ int exec_spawn(ExecCommand *command, } } #endif + +#ifdef HAVE_APPARMOR + if (context->apparmor_profile && use_apparmor()) { + err = aa_change_onexec(context->apparmor_profile); + if (err < 0 && !context->apparmor_profile_ignore) { + r = EXIT_APPARMOR_PROFILE; + goto fail_child; + } + } +#endif } err = build_environment(context, n_fds, watchdog_usec, home, username, shell, &our_env); @@ -1759,6 +1774,9 @@ void exec_context_done(ExecContext *c) { free(c->selinux_context); c->selinux_context = NULL; + free(c->apparmor_profile); + c->apparmor_profile = NULL; + #ifdef HAVE_SECCOMP set_free(c->syscall_filter); c->syscall_filter = NULL; @@ -2188,6 +2206,11 @@ void exec_context_dump(ExecContext *c, FILE* f, const char *prefix) { fprintf(f, "%sSystemCallErrorNumber: %s\n", prefix, strna(errno_to_name(c->syscall_errno))); + + if (c->apparmor_profile) + fprintf(f, + "%sAppArmorProfile: %s%s\n", + prefix, c->apparmor_profile_ignore ? "-" : "", c->apparmor_profile); } void exec_status_start(ExecStatus *s, pid_t pid) { diff --git a/src/core/execute.h b/src/core/execute.h index 5c4c0b4abe..2bfe227145 100644 --- a/src/core/execute.h +++ b/src/core/execute.h @@ -141,6 +141,9 @@ struct ExecContext { bool selinux_context_ignore; char *selinux_context; + bool apparmor_profile_ignore; + char *apparmor_profile; + char **read_write_dirs, **read_only_dirs, **inaccessible_dirs; unsigned long mount_flags; diff --git a/src/core/load-fragment-gperf.gperf.m4 b/src/core/load-fragment-gperf.gperf.m4 index e9995bf0c7..e1bab7a264 100644 --- a/src/core/load-fragment-gperf.gperf.m4 +++ b/src/core/load-fragment-gperf.gperf.m4 @@ -90,7 +90,10 @@ $1.IgnoreSIGPIPE, config_parse_bool, 0, $1.UtmpIdentifier, config_parse_unit_string_printf, 0, offsetof($1, exec_context.utmp_id) m4_ifdef(`HAVE_SELINUX', `$1.SELinuxContext, config_parse_exec_selinux_context, 0, offsetof($1, exec_context)', -`$1.SELinuxContext, config_parse_warn_compat, 0, 0')' +`$1.SELinuxContext, config_parse_warn_compat, 0, 0') +m4_ifdef(`HAVE_APPARMOR', +`$1.AppArmorProfile, config_parse_exec_apparmor_profile,0, offsetof($1, exec_context)', +`$1.AppArmorProfile, config_parse_warn_compat, 0, 0')' )m4_dnl m4_define(`KILL_CONTEXT_CONFIG_ITEMS', `$1.SendSIGKILL, config_parse_bool, 0, offsetof($1, kill_context.send_sigkill) diff --git a/src/core/load-fragment.c b/src/core/load-fragment.c index 27666b937c..e74a790e25 100644 --- a/src/core/load-fragment.c +++ b/src/core/load-fragment.c @@ -61,7 +61,7 @@ #include "seccomp-util.h" #endif -#if !defined(HAVE_SYSV_COMPAT) || !defined(HAVE_SECCOMP) || !defined(HAVE_LIBWRAP) || !defined(HAVE_PAM) || !defined(HAVE_SELINUX) || !defined(HAVE_SMACK) +#if !defined(HAVE_SYSV_COMPAT) || !defined(HAVE_SECCOMP) || !defined(HAVE_LIBWRAP) || !defined(HAVE_PAM) || !defined(HAVE_SELINUX) || !defined(HAVE_SMACK) || !defined(HAVE_APPARMOR) int config_parse_warn_compat( const char *unit, const char *filename, @@ -1192,6 +1192,55 @@ int config_parse_exec_selinux_context( return 0; } +int config_parse_exec_apparmor_profile( + const char *unit, + const char *filename, + unsigned line, + const char *section, + unsigned section_line, + const char *lvalue, + int ltype, + const char *rvalue, + void *data, + void *userdata) { + + ExecContext *c = data; + Unit *u = userdata; + bool ignore; + char *k; + int r; + + assert(filename); + assert(lvalue); + assert(rvalue); + assert(data); + + if (isempty(rvalue)) { + free(c->apparmor_profile); + c->apparmor_profile = NULL; + c->apparmor_profile_ignore = false; + return 0; + } + + if (rvalue[0] == '-') { + ignore = true; + rvalue++; + } else + ignore = false; + + r = unit_name_printf(u, rvalue, &k); + if (r < 0) { + log_syntax(unit, LOG_ERR, filename, line, -r, "Failed to resolve specifiers, ignoring: %s", strerror(-r)); + return 0; + } + + free(c->apparmor_profile); + c->apparmor_profile = k; + c->apparmor_profile_ignore = ignore; + + return 0; +} + int config_parse_timer(const char *unit, const char *filename, unsigned line, @@ -2910,7 +2959,7 @@ void unit_dump_config_items(FILE *f) { const ConfigParserCallback callback; const char *rvalue; } table[] = { -#if !defined(HAVE_SYSV_COMPAT) || !defined(HAVE_SECCOMP) || !defined(HAVE_LIBWRAP) || !defined(HAVE_PAM) || !defined(HAVE_SELINUX) || !defined(HAVE_SMACK) +#if !defined(HAVE_SYSV_COMPAT) || !defined(HAVE_SECCOMP) || !defined(HAVE_LIBWRAP) || !defined(HAVE_PAM) || !defined(HAVE_SELINUX) || !defined(HAVE_SMACK) || !defined(HAVE_APPARMOR) { config_parse_warn_compat, "NOTSUPPORTED" }, #endif { config_parse_int, "INTEGER" }, diff --git a/src/core/load-fragment.h b/src/core/load-fragment.h index 83ecea5da1..4a5ec35cbe 100644 --- a/src/core/load-fragment.h +++ b/src/core/load-fragment.h @@ -89,6 +89,7 @@ int config_parse_job_mode(const char *unit, const char *filename, unsigned line, int config_parse_job_mode_isolate(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_exec_selinux_context(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); int config_parse_personality(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); +int config_parse_exec_apparmor_profile(const char *unit, const char *filename, unsigned line, const char *section, unsigned section_line, const char *lvalue, int ltype, const char *rvalue, void *data, void *userdata); /* gperf prototypes */ const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, unsigned length); diff --git a/src/shared/exit-status.c b/src/shared/exit-status.c index 8b096da7e1..902f55ac65 100644 --- a/src/shared/exit-status.c +++ b/src/shared/exit-status.c @@ -136,6 +136,9 @@ const char* exit_status_to_string(ExitStatus status, ExitStatusLevel level) { case EXIT_PERSONALITY: return "PERSONALITY"; + + case EXIT_APPARMOR_PROFILE: + return "APPARMOR"; } } diff --git a/src/shared/exit-status.h b/src/shared/exit-status.h index dde5bdda8b..de379f1aa0 100644 --- a/src/shared/exit-status.h +++ b/src/shared/exit-status.h @@ -69,7 +69,8 @@ typedef enum ExitStatus { EXIT_NO_NEW_PRIVILEGES, EXIT_SECCOMP, EXIT_SELINUX_CONTEXT, - EXIT_PERSONALITY /* 230 */ + EXIT_PERSONALITY, /* 230 */ + EXIT_APPARMOR_PROFILE } ExitStatus; typedef enum ExitStatusLevel { -- cgit v1.2.3-54-g00ecf