From 1932360adca3f9fe9b47bcfad7b8bd5efbd33bee Mon Sep 17 00:00:00 2001 From: Jason Zaman Date: Thu, 30 Oct 2014 10:22:02 +0800 Subject: [PATCH] Integrate the functionality from runscript_selinux.so runscript used to dlopen() runscript_selinux.so. This adds equivalent functionality directly in to runscript instead. It authenticates with either PAM or shadow and optionally has a dep on audit. X-Gentoo-Bug: 517450 X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=517450 --- mk/os-Linux.mk | 15 ++- mk/pam.mk | 6 + src/rc/rc-selinux.c | 292 +++++++++++++++++++++++++++++++++++++++----- src/rc/rc-selinux.h | 2 +- src/rc/runscript.c | 2 +- 5 files changed, 285 insertions(+), 32 deletions(-) diff --git a/mk/os-Linux.mk b/mk/os-Linux.mk index dc76b968..bb6fa372 100644 --- a/mk/os-Linux.mk +++ b/mk/os-Linux.mk @@ -9,6 +9,19 @@ LIBDL= -Wl,-Bdynamic -ldl ifeq (${MKSELINUX},yes) CPPFLAGS+= -DHAVE_SELINUX -LIBSELINUX= -lselinux +LIBSELINUX?= -lselinux LDADD += $(LIBSELINUX) + +ifneq (${MKPAM},pam) +# if using selinux but not pam then we need crypt +LIBCRYPT?= -lcrypt +LDADD += $(LIBCRYPT) +endif + +endif + +ifeq (${MKAUDIT},yes) +LIBAUDIT?= -laudit +CPPFLAGS+= -DHAVE_AUDIT +LDADD+= ${LIBAUDIT} endif diff --git a/mk/pam.mk b/mk/pam.mk index 15ffb546..199896cc 100644 --- a/mk/pam.mk +++ b/mk/pam.mk @@ -3,6 +3,12 @@ LIBPAM?= -lpam CPPFLAGS+= -DHAVE_PAM LDADD+= ${LIBPAM} +ifeq (${MKSELINUX},yes) +# with selinux, pam_misc is needed too +LIBPAM_MISC?= -lpam_misc +LDADD+= ${LIBPAM_MISC} +endif + PAMDIR?= /etc/pam.d PAMMODE?= 0644 else ifneq (${MKPAM},) diff --git a/src/rc/rc-selinux.c b/src/rc/rc-selinux.c index 518b25df..7124e83e 100644 --- a/src/rc/rc-selinux.c +++ b/src/rc/rc-selinux.c @@ -1,7 +1,7 @@ /* - rc-selinux.c - SELinux helpers to get and set contexts. -*/ + * rc-selinux.c + * SELinux helpers to get and set contexts. + */ /* * Copyright (c) 2014 Jason Zaman @@ -31,11 +31,18 @@ #include #include #include - -#include +#include +#include +#include +#include #include #include +#include +#include + +#include +#include #include "einfo.h" #include "queue.h" @@ -44,11 +51,28 @@ #include "rc-plugin.h" #include "rc-selinux.h" -#define SELINUX_LIB RC_LIBDIR "/runscript_selinux.so" +/* the context files for selinux */ +#define RUN_INIT_FILE "run_init_type" +#define INITRC_FILE "initrc_context" -static void (*selinux_run_init_old) (void); -static void (*selinux_run_init_new) (int argc, char **argv); +#ifdef HAVE_AUDIT +#include +#endif +/* PAM or shadow for authentication */ +#ifdef HAVE_PAM +# define PAM_SERVICE_NAME "run_init" /* the name of this program for PAM */ +# include +# include +#else +# define PASSWORD_PROMPT "Password:" +# include +# include +# include +#endif + + +/* The handle for the fcontext lookups */ static struct selabel_handle *hnd = NULL; int selinux_util_label(const char *path) @@ -133,33 +157,243 @@ int selinux_util_close(void) return 0; } -void selinux_setup(int argc, char **argv) +/* + * This will check the users password and return 0 on success or -1 on fail + * + * We ask for the password to make sure it is intended vs run by malicious software. + * Actual authorization is covered by the policy itself. + */ +static int check_password(char *username) { - void *lib_handle = NULL; + int ret = 1; +#ifdef HAVE_PAM + pam_handle_t *pamh; + int pam_err = 0; + const struct pam_conv pconv = { + misc_conv, + NULL + }; - if (!exists(SELINUX_LIB)) - return; + pam_err = pam_start(PAM_SERVICE_NAME, username, &pconv, &pamh); + if (pam_err != PAM_SUCCESS) { + ret = -1; + goto outpam; + } - lib_handle = dlopen(SELINUX_LIB, RTLD_NOW | RTLD_GLOBAL); - if (!lib_handle) { - eerror("dlopen: %s", dlerror()); + pam_err = pam_authenticate(pamh, PAM_DISALLOW_NULL_AUTHTOK); + if (pam_err != PAM_SUCCESS) { + ret = -1; + goto outpam; + } + + ret = 0; +outpam: + pam_end(pamh, pam_err); + pamh = NULL; + +#else /* authenticating via /etc/shadow instead */ + struct spwd *spw; + char *password; + char *attempt; + + spw = getspnam(username); + if (!spw) { + eerror("Failed to read shadow entry"); + ret = -1; + goto outshadow; + } + + attempt = getpass(PASSWORD_PROMPT); + if (!attempt) { + ret = -1; + goto outshadow; + } + + if (*spw->sp_pwdp == '\0' && *attempt == '\0') { + ret = -1; + goto outshadow; + } + + /* salt must be at least two characters long */ + if (!(spw->sp_pwdp[0] && spw->sp_pwdp[1])) { + ret = -1; + goto outshadow; + } + + /* encrypt the password attempt */ + password = crypt(attempt, spw->sp_pwdp); + + if (password && strcmp(password, spw->sp_pwdp) == 0) + ret = 0; + else + ret = -1; +outshadow: +#endif + return ret; +} + +/* Authenticates the user, returns 0 on success, 1 on fail */ +static int check_auth() +{ + struct passwd *pw; + uid_t uid; + +#ifdef HAVE_AUDIT + uid = audit_getloginuid(); + if (uid == (uid_t) -1) + uid = getuid(); +#else + uid = getuid(); +#endif + + pw = getpwuid(uid); + if (!pw) { + eerror("cannot find your entry in the passwd file."); + return (-1); + } + + printf("Authenticating %s.\n", pw->pw_name); + + /* do the actual check */ + if (check_password(pw->pw_name) == 0) { + return 0; + } + + eerrorx("Authentication failed for %s", pw->pw_name); + return 1; +} + +/* + * Read the context from the given context file. context must be free'd by the user. + */ +static int read_context_file(const char *filename, char **context) +{ + int ret = -1; + FILE *fp; + char filepath[PATH_MAX]; + char *line = NULL; + char *p; + char *p2; + size_t len = 0; + ssize_t read; + + memset(filepath, '\0', PATH_MAX); + snprintf(filepath, PATH_MAX - 1, "%s/%s", selinux_contexts_path(), filename); + + fp = fopen(filepath, "r"); + if (fp == NULL) { + eerror("Failed to open context file: %s", filename); + return -1; + } + + while ((read = getline(&line, &len, fp)) != -1) { + /* cut off spaces before the string */ + p = line; + while (isspace(*p) && *p != '\0') + p++; + + /* empty string, skip */ + if (*p == '\0') + continue; + + /* cut off spaces after the string */ + p2 = p; + while (!isspace(*p2) && *p2 != '\0') + p2++; + *p2 = '\0'; + + *context = xstrdup(p); + ret = 0; + break; + } + + free(line); + fclose(fp); + return ret; +} + +void selinux_setup(char **argv) +{ + char *new_context = NULL; + char *curr_context = NULL; + context_t curr_con; + char *curr_t = NULL; + char *run_init_t = NULL; + + /* Return, if selinux is disabled. */ + if (is_selinux_enabled() < 1) { return; } - selinux_run_init_old = (void (*)(void)) - dlfunc(lib_handle, "selinux_runscript"); - selinux_run_init_new = (void (*)(int, char **)) - dlfunc(lib_handle, "selinux_runscript2"); + if (read_context_file(RUN_INIT_FILE, &run_init_t) != 0) { + /* assume a reasonable default, rather than bailing out */ + run_init_t = xstrdup("run_init_t"); + ewarn("Assuming SELinux run_init type is %s", run_init_t); + } - /* Use new run_init if it exists, else fall back to old */ - if (selinux_run_init_new) - selinux_run_init_new(argc, argv); - else if (selinux_run_init_old) - selinux_run_init_old(); - else - /* This shouldnt happen... probably corrupt lib */ - eerrorx - ("run_init is missing from runscript_selinux.so!"); + /* Get our current context. */ + if (getcon(&curr_context) < 0) { + if (errno == ENOENT) { + /* should only hit this if proc is not mounted. this + * happens on Gentoo right after init starts, when + * the init script processing starts. + */ + goto out; + } else { + perror("getcon"); + exit(1); + } + } - dlclose(lib_handle); + /* extract the type from the context */ + curr_con = context_new(curr_context); + curr_t = xstrdup(context_type_get(curr_con)); + /* dont need them anymore so free() now */ + context_free(curr_con); + free(curr_context); + + /* if we are not in the run_init domain, we should not do anything */ + if (strncmp(run_init_t, curr_t, strlen(run_init_t)) != 0) { + goto out; + } + + free(curr_t); + free(run_init_t); + + if (check_auth() != 0) { + eerrorx("Authentication failed."); + } + + /* Get the context for the script to be run in. */ + if (read_context_file(INITRC_FILE, &new_context) != 0) { + /* assume a reasonable default, rather than bailing out */ + new_context = xstrdup("system_u:system_r:initrc_t"); + ewarn("Assuming SELinux initrc context is %s", new_context); + } + + /* Set the new context */ + if (setexeccon(new_context) < 0) { + eerrorx("Could not set SELinux exec context to %s.", new_context); + } + + free(new_context); + + /* + * exec will recycle ptys so try and use open_init_pty if it exists + * which will open the pty with initrc_devpts_t, if it doesnt exist, + * fall back to plain exec + */ + if (access("/usr/sbin/open_init_pty", X_OK)) { + if (execvp("/usr/sbin/open_init_pty", argv)) { + perror("execvp"); + exit(-1); + } + } else if (execvp(argv[1], argv + 1)) { + perror("execvp"); + exit(-1); + } + +out: + free(run_init_t); + free(curr_t); } diff --git a/src/rc/rc-selinux.h b/src/rc/rc-selinux.h index 8cf73b05..e28f3339 100644 --- a/src/rc/rc-selinux.h +++ b/src/rc/rc-selinux.h @@ -30,6 +30,6 @@ int selinux_util_open(void); int selinux_util_label(const char *path); int selinux_util_close(void); -void selinux_setup(int argc, char **argv); +void selinux_setup(char **argv); #endif diff --git a/src/rc/runscript.c b/src/rc/runscript.c index 882cb8c0..0ac19661 100644 --- a/src/rc/runscript.c +++ b/src/rc/runscript.c @@ -1193,7 +1193,7 @@ openrc_run(int argc, char **argv) #ifdef HAVE_SELINUX /* Ok, we are ready to go, so setup selinux if applicable */ - selinux_setup(argc, argv); + selinux_setup(argv); #endif deps = true;