Bugzilla – Bug 114
Under FreeBSD 4.x (and possibly 5.x), sudo can incorrectly provide elevated privileges to users when %group directives are used and NIS is used.
Last modified: 2003-05-21 23:46:58 MDT
Sudo makes an unsafe assumption about the integrity of static library memory in the getgrnam() and getpwnam() libc calls. It turns out that the inclusion of users via the +@netgroup directive under FreeBSD 4.x (and possibly other variants) can cause getpwnam() to call getgrnam(). Note that FreeBSD's implementation of both functions uses static memory. With that in mind, I'll highlight the problem in the usergr_matches() function from parse.c (called when sudo wants to see if a user is a member of some group listed via the '%' directive in the sudoers file): 1 int 2 usergr_matches(group, user) 3 char *group; 4 char *user; 5 { 6 struct group *grp; 7 struct passwd *pw; 8 char **cur; 9 10 /* make sure we have a valid usergroup, sudo style */ 11 if (*group++ != '%') 12 return(FALSE); 13 14 if ((grp = getgrnam(group)) == NULL) 15 return(FALSE); 16 17 /* 18 * Check against user's real gid as well as group's user list 19 */ 20 if ((pw = getpwnam(user)) == NULL) 21 return(FALSE); 22 23 if (grp->gr_gid == pw->pw_gid) 24 return(TRUE); 25 26 for (cur=grp->gr_mem; *cur; cur++) { 27 if (strcmp(*cur, user) == 0) 28 return(TRUE); 29 } 30 31 return(FALSE); 32 } First, a note: In FreeBSD's getpwnam implementation, if 1) NIS is enabled, 2) a user's entry isn't explicity listed in master.passwd, 3) there are +@netgroup import directives in master.passwd, and 4) the user isn't found in the current netgroup being searched, then the function searches for a group with the same name, and if found, searches within it (using getgrnam()) for the user. Whew, that's a mouthful. This is the situation where sudo gets into trouble. I'll refer to this as +@group inclusion. Notice how getgrnam() is called on line 14, followed by getpwnam() on line 20. When a user is found via +@group inclusion, the call to getpwnam() will call getgrnam() internally, and so _WILL_ overwrite/modify the static group functions' memory. So, if the subsequent check on line 23 doesn't succeed (it won't if the user's default gid != gid of group s/he is included from), the one on 27 will. I think the FreeBSD libc maintainers are partly (mostly?) to blame here; they should have either 1) made this side effect explicit in the getpwnam() manpage, or 2) used separate internal memory for the group search. Anyhow, here is a patch that will fix the problem: --- parse.c Tue Apr 15 18:39:14 2003 +++ parse.c.new Fri May 16 13:46:36 2003 @@ -443,6 +443,7 @@ { struct group *grp; struct passwd *pw; + int gid; char **cur; /* make sure we have a valid usergroup, sudo style */ @@ -451,20 +452,21 @@ if ((grp = getgrnam(group)) == NULL) return(FALSE); + gid = grp->gr_gid; /* - * Check against user's real gid as well as group's user list + * Check against group's user list as well as user's real gid */ - if ((pw = getpwnam(user)) == NULL) - return(FALSE); - - if (grp->gr_gid == pw->pw_gid) - return(TRUE); - for (cur=grp->gr_mem; *cur; cur++) { if (strcmp(*cur, user) == 0) return(TRUE); } + + if ((pw = getpwnam(user)) == NULL) + return(FALSE); + + if (pw->pw_gid == gid) + return(TRUE); return(FALSE); }
It does seem kind of bogus that getpw*() and getgr*() use can clobber each other in the netgroup case but this may also affect systems other than FreeBSD. A similar patch will be included in the next sudo release.