From db8cd4099ae03c37175c6b10bd69abc63cc6db24 Mon Sep 17 00:00:00 2001 From: Tomas Mraz Date: Thu, 11 Jul 2013 14:02:52 +0200 Subject: [PATCH] add auditing of SELinux policy violation in pam_rootok (#965723) - add SELinux helper to pam_pwhistory --- pam-1.1.6-pwhistory-helper.patch | 765 +++++++++++++++++++++++++++++++ pam-1.1.6-rootok-audit.patch | 99 ++++ pam.spec | 10 +- 3 files changed, 873 insertions(+), 1 deletion(-) create mode 100644 pam-1.1.6-pwhistory-helper.patch create mode 100644 pam-1.1.6-rootok-audit.patch diff --git a/pam-1.1.6-pwhistory-helper.patch b/pam-1.1.6-pwhistory-helper.patch new file mode 100644 index 0000000..1d85d79 --- /dev/null +++ b/pam-1.1.6-pwhistory-helper.patch @@ -0,0 +1,765 @@ +diff --git a/modules/pam_pwhistory/Makefile.am b/modules/pam_pwhistory/Makefile.am +index 4bb4d6d..0d7cbea 100644 +--- a/modules/pam_pwhistory/Makefile.am ++++ b/modules/pam_pwhistory/Makefile.am +@@ -1,5 +1,6 @@ + # + # Copyright (c) 2008, 2009 Thorsten Kukuk ++# Copyright (c) 2013 Red Hat, Inc. + # + + CLEANFILES = *~ +@@ -9,25 +10,33 @@ EXTRA_DIST = README $(MANS) $(XMLS) tst-pam_pwhistory + + TESTS = tst-pam_pwhistory + +-man_MANS = pam_pwhistory.8 ++man_MANS = pam_pwhistory.8 pwhistory_helper.8 + +-XMLS = README.xml pam_pwhistory.8.xml ++XMLS = README.xml pam_pwhistory.8.xml pwhistory_helper.8.xml + + securelibdir = $(SECUREDIR) + secureconfdir = $(SCONFIGDIR) + +-AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include +-AM_LDFLAGS = -no-undefined -avoid-version -module ++AM_CFLAGS = -I$(top_srcdir)/libpam/include -I$(top_srcdir)/libpamc/include \ ++ -DPWHISTORY_HELPER=\"$(sbindir)/pwhistory_helper\" ++ ++pam_pwhistory_la_LDFLAGS = -no-undefined -avoid-version -module + if HAVE_VERSIONING +- AM_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map ++ pam_pwhistory_la_LDFLAGS += -Wl,--version-script=$(srcdir)/../modules.map + endif + + noinst_HEADERS = opasswd.h + + securelib_LTLIBRARIES = pam_pwhistory.la ++pam_pwhistory_la_CFLAGS = $(AM_CFLAGS) + pam_pwhistory_la_LIBADD = $(top_builddir)/libpam/libpam.la @LIBCRYPT@ + pam_pwhistory_la_SOURCES = pam_pwhistory.c opasswd.c + ++sbin_PROGRAMS = pwhistory_helper ++pwhistory_helper_CFLAGS = $(AM_CFLAGS) -DHELPER_COMPILE=\"pwhistory_helper\" ++pwhistory_helper_SOURCES = pwhistory_helper.c opasswd.c ++pwhistory_helper_LDADD = $(top_builddir)/libpam/libpam.la @LIBCRYPT@ ++ + if ENABLE_REGENERATE_MAN + noinst_DATA = README + README: pam_pwhistory.8.xml +diff --git a/modules/pam_pwhistory/opasswd.c b/modules/pam_pwhistory/opasswd.c +index 836d713..f1112ac 100644 +--- a/modules/pam_pwhistory/opasswd.c ++++ b/modules/pam_pwhistory/opasswd.c +@@ -38,6 +38,7 @@ + #endif + + #include ++#include + #include + #include + #include +@@ -47,6 +48,7 @@ + #include + #include + #include ++#include + #include + + #if defined (HAVE_XCRYPT_H) +@@ -55,7 +57,14 @@ + #include + #endif + ++#ifdef HELPER_COMPILE ++#define pam_modutil_getpwnam(h,n) getpwnam(n) ++#define pam_modutil_getspnam(h,n) getspnam(n) ++#define pam_syslog(h,a,...) helper_log_err(a,__VA_ARGS__) ++#else ++#include + #include ++#endif + #include + + #include "opasswd.h" +@@ -76,6 +85,19 @@ typedef struct { + char *old_passwords; + } opwd; + ++#ifdef HELPER_COMPILE ++void ++helper_log_err(int err, const char *format, ...) ++{ ++ va_list args; ++ ++ va_start(args, format); ++ openlog(HELPER_COMPILE, LOG_CONS | LOG_PID, LOG_AUTHPRIV); ++ vsyslog(err, format, args); ++ va_end(args); ++ closelog(); ++} ++#endif + + static int + parse_entry (char *line, opwd *data) +@@ -112,8 +134,8 @@ compare_password(const char *newpass, const char *oldpass) + } + + /* Check, if the new password is already in the opasswd file. */ +-int +-check_old_pass (pam_handle_t *pamh, const char *user, ++PAMH_ARG_DECL(int ++check_old_pass, const char *user, + const char *newpass, int debug) + { + int retval = PAM_SUCCESS; +@@ -208,9 +230,9 @@ check_old_pass (pam_handle_t *pamh, const char *user, + return retval; + } + +-int +-save_old_pass (pam_handle_t *pamh, const char *user, uid_t uid, +- const char *oldpass, int howmany, int debug UNUSED) ++PAMH_ARG_DECL(int ++save_old_pass, const char *user, ++ int howmany, int debug UNUSED) + { + char opasswd_tmp[] = TMP_PASSWORDS_FILE; + struct stat opasswd_stat; +@@ -221,10 +243,30 @@ save_old_pass (pam_handle_t *pamh, const char *user, uid_t uid, + char *buf = NULL; + size_t buflen = 0; + int found = 0; ++ struct passwd *pwd; ++ const char *oldpass; ++ ++ pwd = pam_modutil_getpwnam (pamh, user); ++ if (pwd == NULL) ++ return PAM_USER_UNKNOWN; + + if (howmany <= 0) + return PAM_SUCCESS; + ++ if ((strcmp(pwd->pw_passwd, "x") == 0) || ++ ((pwd->pw_passwd[0] == '#') && ++ (pwd->pw_passwd[1] == '#') && ++ (strcmp(pwd->pw_name, pwd->pw_passwd + 2) == 0))) ++ { ++ struct spwd *spw = pam_modutil_getspnam (pamh, user); ++ ++ if (spw == NULL) ++ return PAM_USER_UNKNOWN; ++ oldpass = spw->sp_pwdp; ++ } ++ else ++ oldpass = pwd->pw_passwd; ++ + if (oldpass == NULL || *oldpass == '\0') + return PAM_SUCCESS; + +@@ -447,7 +489,7 @@ save_old_pass (pam_handle_t *pamh, const char *user, uid_t uid, + { + char *out; + +- if (asprintf (&out, "%s:%d:1:%s\n", user, uid, oldpass) < 0) ++ if (asprintf (&out, "%s:%d:1:%s\n", user, pwd->pw_uid, oldpass) < 0) + { + retval = PAM_AUTHTOK_ERR; + if (oldpf) +diff --git a/modules/pam_pwhistory/opasswd.h b/modules/pam_pwhistory/opasswd.h +index db3e656..21430be 100644 +--- a/modules/pam_pwhistory/opasswd.h ++++ b/modules/pam_pwhistory/opasswd.h +@@ -36,10 +36,25 @@ + #ifndef __OPASSWD_H__ + #define __OPASSWD_H__ + +-extern int check_old_pass (pam_handle_t *pamh, const char *user, +- const char *newpass, int debug); +-extern int save_old_pass (pam_handle_t *pamh, const char *user, +- uid_t uid, const char *oldpass, +- int howmany, int debug); ++#define PAM_PWHISTORY_RUN_HELPER PAM_CRED_INSUFFICIENT ++ ++#ifdef HELPER_COMPILE ++#define PAMH_ARG_DECL(fname, ...) fname(__VA_ARGS__) ++#define PAMH_ARG(...) __VA_ARGS__ ++#else ++#define PAMH_ARG_DECL(fname, ...) fname(pam_handle_t *pamh, __VA_ARGS__) ++#define PAMH_ARG(...) pamh, __VA_ARGS__ ++#endif ++ ++#ifdef HELPER_COMPILE ++void ++helper_log_err(int err, const char *format, ...); ++#endif ++ ++PAMH_ARG_DECL(int ++check_old_pass, const char *user, const char *newpass, int debug); ++ ++PAMH_ARG_DECL(int ++save_old_pass, const char *user, int howmany, int debug); + + #endif /* __OPASSWD_H__ */ +diff --git a/modules/pam_pwhistory/pam_pwhistory.c b/modules/pam_pwhistory/pam_pwhistory.c +index 654edd3..f54fd2a 100644 +--- a/modules/pam_pwhistory/pam_pwhistory.c ++++ b/modules/pam_pwhistory/pam_pwhistory.c +@@ -46,10 +46,14 @@ + #include + #include + #include +-#include + #include + #include + #include ++#include ++#include ++#include ++#include ++#include + + #include + #include +@@ -59,6 +63,7 @@ + #include "opasswd.h" + + #define DEFAULT_BUFLEN 2048 ++#define MAX_FD_NO 20000 + + struct options_t { + int debug; +@@ -102,6 +107,184 @@ parse_option (pam_handle_t *pamh, const char *argv, options_t *options) + pam_syslog (pamh, LOG_ERR, "pam_pwhistory: unknown option: %s", argv); + } + ++static int ++run_save_helper(pam_handle_t *pamh, const char *user, ++ int howmany, int debug) ++{ ++ int retval, child; ++ struct sigaction newsa, oldsa; ++ ++ memset(&newsa, '\0', sizeof(newsa)); ++ newsa.sa_handler = SIG_DFL; ++ sigaction(SIGCHLD, &newsa, &oldsa); ++ ++ child = fork(); ++ if (child == 0) ++ { ++ int i = 0; ++ struct rlimit rlim; ++ int dummyfds[2]; ++ static char *envp[] = { NULL }; ++ char *args[] = { NULL, NULL, NULL, NULL, NULL, NULL }; ++ ++ /* replace std file descriptors with a dummy pipe */ ++ if (pipe2(dummyfds, O_NONBLOCK) == 0) ++ { ++ dup2(dummyfds[0], STDIN_FILENO); ++ dup2(dummyfds[1], STDOUT_FILENO); ++ dup2(dummyfds[1], STDERR_FILENO); ++ } ++ ++ if (getrlimit(RLIMIT_NOFILE,&rlim) == 0) ++ { ++ if (rlim.rlim_max >= MAX_FD_NO) ++ rlim.rlim_max = MAX_FD_NO; ++ for (i = STDERR_FILENO + 1; i < (int)rlim.rlim_max; i++) ++ { ++ if (i != dummyfds[0]) ++ close(i); ++ } ++ } ++ ++ /* exec binary helper */ ++ args[0] = strdup(PWHISTORY_HELPER); ++ args[1] = strdup("save"); ++ args[2] = x_strdup(user); ++ asprintf(&args[3], "%d", howmany); ++ asprintf(&args[4], "%d", debug); ++ ++ execve(args[0], args, envp); ++ ++ _exit(PAM_SYSTEM_ERR); ++ } ++ else if (child > 0) ++ { ++ /* wait for child */ ++ int rc = 0; ++ rc = waitpid(child, &retval, 0); /* wait for helper to complete */ ++ if (rc < 0) ++ { ++ pam_syslog(pamh, LOG_ERR, "pwhistory_helper save waitpid returned %d: %m", rc); ++ retval = PAM_SYSTEM_ERR; ++ } ++ else if (!WIFEXITED(retval)) ++ { ++ pam_syslog(pamh, LOG_ERR, "pwhistory_helper save abnormal exit: %d", retval); ++ retval = PAM_SYSTEM_ERR; ++ } ++ else ++ { ++ retval = WEXITSTATUS(retval); ++ } ++ } ++ else ++ { ++ retval = PAM_SYSTEM_ERR; ++ } ++ ++ sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */ ++ ++ return retval; ++} ++ ++static int ++run_check_helper(pam_handle_t *pamh, const char *user, ++ const char *newpass, int debug) ++{ ++ int retval, child, fds[2]; ++ struct sigaction newsa, oldsa; ++ ++ /* create a pipe for the password */ ++ if (pipe(fds) != 0) ++ return PAM_SYSTEM_ERR; ++ ++ memset(&newsa, '\0', sizeof(newsa)); ++ newsa.sa_handler = SIG_DFL; ++ sigaction(SIGCHLD, &newsa, &oldsa); ++ ++ child = fork(); ++ if (child == 0) ++ { ++ int i = 0; ++ struct rlimit rlim; ++ int dummyfds[2]; ++ static char *envp[] = { NULL }; ++ char *args[] = { NULL, NULL, NULL, NULL, NULL }; ++ ++ /* reopen stdin as pipe */ ++ dup2(fds[0], STDIN_FILENO); ++ ++ /* replace std file descriptors with a dummy pipe */ ++ if (pipe2(dummyfds, O_NONBLOCK) == 0) ++ { ++ dup2(dummyfds[1], STDOUT_FILENO); ++ dup2(dummyfds[1], STDERR_FILENO); ++ } ++ ++ if (getrlimit(RLIMIT_NOFILE,&rlim) == 0) ++ { ++ if (rlim.rlim_max >= MAX_FD_NO) ++ rlim.rlim_max = MAX_FD_NO; ++ for (i = STDERR_FILENO + 1; i < (int)rlim.rlim_max; i++) ++ { ++ if (i != dummyfds[0]) ++ close(i); ++ } ++ } ++ ++ /* exec binary helper */ ++ args[0] = strdup(PWHISTORY_HELPER); ++ args[1] = strdup("check"); ++ args[2] = x_strdup(user); ++ asprintf(&args[3], "%d", debug); ++ ++ execve(args[0], args, envp); ++ ++ _exit(PAM_SYSTEM_ERR); ++ } ++ else if (child > 0) ++ { ++ /* wait for child */ ++ int rc = 0; ++ if (newpass == NULL) ++ newpass = ""; ++ ++ /* send the password to the child */ ++ if (write(fds[1], newpass, strlen(newpass)+1) == -1) ++ { ++ pam_syslog(pamh, LOG_ERR, "Cannot send password to helper: %m"); ++ retval = PAM_SYSTEM_ERR; ++ } ++ newpass = NULL; ++ close(fds[0]); /* close here to avoid possible SIGPIPE above */ ++ close(fds[1]); ++ rc = waitpid(child, &retval, 0); /* wait for helper to complete */ ++ if (rc < 0) ++ { ++ pam_syslog(pamh, LOG_ERR, "pwhistory_helper check waitpid returned %d: %m", rc); ++ retval = PAM_SYSTEM_ERR; ++ } ++ else if (!WIFEXITED(retval)) ++ { ++ pam_syslog(pamh, LOG_ERR, "pwhistory_helper check abnormal exit: %d", retval); ++ retval = PAM_SYSTEM_ERR; ++ } ++ else ++ { ++ retval = WEXITSTATUS(retval); ++ } ++ } ++ else ++ { ++ close(fds[0]); ++ close(fds[1]); ++ retval = PAM_SYSTEM_ERR; ++ } ++ ++ sigaction(SIGCHLD, &oldsa, NULL); /* restore old signal handler */ ++ ++ return retval; ++} + + /* This module saves the current crypted password in /etc/security/opasswd + and then compares the new password with all entries in this file. */ +@@ -109,7 +292,6 @@ parse_option (pam_handle_t *pamh, const char *argv, options_t *options) + PAM_EXTERN int + pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv) + { +- struct passwd *pwd; + const char *newpass; + const char *user; + int retval, tries; +@@ -154,31 +336,13 @@ pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv) + return PAM_SUCCESS; + } + +- pwd = pam_modutil_getpwnam (pamh, user); +- if (pwd == NULL) +- return PAM_USER_UNKNOWN; ++ retval = save_old_pass (pamh, user, options.remember, options.debug); + +- if ((strcmp(pwd->pw_passwd, "x") == 0) || +- ((pwd->pw_passwd[0] == '#') && +- (pwd->pw_passwd[1] == '#') && +- (strcmp(pwd->pw_name, pwd->pw_passwd + 2) == 0))) +- { +- struct spwd *spw = pam_modutil_getspnam (pamh, user); +- if (spw == NULL) +- return PAM_USER_UNKNOWN; ++ if (retval == PAM_PWHISTORY_RUN_HELPER) ++ retval = run_save_helper(pamh, user, options.remember, options.debug); + +- retval = save_old_pass (pamh, user, pwd->pw_uid, spw->sp_pwdp, +- options.remember, options.debug); +- if (retval != PAM_SUCCESS) +- return retval; +- } +- else +- { +- retval = save_old_pass (pamh, user, pwd->pw_uid, pwd->pw_passwd, +- options.remember, options.debug); +- if (retval != PAM_SUCCESS) +- return retval; +- } ++ if (retval != PAM_SUCCESS) ++ return retval; + + newpass = NULL; + tries = 0; +@@ -207,8 +371,11 @@ pam_sm_chauthtok (pam_handle_t *pamh, int flags, int argc, const char **argv) + if (options.debug) + pam_syslog (pamh, LOG_DEBUG, "check against old password file"); + +- if (check_old_pass (pamh, user, newpass, +- options.debug) != PAM_SUCCESS) ++ retval = check_old_pass (pamh, user, newpass, options.debug); ++ if (retval == PAM_PWHISTORY_RUN_HELPER) ++ retval = run_check_helper(pamh, user, newpass, options.debug); ++ ++ if (retval != PAM_SUCCESS) + { + if (getuid() || options.enforce_for_root || + (flags & PAM_CHANGE_EXPIRED_AUTHTOK)) +diff --git a/modules/pam_pwhistory/pwhistory_helper.8.xml b/modules/pam_pwhistory/pwhistory_helper.8.xml +new file mode 100644 +index 0000000..5f14d39 +--- /dev/null ++++ b/modules/pam_pwhistory/pwhistory_helper.8.xml +@@ -0,0 +1,68 @@ ++ ++ ++ ++ ++ ++ ++ pwhistory_helper ++ 8 ++ Linux-PAM Manual ++ ++ ++ ++ pwhistory_helper ++ Helper binary that transfers password hashes from passwd or shadow to opasswd ++ ++ ++ ++ ++ pwhistory_helper ++ ++ ... ++ ++ ++ ++ ++ ++ ++ DESCRIPTION ++ ++ ++ pwhistory_helper is a helper program for the ++ pam_pwhistory module that transfers password hashes ++ from passwd or shadow file to the opasswd file and checks a password ++ supplied by user against the existing hashes in the opasswd file. ++ ++ ++ ++ The purpose of the helper is to enable tighter confinement of ++ login and password changing services. The helper is thus called only ++ when SELinux is enabled on the system. ++ ++ ++ ++ The interface of the helper - command line options, and input/output ++ data format are internal to the pam_pwhistory ++ module and it should not be called directly from applications. ++ ++ ++ ++ ++ SEE ALSO ++ ++ ++ pam_pwhistory8 ++ ++ ++ ++ ++ ++ AUTHOR ++ ++ Written by Tomas Mraz based on the code originally in ++ pam_pwhistory module. ++ ++ ++ ++ +diff --git a/modules/pam_pwhistory/pwhistory_helper.c b/modules/pam_pwhistory/pwhistory_helper.c +new file mode 100644 +index 0000000..b07ab81 +--- /dev/null ++++ b/modules/pam_pwhistory/pwhistory_helper.c +@@ -0,0 +1,209 @@ ++/* ++ * Copyright (c) 2013 Red Hat, Inc. ++ * Author: Tomas Mraz ++ * ++ * Redistribution and use in source and binary forms, with or without ++ * modification, are permitted provided that the following conditions ++ * are met: ++ * 1. Redistributions of source code must retain the above copyright ++ * notice, and the entire permission notice in its entirety, ++ * including the disclaimer of warranties. ++ * 2. Redistributions in binary form must reproduce the above copyright ++ * notice, this list of conditions and the following disclaimer in the ++ * documentation and/or other materials provided with the distribution. ++ * 3. The name of the author may not be used to endorse or promote ++ * products derived from this software without specific prior ++ * written permission. ++ * ++ * ALTERNATIVELY, this product may be distributed under the terms of ++ * the GNU Public License, in which case the provisions of the GPL are ++ * required INSTEAD OF the above restrictions. (This clause is ++ * necessary due to a potential bad interaction between the GPL and ++ * the restrictions contained in a BSD-style copyright.) ++ * ++ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED ++ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES ++ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE ++ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, ++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES ++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR ++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) ++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, ++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED ++ * OF THE POSSIBILITY OF SUCH DAMAGE. ++ */ ++ ++#include "config.h" ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include "opasswd.h" ++ ++#define MAXPASS 200 ++ ++static void ++su_sighandler(int sig) ++{ ++#ifndef SA_RESETHAND ++ /* emulate the behaviour of the SA_RESETHAND flag */ ++ if ( sig == SIGILL || sig == SIGTRAP || sig == SIGBUS || sig = SIGSERV ) { ++ struct sigaction sa; ++ memset(&sa, '\0', sizeof(sa)); ++ sa.sa_handler = SIG_DFL; ++ sigaction(sig, &sa, NULL); ++ } ++#endif ++ if (sig > 0) { ++ _exit(sig); ++ } ++} ++ ++static void ++setup_signals(void) ++{ ++ struct sigaction action; /* posix signal structure */ ++ ++ /* ++ * Setup signal handlers ++ */ ++ (void) memset((void *) &action, 0, sizeof(action)); ++ action.sa_handler = su_sighandler; ++#ifdef SA_RESETHAND ++ action.sa_flags = SA_RESETHAND; ++#endif ++ (void) sigaction(SIGILL, &action, NULL); ++ (void) sigaction(SIGTRAP, &action, NULL); ++ (void) sigaction(SIGBUS, &action, NULL); ++ (void) sigaction(SIGSEGV, &action, NULL); ++ action.sa_handler = SIG_IGN; ++ action.sa_flags = 0; ++ (void) sigaction(SIGTERM, &action, NULL); ++ (void) sigaction(SIGHUP, &action, NULL); ++ (void) sigaction(SIGINT, &action, NULL); ++ (void) sigaction(SIGQUIT, &action, NULL); ++} ++ ++static int ++read_passwords(int fd, int npass, char **passwords) ++{ ++ int rbytes = 0; ++ int offset = 0; ++ int i = 0; ++ char *pptr; ++ while (npass > 0) ++ { ++ rbytes = read(fd, passwords[i]+offset, MAXPASS-offset); ++ ++ if (rbytes < 0) ++ { ++ if (errno == EINTR) continue; ++ break; ++ } ++ if (rbytes == 0) ++ break; ++ ++ while (npass > 0 && (pptr=memchr(passwords[i]+offset, '\0', rbytes)) ++ != NULL) ++ { ++ rbytes -= pptr - (passwords[i]+offset) + 1; ++ i++; ++ offset = 0; ++ npass--; ++ if (rbytes > 0) ++ { ++ if (npass > 0) ++ memcpy(passwords[i], pptr+1, rbytes); ++ memset(pptr+1, '\0', rbytes); ++ } ++ } ++ offset += rbytes; ++ } ++ ++ /* clear up */ ++ if (offset > 0 && npass > 0) ++ memset(passwords[i], '\0', offset); ++ ++ return i; ++} ++ ++ ++static int ++check_history(const char *user, const char *debug) ++{ ++ char pass[MAXPASS + 1]; ++ char *passwords[] = { pass }; ++ int npass; ++ int dbg = atoi(debug); /* no need to be too fancy here */ ++ int retval; ++ ++ /* read the password from stdin (a pipe from the pam_pwhistory module) */ ++ npass = read_passwords(STDIN_FILENO, 1, passwords); ++ ++ if (npass != 1) ++ { /* is it a valid password? */ ++ helper_log_err(LOG_DEBUG, "no password supplied"); ++ return PAM_AUTHTOK_ERR; ++ } ++ ++ retval = check_old_pass(user, pass, dbg); ++ ++ memset(pass, '\0', MAXPASS); /* clear memory of the password */ ++ ++ return retval; ++} ++ ++static int ++save_history(const char *user, const char *howmany, const char *debug) ++{ ++ int num = atoi(howmany); ++ int dbg = atoi(debug); /* no need to be too fancy here */ ++ int retval; ++ ++ retval = save_old_pass(user, num, dbg); ++ ++ return retval; ++} ++ ++int ++main(int argc, char *argv[]) ++{ ++ const char *option; ++ const char *user; ++ ++ /* ++ * Catch or ignore as many signal as possible. ++ */ ++ setup_signals(); ++ ++ /* ++ * we establish that this program is running with non-tty stdin. ++ * this is to discourage casual use. ++ */ ++ ++ if (isatty(STDIN_FILENO) || argc < 4) ++ { ++ fprintf(stderr, ++ "This binary is not designed for running in this way.\n"); ++ sleep(10); /* this should discourage/annoy the user */ ++ return PAM_SYSTEM_ERR; ++ } ++ ++ option = argv[1]; ++ user = argv[2]; ++ ++ if (strcmp(option, "check") == 0 && argc == 4) ++ return check_history(user, argv[3]); ++ else if (strcmp(option, "save") == 0 && argc == 5) ++ return save_history(user, argv[3], argv[4]); ++ ++ return PAM_SYSTEM_ERR; ++} ++ diff --git a/pam-1.1.6-rootok-audit.patch b/pam-1.1.6-rootok-audit.patch new file mode 100644 index 0000000..d396175 --- /dev/null +++ b/pam-1.1.6-rootok-audit.patch @@ -0,0 +1,99 @@ +diff -up Linux-PAM-1.1.6/modules/pam_rootok/Makefile.am.audit Linux-PAM-1.1.6/modules/pam_rootok/Makefile.am +--- Linux-PAM-1.1.6/modules/pam_rootok/Makefile.am.audit 2012-08-15 13:08:43.000000000 +0200 ++++ Linux-PAM-1.1.6/modules/pam_rootok/Makefile.am 2013-07-04 10:30:21.000000000 +0200 +@@ -25,7 +25,7 @@ if HAVE_VERSIONING + endif + + securelib_LTLIBRARIES = pam_rootok.la +-pam_rootok_la_LIBADD = $(top_builddir)/libpam/libpam.la @LIBSELINUX@ ++pam_rootok_la_LIBADD = $(top_builddir)/libpam/libpam.la @LIBSELINUX@ @LIBAUDIT@ + + if ENABLE_REGENERATE_MAN + noinst_DATA = README +diff -up Linux-PAM-1.1.6/modules/pam_rootok/pam_rootok.c.audit Linux-PAM-1.1.6/modules/pam_rootok/pam_rootok.c +--- Linux-PAM-1.1.6/modules/pam_rootok/pam_rootok.c.audit 2012-08-15 13:08:43.000000000 +0200 ++++ Linux-PAM-1.1.6/modules/pam_rootok/pam_rootok.c 2013-03-22 09:41:48.000000000 +0100 +@@ -28,7 +28,11 @@ + + #ifdef WITH_SELINUX + #include +-#include ++#include ++#endif ++ ++#ifdef HAVE_LIBAUDIT ++#include + #endif + + /* argument parsing */ +@@ -55,6 +59,61 @@ _pam_parse (const pam_handle_t *pamh, in + return ctrl; + } + ++#ifdef WITH_SELINUX ++static int ++log_callback (int type, const char *fmt, ...) ++{ ++ int audit_fd; ++ va_list ap; ++ ++ va_start(ap, fmt); ++#ifdef HAVE_LIBAUDIT ++ audit_fd = audit_open(); ++ ++ if (audit_fd >= 0) { ++ char *buf; ++ ++ if (vasprintf (&buf, fmt, ap) < 0) ++ return 0; ++ audit_log_user_avc_message(audit_fd, AUDIT_USER_AVC, buf, NULL, NULL, ++ NULL, 0); ++ audit_close(audit_fd); ++ free(buf); ++ return 0; ++ } ++ ++#endif ++ vsyslog (LOG_USER | LOG_INFO, fmt, ap); ++ va_end(ap); ++ return 0; ++} ++ ++static int ++selinux_check_root (void) ++{ ++ int status = -1; ++ security_context_t user_context; ++ union selinux_callback old_callback; ++ ++ if (is_selinux_enabled() < 1) ++ return 0; ++ ++ old_callback = selinux_get_callback(SELINUX_CB_LOG); ++ /* setup callbacks */ ++ selinux_set_callback(SELINUX_CB_LOG, (union selinux_callback) &log_callback); ++ if ((status = getprevcon(&user_context)) < 0) { ++ selinux_set_callback(SELINUX_CB_LOG, old_callback); ++ return status; ++ } ++ ++ status = selinux_check_access(user_context, user_context, "passwd", "passwd", NULL); ++ ++ selinux_set_callback(SELINUX_CB_LOG, old_callback); ++ freecon(user_context); ++ return status; ++} ++#endif ++ + static int + check_for_root (pam_handle_t *pamh, int ctrl) + { +@@ -62,7 +121,7 @@ check_for_root (pam_handle_t *pamh, int + + if (getuid() == 0) + #ifdef WITH_SELINUX +- if (is_selinux_enabled()<1 || checkPasswdAccess(PASSWD__ROOTOK)==0) ++ if (selinux_check_root() == 0 || security_getenforce() == 0) + #endif + retval = PAM_SUCCESS; + diff --git a/pam.spec b/pam.spec index a906b04..f909d46 100644 --- a/pam.spec +++ b/pam.spec @@ -3,7 +3,7 @@ Summary: An extensible library which provides authentication for applications Name: pam Version: 1.1.6 -Release: 11%{?dist} +Release: 12%{?dist} # The library is BSD licensed with option to relicense as GPLv2+ # - this option is redundant as the BSD license allows that anyway. # pam_timestamp, pam_loginuid, and pam_console modules are GPLv2+. @@ -58,6 +58,8 @@ Patch26: pam-1.1.6-lastlog-retval.patch Patch27: pam-1.1.6-strict-aliasing.patch # Upstreamed Patch28: pam-1.1.6-selinux-manualctx.patch +Patch29: pam-1.1.6-pwhistory-helper.patch +Patch30: pam-1.1.6-rootok-audit.patch %define _pamlibdir %{_libdir} %define _moduledir %{_libdir}/security @@ -141,6 +143,8 @@ mv pam-redhat-%{pam_redhat_version}/* modules %patch26 -p1 -b .retval %patch27 -p1 -b .strict-aliasing %patch28 -p1 -b .manualctx +%patch29 -p1 -b .pwhhelper +%patch30 -p1 -b .audit %build @@ -388,6 +392,10 @@ fi %doc doc/adg/*.txt doc/adg/html %changelog +* Thu Jul 11 2013 Tomáš Mráz 1.1.6-12 +- add auditing of SELinux policy violation in pam_rootok (#965723) +- add SELinux helper to pam_pwhistory + * Tue May 7 2013 Tomáš Mráz 1.1.6-11 - the default isadir is more correct