diff --git a/common/sudo_conf.c b/common/sudo_conf.c index da3b4d115..92628cb60 100644 --- a/common/sudo_conf.c +++ b/common/sudo_conf.c @@ -100,11 +100,13 @@ static struct sudo_conf_table sudo_conf_table[] = { static struct sudo_conf_data { bool disable_coredump; + int group_source; const char *debug_flags; struct sudo_conf_paths paths[3]; struct plugin_info_list plugins; } sudo_conf_data = { true, + GROUP_SOURCE_ADAPTIVE, NULL, { #define SUDO_CONF_ASKPASS_IDX 0 @@ -125,7 +127,6 @@ set_variable(const char *entry) { #undef DC_LEN #define DC_LEN (sizeof("disable_coredump") - 1) - /* Currently the only variable supported is "disable_coredump". */ if (strncmp(entry, "disable_coredump", DC_LEN) == 0 && isblank((unsigned char)entry[DC_LEN])) { entry += DC_LEN + 1; @@ -134,6 +135,24 @@ set_variable(const char *entry) sudo_conf_data.disable_coredump = atobool(entry); } #undef DC_LEN +#undef GS_LEN +#define GS_LEN (sizeof("group_source") - 1) + if (strncmp(entry, "group_source", GS_LEN) == 0 && + isblank((unsigned char)entry[GS_LEN])) { + entry += GS_LEN + 1; + while (isblank((unsigned char)*entry)) + entry++; + if (strcasecmp(entry, "adaptive") == 0) { + sudo_conf_data.group_source = GROUP_SOURCE_ADAPTIVE; + } else if (strcasecmp(entry, "static") == 0) { + sudo_conf_data.group_source = GROUP_SOURCE_STATIC; + } else if (strcasecmp(entry, "dynamic") == 0) { + sudo_conf_data.group_source = GROUP_SOURCE_DYNAMIC; + } else { + warningx(_("unsupported group source `%s' in %s, line %d"), entry, + _PATH_SUDO_CONF, lineno); + } + } return true; } @@ -277,6 +296,12 @@ sudo_conf_debug_flags(void) return sudo_conf_data.debug_flags; } +int +sudo_conf_group_source(void) +{ + return sudo_conf_data.group_source; +} + struct plugin_info_list * sudo_conf_plugins(void) { diff --git a/include/sudo_conf.h b/include/sudo_conf.h index 22b9a43bb..974e28020 100644 --- a/include/sudo_conf.h +++ b/include/sudo_conf.h @@ -19,6 +19,10 @@ #include "list.h" +#define GROUP_SOURCE_ADAPTIVE 0 +#define GROUP_SOURCE_STATIC 1 +#define GROUP_SOURCE_DYNAMIC 2 + struct plugin_info { struct plugin_info *prev; /* required */ struct plugin_info *next; /* required */ @@ -38,5 +42,6 @@ const char *sudo_conf_noexec_path(void); const char *sudo_conf_debug_flags(void); struct plugin_info_list *sudo_conf_plugins(void); bool sudo_conf_disable_coredump(void); +int sudo_conf_group_source(void); #endif /* _SUDO_CONF_H */ diff --git a/src/sudo.c b/src/sudo.c index 1246b26f5..c19244e79 100644 --- a/src/sudo.c +++ b/src/sudo.c @@ -336,29 +336,23 @@ fix_fds(void) /* * Allocate space for groups and fill in using getgrouplist() - * for when we cannot use getgroups(). + * for when we cannot (or don't want to) use getgroups(). */ static int -fill_group_list(struct user_details *ud) +fill_group_list(struct user_details *ud, int maxgroups) { - int maxgroups, tries, rval = -1; + int tries, rval = -1; debug_decl(fill_group_list, SUDO_DEBUG_UTIL) -#if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX) - maxgroups = (int)sysconf(_SC_NGROUPS_MAX); - if (maxgroups < 0) -#endif - maxgroups = NGROUPS_MAX; - /* * It is possible to belong to more groups in the group database - * than NGROUPS_MAX. We start off with NGROUPS_MAX * 2 entries + * than NGROUPS_MAX. We start off with NGROUPS_MAX * 4 entries * and double this as needed. */ ud->groups = NULL; - ud->ngroups = maxgroups; + ud->ngroups = maxgroups << 1; for (tries = 0; tries < 10 && rval == -1; tries++) { - ud->ngroups *= 2; + ud->ngroups <<= 1; efree(ud->groups); ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T)); rval = getgrouplist(ud->username, ud->gid, ud->groups, &ud->ngroups); @@ -371,25 +365,35 @@ get_user_groups(struct user_details *ud) { char *cp, *gid_list = NULL; size_t glsize; - int i, len; + int i, len, maxgroups, group_source; debug_decl(get_user_groups, SUDO_DEBUG_UTIL) - /* - * Systems with mbr_check_membership() support more than NGROUPS_MAX - * groups so we cannot use getgroups(). - */ +#if defined(HAVE_SYSCONF) && defined(_SC_NGROUPS_MAX) + maxgroups = (int)sysconf(_SC_NGROUPS_MAX); + if (maxgroups < 0) +#endif + maxgroups = NGROUPS_MAX; + ud->groups = NULL; -#ifndef HAVE_MBR_CHECK_MEMBERSHIP - if ((ud->ngroups = getgroups(0, NULL)) > 0) { - ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T)); - if (getgroups(ud->ngroups, ud->groups) < 0) { - efree(ud->groups); - ud->groups = NULL; + group_source = sudo_conf_group_source(); + if (group_source != GROUP_SOURCE_DYNAMIC) { + if ((ud->ngroups = getgroups(0, NULL)) > 0) { + /* Use groups from kernel if not too many or source is static. */ + if (ud->ngroups < maxgroups || group_source == GROUP_SOURCE_STATIC) { + ud->groups = emalloc2(ud->ngroups, sizeof(GETGROUPS_T)); + if (getgroups(ud->ngroups, ud->groups) < 0) { + efree(ud->groups); + ud->groups = NULL; + } + } } } -#endif /* HAVE_MBR_CHECK_MEMBERSHIP */ if (ud->groups == NULL) { - if (fill_group_list(ud) == -1) + /* + * Query group database if kernel list is too small or disabled. + * Typically, this is because NFS can only support up to 16 groups. + */ + if (fill_group_list(ud, maxgroups) == -1) error(1, _("unable to get group vector")); }