diff options
Diffstat (limited to 'modules/pam_limits/pam_limits.c')
-rw-r--r-- | modules/pam_limits/pam_limits.c | 293 |
1 files changed, 237 insertions, 56 deletions
diff --git a/modules/pam_limits/pam_limits.c b/modules/pam_limits/pam_limits.c index b791cdce..87bb4b70 100644 --- a/modules/pam_limits/pam_limits.c +++ b/modules/pam_limits/pam_limits.c @@ -13,7 +13,7 @@ * See end for Copyright information */ -#if !defined(linux) && !defined(__linux) +#ifndef __linux__ #warning THIS CODE IS KNOWN TO WORK ONLY ON LINUX !!! #endif @@ -28,6 +28,7 @@ #include <syslog.h> #include <stdarg.h> #include <signal.h> +#include <sys/prctl.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/resource.h> @@ -46,10 +47,14 @@ #include <libaudit.h> #endif +#ifndef PR_SET_NO_NEW_PRIVS +# define PR_SET_NO_NEW_PRIVS 38 /* from <linux/prctl.h> */ +#endif + /* Module defines */ #define LINE_LENGTH 1024 -#define LIMITS_DEF_USER 0 /* limit was set by an user entry */ +#define LIMITS_DEF_USER 0 /* limit was set by a user entry */ #define LIMITS_DEF_GROUP 1 /* limit was set by a group entry */ #define LIMITS_DEF_ALLGROUP 2 /* limit was set by a group entry */ #define LIMITS_DEF_ALL 3 /* limit was set by an all entry */ @@ -88,6 +93,7 @@ struct pam_limit_s { int flag_numsyslogins; /* whether to limit logins only for a specific user or to count all logins */ int priority; /* the priority to run user process with */ + int nonewprivs; /* whether to prctl(PR_SET_NO_NEW_PRIVS) */ struct user_limits_struct limits[RLIM_NLIMITS]; const char *conf_file; int utmp_after_pam_call; @@ -98,6 +104,7 @@ struct pam_limit_s { #define LIMIT_NUMSYSLOGINS RLIM_NLIMITS+2 #define LIMIT_PRI RLIM_NLIMITS+3 +#define LIMIT_NONEWPRIVS RLIM_NLIMITS+4 #define LIMIT_SOFT 1 #define LIMIT_HARD 2 @@ -116,9 +123,14 @@ struct pam_limit_s { #define PAM_SET_ALL 0x0010 /* Limits from globbed files. */ -#define LIMITS_CONF_GLOB LIMITS_FILE_DIR +#define LIMITS_CONF_GLOB (LIMITS_FILE_DIR "/*.conf") -#define CONF_FILE (pl->conf_file != NULL)?pl->conf_file:LIMITS_FILE +#define LIMITS_FILE (SCONFIGDIR "/limits.conf") + +#ifdef VENDOR_SCONFIGDIR +#define VENDOR_LIMITS_FILE (VENDOR_SCONFIGDIR "/limits.conf") +#define VENDOR_LIMITS_CONF_GLOB (VENDOR_SCONFIGDIR "/limits.d/*.conf") +#endif static int _pam_parse (const pam_handle_t *pamh, int argc, const char **argv, @@ -271,8 +283,8 @@ check_logins (pam_handle_t *pamh, const char *name, int limit, int ctrl, } if (!pl->flag_numsyslogins) { char user[sizeof(ut->UT_USER) + 1]; - user[0] = '\0'; - strncat(user, ut->UT_USER, sizeof(ut->UT_USER)); + memcpy(user, ut->UT_USER, sizeof(ut->UT_USER)); + user[sizeof(ut->UT_USER)] = '\0'; if (((pl->login_limit_def == LIMITS_DEF_USER) || (pl->login_limit_def == LIMITS_DEF_GROUP) @@ -484,6 +496,41 @@ static int init_limits(pam_handle_t *pamh, struct pam_limit_s *pl, int ctrl) return retval; } +/* + * Read the contents of <pathname> and return it in *valuep + * return 1 if conversion succeeds, result is in *valuep + * return 0 if conversion fails, *valuep is untouched. + */ +static int +value_from_file(const char *pathname, rlim_t *valuep) +{ + char buf[128]; + FILE *fp; + int retval; + + retval = 0; + + if ((fp = fopen(pathname, "r")) != NULL) { + if (fgets(buf, sizeof(buf), fp) != NULL) { + char *endptr; + unsigned long long value; + + errno = 0; + value = strtoull(buf, &endptr, 10); + if (endptr != buf && + (value != ULLONG_MAX || errno == 0) && + (unsigned long long) (rlim_t) value == value) { + *valuep = (rlim_t) value; + retval = 1; + } + } + + fclose(fp); + } + + return retval; +} + static void process_limit (const pam_handle_t *pamh, int source, const char *lim_type, const char *lim_item, const char *lim_value, @@ -551,6 +598,8 @@ process_limit (const pam_handle_t *pamh, int source, const char *lim_type, pl->flag_numsyslogins = 1; } else if (strcmp(lim_item, "priority") == 0) { limit_item = LIMIT_PRI; + } else if (strcmp(lim_item, "nonewprivs") == 0) { + limit_item = LIMIT_NONEWPRIVS; } else { pam_syslog(pamh, LOG_DEBUG, "unknown limit item '%s'", lim_item); return; @@ -562,11 +611,23 @@ process_limit (const pam_handle_t *pamh, int source, const char *lim_type, limit_type=LIMIT_HARD; else if (strcmp(lim_type,"-")==0) limit_type=LIMIT_SOFT | LIMIT_HARD; - else if (limit_item != LIMIT_LOGIN && limit_item != LIMIT_NUMSYSLOGINS) { + else if (limit_item != LIMIT_LOGIN && limit_item != LIMIT_NUMSYSLOGINS + && limit_item != LIMIT_NONEWPRIVS) { pam_syslog(pamh, LOG_DEBUG, "unknown limit type '%s'", lim_type); return; } - if (limit_item != LIMIT_PRI + if (limit_item == LIMIT_NONEWPRIVS) { + /* just require a bool-style 0 or 1 */ + if (strcmp(lim_value, "0") == 0) { + int_value = 0; + } else if (strcmp(lim_value, "1") == 0) { + int_value = 1; + } else { + pam_syslog(pamh, LOG_DEBUG, + "wrong limit value '%s' for limit type '%s'", + lim_value, lim_type); + } + } else if (limit_item != LIMIT_PRI #ifdef RLIMIT_NICE && limit_item != RLIMIT_NICE #endif @@ -649,11 +710,26 @@ process_limit (const pam_handle_t *pamh, int source, const char *lim_type, rlimit_value = 20 - int_value; break; #endif + case RLIMIT_NOFILE: + /* + * If nofile is to be set to "unlimited", try to set it to + * the value in /proc/sys/fs/nr_open instead. + */ + if (rlimit_value == RLIM_INFINITY) { + if (!value_from_file("/proc/sys/fs/nr_open", &rlimit_value)) + pam_syslog(pamh, LOG_WARNING, + "Cannot set \"nofile\" to a sensible value"); + else if (ctrl & PAM_DEBUG_ARG) + pam_syslog(pamh, LOG_DEBUG, "Setting \"nofile\" limit to %llu", + (unsigned long long) rlimit_value); + } + break; } if ( (limit_item != LIMIT_LOGIN) && (limit_item != LIMIT_NUMSYSLOGINS) - && (limit_item != LIMIT_PRI) ) { + && (limit_item != LIMIT_PRI) + && (limit_item != LIMIT_NONEWPRIVS) ) { if (limit_type & LIMIT_SOFT) { if (pl->limits[limit_item].src_soft < source) { return; @@ -674,14 +750,16 @@ process_limit (const pam_handle_t *pamh, int source, const char *lim_type, /* recent kernels support negative priority limits (=raise priority) */ if (limit_item == LIMIT_PRI) { - pl->priority = int_value; + pl->priority = int_value; + } else if (limit_item == LIMIT_NONEWPRIVS) { + pl->nonewprivs = int_value; } else { - if (pl->login_limit_def < source) { - return; - } else { - pl->login_limit = int_value; - pl->login_limit_def = source; - } + if (pl->login_limit_def < source) { + return; + } else { + pl->login_limit = int_value; + pl->login_limit_def = source; + } } } return; @@ -737,18 +815,22 @@ parse_uid_range(pam_handle_t *pamh, const char *domain, static int parse_config_file(pam_handle_t *pamh, const char *uname, uid_t uid, gid_t gid, - int ctrl, struct pam_limit_s *pl) + int ctrl, struct pam_limit_s *pl, const int conf_file_set_by_user) { FILE *fil; char buf[LINE_LENGTH]; - /* check for the LIMITS_FILE */ + /* check for the conf_file */ if (ctrl & PAM_DEBUG_ARG) - pam_syslog(pamh, LOG_DEBUG, "reading settings from '%s'", CONF_FILE); - fil = fopen(CONF_FILE, "r"); + pam_syslog(pamh, LOG_DEBUG, "reading settings from '%s'", pl->conf_file); + fil = fopen(pl->conf_file, "r"); if (fil == NULL) { - pam_syslog (pamh, LOG_WARNING, - "cannot read settings from %s: %m", CONF_FILE); + if (errno == ENOENT && !conf_file_set_by_user) + return PAM_SUCCESS; /* file is not there and it has not been set by the conf= argument */ + + pam_syslog(pamh, LOG_WARNING, + "cannot read settings from %s: %s", pl->conf_file, + strerror(errno)); return PAM_SERVICE_ERR; } @@ -995,36 +1077,142 @@ static int setup_limits(pam_handle_t *pamh, retval |= LOGIN_ERR; } + if (pl->nonewprivs) { + if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) < 0) { + pam_syslog(pamh, LOG_ERR, "Could not set prctl(PR_SET_NO_NEW_PRIVS): %m"); + retval |= LIMIT_ERR; + } + } + return retval; } +/* --- evaluting all files in VENDORDIR/security/limits.d and /etc/security/limits.d --- */ +static const char * +base_name(const char *path) +{ + const char *base = strrchr(path, '/'); + return base ? base+1 : path; +} + +static int +compare_filename(const void *a, const void *b) +{ + return strcmp(base_name(* (const char * const *) a), + base_name(* (const char * const *) b)); +} + +/* Evaluating a list of files which have to be parsed in the right order: + * + * - If etc/security/limits.d/@filename@.conf exists, then + * %vendordir%/security/limits.d/@filename@.conf should not be used. + * - All files in both limits.d directories are sorted by their @filename@.conf in + * lexicographic order regardless of which of the directories they reside in. */ +static char ** +read_limits_dir(pam_handle_t *pamh) +{ + glob_t globbuf; + size_t i=0; + int glob_rv = glob(LIMITS_CONF_GLOB, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf); + char **file_list; + size_t file_list_size = glob_rv == 0 ? globbuf.gl_pathc : 0; + +#ifdef VENDOR_LIMITS_CONF_GLOB + glob_t globbuf_vendor; + int glob_rv_vendor = glob(VENDOR_LIMITS_CONF_GLOB, GLOB_ERR | GLOB_NOSORT, NULL, &globbuf_vendor); + if (glob_rv_vendor == 0) + file_list_size += globbuf_vendor.gl_pathc; +#endif + file_list = malloc((file_list_size + 1) * sizeof(char*)); + if (file_list == NULL) { + pam_syslog(pamh, LOG_ERR, "Cannot allocate memory for file list: %m"); +#ifdef VENDOR_ACCESS_CONF_GLOB + if (glob_rv_vendor == 0) + globfree(&globbuf_vendor); +#endif + if (glob_rv == 0) + globfree(&globbuf); + return NULL; + } + + if (glob_rv == 0) { + for (i = 0; i < globbuf.gl_pathc; i++) { + file_list[i] = strdup(globbuf.gl_pathv[i]); + if (file_list[i] == NULL) { + pam_syslog(pamh, LOG_ERR, "strdup failed: %m"); + break; + } + } + } +#ifdef VENDOR_LIMITS_CONF_GLOB + if (glob_rv_vendor == 0) { + for (size_t j = 0; j < globbuf_vendor.gl_pathc; j++) { + if (glob_rv == 0 && globbuf.gl_pathc > 0) { + int double_found = 0; + for (size_t k = 0; k < globbuf.gl_pathc; k++) { + if (strcmp(base_name(globbuf.gl_pathv[k]), + base_name(globbuf_vendor.gl_pathv[j])) == 0) { + double_found = 1; + break; + } + } + if (double_found) + continue; + } + file_list[i] = strdup(globbuf_vendor.gl_pathv[j]); + if (file_list[i] == NULL) { + pam_syslog(pamh, LOG_ERR, "strdup failed: %m"); + break; + } + i++; + } + globfree(&globbuf_vendor); + } +#endif + file_list[i] = NULL; + qsort(file_list, i, sizeof(char *), compare_filename); + if (glob_rv == 0) + globfree(&globbuf); + + return file_list; +} + /* now the session stuff */ int pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED, int argc, const char **argv) { - int retval; - int i; - int glob_rc; + int retval, i; char *user_name; struct passwd *pwd; int ctrl; struct pam_limit_s plstruct; struct pam_limit_s *pl = &plstruct; - glob_t globbuf; - const char *oldlocale; D(("called.")); memset(pl, 0, sizeof(*pl)); - memset(&globbuf, 0, sizeof(globbuf)); ctrl = _pam_parse(pamh, argc, argv, pl); retval = pam_get_item( pamh, PAM_USER, (void*) &user_name ); if ( user_name == NULL || retval != PAM_SUCCESS ) { pam_syslog(pamh, LOG_ERR, "open_session - error recovering username"); return PAM_SESSION_ERR; - } + } + + int conf_file_set_by_user = (pl->conf_file != NULL); + if (pl->conf_file == NULL) { + pl->conf_file = LIMITS_FILE; +#ifdef VENDOR_LIMITS_FILE + /* + * Check whether LIMITS_FILE file is available. + * If it does not exist, fall back to VENDOR_LIMITS_FILE file. + */ + struct stat buffer; + if (stat(pl->conf_file, &buffer) != 0 && errno == ENOENT) + pl->conf_file = VENDOR_LIMITS_FILE; +#endif + } pwd = pam_modutil_getpwnam(pamh, user_name); if (!pwd) { @@ -1040,46 +1228,39 @@ pam_sm_open_session (pam_handle_t *pamh, int flags UNUSED, return PAM_ABORT; } - retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl); + retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, + ctrl, pl, conf_file_set_by_user); if (retval == PAM_IGNORE) { - D(("the configuration file ('%s') has an applicable '<domain> -' entry", CONF_FILE)); + D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file)); return PAM_SUCCESS; } - if (retval != PAM_SUCCESS || pl->conf_file != NULL) + if (retval != PAM_SUCCESS || conf_file_set_by_user) /* skip reading limits.d if config file explicitly specified */ goto out; /* Read subsequent *.conf files, if they exist. */ - - /* set the LC_COLLATE so the sorting order doesn't depend - on system locale */ - - oldlocale = setlocale(LC_COLLATE, "C"); - glob_rc = glob(LIMITS_CONF_GLOB, GLOB_ERR, NULL, &globbuf); - - if (oldlocale != NULL) - setlocale (LC_COLLATE, oldlocale); - - if (!glob_rc) { - /* Parse the *.conf files. */ - for (i = 0; globbuf.gl_pathv[i] != NULL; i++) { - pl->conf_file = globbuf.gl_pathv[i]; - retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl); - if (retval == PAM_IGNORE) { - D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file)); - globfree(&globbuf); - return PAM_SUCCESS; - } - if (retval != PAM_SUCCESS) - goto out; + char **filename_list = read_limits_dir(pamh); + if (filename_list != NULL) { + for (i = 0; filename_list[i] != NULL; i++) { + pl->conf_file = filename_list[i]; + retval = parse_config_file(pamh, pwd->pw_name, pwd->pw_uid, pwd->pw_gid, ctrl, pl, 0); + if (retval != PAM_SUCCESS) + break; } + for (i = 0; filename_list[i] != NULL; i++) + free(filename_list[i]); + free(filename_list); + } + + if (retval == PAM_IGNORE) { + D(("the configuration file ('%s') has an applicable '<domain> -' entry", pl->conf_file)); + return PAM_SUCCESS; } out: - globfree(&globbuf); if (retval != PAM_SUCCESS) { - pam_syslog(pamh, LOG_ERR, "error parsing the configuration file: '%s' ",CONF_FILE); + pam_syslog(pamh, LOG_ERR, "error parsing the configuration file: '%s' ", pl->conf_file); return retval; } |