From fe4de9a66d112cb9ddd5977dcce075323f29a39a Mon Sep 17 00:00:00 2001 From: Dwight Engen Date: Tue, 24 Sep 2013 11:13:02 -0400 Subject: [PATCH] refactor AppArmor into LSM backend, add SELinux support Currently, a maximum of one LSM within LXC will be initialized and used. If in the future stacked LSMs become a reality, we can support it without changing the configuration syntax and add support for more than a single LSM at a time to the lsm code. Generic LXC code should note that lsm_process_label_set() will take effect "now" for AppArmor, and upon exec() for SELinux. - fix Oracle template mounting of proc and sysfs, needed when using SELinux Signed-off-by: Dwight Engen Acked-by: Serge Hallyn Signed-off-by: Serge Hallyn --- configure.ac | 14 +++ doc/lxc.conf.sgml.in | 25 +++++ src/lxc/Makefile.am | 21 +++- src/lxc/apparmor.h | 56 ----------- src/lxc/attach.c | 31 +++--- src/lxc/attach.h | 2 +- src/lxc/conf.c | 43 ++------ src/lxc/conf.h | 12 +-- src/lxc/confile.c | 56 +++++++---- src/lxc/{ => lsm}/apparmor.c | 185 +++++++++++++---------------------- src/lxc/lsm/lsm.c | 156 +++++++++++++++++++++++++++++ src/lxc/lsm/lsm.h | 52 ++++++++++ src/lxc/lsm/nop.c | 46 +++++++++ src/lxc/lsm/selinux.c | 101 +++++++++++++++++++ src/lxc/start.c | 15 ++- src/lxc/start.h | 3 - templates/lxc-oracle.in | 9 +- 17 files changed, 562 insertions(+), 265 deletions(-) delete mode 100644 src/lxc/apparmor.h rename src/lxc/{ => lsm}/apparmor.c (51%) create mode 100644 src/lxc/lsm/lsm.c create mode 100644 src/lxc/lsm/lsm.h create mode 100644 src/lxc/lsm/nop.c create mode 100644 src/lxc/lsm/selinux.c diff --git a/configure.ac b/configure.ac index 092a67d7c..92a4690dc 100644 --- a/configure.ac +++ b/configure.ac @@ -117,6 +117,20 @@ AM_COND_IF([ENABLE_APPARMOR], AC_CHECK_LIB([apparmor], [aa_change_profile],[],[AC_MSG_ERROR([You must install the AppArmor development package in order to compile lxc])]) AC_SUBST([APPARMOR_LIBS], [-lapparmor])]) +# SELinux +AC_ARG_ENABLE([selinux], + [AC_HELP_STRING([--enable-selinux], [enable SELinux support])], + [], [enable_selinux=check]) + +if test "x$enable_selinux" = xcheck; then + AC_CHECK_LIB([selinux],[setexeccon_raw],[enable_selinux=yes],[enable_selinux=no]) +fi +AM_CONDITIONAL([ENABLE_SELINUX], [test "x$enable_selinux" = "xyes"]) +AM_COND_IF([ENABLE_SELINUX], + [AC_CHECK_HEADER([selinux/selinux.h],[],[AC_MSG_ERROR([You must install the SELinux development package in order to compile lxc])]) + AC_CHECK_LIB([selinux], [setexeccon_raw],[],[AC_MSG_ERROR([You must install the SELinux development package in order to compile lxc])]) + AC_SUBST([SELINUX_LIBS])]) + # Seccomp syscall filter AC_ARG_ENABLE([seccomp], [AC_HELP_STRING([--enable-seccomp], [enable seccomp])], diff --git a/doc/lxc.conf.sgml.in b/doc/lxc.conf.sgml.in index dc416e856..bad553c17 100644 --- a/doc/lxc.conf.sgml.in +++ b/doc/lxc.conf.sgml.in @@ -810,6 +810,31 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + + SELinux context + + If lxc was compiled and installed with SELinux support, and the host + system has SELinux enabled, then the SELinux context under which the + container should be run can be specified in the container + configuration. The default is unconfined_t, + which means that lxc will not attempt to change contexts. + + + + + + + + + Specify the SELinux context under which the container should + be run or unconfined_t. For example + + lxc.se_context = unconfined_u:unconfined_r:lxc_t:s0-s0:c0.c1023 + + + + + Seccomp configuration diff --git a/src/lxc/Makefile.am b/src/lxc/Makefile.am index f19a9944f..873b97d04 100644 --- a/src/lxc/Makefile.am +++ b/src/lxc/Makefile.am @@ -37,6 +37,18 @@ sodir=$(libdir) # use PROGRAMS to avoid complains from automake so_PROGRAMS = liblxc.so +LSM_SOURCES = \ + lsm/nop.c \ + lsm/lsm.h lsm/lsm.c + +if ENABLE_APPARMOR +LSM_SOURCES += lsm/apparmor.c +endif + +if ENABLE_SELINUX +LSM_SOURCES += lsm/selinux.c +endif + liblxc_so_SOURCES = \ arguments.c arguments.h \ bdev.c bdev.h \ @@ -73,10 +85,11 @@ liblxc_so_SOURCES = \ af_unix.c af_unix.h \ \ lxcutmp.c lxcutmp.h \ - apparmor.c apparmor.h \ lxclock.h lxclock.c \ lxccontainer.c lxccontainer.h \ - version.c version.h + version.c version.h \ + \ + $(LSM_SOURCES) if IS_BIONIC liblxc_so_SOURCES += \ @@ -107,6 +120,10 @@ if ENABLE_APPARMOR AM_CFLAGS += -DHAVE_APPARMOR endif +if ENABLE_SELINUX +AM_CFLAGS += -DHAVE_SELINUX +endif + if HAVE_NEWUIDMAP AM_CFLAGS += -DHAVE_NEWUIDMAP endif diff --git a/src/lxc/apparmor.h b/src/lxc/apparmor.h deleted file mode 100644 index e27a72859..000000000 --- a/src/lxc/apparmor.h +++ /dev/null @@ -1,56 +0,0 @@ -/* apparmor - * - * Copyright © 2012 Serge Hallyn . - * Copyright © 2012 Canonical Ltd. - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - */ - -#include /* for lxc_handler */ -#include - -struct lxc_handler; - -/* - * apparmor_handler_init is really just a wrapper around check_apparmor_enabled - * to allow us to keep from having #ifdef APPARMOR in start.c - */ -extern void apparmor_handler_init(struct lxc_handler *handler); - -#if HAVE_APPARMOR -extern char *aa_get_profile(pid_t pid); -extern int do_apparmor_load(int aa_enabled, char *aa_profile, - int umount_proc, int dropprivs); -extern int apparmor_load(struct lxc_handler *handler); -extern int attach_apparmor(char *profile); -extern int lsm_mount_proc_if_needed(char *root_src, char *rootfs_tgt); -#else -static inline char *aa_get_profile(pid_t pid) { - return NULL; -} -static inline int do_apparmor_load(int aa_enabled, char *aa_profile, - int umount_proc, int dropprivs) { - return 0; -} -static inline int attach_apparmor(char *profile) { - return 0; -} -static inline int apparmor_load(struct lxc_handler *handler) { - return 0; -} -static inline int lsm_mount_proc_if_needed(char *root_src, char *rootfs_tgt) { - return 0; -} -#endif diff --git a/src/lxc/attach.c b/src/lxc/attach.c index cc95079a5..37cefb020 100644 --- a/src/lxc/attach.c +++ b/src/lxc/attach.c @@ -46,11 +46,11 @@ #include "attach.h" #include "caps.h" #include "config.h" -#include "apparmor.h" #include "utils.h" #include "commands.h" #include "cgroup.h" #include "lxclock.h" +#include "lsm/lsm.h" #if HAVE_SYS_PERSONALITY_H #include @@ -129,7 +129,7 @@ struct lxc_proc_context_info *lxc_proc_get_context_info(pid_t pid) errno = ENOENT; goto out_error; } - info->aa_profile = aa_get_profile(pid); + info->lsm_label = lsm_process_label_get(pid); return info; @@ -138,6 +138,13 @@ out_error: return NULL; } +static void lxc_proc_put_context_info(struct lxc_proc_context_info *ctx) +{ + if (ctx->lsm_label) + free(ctx->lsm_label); + free(ctx); +} + int lxc_attach_to_ns(pid_t pid, int which) { char path[MAXPATHLEN]; @@ -644,8 +651,7 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun ERROR("failed to automatically determine the " "namespaces which the container unshared"); free(cwd); - free(init_ctx->aa_profile); - free(init_ctx); + lxc_proc_put_context_info(init_ctx); return -1; } } @@ -683,8 +689,7 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun if (ret < 0) { SYSERROR("could not set up required IPC mechanism for attaching"); free(cwd); - free(init_ctx->aa_profile); - free(init_ctx); + lxc_proc_put_context_info(init_ctx); return -1; } @@ -705,8 +710,7 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun if (pid < 0) { SYSERROR("failed to create first subprocess"); free(cwd); - free(init_ctx->aa_profile); - free(init_ctx); + lxc_proc_put_context_info(init_ctx); return -1; } @@ -794,8 +798,7 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun process_lock(); close(ipc_sockets[0]); process_unlock(); - free(init_ctx->aa_profile); - free(init_ctx); + lxc_proc_put_context_info(init_ctx); /* we're done, the child process should now execute whatever * it is that the user requested. The parent can now track it @@ -815,8 +818,7 @@ int lxc_attach(const char* name, const char* lxcpath, lxc_attach_exec_t exec_fun process_unlock(); if (to_cleanup_pid) (void) wait_for_pid(to_cleanup_pid); - free(init_ctx->aa_profile); - free(init_ctx); + lxc_proc_put_context_info(init_ctx); return -1; } @@ -918,7 +920,7 @@ int attach_child_main(void* data) /* load apparmor profile */ if ((options->namespaces & CLONE_NEWNS) && (options->attach_flags & LXC_ATTACH_APPARMOR)) { - ret = attach_apparmor(init_ctx->aa_profile); + ret = lsm_process_label_set(init_ctx->lsm_label, 0); if (ret < 0) { shutdown(ipc_socket, SHUT_RDWR); rexit(-1); @@ -1021,8 +1023,7 @@ int attach_child_main(void* data) shutdown(ipc_socket, SHUT_RDWR); close(ipc_socket); - free(init_ctx->aa_profile); - free(init_ctx); + lxc_proc_put_context_info(init_ctx); /* The following is done after the communication socket is * shut down. That way, all errors that might (though diff --git a/src/lxc/attach.h b/src/lxc/attach.h index 518d08662..b5f3ea1c3 100644 --- a/src/lxc/attach.h +++ b/src/lxc/attach.h @@ -28,7 +28,7 @@ #include "attach_options.h" struct lxc_proc_context_info { - char *aa_profile; + char *lsm_label; unsigned long personality; unsigned long long capability_mask; }; diff --git a/src/lxc/conf.c b/src/lxc/conf.c index e933c9a3a..18a92c9d2 100644 --- a/src/lxc/conf.c +++ b/src/lxc/conf.c @@ -74,10 +74,7 @@ #include "bdev.h" #include "cgroup.h" #include "lxclock.h" - -#if HAVE_APPARMOR -#include -#endif +#include "lsm/lsm.h" #if HAVE_SYS_CAPABILITY_H #include @@ -2398,12 +2395,9 @@ struct lxc_conf *lxc_conf_init(void) lxc_list_init(&new->id_map); for (i=0; ihooks[i]); -#if HAVE_APPARMOR - new->aa_profile = NULL; -#endif -#if HAVE_APPARMOR /* || HAVE_SMACK || HAVE_SELINUX */ + new->lsm_aa_profile = NULL; + new->lsm_se_context = NULL; new->lsm_umount_proc = 0; -#endif return new; } @@ -3043,10 +3037,6 @@ int uid_shift_ttys(int pid, struct lxc_conf *conf) int lxc_setup(const char *name, struct lxc_conf *lxc_conf, const char *lxcpath, struct cgroup_process_info *cgroup_info) { -#if HAVE_APPARMOR /* || HAVE_SMACK || HAVE_SELINUX */ - int mounted; -#endif - if (setup_utsname(lxc_conf->utsname)) { ERROR("failed to setup the utsname for '%s'", name); return -1; @@ -3140,24 +3130,11 @@ int lxc_setup(const char *name, struct lxc_conf *lxc_conf, const char *lxcpath, return -1; } -#if HAVE_APPARMOR /* || HAVE_SMACK || HAVE_SELINUX */ - INFO("rootfs path is .%s., mount is .%s.", lxc_conf->rootfs.path, - lxc_conf->rootfs.mount); - if (lxc_conf->rootfs.path == NULL || strlen(lxc_conf->rootfs.path) == 0) { - if (mount("proc", "/proc", "proc", 0, NULL)) { - SYSERROR("Failed mounting /proc, proceeding"); - mounted = 0; - } else - mounted = 1; - } else - mounted = lsm_mount_proc_if_needed(lxc_conf->rootfs.path, lxc_conf->rootfs.mount); - if (mounted == -1) { - SYSERROR("failed to mount /proc in the container."); + /* mount /proc if needed for LSM transition */ + if (lsm_proc_mount(lxc_conf) < 0) { + ERROR("failed to LSM mount proc for '%s'", name); return -1; - } else if (mounted == 1) { - lxc_conf->lsm_umount_proc = 1; } -#endif if (setup_pivot_root(&lxc_conf->rootfs)) { ERROR("failed to set rootfs for '%s'", name); @@ -3488,10 +3465,10 @@ void lxc_conf_free(struct lxc_conf *conf) if (conf->rcfile) free(conf->rcfile); lxc_clear_config_network(conf); -#if HAVE_APPARMOR - if (conf->aa_profile) - free(conf->aa_profile); -#endif + if (conf->lsm_aa_profile) + free(conf->lsm_aa_profile); + if (conf->lsm_se_context) + free(conf->lsm_se_context); lxc_seccomp_free(conf); lxc_clear_config_caps(conf); lxc_clear_config_keepcaps(conf); diff --git a/src/lxc/conf.h b/src/lxc/conf.h index d99bdfe42..8c2dc4e8f 100644 --- a/src/lxc/conf.h +++ b/src/lxc/conf.h @@ -247,9 +247,8 @@ enum { * @tty_info : tty data * @console : console data * @ttydir : directory (under /dev) in which to create console and ttys -#if HAVE_APPARMOR - * @aa_profile : apparmor profile to switch to -#endif + * @lsm_aa_profile : apparmor profile to switch to or NULL + * @lsm_se_context : selinux type to switch to or NULL */ enum lxchooks { LXCHOOK_PRESTART, LXCHOOK_PREMOUNT, LXCHOOK_MOUNT, LXCHOOK_AUTODEV, @@ -285,13 +284,10 @@ struct lxc_conf { char *ttydir; int close_all_fds; struct lxc_list hooks[NUM_LXC_HOOKS]; -#if HAVE_APPARMOR - char *aa_profile; -#endif -#if HAVE_APPARMOR /* || HAVE_SELINUX || HAVE_SMACK */ + char *lsm_aa_profile; + char *lsm_se_context; int lsm_umount_proc; -#endif char *seccomp; // filename with the seccomp rules #if HAVE_SCMP_FILTER_CTX scmp_filter_ctx *seccomp_ctx; diff --git a/src/lxc/confile.c b/src/lxc/confile.c index b378c3ae8..a623d887e 100644 --- a/src/lxc/confile.c +++ b/src/lxc/confile.c @@ -57,9 +57,8 @@ static int config_pts(const char *, const char *, struct lxc_conf *); static int config_tty(const char *, const char *, struct lxc_conf *); static int config_ttydir(const char *, const char *, struct lxc_conf *); static int config_kmsg(const char *, const char *, struct lxc_conf *); -#if HAVE_APPARMOR -static int config_aa_profile(const char *, const char *, struct lxc_conf *); -#endif +static int config_lsm_aa_profile(const char *, const char *, struct lxc_conf *); +static int config_lsm_se_context(const char *, const char *, struct lxc_conf *); static int config_cgroup(const char *, const char *, struct lxc_conf *); static int config_idmap(const char *, const char *, struct lxc_conf *); static int config_loglevel(const char *, const char *, struct lxc_conf *); @@ -100,9 +99,8 @@ static struct lxc_config_t config[] = { { "lxc.tty", config_tty }, { "lxc.devttydir", config_ttydir }, { "lxc.kmsg", config_kmsg }, -#if HAVE_APPARMOR - { "lxc.aa_profile", config_aa_profile }, -#endif + { "lxc.aa_profile", config_lsm_aa_profile }, + { "lxc.se_context", config_lsm_se_context }, { "lxc.cgroup", config_cgroup }, { "lxc.id_map", config_idmap }, { "lxc.loglevel", config_loglevel }, @@ -967,9 +965,8 @@ static int config_kmsg(const char *key, const char *value, return 0; } -#if HAVE_APPARMOR -static int config_aa_profile(const char *key, const char *value, - struct lxc_conf *lxc_conf) +static int config_lsm_aa_profile(const char *key, const char *value, + struct lxc_conf *lxc_conf) { char *path; @@ -981,13 +978,32 @@ static int config_aa_profile(const char *key, const char *value, return -1; } - if (lxc_conf->aa_profile) - free(lxc_conf->aa_profile); - lxc_conf->aa_profile = path; + if (lxc_conf->lsm_aa_profile) + free(lxc_conf->lsm_aa_profile); + lxc_conf->lsm_aa_profile = path; + + return 0; +} + +static int config_lsm_se_context(const char *key, const char *value, + struct lxc_conf *lxc_conf) +{ + char *path; + + if (!value || strlen(value) == 0) + return 0; + path = strdup(value); + if (!path) { + SYSERROR("failed to strdup '%s': %m", value); + return -1; + } + + if (lxc_conf->lsm_se_context) + free(lxc_conf->lsm_se_context); + lxc_conf->lsm_se_context = path; return 0; } -#endif static int config_logfile(const char *key, const char *value, struct lxc_conf *lxc_conf) @@ -1913,10 +1929,10 @@ int lxc_get_config_item(struct lxc_conf *c, const char *key, char *retv, v = c->ttydir; else if (strcmp(key, "lxc.arch") == 0) return lxc_get_arch_entry(c, retv, inlen); -#if HAVE_APPARMOR else if (strcmp(key, "lxc.aa_profile") == 0) - v = c->aa_profile; -#endif + v = c->lsm_aa_profile; + else if (strcmp(key, "lxc.se_context") == 0) + v = c->lsm_se_context; else if (strcmp(key, "lxc.logfile") == 0) v = lxc_log_get_file(); else if (strcmp(key, "lxc.loglevel") == 0) @@ -2000,10 +2016,10 @@ void write_config(FILE *fout, struct lxc_conf *c) default: break; } #endif -#if HAVE_APPARMOR - if (c->aa_profile) - fprintf(fout, "lxc.aa_profile = %s\n", c->aa_profile); -#endif + if (c->lsm_aa_profile) + fprintf(fout, "lxc.aa_profile = %s\n", c->lsm_aa_profile); + if (c->lsm_se_context) + fprintf(fout, "lxc.se_context = %s\n", c->lsm_se_context); if (c->loglevel != LXC_LOG_PRIORITY_NOTSET) fprintf(fout, "lxc.loglevel = %s\n", lxc_log_priority_to_string(c->loglevel)); if (c->logfile) diff --git a/src/lxc/apparmor.c b/src/lxc/lsm/apparmor.c similarity index 51% rename from src/lxc/apparmor.c rename to src/lxc/lsm/apparmor.c index c31cce760..c13613a61 100644 --- a/src/lxc/apparmor.c +++ b/src/lxc/lsm/apparmor.c @@ -25,22 +25,43 @@ #include #include #include - +#include #include "log.h" -#include "apparmor.h" #include "lxclock.h" +#include "lsm/lsm.h" lxc_log_define(lxc_apparmor, lxc); -#if HAVE_APPARMOR -#include - +#define AA_DEF_PROFILE "lxc-container-default" #define AA_MOUNT_RESTR "/sys/kernel/security/apparmor/features/mount/mask" #define AA_ENABLED_FILE "/sys/module/apparmor/parameters/enabled" +/* aa_getcon is not working right now. Use our hand-rolled version below */ +static int apparmor_enabled(void) +{ + struct stat statbuf; + FILE *fin; + char e; + int ret; + + ret = stat(AA_MOUNT_RESTR, &statbuf); + if (ret != 0) + return 0; + process_lock(); + fin = fopen(AA_ENABLED_FILE, "r"); + process_unlock(); + if (!fin) + return 0; + ret = fscanf(fin, "%c", &e); + process_lock(); + fclose(fin); + process_unlock(); + if (ret == 1 && e == 'Y') + return 1; + return 0; +} -/* caller must free the returned profile */ -extern char *aa_get_profile(pid_t pid) +static char *apparmor_process_label_get(pid_t pid) { char path[100], *space; int ret; @@ -65,7 +86,6 @@ again: } sz += 1024; buf = realloc(buf, sz); - memset(buf, 0, sz); if (!buf) { ERROR("out of memory"); process_lock(); @@ -73,17 +93,18 @@ again: process_unlock(); return NULL; } + memset(buf, 0, sz); ret = fread(buf, 1, sz - 1, f); process_lock(); fclose(f); process_unlock(); - if (ret >= sz) - goto again; if (ret < 0) { ERROR("reading %s\n", path); free(buf); return NULL; } + if (ret >= sz) + goto again; space = index(buf, '\n'); if (space) *space = '\0'; @@ -93,9 +114,9 @@ again: return buf; } -static int aa_am_unconfined(void) +static int apparmor_am_unconfined(void) { - char *p = aa_get_profile(getpid()); + char *p = apparmor_process_label_get(getpid()); int ret = 0; if (!p || strcmp(p, "unconfined") == 0) ret = 1; @@ -104,127 +125,55 @@ static int aa_am_unconfined(void) return ret; } -/* aa_getcon is not working right now. Use our hand-rolled version below */ -static int check_apparmor_enabled(void) +/* + * apparmor_process_label_set: Set AppArmor process profile + * + * @label : the profile to set + * @default : use the default profile if label is NULL + * + * Returns 0 on success, < 0 on failure + * + * Notes: This relies on /proc being available. The new context + * will take effect immediately. + */ +static int apparmor_process_label_set(const char *label, int use_default) { - struct stat statbuf; - FILE *fin; - char e; - int ret; - - ret = stat(AA_MOUNT_RESTR, &statbuf); - if (ret != 0) + if (!apparmor_enabled()) return 0; - process_lock(); - fin = fopen(AA_ENABLED_FILE, "r"); - process_unlock(); - if (!fin) - return 0; - ret = fscanf(fin, "%c", &e); - process_lock(); - fclose(fin); - process_unlock(); - if (ret == 1 && e == 'Y') - return 1; - return 0; -} -extern void apparmor_handler_init(struct lxc_handler *handler) -{ - handler->aa_enabled = check_apparmor_enabled(); - INFO("aa_enabled set to %d\n", handler->aa_enabled); -} - -#define AA_DEF_PROFILE "lxc-container-default" - -extern int do_apparmor_load(int aa_enabled, char *aa_profile, - int umount_proc, int dropprivs) -{ - if (!aa_enabled) { - INFO("apparmor not enabled"); - return 0; + if (!label) { + if (use_default) + label = AA_DEF_PROFILE; + else + return 0; } - INFO("setting up apparmor"); - - if (!aa_profile) - aa_profile = AA_DEF_PROFILE; - if (strcmp(aa_profile, "unconfined") == 0 && !dropprivs && aa_am_unconfined()) { + if (strcmp(label, "unconfined") == 0 && apparmor_am_unconfined()) { INFO("apparmor profile unchanged"); return 0; } - //if (aa_change_onexec(aa_profile) < 0) { - if (aa_change_profile(aa_profile) < 0) { - SYSERROR("failed to change apparmor profile to %s", aa_profile); + /* XXX: instant instead of aa_change_onexec(), may be used by attach + * when using a function that doesn't exec + */ + if (aa_change_profile(label) < 0) { + SYSERROR("failed to change apparmor profile to %s", label); return -1; } - if (umount_proc == 1) - umount("/proc"); - - INFO("changed apparmor profile to %s", aa_profile); + INFO("changed apparmor profile to %s", label); return 0; } -extern int apparmor_load(struct lxc_handler *handler) -{ - if (!handler->conf->aa_profile) - handler->conf->aa_profile = AA_DEF_PROFILE; - return do_apparmor_load(handler->aa_enabled, - handler->conf->aa_profile, - handler->conf->lsm_umount_proc, 0); -} - -extern int attach_apparmor(char *profile) -{ - if (!profile) - return 0; - if (!check_apparmor_enabled()) - return 0; - if (strcmp(profile, "unconfined") == 0) - return 0; - return do_apparmor_load(1, profile, 0, 1); -} +static struct lsm_drv apparmor_drv = { + .name = "AppArmor", + .process_label_get = apparmor_process_label_get, + .process_label_set = apparmor_process_label_set, +}; -/* - * this will likely move to a generic lsm.c, as selinux and smack will both - * also want proc mounted in the container so as to transition - */ -extern int lsm_mount_proc_if_needed(char *root_src, char *rootfs_tgt) +struct lsm_drv *lsm_apparmor_drv_init(void) { - char path[MAXPATHLEN]; - char link[20]; - int linklen, ret; - - ret = snprintf(path, MAXPATHLEN, "%s/proc/self", root_src ? rootfs_tgt : ""); - if (ret < 0 || ret >= MAXPATHLEN) { - SYSERROR("proc path name too long"); - return -1; - } - memset(link, 0, 20); - linklen = readlink(path, link, 20); - INFO("I am %d, /proc/self points to %s\n", getpid(), link); - ret = snprintf(path, MAXPATHLEN, "%s/proc", root_src ? rootfs_tgt : ""); - if (linklen < 0) /* /proc not mounted */ - goto domount; - /* can't be longer than rootfs/proc/1 */ - if (strncmp(link, "1", linklen) != 0) { - /* wrong /procs mounted */ - umount2(path, MNT_DETACH); /* ignore failure */ - goto domount; - } - /* the right proc is already mounted */ - return 0; - -domount: - if (mount("proc", path, "proc", 0, NULL)) - return -1; - INFO("Mounted /proc for the container\n"); - return 1; -} -#else -extern void apparmor_handler_init(struct lxc_handler *handler) { - INFO("apparmor_load - apparmor is disabled"); + if (!apparmor_enabled()) + return NULL; + return &apparmor_drv; } -#endif diff --git a/src/lxc/lsm/lsm.c b/src/lxc/lsm/lsm.c new file mode 100644 index 000000000..3974f11d9 --- /dev/null +++ b/src/lxc/lsm/lsm.c @@ -0,0 +1,156 @@ +/* + * lxc: linux Container library + * + * Authors: + * Copyright © 2012 Serge Hallyn + * Copyright © 2012 Canonical Ltd. + * Dwight Engen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#if HAVE_APPARMOR || HAVE_SELINUX + +#include +#include +#include +#include +#include + +#include "conf.h" +#include "log.h" +#include "lsm/lsm.h" + +lxc_log_define(lxc_lsm, lxc); + +static struct lsm_drv *drv = NULL; + +extern struct lsm_drv *lsm_apparmor_drv_init(void); +extern struct lsm_drv *lsm_selinux_drv_init(void); +extern struct lsm_drv *lsm_nop_drv_init(void); + +__attribute__((constructor)) +void lsm_init(void) +{ + if (drv) { + INFO("LSM security driver %s", drv->name); + return; + } + + #if HAVE_APPARMOR + drv = lsm_apparmor_drv_init(); + #endif + #if HAVE_SELINUX + if (!drv) + drv = lsm_selinux_drv_init(); + #endif + + if (!drv) + drv = lsm_nop_drv_init(); + INFO("Initialized LSM security driver %s", drv->name); +} + +char *lsm_process_label_get(pid_t pid) +{ + if (!drv) { + ERROR("LSM driver not inited"); + return NULL; + } + return drv->process_label_get(pid); +} + +int lsm_process_label_set(const char *label, int use_default) +{ + if (!drv) { + ERROR("LSM driver not inited"); + return -1; + } + return drv->process_label_set(label, use_default); +} + +/* + * _lsm_mount_proc: Mount /proc inside container to enable + * security domain transition + * + * @rootfs : the rootfs where proc should be mounted + * + * Returns < 0 on failure, 0 if the correct proc was already mounted + * and 1 if a new proc was mounted. + */ +static int _lsm_proc_mount(const char *rootfs) +{ + char path[MAXPATHLEN]; + char link[20]; + int linklen, ret; + + ret = snprintf(path, MAXPATHLEN, "%s/proc/self", rootfs); + if (ret < 0 || ret >= MAXPATHLEN) { + SYSERROR("proc path name too long"); + return -1; + } + memset(link, 0, 20); + linklen = readlink(path, link, 20); + INFO("I am %d, /proc/self points to '%s'", getpid(), link); + ret = snprintf(path, MAXPATHLEN, "%s/proc", rootfs); + if (linklen < 0) /* /proc not mounted */ + goto domount; + /* can't be longer than rootfs/proc/1 */ + if (strncmp(link, "1", linklen) != 0) { + /* wrong /procs mounted */ + umount2(path, MNT_DETACH); /* ignore failure */ + goto domount; + } + /* the right proc is already mounted */ + return 0; + +domount: + if (mount("proc", path, "proc", 0, NULL)) + return -1; + INFO("Mounted /proc in container for security transition"); + return 1; +} + +int lsm_proc_mount(struct lxc_conf *lxc_conf) +{ + int mounted; + + if (!drv || strcmp(drv->name, "nop") == 0) + return 0; + + if (lxc_conf->rootfs.path == NULL || strlen(lxc_conf->rootfs.path) == 0) { + if (mount("proc", "/proc", "proc", 0, NULL)) { + SYSERROR("Failed mounting /proc, proceeding"); + mounted = 0; + } else + mounted = 1; + } else + mounted = _lsm_proc_mount(lxc_conf->rootfs.mount); + if (mounted == -1) { + SYSERROR("failed to mount /proc in the container."); + return -1; + } else if (mounted == 1) { + lxc_conf->lsm_umount_proc = 1; + } + return 0; +} + +void lsm_proc_unmount(struct lxc_conf *lxc_conf) +{ + if (lxc_conf->lsm_umount_proc == 1) { + umount("/proc"); + lxc_conf->lsm_umount_proc = 0; + } +} +#endif diff --git a/src/lxc/lsm/lsm.h b/src/lxc/lsm/lsm.h new file mode 100644 index 000000000..2a82c66d7 --- /dev/null +++ b/src/lxc/lsm/lsm.h @@ -0,0 +1,52 @@ +/* + * lxc: linux Container library + * + * Copyright © 2013 Oracle. + * + * Authors: + * Dwight Engen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __lxc_lsm_h +#define __lxc_lsm_h + +struct lxc_conf; + +#include + +struct lsm_drv { + const char *name; + + char *(*process_label_get)(pid_t pid); + int (*process_label_set)(const char *label, int use_default); +}; + +#if HAVE_APPARMOR || HAVE_SELINUX +void lsm_init(void); +char *lsm_process_label_get(pid_t pid); +int lsm_process_label_set(const char *label, int use_default); +int lsm_proc_mount(struct lxc_conf *lxc_conf); +void lsm_proc_unmount(struct lxc_conf *lxc_conf); +#else +static inline void lsm_init(void) { } +static inline char *lsm_process_label_get(pid_t pid) { return NULL; } +static inline int lsm_process_label_set(char *label, int use_default) { return 0; } +static inline int lsm_proc_mount(struct lxc_conf *lxc_conf) { return 0; } +static inline void lsm_proc_unmount(struct lxc_conf *lxc_conf) { } +#endif + +#endif diff --git a/src/lxc/lsm/nop.c b/src/lxc/lsm/nop.c new file mode 100644 index 000000000..9184e6b18 --- /dev/null +++ b/src/lxc/lsm/nop.c @@ -0,0 +1,46 @@ +/* + * lxc: linux Container library + * + * Copyright © 2013 Oracle. + * + * Authors: + * Dwight Engen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include "lsm/lsm.h" + +static char *nop_process_label_get(pid_t pid) +{ + return NULL; +} + +static int nop_process_label_set(const char *label, int use_default) +{ + return 0; +} + +static struct lsm_drv nop_drv = { + .name = "nop", + .process_label_get = nop_process_label_get, + .process_label_set = nop_process_label_set, +}; + +struct lsm_drv *lsm_nop_drv_init(void) +{ + return &nop_drv; +} diff --git a/src/lxc/lsm/selinux.c b/src/lxc/lsm/selinux.c new file mode 100644 index 000000000..6e44e8b56 --- /dev/null +++ b/src/lxc/lsm/selinux.c @@ -0,0 +1,101 @@ +/* + * lxc: linux Container library + * + * Copyright © 2013 Oracle. + * + * Authors: + * Dwight Engen + * + * This library is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2, as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include +#include +#include +#include +#include +#include "log.h" +#include "lsm/lsm.h" + +#define DEFAULT_LABEL "unconfined_t" + +lxc_log_define(lxc_lsm_selinux, lxc); + +/* + * selinux_process_label_get: Get SELinux context of a process + * + * @pid : the pid to get, or 0 for self + * + * Returns the context of the given pid. The caller must free() + * the returned string. + * + * Note that this relies on /proc being available. + */ +static char *selinux_process_label_get(pid_t pid) +{ + security_context_t ctx; + char *label; + + if (getpidcon_raw(pid, &ctx) < 0) { + SYSERROR("failed to get SELinux context for pid %d", pid); + return NULL; + } + label = strdup((char *)ctx); + freecon(ctx); + return label; +} + +/* + * selinux_process_label_set: Set SELinux context of a process + * + * @label : the context to set + * @default : use the default context if label is NULL + * + * Returns 0 on success, < 0 on failure + * + * Notes: This relies on /proc being available. The new context + * will take effect on the next exec(2). + */ +static int selinux_process_label_set(const char *label, int use_default) +{ + if (!label) { + if (use_default) + label = DEFAULT_LABEL; + else + return -1; + } + if (!strcmp(label, "unconfined_t")) + return 0; + + if (setexeccon_raw((char *)label) < 0) { + SYSERROR("failed to set new SELinux context %s", label); + return -1; + } + + INFO("changed SELinux context to %s", label); + return 0; +} + +static struct lsm_drv selinux_drv = { + .name = "SELinux", + .process_label_get = selinux_process_label_get, + .process_label_set = selinux_process_label_set, +}; + +struct lsm_drv *lsm_selinux_drv_init(void) +{ + if (!is_selinux_enabled()) + return NULL; + return &selinux_drv; +} diff --git a/src/lxc/start.c b/src/lxc/start.c index c968bb134..753840358 100644 --- a/src/lxc/start.c +++ b/src/lxc/start.c @@ -68,10 +68,10 @@ #include "console.h" #include "sync.h" #include "namespace.h" -#include "apparmor.h" #include "lxcseccomp.h" #include "caps.h" #include "lxclock.h" +#include "lsm/lsm.h" lxc_log_define(lxc_start, lxc); @@ -285,7 +285,8 @@ struct lxc_handler *lxc_init(const char *name, struct lxc_conf *conf, const char handler->lxcpath = lxcpath; handler->pinfd = -1; - apparmor_handler_init(handler); + lsm_init(); + handler->name = strdup(name); if (!handler->name) { ERROR("failed to allocate memory"); @@ -555,8 +556,16 @@ static int do_start(void *data) if (lxc_sync_barrier_parent(handler, LXC_SYNC_CGROUP)) return -1; - if (apparmor_load(handler) < 0) + /* XXX: hmm apparmor switches right away since it uses + * aa_change_profile() and not aa_change_onexec(). SELinux on the other + * hand is going to transition on exec(). Is it bad to run the stuff + * between here and exec() in the more privileged context? + */ + if (lsm_process_label_set(handler->conf->lsm_aa_profile ? + handler->conf->lsm_aa_profile : + handler->conf->lsm_se_context, 1) < 0) goto out_warn_father; + lsm_proc_unmount(handler->conf); if (lxc_seccomp_load(handler->conf) != 0) goto out_warn_father; diff --git a/src/lxc/start.h b/src/lxc/start.h index 9bf602442..c35c5c481 100644 --- a/src/lxc/start.h +++ b/src/lxc/start.h @@ -50,9 +50,6 @@ struct lxc_handler { struct lxc_operations *ops; void *data; int sv[2]; -#if HAVE_APPARMOR - int aa_enabled; -#endif int pinfd; const char *lxcpath; struct cgroup_process_info *cgroup; diff --git a/templates/lxc-oracle.in b/templates/lxc-oracle.in index 98ea609f0..c0fcd3078 100644 --- a/templates/lxc-oracle.in +++ b/templates/lxc-oracle.in @@ -110,10 +110,8 @@ EOF # this file has to exist for libvirt/Virtual machine monitor to boot the container touch $container_rootfs/etc/mtab - # don't put devpts in here, it will already be mounted for us by lxc/libvirt + # don't put devpts,proc, nor sysfs in here, it will already be mounted for us by lxc/libvirt cat < $container_rootfs/etc/fstab -proc /proc proc nodev,noexec,nosuid 0 0 -sysfs /sys sysfs defaults 0 0 EOF # remove module stuff for iptables it just shows errors that are not @@ -404,9 +402,8 @@ lxc.cgroup.devices.allow = c 5:2 rwm # /dev/ptmx pty master EOF cat < $cfg_dir/fstab || die "unable to create $cfg_dir/fstab" -proc $container_rootfs/proc proc nodev,noexec,nosuid 0 0 -devpts $container_rootfs/dev/pts devpts defaults 0 0 -sysfs $container_rootfs/sys sysfs defaults 0 0 +proc proc proc nodev,noexec,nosuid 0 0 +sysfs sys sysfs defaults 0 0 EOF } -- 2.39.2