diff --color -ru a/libcap/cap_proc.c b/libcap/cap_proc.c --- a/libcap/cap_proc.c 2021-12-22 12:33:20.739126763 +0100 +++ b/libcap/cap_proc.c 2021-12-22 12:33:53.195733115 +0100 @@ -406,6 +406,29 @@ } /* + * cap_prctl performs a prctl() 6 argument call on the current + * thread. Use cap_prctlw() if you want to perform a POSIX semantics + * prctl() system call. + */ +int cap_prctl(long int pr_cmd, long int arg1, long int arg2, + long int arg3, long int arg4, long int arg5) +{ + return prctl(pr_cmd, arg1, arg2, arg3, arg4, arg5); +} + +/* + * cap_prctlw performs a POSIX semantics prctl() call. That is a 6 arg + * prctl() call that executes on all available threads when libpsx is + * linked. The suffix 'w' refers to the fact one only ever needs to + * invoke this is if the call will write some kernel state. + */ +int cap_prctlw(long int pr_cmd, long int arg1, long int arg2, + long int arg3, long int arg4, long int arg5) +{ + return _libcap_wprctl6(&multithread, pr_cmd, arg1, arg2, arg3, arg4, arg5); +} + +/* * Some predefined constants */ #define CAP_SECURED_BITS_BASIC \ diff --color -ru a/libcap/include/sys/capability.h b/libcap/include/sys/capability.h --- a/libcap/include/sys/capability.h 2021-02-05 06:52:17.000000000 +0100 +++ b/libcap/include/sys/capability.h 2021-12-22 12:33:53.196733134 +0100 @@ -175,6 +175,11 @@ extern unsigned cap_get_secbits(void); extern int cap_set_secbits(unsigned bits); +extern int cap_prctl(long int pr_cmd, long int arg1, long int arg2, + long int arg3, long int arg4, long int arg5); +extern int cap_prctlw(long int pr_cmd, long int arg1, long int arg2, + long int arg3, long int arg4, long int arg5); + extern int cap_setuid(uid_t uid); extern int cap_setgroups(gid_t gid, size_t ngroups, const gid_t groups[]); diff --color -ru a/pam_cap/pam_cap.c b/pam_cap/pam_cap.c --- a/pam_cap/pam_cap.c 2021-12-22 12:33:20.740126781 +0100 +++ b/pam_cap/pam_cap.c 2021-12-22 12:33:53.196733134 +0100 @@ -21,6 +21,7 @@ #include #include #include +#include #include #include @@ -33,8 +34,11 @@ struct pam_cap_s { int debug; + int keepcaps; + int defer; const char *user; const char *conf_filename; + pam_handle_t *pamh; }; /* @@ -178,6 +182,33 @@ } /* + * This is the "defer" cleanup function that actually applies the IAB + * tuple. This happens really late in the PAM session, hopefully after + * the application has performed its setuid() function. + */ +static void iab_apply(pam_handle_t *pamh, void *data, int error_status) +{ + cap_iab_t iab = data; + int retval = error_status & ~(PAM_DATA_REPLACE|PAM_DATA_SILENT); + + data = NULL; + if (error_status & PAM_DATA_REPLACE) { + goto done; + } + + if (retval != PAM_SUCCESS || !(error_status & PAM_DATA_SILENT)) { + goto done; + } + + if (cap_iab_set_proc(iab) != 0) { + D(("IAB setting failed")); + } + +done: + cap_free(iab); +} + +/* * Set capabilities for current process to match the current * permitted+executable sets combined with the configured inheritable * set. @@ -230,12 +261,21 @@ goto cleanup_conf; } - if (!cap_iab_set_proc(iab)) { + if (cs->defer) { + D(("configured to delay applying IAB")); + pam_set_data(cs->pamh, "pam_cap_iab", iab, iab_apply); + iab = NULL; + } else if (!cap_iab_set_proc(iab)) { D(("able to set the IAB [%s] value", conf_caps)); ok = 1; } cap_free(iab); + if (cs->keepcaps) { + D(("setting keepcaps")); + (void) cap_prctlw(PR_SET_KEEPCAPS, 1, 0, 0, 0, 0); + } + cleanup_conf: memset(conf_caps, 0, conf_caps_length); _pam_drop(conf_caps); @@ -268,6 +308,10 @@ pcs->debug = 1; } else if (!strncmp(*argv, "config=", 7)) { pcs->conf_filename = 7 + *argv; + } else if (!strcmp(*argv, "keepcaps")) { + pcs->keepcaps = 1; + } else if (!strcmp(*argv, "defer")) { + pcs->defer = 1; } else { _pam_log(LOG_ERR, "unknown option; %s", *argv); } @@ -353,6 +397,7 @@ return PAM_AUTH_ERR; } + pcs.pamh = pamh; retval = set_capabilities(&pcs); memset(&pcs, 0, sizeof(pcs));