diff --git a/ChangeLog b/ChangeLog index 5c8dba9..856829c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -99,6 +99,31 @@ * tcb.spec: Remove -DENABLE_SETFSUGID. * ci/run-build-and-tests.sh: Likewise. +2021-10-12 Björn Esser + + pam_tcb: Allow for authentication if a system policy requires the + root user to acquire special capabilities. + * pam_tcb/support.c (unix_verify_password_plain): Allow the helper + binary to be run as root if e.g. SELinux prevents access to file + storing the hashed user password. + * progs/tcb_chkpwd.c (unix_verify_password): Likewise. + * pam_tcb/support.c (unix_run_helper_binary): Refactor function to + be non-static and to allow for more versatile use. + * pam_tcb/support.h (unix_run_helper_binary): New function. + * pam_tcb/support.c (run_chkpwd_binary): New static function wrapper + around unix_run_helper_binary(). + * pam_tcb/support.c (unix_verify_password_plain): + Replace call to unix_run_helper_binary() with run_chkpwd_binary(). + * progs/tcb_chkpwd.c: Refactor the helper program to also perform + verifications for the expiration of user accounts. + * pam_tcb/pam_unix_acct.c (pam_sm_acct_mgmt): Perform expiration + verification of a user account through an external helper binary + if the verification fails for insufficient credentials. + * pam_tcb/pam_unix_acct.c (run_chkpwd_binary): New static function + wrapper around unix_run_helper_binary(). + * pam_tcb/Makefile: Add custom rule with "CHKPWD_HELPER" macro + definined to compile pam_unix_acct.o. + 2021-09-30 Björn Esser pam_tcb: Fix "-Wpedantic". diff --git a/pam_tcb/Makefile b/pam_tcb/Makefile index 876ee70..6b8c1b3 100644 --- a/pam_tcb/Makefile +++ b/pam_tcb/Makefile @@ -22,6 +22,10 @@ $(PAM_TCB): $(LIBOBJ) $(PAM_MAP) .c.o: $(CC) $(CFLAGS) -fPIC -c $< -o $@ +pam_unix_acct.o: pam_unix_acct.c + $(CC) $(CFLAGS) -DCHKPWD_HELPER=\"$(LIBEXECDIR)/chkpwd/tcb_chkpwd\" \ + -fPIC -c $< -o $@ + support.o: support.c $(CC) $(CFLAGS) -DCHKPWD_HELPER=\"$(LIBEXECDIR)/chkpwd/tcb_chkpwd\" \ -fPIC -c $< -o $@ diff --git a/pam_tcb/pam_unix_acct.c b/pam_tcb/pam_unix_acct.c index a69b931..7399a56 100644 --- a/pam_tcb/pam_unix_acct.c +++ b/pam_tcb/pam_unix_acct.c @@ -76,6 +76,29 @@ static int acct_shadow(unused pam_handle_t *pamh, const void *void_user) return ACCT_SUCCESS; } +/* + * Use an external helper binary to perform account management. + */ +static int run_chkpwd_binary(const char *user) +{ + char *argv[] = { CHKPWD_HELPER, "chkacct", NULL }; + char config[8] = "shadow\0\0"; + int retval_helper; + + if (!pam_unix_param.helper) + goto end; + + if (unix_run_helper_binary (user, "NULL", pam_unix_param.helper, + argv, config, (void *)&retval_helper, + sizeof(retval_helper))) + goto end; + + return retval_helper; + +end: + return ACCT_0; +} + /* * The account management entry point. */ @@ -112,6 +135,14 @@ PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, retval = acct_shadow(pamh, user); else retval = _unix_fork(pamh, acct_shadow, user); + if (retval == ACCT_2) { + uid_t uid = getuid(); + if (uid == geteuid() && (uid == pw->pw_uid || uid == 0)) { + /* We are not privileged enough perhaps this is the reason? */ + D(("running helper binary")); + retval = run_chkpwd_binary(user); + } + } if (retval > 255) { daysleft = retval / 256; retval %= 256; diff --git a/pam_tcb/support.c b/pam_tcb/support.c index f312b74..4c32c15 100644 --- a/pam_tcb/support.c +++ b/pam_tcb/support.c @@ -279,29 +279,29 @@ int _unix_blankpasswd(pam_handle_t *pamh, const char *user) } /* - * Verify the password of a user. + * Run a helper binary. */ - -static int unix_run_helper_binary(const char *user, const char *pass) +int unix_run_helper_binary(const char *user, const char *pass, + const char *helper_binary, char *const argv[], + const char config[8], void *retval_helper, + size_t retval_size) { - int retval = PAM_AUTH_ERR, child, fail = 0, status, fds[2], retpipe[2]; + int child, retval = 0, status, fds[2], retpipe[2]; sighandler_t sigchld, sigpipe; int len; - char *argv[] = {CHKPWD_HELPER, NULL}; char *envp[] = {NULL}; D(("called")); - if (!pam_unix_param.helper) - return PAM_AUTH_ERR; - /* create a pipe for the password */ if (pipe(fds)) { D(("could not make pipe")); + retval = 1; goto out; } if (pipe(retpipe)) { D(("could not make pipe")); + retval = 1; goto out_pipe; } @@ -311,6 +311,7 @@ static int unix_run_helper_binary(const char *user, const char *pass) switch ((child = fork())) { case -1: D(("fork failed")); + retval = 1; goto out_signal; case 0: @@ -327,7 +328,7 @@ static int unix_run_helper_binary(const char *user, const char *pass) _exit(1); /* exec binary helper */ - execve(pam_unix_param.helper, argv, envp); + execve(helper_binary, argv, envp); /* should not get here: exit with error */ D(("helper binary is not available")); @@ -337,39 +338,29 @@ static int unix_run_helper_binary(const char *user, const char *pass) /* wait for child */ close(fds[0]); close(retpipe[1]); - if (on(UNIX__NULLOK)) { - if (write_loop(fds[1], "nullok\0\0", 8) != 8) - fail = 1; - } else { - if (write_loop(fds[1], "nonull\0\0", 8) != 8) - fail = 1; - } + if (write_loop(fds[1], config, 8) != 8) + retval = 1; len = strlen(user) + 1; - if (write_loop(fds[1], user, len) != len) - fail = 1; - else { + if (write_loop(fds[1], user, len) != len) { + retval = 1; + } else { len = strlen(pass) + 1; if (write_loop(fds[1], pass, len) != len) - fail = 1; + retval = 1; } pass = NULL; close(fds[1]); /* wait for helper to complete */ if (waitpid(child, &status, 0) != child) { status = 0; - fail = 1; + retval = 1; } - if (read_loop(retpipe[0], (char *)&retval, sizeof(retval)) != - sizeof(retval)) - fail = 1; + if (read_loop(retpipe[0], (char *)retval_helper, retval_size) != + (int)retval_size) + retval = 1; close(retpipe[0]); if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) - fail = 1; - if (fail) - retval = PAM_AUTH_ERR; - else - retval = (retval == TCB_MAGIC) ? - PAM_SUCCESS : PAM_AUTH_ERR; + retval = 1; } out_signal: @@ -387,6 +378,37 @@ static int unix_run_helper_binary(const char *user, const char *pass) return retval; } +/* + * Verify the password of a user. + */ + +static int run_chkpwd_binary(const char *user, const char *pass) +{ + char *argv[] = { CHKPWD_HELPER, "chkpwd", NULL }; + char config[8]; + int retval_helper; + + if (!pam_unix_param.helper) + goto end; + + if (on(UNIX__NULLOK)) { + memcpy(config, "nullok\0\0", 8); + } else { + memcpy(config, "nonull\0\0", 8); + } + + if (unix_run_helper_binary (user, pass, pam_unix_param.helper, + argv, config, (void *)&retval_helper, + sizeof(retval_helper))) + goto end; + + if (retval_helper == TCB_MAGIC) + return PAM_SUCCESS; + +end: + return PAM_AUTH_ERR; +} + static int check_crypt(pam_handle_t *pamh, const char *pass, const char *stored_hash) { @@ -475,10 +497,10 @@ static int unix_verify_password_plain(pam_handle_t *pamh, if (!salt) { /* we're not faking, we have an existing user, so... */ uid_t uid = getuid(); - if (uid == geteuid() && uid == pw->pw_uid && uid != 0) { - /* We are not root perhaps this is the reason? */ + if (uid == geteuid() && (uid == pw->pw_uid || uid == 0)) { + /* We are not privileged enough perhaps this is the reason? */ D(("running helper binary")); - retval = unix_run_helper_binary(user, pass); + retval = run_chkpwd_binary(user, pass); } else { D(("user's record unavailable")); pam_syslog(pamh, LOG_ALERT, diff --git a/pam_tcb/support.h b/pam_tcb/support.h index 012511a..e747332 100644 --- a/pam_tcb/support.h +++ b/pam_tcb/support.h @@ -184,6 +184,10 @@ extern int _set_ctrl(pam_handle_t *, int flags, int argc, const char **argv); extern int _unix_blankpasswd(pam_handle_t *, const char *user); extern int _unix_verify_password(pam_handle_t *, const char *, const char *); extern int unix_getspnam(struct spwd **, const struct passwd *); +extern int unix_run_helper_binary(const char *user, const char *pass, + const char *helper_binary, char *const argv[], + const char helper_command[8], + void *retval_helper, size_t retval_size); extern char *crypt_wrapper(pam_handle_t *, const char *, const char *); extern char *do_crypt(pam_handle_t *, const char *); diff --git a/progs/tcb_chkpwd.c b/progs/tcb_chkpwd.c index e759307..f9eed5f 100644 --- a/progs/tcb_chkpwd.c +++ b/progs/tcb_chkpwd.c @@ -12,6 +12,7 @@ #include #include #include +#include #include #include @@ -25,12 +26,90 @@ IO_LOOP(write_loop, write, const) #define AUTH_PASSED TCB_MAGIC #define AUTH_FAILED 1 +enum { + ACCT_0 = 0, + ACCT_1, + ACCT_2, + ACCT_3, + ACCT_4, + ACCT_5, + ACCT_6, + ACCT_7, + ACCT_SUCCESS = 255 +}; + static void zeroise(char *str) { while (*str) *(str++) = '\0'; } +static int unix_getspnam(struct spwd **spw, const struct passwd *pw, int shadow) +{ + if (shadow) { + *spw = getspnam(pw->pw_name); + endspent(); + return 0; + } + + return 1; +} + +static int acct_shadow(const void *void_user, int shadow) +{ + int daysleft; + time_t curdays; + const char *user = void_user; + struct passwd *pw; + struct spwd *spw = NULL; + + pw = getpwnam(user); + endpwent(); + if (pw) { + uid_t uid = getuid(); + if (uid != pw->pw_uid && uid != 0) + return ACCT_1; + } + if (!pw) + return ACCT_1; /* shouldn't happen */ + if (!shadow && strcmp(pw->pw_passwd, "x") + && strcmp(pw->pw_passwd, "*NP*")) + return ACCT_SUCCESS; + + if (unix_getspnam(&spw, pw, shadow)) + return ACCT_1; + + if (!spw) + return ACCT_2; + + curdays = time(NULL) / (60 * 60 * 24); + syslog(LOG_DEBUG, "today is %ld, last change %ld", + curdays, spw->sp_lstchg); + if ((curdays > spw->sp_expire) && (spw->sp_expire != -1)) + return ACCT_3; + + if ((curdays > (spw->sp_lstchg + spw->sp_max + spw->sp_inact)) && + (spw->sp_max != -1) && (spw->sp_inact != -1) && + (spw->sp_lstchg != 0)) + return ACCT_4; + + syslog(LOG_DEBUG, "when was the last change"); + if (spw->sp_lstchg == 0) + return ACCT_5; + + if (((spw->sp_lstchg + spw->sp_max) < curdays) && + (spw->sp_max != -1)) + return ACCT_6; + + if ((curdays > (spw->sp_lstchg + spw->sp_max - spw->sp_warn)) && + (spw->sp_max != -1) && (spw->sp_warn != -1)) { + daysleft = (spw->sp_lstchg + spw->sp_max) - curdays; + return ACCT_7 + 256 * daysleft; + } + + return ACCT_SUCCESS; +} + static int unix_verify_password(const char *user, const char *pass, int nullok) { struct passwd *pw; @@ -43,7 +122,8 @@ static int unix_verify_password(const char *user, const char *pass, int nullok) stored_hash = NULL; if (pw) { - if (getuid() != pw->pw_uid) + uid_t uid = getuid(); + if (uid != pw->pw_uid && uid != 0) return AUTH_FAILED; if (!strcmp(pw->pw_passwd, "x")) { @@ -88,27 +168,36 @@ static int is_two_strings(char *data, unsigned int len) return (1 + strlen(data) < len); } -int main(void) +static int acctverify(int shadow) { - char option[8]; - char userandpass[MAX_DATA_LENGTH + 1]; - int datalen, nullok, retval; + int datalen, retval; + char username[MAX_DATA_LENGTH + 1]; - openlog("tcb_chkpwd", LOG_CONS | LOG_PID, LOG_AUTH); + retval = ACCT_0; - if (isatty(STDIN_FILENO) || isatty(STDOUT_FILENO)) { - syslog(LOG_NOTICE, "inappropriate use by UID %d", getuid()); - return 1; - } + /* read the user from stdin (a pipe from the PAM module) */ + datalen = read_loop(STDIN_FILENO, username, MAX_DATA_LENGTH); + if (datalen < 0) + syslog(LOG_DEBUG, "no username supplied"); + else if (datalen >= MAX_DATA_LENGTH) + syslog(LOG_DEBUG, "username too long"); + else + retval = acct_shadow(username, shadow); - /* read the nullok/nonull option */ - memset(option, 0, sizeof(option)); - if (read_loop(STDIN_FILENO, option, sizeof(option)) <= 0) { - syslog(LOG_DEBUG, "no option supplied"); + memset(username, 0, sizeof(username)); + + /* return pass or fail */ + if (write_loop(STDOUT_FILENO, (char *)&retval, sizeof(retval)) == + sizeof(retval)) + return retval == ACCT_SUCCESS ? 0 : 1; + else return 1; - } - option[sizeof(option) - 1] = '\0'; - nullok = !strcmp(option, "nullok"); +} + +static int passverify(int nullok) +{ + int datalen, retval; + char userandpass[MAX_DATA_LENGTH + 1]; retval = AUTH_FAILED; @@ -133,3 +222,38 @@ int main(void) else return 1; } + +int main(int argc, char* argv[]) +{ + char option[8]; + int flag, retval = 1; + + openlog("tcb_chkpwd", LOG_CONS | LOG_PID, LOG_AUTH); + + if (argc != 2 || isatty(STDIN_FILENO) || isatty(STDOUT_FILENO)) { + syslog(LOG_NOTICE, "inappropriate use by UID %d", getuid()); + goto out; + } + + /* read the applicable option from pipe */ + memset(option, 0, sizeof(option)); + if (read_loop(STDIN_FILENO, option, sizeof(option)) <= 0) { + syslog(LOG_DEBUG, "no option supplied"); + goto out; + } + option[sizeof(option) - 1] = '\0'; + + if (!strcmp(argv[1], "chkacct")) { + flag = !strcmp(option, "shadow"); + retval = acctverify(flag); + goto out; + } + + if (!strcmp(argv[1], "chkpwd")) { + flag = !strcmp(option, "nullok"); + retval = passverify(flag); + } + +out: + return retval; +}