From 0186018d3dbab213040f2c90bd92148461fb83e3 Mon Sep 17 00:00:00 2001 From: "Todd C. Miller" Date: Wed, 4 Aug 2010 09:58:50 -0400 Subject: [PATCH] Reference count cached passwd and group structs. The cache holds one reference itself and another is added by sudo_getgr{gid,nam} and sudo_getpw{uid,nam}. The final ref on the runas and user passwd and group structs are persistent for now. --- plugins/sudoers/check.c | 4 + plugins/sudoers/ldap.c | 10 +- plugins/sudoers/match.c | 36 ++++-- plugins/sudoers/pwutil.c | 235 +++++++++++++++++++++++--------------- plugins/sudoers/sudoers.c | 20 +++- plugins/sudoers/sudoers.h | 4 + 6 files changed, 201 insertions(+), 108 deletions(-) diff --git a/plugins/sudoers/check.c b/plugins/sudoers/check.c index 156f051b8..8b2940b0e 100644 --- a/plugins/sudoers/check.c +++ b/plugins/sudoers/check.c @@ -648,6 +648,10 @@ remove_timestamp(int remove) efree(timestampdir); efree(timestampfile); + if (auth_pw) { + pw_delref(auth_pw); + auth_pw = NULL; + } } /* diff --git a/plugins/sudoers/ldap.c b/plugins/sudoers/ldap.c index 4f340742c..aa64396a8 100644 --- a/plugins/sudoers/ldap.c +++ b/plugins/sudoers/ldap.c @@ -801,13 +801,17 @@ sudo_ldap_build_pass1(struct passwd *pw) sz = 29 + strlen(pw->pw_name); /* Add space for groups */ - if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) + if ((grp = sudo_getgrgid(pw->pw_gid)) != NULL) { sz += 12 + strlen(grp->gr_name); /* primary group */ + gr_delref(grp); + } for (i = 0; i < user_ngroups; i++) { if (user_groups[i] == pw->pw_gid) continue; - if ((grp = sudo_getgrgid(user_groups[i])) != NULL) + if ((grp = sudo_getgrgid(user_groups[i])) != NULL) { sz += 12 + strlen(grp->gr_name); /* supplementary group */ + gr_delref(grp); + } } buf = emalloc(sz); @@ -821,6 +825,7 @@ sudo_ldap_build_pass1(struct passwd *pw) (void) strlcat(buf, "(sudoUser=%", sz); (void) strlcat(buf, grp->gr_name, sz); (void) strlcat(buf, ")", sz); + gr_delref(grp); } /* Append supplementary groups */ @@ -831,6 +836,7 @@ sudo_ldap_build_pass1(struct passwd *pw) (void) strlcat(buf, "(sudoUser=%", sz); (void) strlcat(buf, grp->gr_name, sz); (void) strlcat(buf, ")", sz); + gr_delref(grp); } } diff --git a/plugins/sudoers/match.c b/plugins/sudoers/match.c index 6093b8893..bee12a529 100644 --- a/plugins/sudoers/match.c +++ b/plugins/sudoers/match.c @@ -771,25 +771,41 @@ group_matches(char *sudoers_group, struct group *gr) int usergr_matches(char *group, char *user, struct passwd *pw) { + int matched = FALSE; + struct passwd *pw0 = NULL; + /* make sure we have a valid usergroup, sudo style */ if (*group++ != '%') - return(FALSE); + goto done; - if (*group == ':' && def_group_plugin) - return(group_plugin_query(user, group + 1, pw)); + if (*group == ':' && def_group_plugin) { + matched = group_plugin_query(user, group + 1, pw); + goto done; + } /* look up user's primary gid in the passwd file */ - if (pw == NULL && (pw = sudo_getpwnam(user)) == NULL) - return(FALSE); + if (pw == NULL) { + if ((pw0 = sudo_getpwnam(user)) == NULL) + goto done; + pw = pw0; + } - if (user_in_group(pw, group)) - return(TRUE); + if (user_in_group(pw, group)) { + matched = TRUE; + goto done; + } /* not a Unix group, could be an external group */ - if (def_group_plugin && group_plugin_query(user, group, pw)) - return(TRUE); + if (def_group_plugin && group_plugin_query(user, group, pw)) { + matched = TRUE; + goto done; + } - return(FALSE); +done: + if (pw0 != NULL) + pw_delref(pw0); + + return(matched); } /* diff --git a/plugins/sudoers/pwutil.c b/plugins/sudoers/pwutil.c index 485161320..4f25cdb29 100644 --- a/plugins/sudoers/pwutil.c +++ b/plugins/sudoers/pwutil.c @@ -66,7 +66,10 @@ static int cmp_grgid(const void *, const void *); #define cmp_grnam cmp_pwnam +#define ptr_to_item(p) ((struct cache_item *)((char *)(p) - sizeof(struct cache_item))) + struct cache_item { + unsigned int refcnt; /* key */ union { uid_t uid; @@ -188,10 +191,40 @@ make_pwitem(const struct passwd *pw, const char *name) item->k.uid = pw->pw_uid; } item->d.pw = newpw; + item->refcnt = 1; return(item); } +void +pw_addref(struct passwd *pw) +{ + ptr_to_item(pw)->refcnt++; +} + +static void +pw_delref_item(void *v) +{ + struct cache_item *item = v; + struct passwd *pw = item->d.pw; + + if (--item->refcnt == 0) { + if (pw != NULL && pw->pw_passwd != NULL) { + zero_bytes(pw->pw_passwd, strlen(pw->pw_passwd)); + if ((char *)pw->pw_passwd < (char *)pw || + (char *)pw->pw_passwd > (char *)pw->pw_gecos) + efree(pw->pw_passwd); /* free if separate allocation */ + } + efree(item); + } +} + +void +pw_delref(struct passwd *pw) +{ + pw_delref_item(ptr_to_item(pw)); +} + /* * Get a password entry by uid and allocate space for it. * Fills in pw_passwd from shadow file if necessary. @@ -225,6 +258,7 @@ sudo_getpwuid(uid_t uid) uid, item->d.pw->pw_name); } else { item = emalloc(sizeof(*item)); + item->refcnt = 1; item->k.uid = uid; item->d.pw = NULL; if (rbinsert(pwcache_byuid, item) != NULL) @@ -234,6 +268,7 @@ sudo_getpwuid(uid_t uid) aix_restoreauthdb(); #endif done: + item->refcnt++; return(item->d.pw); } @@ -271,6 +306,7 @@ sudo_getpwnam(const char *name) } else { len = strlen(name) + 1; item = emalloc(sizeof(*item) + len); + item->refcnt = 1; item->k.name = (char *) item + sizeof(*item); memcpy(item->k.name, name, len); item->d.pw = NULL; @@ -281,6 +317,7 @@ sudo_getpwnam(const char *name) aix_restoreauthdb(); #endif done: + item->refcnt++; return(item->d.pw); } @@ -318,71 +355,28 @@ sudo_fakepwnam(const char *user, gid_t gid) pw->pw_shell = pw->pw_dir + 2; memcpy(pw->pw_shell, _PATH_BSHELL, sizeof(_PATH_BSHELL)); + item->refcnt = 1; + item->d.pw = pw; if (i == 0) { /* Store by uid, overwriting cached version. */ item->k.uid = pw->pw_uid; - item->d.pw = pw; if ((node = rbinsert(pwcache_byuid, item)) != NULL) { - efree(node->data); + pw_delref_item(node->data); node->data = item; } } else { /* Store by name, overwriting cached version. */ item->k.name = pw->pw_name; - item->d.pw = pw; if ((node = rbinsert(pwcache_byname, item)) != NULL) { - efree(node->data); + pw_delref_item(node->data); node->data = item; } } } + item->refcnt++; return(pw); } -/* - * Take a gid in string form "#123" and return a faked up group struct. - */ -struct group * -sudo_fakegrnam(const char *group) -{ - struct cache_item *item; - struct group *gr; - struct rbnode *node; - size_t len, namelen; - int i; - - namelen = strlen(group); - len = sizeof(*item) + sizeof(*gr) + namelen + 1; - - for (i = 0; i < 2; i++) { - item = emalloc(len); - zero_bytes(item, sizeof(*item) + sizeof(*gr)); - gr = (struct group *) ((char *)item + sizeof(*item)); - gr->gr_gid = (gid_t) atoi(group + 1); - gr->gr_name = (char *)gr + sizeof(struct group); - memcpy(gr->gr_name, group, namelen + 1); - - if (i == 0) { - /* Store by gid, overwriting cached version. */ - item->k.gid = gr->gr_gid; - item->d.gr = gr; - if ((node = rbinsert(grcache_bygid, item)) != NULL) { - efree(node->data); - node->data = item; - } - } else { - /* Store by name, overwriting cached version. */ - item->k.name = gr->gr_name; - item->d.gr = gr; - if ((node = rbinsert(grcache_byname, item)) != NULL) { - efree(node->data); - node->data = item; - } - } - } - return(gr); -} - void sudo_setpwent(void) { @@ -394,46 +388,25 @@ sudo_setpwent(void) pwcache_byname = rbcreate(cmp_pwnam); } -#ifdef PURIFY -static void pw_free(void *); - void sudo_freepwcache(void) { if (pwcache_byuid != NULL) { - rbdestroy(pwcache_byuid, pw_free); + rbdestroy(pwcache_byuid, pw_delref_item); pwcache_byuid = NULL; } if (pwcache_byname != NULL) { - rbdestroy(pwcache_byname, pw_free); + rbdestroy(pwcache_byname, pw_delref_item); pwcache_byname = NULL; } } -static void -pw_free(void *v) -{ - struct cache_item *item = (struct cache_item *) v; - struct passwd *pw = item->d.pw; - - if (pw != NULL && pw->pw_passwd != NULL) { - zero_bytes(pw->pw_passwd, strlen(pw->pw_passwd)); - if ((char *)pw->pw_passwd < (char *)pw || - (char *)pw->pw_passwd > (char *)pw->pw_gecos) - efree(pw->pw_passwd); /* free if separate allocation */ - } - efree(item); -} -#endif /* PURIFY */ - void sudo_endpwent(void) { endpwent(); sudo_endspent(); -#ifdef PURIFY - sudo_freepwcache(); /* XXX - use after free */ -#endif + sudo_freepwcache(); } /* @@ -508,10 +481,32 @@ make_gritem(const struct group *gr, const char *name) item->k.gid = gr->gr_gid; } item->d.gr = newgr; + item->refcnt = 1; return(item); } +void +gr_addref(struct group *gr) +{ + ptr_to_item(gr)->refcnt++; +} + +static void +gr_delref_item(void *v) +{ + struct cache_item *item = v; + + if (--item->refcnt == 0) + efree(item); +} + +void +gr_delref(struct group *gr) +{ + gr_delref_item(ptr_to_item(gr)); +} + /* * Get a group entry by gid and allocate space for it. */ @@ -536,12 +531,14 @@ sudo_getgrgid(gid_t gid) gid, key.d.gr->gr_name); } else { item = emalloc(sizeof(*item)); + item->refcnt = 1; item->k.gid = gid; item->d.gr = NULL; if (rbinsert(grcache_bygid, item) != NULL) errorx(1, "unable to cache gid %lu, already exists, gid"); } done: + item->refcnt++; return(item->d.gr); } @@ -570,6 +567,7 @@ sudo_getgrnam(const char *name) } else { len = strlen(name) + 1; item = emalloc(sizeof(*item) + len); + item->refcnt = 1; item->k.name = (char *) item + sizeof(*item); memcpy(item->k.name, name, len); item->d.gr = NULL; @@ -577,9 +575,55 @@ sudo_getgrnam(const char *name) errorx(1, "unable to cache group %s, already exists", name); } done: + item->refcnt++; return(item->d.gr); } +/* + * Take a gid in string form "#123" and return a faked up group struct. + */ +struct group * +sudo_fakegrnam(const char *group) +{ + struct cache_item *item; + struct group *gr; + struct rbnode *node; + size_t len, namelen; + int i; + + namelen = strlen(group); + len = sizeof(*item) + sizeof(*gr) + namelen + 1; + + for (i = 0; i < 2; i++) { + item = emalloc(len); + zero_bytes(item, sizeof(*item) + sizeof(*gr)); + gr = (struct group *) ((char *)item + sizeof(*item)); + gr->gr_gid = (gid_t) atoi(group + 1); + gr->gr_name = (char *)gr + sizeof(struct group); + memcpy(gr->gr_name, group, namelen + 1); + + item->refcnt = 1; + item->d.gr = gr; + if (i == 0) { + /* Store by gid, overwriting cached version. */ + item->k.gid = gr->gr_gid; + if ((node = rbinsert(grcache_bygid, item)) != NULL) { + gr_delref_item(node->data); + node->data = item; + } + } else { + /* Store by name, overwriting cached version. */ + item->k.name = gr->gr_name; + if ((node = rbinsert(grcache_byname, item)) != NULL) { + gr_delref_item(node->data); + node->data = item; + } + } + } + item->refcnt++; + return(gr); +} + void sudo_setgrent(void) { @@ -590,28 +634,24 @@ sudo_setgrent(void) grcache_byname = rbcreate(cmp_grnam); } -#ifdef PURIFY void sudo_freegrcache(void) { if (grcache_bygid != NULL) { - rbdestroy(grcache_bygid, free); + rbdestroy(grcache_bygid, gr_delref_item); grcache_bygid = NULL; } if (grcache_byname != NULL) { - rbdestroy(grcache_byname, NULL); + rbdestroy(grcache_byname, gr_delref_item); grcache_byname = NULL; } } -#endif /* PURIFY */ void sudo_endgrent(void) { endgrent(); -#ifdef PURIFY - sudo_freegrcache(); /* XXX - use after free */ -#endif + sudo_freegrcache(); } int @@ -625,6 +665,7 @@ user_in_group(struct passwd *pw, const char *group) int i; #endif struct group *grp; + int retval = FALSE; #ifdef HAVE_SETAUTHDB aix_setauthdb(pw->pw_name); @@ -634,23 +675,29 @@ user_in_group(struct passwd *pw, const char *group) aix_restoreauthdb(); #endif if (grp == NULL) - return(FALSE); + goto done; /* check against user's primary (passwd file) gid */ - if (grp->gr_gid == pw->pw_gid) - return(TRUE); + if (grp->gr_gid == pw->pw_gid) { + retval = TRUE; + goto done; + } #ifdef HAVE_MBR_CHECK_MEMBERSHIP /* If we are matching the invoking user use the stashed uuid. */ if (strcmp(pw->pw_name, user_name) == 0) { if (mbr_gid_to_uuid(grp->gr_gid, gu) == 0 && - mbr_check_membership(user_uuid, gu, &ismember) == 0 && ismember) - return(TRUE); + mbr_check_membership(user_uuid, gu, &ismember) == 0 && ismember) { + retval = TRUE; + goto done; + } } else { if (mbr_uid_to_uuid(pw->pw_uid, uu) == 0 && mbr_gid_to_uuid(grp->gr_gid, gu) == 0 && - mbr_check_membership(uu, gu, &ismember) == 0 && ismember) - return(TRUE); + mbr_check_membership(uu, gu, &ismember) == 0 && ismember) { + retval = TRUE; + goto done; + } } #else /* HAVE_MBR_CHECK_MEMBERSHIP */ # ifdef HAVE_GETGROUPS @@ -661,20 +708,26 @@ user_in_group(struct passwd *pw, const char *group) if (user_ngroups >= 0 && strcmp(pw->pw_name, list_pw ? list_pw->pw_name : user_name) == 0) { for (i = 0; i < user_ngroups; i++) { - if (grp->gr_gid == user_groups[i]) - return(TRUE); + if (grp->gr_gid == user_groups[i]) { + retval = TRUE; + goto done; + } } } else # endif /* HAVE_GETGROUPS */ { if (grp != NULL && grp->gr_mem != NULL) { for (gr_mem = grp->gr_mem; *gr_mem; gr_mem++) { - if (strcmp(*gr_mem, pw->pw_name) == 0) - return(TRUE); + if (strcmp(*gr_mem, pw->pw_name) == 0) { + retval = TRUE; + goto done; + } } } } #endif /* HAVE_MBR_CHECK_MEMBERSHIP */ - return(FALSE); +done: + gr_delref(grp); + return(retval); } diff --git a/plugins/sudoers/sudoers.c b/plugins/sudoers/sudoers.c index 328d90389..47b07d670 100644 --- a/plugins/sudoers/sudoers.c +++ b/plugins/sudoers/sudoers.c @@ -406,6 +406,7 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], log_error(0, "timestamp owner (%s): No such user", def_timestampowner); timestamp_uid = pw->pw_uid; + pw_delref(pw); } /* If given the -P option, set the "preserve_groups" flag. */ @@ -459,6 +460,8 @@ sudoers_policy_main(int argc, char * const argv[], int pwflag, char *env_add[], struct passwd *pw; if ((pw = sudo_getpwnam(prev_user)) != NULL) { + if (sudo_user.pw != NULL) + pw_delref(sudo_user.pw); sudo_user.pw = pw; #ifdef HAVE_MBR_CHECK_MEMBERSHIP mbr_uid_to_uuid(user_uid, user_uuid); @@ -1066,12 +1069,14 @@ set_fqdn(void) } /* - * Get passwd entry for the user we are going to run commands as. - * By default, this is "root". Updates runas_pw as a side effect. + * Get passwd entry for the user we are going to run commands as + * and store it in runas_pw. By default, commands run as "root". */ static void set_runaspw(char *user) { + if (runas_pw != NULL) + pw_delref(runas_pw); if (*user == '#') { if ((runas_pw = sudo_getpwuid(atoi(user + 1))) == NULL) runas_pw = sudo_fakepwnam(user, runas_gr ? runas_gr->gr_gid : 0); @@ -1084,12 +1089,14 @@ set_runaspw(char *user) } /* - * Get group entry for the group we are going to run commands as. - * Updates runas_pw as a side effect. + * Get group entry for the group we are going to run commands as + * and store it in runas_gr. */ static void set_runasgr(char *group) { + if (runas_gr != NULL) + gr_delref(runas_gr); if (*group == '#') { if ((runas_gr = sudo_getgrgid(atoi(group + 1))) == NULL) runas_gr = sudo_fakegrnam(group); @@ -1119,9 +1126,12 @@ get_authpw(void) if (runas_pw->pw_name == NULL) log_error(NO_MAIL|MSG_ONLY, "unknown uid: %lu", (unsigned long) runas_pw->pw_uid); + pw_addref(runas_pw); pw = runas_pw; - } else + } else { + pw_addref(sudo_user.pw); pw = sudo_user.pw; + } return(pw); } diff --git a/plugins/sudoers/sudoers.h b/plugins/sudoers/sudoers.h index 5b726af6a..ccd880d2f 100644 --- a/plugins/sudoers/sudoers.h +++ b/plugins/sudoers/sudoers.h @@ -270,6 +270,10 @@ struct passwd *sudo_getpwuid(uid_t); struct group *sudo_getgrnam(const char *); struct group *sudo_fakegrnam(const char *); struct group *sudo_getgrgid(gid_t); +void gr_addref(struct group *); +void gr_delref(struct group *); +void pw_addref(struct passwd *); +void pw_delref(struct passwd *); int user_in_group(struct passwd *, const char *); /* timestr.c */ -- 2.40.0