From 0ae966283374e5324b13aae7bc52f2c4c7488c47 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Fri, 15 Jun 2018 14:05:13 -0600 Subject: [PATCH] Add sudo_getgrouplist2() to dynamically allocate the group vector. This allows us to avoid repeatedly calling getgrouplist() with a statically sized vector on macOS, Solaris, HP-UX, and AIX. --- configure | 6 - configure.ac | 1 - include/sudo_compat.h | 5 +- include/sudo_util.h | 4 + lib/util/Makefile.in | 8 +- lib/util/getgrouplist.c | 261 +++++++++++++++++++++++++++++++++++----- lib/util/util.exp.in | 1 + 7 files changed, 240 insertions(+), 46 deletions(-) diff --git a/configure b/configure index cfde56038..fe01004c0 100755 --- a/configure +++ b/configure @@ -19149,12 +19149,6 @@ fi ;; esac - case " $LIBOBJS " in - *" getgrouplist.$ac_objext "* ) ;; - *) LIBOBJS="$LIBOBJS getgrouplist.$ac_objext" - ;; -esac - for _sym in sudo_getgrouplist; do COMPAT_EXP="${COMPAT_EXP}${_sym} diff --git a/configure.ac b/configure.ac index c3995bdaa..f95b7ba94 100644 --- a/configure.ac +++ b/configure.ac @@ -2538,7 +2538,6 @@ AC_CHECK_FUNCS([getgrouplist], [], [ ]) ;; esac - AC_LIBOBJ(getgrouplist) SUDO_APPEND_COMPAT_EXP(sudo_getgrouplist) ]) AC_CHECK_FUNCS([getline], [], [ diff --git a/include/sudo_compat.h b/include/sudo_compat.h index a2c7f9bd6..62ba7c769 100644 --- a/include/sudo_compat.h +++ b/include/sudo_compat.h @@ -392,13 +392,10 @@ __dso_public char *sudo_getcwd(char *, size_t size); # define getcwd(_a, _b) sudo_getcwd((_a), (_b)) #endif /* PREFER_PORTABLE_GETCWD */ #ifndef HAVE_GETGROUPLIST -__dso_public int sudo_getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp); +__dso_public int sudo_getgrouplist(const char *name, GETGROUPS_T basegid, GETGROUPS_T *groups, int *ngroupsp); # undef getgrouplist # define getgrouplist(_a, _b, _c, _d) sudo_getgrouplist((_a), (_b), (_c), (_d)) #endif /* GETGROUPLIST */ -#if defined(HAVE_GETGROUPLIST_2) && !HAVE_DECL_GETGROUPLIST_2 -int getgrouplist_2(const char *name, gid_t basegid, gid_t **groups); -#endif /* HAVE_GETGROUPLIST_2 && !HAVE_DECL_GETGROUPLIST_2 */ #ifndef HAVE_GETLINE __dso_public ssize_t sudo_getline(char **bufp, size_t *bufsizep, FILE *fp); # undef getline diff --git a/include/sudo_util.h b/include/sudo_util.h index c7218b228..2d73107aa 100644 --- a/include/sudo_util.h +++ b/include/sudo_util.h @@ -179,6 +179,10 @@ __dso_public int sudo_gettime_real_v1(struct timespec *ts); __dso_public int sudo_parse_gids_v1(const char *gidstr, const gid_t *basegid, GETGROUPS_T **gidsp); #define sudo_parse_gids(_a, _b, _c) sudo_parse_gids_v1((_a), (_b), (_c)) +/* getgrouplist.c */ +__dso_public int sudo_getgrouplist2_v1(const char *name, gid_t basegid, GETGROUPS_T **groupsp, int *ngroupsp); +#define sudo_getgrouplist2(_a, _b, _c, _d) sudo_getgrouplist2_v1((_a), (_b), (_c), (_d)) + /* key_val.c */ __dso_public char *sudo_new_key_val_v1(const char *key, const char *value); #define sudo_new_key_val(_a, _b) sudo_new_key_val_v1((_a), (_b)) diff --git a/lib/util/Makefile.in b/lib/util/Makefile.in index e9e8113c2..70cc48e6f 100644 --- a/lib/util/Makefile.in +++ b/lib/util/Makefile.in @@ -109,10 +109,10 @@ DEVEL = @DEVEL@ SHELL = @SHELL@ LTOBJS = @DIGEST@ event.lo fatal.lo key_val.lo gethostname.lo gettime.lo \ - gidlist.lo lbuf.lo locking.lo parseln.lo progname.lo secure_path.lo \ - setgroups.lo strsplit.lo strtobool.lo strtoid.lo strtomode.lo \ - sudo_conf.lo sudo_debug.lo sudo_dso.lo term.lo ttyname_dev.lo \ - ttysize.lo @COMMON_OBJS@ @LTLIBOBJS@ + getgrouplist.lo gidlist.lo lbuf.lo locking.lo parseln.lo progname.lo \ + secure_path.lo setgroups.lo strsplit.lo strtobool.lo strtoid.lo \ + strtomode.lo sudo_conf.lo sudo_debug.lo sudo_dso.lo term.lo \ + ttyname_dev.lo ttysize.lo @COMMON_OBJS@ @LTLIBOBJS@ ATOFOO_TEST_OBJS = atofoo_test.lo diff --git a/lib/util/getgrouplist.c b/lib/util/getgrouplist.c index 174e0c28c..ed4d58231 100644 --- a/lib/util/getgrouplist.c +++ b/lib/util/getgrouplist.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010, 2011, 2013-2016 + * Copyright (c) 2010, 2011, 2013-2018 * Todd C. Miller * * Permission to use, copy, modify, and distribute this software for any @@ -17,8 +17,6 @@ #include -#ifndef HAVE_GETGROUPLIST - #include #include #include @@ -29,6 +27,8 @@ # include #endif /* HAVE_STRINGS_H */ #include +#include +#include #ifdef HAVE_NSS_SEARCH # include # include @@ -43,22 +43,103 @@ #include "sudo_compat.h" #include "sudo_util.h" -#if defined(HAVE_GETGRSET) +#ifndef HAVE_GETGROUPLIST +int +sudo_getgrouplist(const char *name, GETGROUPS_T basegid, GETGROUPS_T *groups, + int *ngroupsp) +{ + return sudo_getgrouplist2(name, basegid, &groups, ngroupsp); +} +#endif /* HAVE_GETGROUPLIST */ + +#if defined(HAVE_GETGROUPLIST) + +#if defined(HAVE_GETGROUPLIST_2) && !HAVE_DECL_GETGROUPLIST_2 +int getgrouplist_2(const char *name, GETGROUPS_T basegid, GETGROUPS_T **groups); +#endif /* HAVE_GETGROUPLIST_2 && !HAVE_DECL_GETGROUPLIST_2 */ + /* - * BSD-compatible getgrouplist(3) using AIX getgrset(3) + * Extended getgrouplist(3) using getgrouplist(3) and getgrouplist_2(3) */ int -sudo_getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp) +sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid, + GETGROUPS_T **groupsp, int *ngroupsp) { + GETGROUPS_T *groups = *groupsp; + int ngroups; +#ifndef HAVE_GETGROUPLIST_2 + int grpsize, tries; +#endif + + /* For static group vector, just use getgrouplist(3). */ + if (groups != NULL) + return getgrouplist(name, basegid, groups, ngroupsp); + +#ifdef HAVE_GETGROUPLIST_2 + if ((ngroups = getgrouplist_2(name, basegid, groupsp)) == -1) + return -1; + *ngroupsp = ngroups; + return 0; +#else + grpsize = (int)sysconf(_SC_NGROUPS_MAX); + if (grpsize < 0) + grpsize = NGROUPS_MAX; + /* + * It is possible to belong to more groups in the group database + * than NGROUPS_MAX. We start off with NGROUPS_MAX * 4 entries + * and double this as needed. + */ + grpsize <<= 1; + for (tries = 0; tries < 10; tries++) { + free(groups); + groups = reallocarray(NULL, grpsize, 2 * sizeof(*groups)); + if (groups == NULL) + return -1; + grpsize <<= 1; + ngroups = grpsize; + if (getgrouplist(name, basegid, groups, &ngroups) == 0) { + *groupsp = groups; + *ngroupsp = ngroups; + return 0; + } + } + free(groups); + return -1; +#endif /* HAVE_GETGROUPLIST_2 */ +} + +#elif defined(HAVE_GETGRSET) + +/* + * Extended getgrouplist(3) using AIX getgrset(3) + */ +int +sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid, + GETGROUPS_T **groupsp, int *ngroupsp) +{ + GETGROUPS_T *groups = *groupsp; char *cp, *grset = NULL; int ngroups = 1; int grpsize = *ngroupsp; int ret = -1; gid_t gid; + if (groups == NULL) { + /* Dynamically-sized group vector. */ + grpsize = (int)sysconf(_SC_NGROUPS_MAX); + if (grpsize < 0) + grpsize = NGROUPS_MAX; + groups = reallocarray(NULL, grpsize, 4 * sizeof(*groups)); + if (groups == NULL) + return -1; + grpsize <<= 2; + } else { + /* Static group vector. */ + if (grpsize <= 0) + return -1; + } + /* We support BSD semantics where the first element is the base gid */ - if (grpsize <= 0) - return -1; groups[0] = basegid; #ifdef HAVE_SETAUTHDB @@ -71,8 +152,23 @@ sudo_getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp) for (cp = strtok_r(grset, ",", &last); cp != NULL; cp = strtok_r(NULL, ",", &last)) { gid = sudo_strtoid(cp, NULL, NULL, &errstr); if (errstr == NULL && gid != basegid) { - if (ngroups == grpsize) - goto done; + if (ngroups == grpsize) { + GETGROUPS_T *tmp; + + if (*groupsp != NULL) { + /* Static group vector. */ + goto done; + } + tmp = reallocarray(groups, grpsize, 2 * sizeof(*groups)); + if (tmp == NULL) { + free(groups); + groups = NULL; + ngroups = 0; + goto done; + } + groups = tmp; + grpsize <<= 1; + } groups[ngroups++] = gid; } } @@ -84,6 +180,7 @@ done: #ifdef HAVE_SETAUTHDB aix_restoreauthdb(); #endif + *groupsp = groups; *ngroupsp = ngroups; return ret; @@ -193,7 +290,8 @@ str2grp(const char *instr, int inlen, void *ent, char *buf, int buflen) } static nss_status_t -process_cstr(const char *instr, int inlen, struct nss_groupsbymem *gbm) +process_cstr(const char *instr, int inlen, struct nss_groupsbymem *gbm, + int dynamic) { const char *user = gbm->username; nss_status_t ret = NSS_NOTFOUND; @@ -202,6 +300,10 @@ process_cstr(const char *instr, int inlen, struct nss_groupsbymem *gbm) char **gr_mem; int error, i; + /* Hack to let us check whether the query was handled by nscd or us. */ + if (gbm->force_slow_way != 0) + gbm->force_slow_way = 2; + buf = _nss_XbyY_buf_alloc(sizeof(struct group), NSS_BUFLEN_GROUP); if (buf == NULL) return NSS_UNAVAIL; @@ -219,6 +321,17 @@ process_cstr(const char *instr, int inlen, struct nss_groupsbymem *gbm) if (gbm->gid_array[i] == grp->gr_gid) goto done; /* already present */ } + if (i == gbm->maxgids && dynamic) { + GETGROUPS_T *tmp = reallocarray(gbm->gid_array, gbm->maxgids, + 2 * sizeof(GETGROUPS_T)); + if (tmp == NULL) { + /* Out of memory, just return what we have. */ + dynamic = 0; + } else { + gbm->gid_array = tmp; + gbm->maxgids <<= 1; + } + } /* Store gid if there is space. */ if (i < gbm->maxgids) gbm->gid_array[i] = grp->gr_gid; @@ -232,35 +345,91 @@ done: return ret; } +static nss_status_t +process_cstr_static(const char *instr, int inlen, struct nss_groupsbymem *gbm) +{ + return process_cstr(instr, inlen, gbm, 0); +} + +static nss_status_t +process_cstr_dynamic(const char *instr, int inlen, struct nss_groupsbymem *gbm) +{ + return process_cstr(instr, inlen, gbm, 1); +} + /* - * BSD-compatible getgrouplist(3) using nss_search(3) + * Extended getgrouplist(3) using nss_search(3) */ int -sudo_getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp) +sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid, + GETGROUPS_T **groupsp, int *ngroupsp) { struct nss_groupsbymem gbm; static DEFINE_NSS_DB_ROOT(db_root); - /* We support BSD semantics where the first element is the base gid */ - if (*ngroupsp <= 0) - return -1; - groups[0] = basegid; - memset(&gbm, 0, sizeof(gbm)); gbm.username = name; - gbm.gid_array = groups; + gbm.gid_array = *groupsp; gbm.maxgids = *ngroupsp; gbm.numgids = 1; /* for basegid */ gbm.force_slow_way = 1; gbm.str2ent = str2grp; - gbm.process_cstr = process_cstr; + + if (gbm.gid_array == NULL) { + /* Dynamically-sized group vector. */ + gbm.maxgids = (int)sysconf(_SC_NGROUPS_MAX); + if (gbm.maxgids < 0) + gbm.maxgids = NGROUPS_MAX; + gbm.gid_array = reallocarray(NULL, gbm.maxgids, 4 * sizeof(GETGROUPS_T)); + if (gbm.gid_array == NULL) + return -1; + gbm.maxgids <<= 2; + gbm.process_cstr = process_cstr_dynamic; + } else { + /* Static group vector. */ + if (gbm.maxgids <= 0) + return -1; + gbm.process_cstr = process_cstr_static; + } + + /* We support BSD semantics where the first element is the base gid */ + gbm.gid_array[0] = basegid; /* * Can't use nss_search return value since it may return NSS_UNAVAIL * when no nsswitch.conf entry (e.g. compat mode). */ - (void)nss_search(&db_root, _nss_initf_group, NSS_DBOP_GROUP_BYMEMBER, &gbm); + for (;;) { + GETGROUPS_T *tmp; + + (void)nss_search(&db_root, _nss_initf_group, NSS_DBOP_GROUP_BYMEMBER, + &gbm); + /* + * If this was a statically-sized group vector or nscd was not used + * we are done. + */ + if (gbm.process_cstr != process_cstr_dynamic || gbm.force_slow_way == 2) + break; + + /* + * If gid_array is full and the query was handled by nscd, there + * may be more data, so double gid_array and try again. + */ + if (gbm.numgids != gbm.maxgids) + break; + + tmp = reallocarray(gbm.gid_array, gbm.maxgids, 2 * sizeof(GETGROUPS_T)); + if (tmp == NULL) { + free(gbm.gid_array); + return -1; + } + gbm.gid_array = tmp; + gbm.maxgids <<= 1; + } + + /* Note: we can only detect a too-small group list if nscd is not used. */ + *groupsp = gbm.gid_array; if (gbm.numgids <= gbm.maxgids) { *ngroupsp = gbm.numgids; return 0; @@ -269,22 +438,37 @@ sudo_getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp) return -1; } -#else /* !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */ +#else /* !HAVE_GETGROUPLIST && !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */ /* - * BSD-compatible getgrouplist(3) using getgrent(3) + * Extended getgrouplist(3) using getgrent(3) */ int -sudo_getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp) +sudo_getgrouplist2_v1(const char *name, GETGROUPS_T basegid, + GETGROUPS_T **groupsp, int *ngroupsp) { - int i, ngroups = 1; + GETGROUPS_T *groups = *groupsp; int grpsize = *ngroupsp; + int i, ngroups = 1; int ret = -1; struct group *grp; + if (groups == NULL) { + /* Dynamically-sized group vector. */ + grpsize = (int)sysconf(_SC_NGROUPS_MAX); + if (grpsize < 0) + grpsize = NGROUPS_MAX; + groups = reallocarray(NULL, grpsize, 4 * sizeof(*groups)); + if (groups == NULL) + return -1; + grpsize <<= 2; + } else { + /* Static group vector. */ + if (grpsize <= 0) + return -1; + } + /* We support BSD semantics where the first element is the base gid */ - if (grpsize <= 0) - return -1; groups[0] = basegid; setgrent(); @@ -305,8 +489,23 @@ sudo_getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp) break; } if (i == ngroups) { - if (ngroups == grpsize) - goto done; + if (ngroups == grpsize) { + GETGROUPS_T *tmp; + + if (*groupsp != NULL) { + /* Static group vector. */ + goto done; + } + tmp = reallocarray(groups, grpsize, 2 * sizeof(*groups)); + if (tmp == NULL) { + free(groups); + groups = NULL; + ngroups = 0; + goto done; + } + groups = tmp; + grpsize <<= 1; + } groups[ngroups++] = grp->gr_gid; } } @@ -314,9 +513,9 @@ sudo_getgrouplist(const char *name, gid_t basegid, gid_t *groups, int *ngroupsp) done: endgrent(); + *groupsp = groups; *ngroupsp = ngroups; return ret; } -#endif /* !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */ -#endif /* HAVE_GETGROUPLIST */ +#endif /* !HAVE_GETGROUPLIST && !HAVE_GETGRSET && !HAVE__GETGROUPSBYMEMBER */ diff --git a/lib/util/util.exp.in b/lib/util/util.exp.in index e00076830..4ae914348 100644 --- a/lib/util/util.exp.in +++ b/lib/util/util.exp.in @@ -69,6 +69,7 @@ sudo_fatal_callback_register_v1 sudo_fatal_nodebug_v1 sudo_fatalx_nodebug_v1 sudo_get_ttysize_v1 +sudo_getgrouplist2_v1 sudo_gethostname_v1 sudo_gettime_mono_v1 sudo_gettime_real_v1 -- 2.40.0