From nobody@FreeBSD.org  Wed Jun  6 02:11:41 2007
Return-Path: <nobody@FreeBSD.org>
Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52])
	by hub.freebsd.org (Postfix) with ESMTP id 51FDA16A488
	for <freebsd-gnats-submit@FreeBSD.org>; Wed,  6 Jun 2007 02:11:41 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (www.freebsd.org [69.147.83.33])
	by mx1.freebsd.org (Postfix) with ESMTP id 2F8C013C46A
	for <freebsd-gnats-submit@FreeBSD.org>; Wed,  6 Jun 2007 02:11:41 +0000 (UTC)
	(envelope-from nobody@FreeBSD.org)
Received: from www.freebsd.org (localhost [127.0.0.1])
	by www.freebsd.org (8.13.1/8.13.1) with ESMTP id l562BeKY041215
	for <freebsd-gnats-submit@FreeBSD.org>; Wed, 6 Jun 2007 02:11:40 GMT
	(envelope-from nobody@www.freebsd.org)
Received: (from nobody@localhost)
	by www.freebsd.org (8.13.1/8.13.1/Submit) id l562Bear041214;
	Wed, 6 Jun 2007 02:11:40 GMT
	(envelope-from nobody)
Message-Id: <200706060211.l562Bear041214@www.freebsd.org>
Date: Wed, 6 Jun 2007 02:11:40 GMT
From: Dean Povey<dean.povey@quest.com>
To: freebsd-gnats-submit@FreeBSD.org
Subject: initgroups fails rather than truncates if number of groups > NGROUPS_MAX meaning the user can no longer login
X-Send-Pr-Version: www-3.0

>Number:         113398
>Category:       bin
>Synopsis:       [libc] [patch] initgroups fails rather than truncates if number of groups > NGROUPS_MAX meaning the user can no longer login
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    freebsd-bugs
>State:          closed
>Quarter:        
>Keywords:       
>Date-Required:  
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Wed Jun 06 02:20:01 GMT 2007
>Closed-Date:    Tue Mar 01 10:14:39 EST 2011
>Last-Modified:  Tue Mar 01 10:14:39 EST 2011
>Originator:     Dean Povey
>Release:        5.2 (although I believe this bug exists in the current HEAD)
>Organization:
Quest Software
>Environment:
>Description:
A few years ago a change was made to initgroups.c (revision 1.8 in CVS)
to have initgroups fail rather than silently truncate the number of groups
when more than NGROUPS_MAX is returned by getgrouplist.

While the reasoning behind this change is understandable back in the
pre-NSS days when you could reasonably assume all groups came from
/etc/groups this is not appropriate for NSS based systems which source
their group information from external sources such as LDAP.  In this
situation a change to the external directory to add an additional group
(which does not cause an issue on systems such as Linux or Solaris that
truncate), will mean that the user can no longer log in on FreeBSD.  The
specific scenario which this breaks is the case where an Active Directory
repository that is supplemented with Unix UID and GID information is
being used via NSS.  It is extremely common for the number of groups in
this scenario to exceed NGROUPS_MAX (typically 15 because of the NFS
restriction).




>How-To-Repeat:
1. Create a user and add them to more than 16 groups.
2. Attempt to login, login fails.

>Fix:
I believe the correct approach is to revert the change to initgroups.c
which added the error checking and allow the groups to be silently truncated.
Any applications that care whether the group list is truncated should be
calling getgrouplist first to determine the number of groups anyway and
then either logging this fact or failing.  This would make FreeBSD
consistent with Linux and Solaris (and I believe although have not
verified that HP-UX and AIX also work this way).

The only side-effect is that in cases where the group permissions on
objects are more restrictive than the "others" permission this could
lead to a security problem.  However, this is generally a bad idea anyway
because the behavior is not secure on other platforms.  It seems better
to recommend against this edge case than deny the user the ability to login.

>Release-Note:
>Audit-Trail:

From: "Dean Povey" <Dean.Povey@quest.com>
To: <bug-followup@FreeBSD.org>,
	"Dean Povey" <Dean.Povey@quest.com>
Cc:  
Subject: Re: misc/113398: initgroups fails rather than truncates if number of groups &gt; NGROUPS_MAX meaning the user can no longer login
Date: Wed, 6 Jun 2007 12:45:42 +1000

 Additional note: The following link documents the NGROUPS_MAX limit on
 various OS'es.  You can see that because FreeBSD has the most
 conservative limit this problem is very likely to be encountered in a
 heterogeneous UNIX environment where groups information is sourced from
 a common database (LDAP,NIS,Active Directory).
 
 http://j3e.de/ngroups.html
 
 

From: Denis Barov <dindin@yandex-team.ru>
To: bug-followup@FreeBSD.org, dean.povey@quest.com
Cc:  
Subject: Re: kern/113398: [libc] initgroups fails rather than truncates if number of groups > NGROUPS_MAX meaning the user can no longer login
Date: Wed, 27 Feb 2008 20:03:43 +0300

 This problem becomes hard trouble when using paw_winbind in large windows
 domain.
 
 Here patch to increase maximum groups to 64.
 
 in sys/user.h:
 
 #ifdef i386
 #define KINFO_PROC_SIZE 768
 #endif
 
 One groupe takes 32 bits of memory, 16 groups taking 4*16 bytes, 64 groups
 taking 4*64 then new KINFO_PROC_SIZE size is:
 768-(16*4)+(64*4) = 960 
 
 and same for other arcs
 
 
 -- 
 Cheers
 Denis Barov
 
 --- src/sys/sys/user.h  Fri Mar 16 09:35:27 2007
 +++ src/sys/sys/user.h  Fri Mar 16 09:29:08 2007
 @@ -86,16 +86,18 @@
  #define    KINFO_PROC_SIZE 1088
  #endif
  #ifdef __amd64__
 -#define    KINFO_PROC_SIZE 1088
 +#define    KINFO_PROC_SIZE 1280
 +//#define  KINFO_PROC_SIZE 1088
  #endif
  #ifdef __arm__
  #define    KINFO_PROC_SIZE 792
  #endif
  #ifdef __ia64__
 -#define    KINFO_PROC_SIZE 1088
 +#define    KINFO_PROC_SIZE 1280
 +//#define  KINFO_PROC_SIZE 1088
  #endif
  #ifdef __i386__
 -#define    KINFO_PROC_SIZE 768
 +#define    KINFO_PROC_SIZE 960
  #endif
  #ifdef __powerpc__
  #define    KINFO_PROC_SIZE 768
 @@ -112,7 +114,7 @@
  #define    OCOMMLEN    16      /* size of returned ki_ocomm name */
  #define    COMMLEN     19      /* size of returned ki_comm name */
  #define    KI_EMULNAMELEN  16      /* size of returned ki_emul */
 -#define    KI_NGROUPS  16      /* number of groups in ki_groups */
 +#define    KI_NGROUPS  64      /* number of groups in ki_groups */
  #define    LOGNAMELEN  17      /* size of returned ki_login */
 
  struct kinfo_proc {
 --- src/sys/sys/syslimits.h Fri Mar 16 00:41:57 2007
 +++ src/sys/sys/syslimits.h Mon Feb 26 21:23:12 2007
 @@ -58,7 +58,7 @@
  #define    MAX_CANON         255   /* max bytes in term canon input line */
  #define    MAX_INPUT         255   /* max bytes in terminal input */
  #define    NAME_MAX          255   /* max bytes in a file name */
 -#define    NGROUPS_MAX        16   /* max supplemental group id's */
 +#define    NGROUPS_MAX        64   /* max supplemental group id's */
  #ifndef OPEN_MAX
  #define    OPEN_MAX           64   /* max open files per process */
  #endif
 
 
 

From: Denis Barov <dindin@yandex-team.ru>
To: bug-followup@FreeBSD.org, dean.povey@quest.com
Cc:  
Subject: Re: kern/113398: [libc] initgroups fails rather than truncates if
Date: Wed, 27 Feb 2008 23:06:54 +0300

 number of groups > NGROUPS_MAX meaning the user can no longer login
 Message-ID: <20080227170342.GE1139@sepulca.yandex.ru>
 MIME-Version: 1.0
 Content-Type: text/plain; charset=koi8-r
 Content-Disposition: inline
 X-Operating-System: FreeBSD sepulca.yandex.ru 6.3-STABLE FreeBSD 6.3-STABLE
 User-Agent: mutt-ng/devel-r804 (FreeBSD)
 Status: RO
 
 I'm sorry, previous patch just  increases number of groups to 64, and this
 one fixes problem.
 
 -- 
 Cheers
 Denis Barov
 
 diff -urN src/lib/libc/gen/initgroups.c.orig src/lib/libc/gen/initgroups.c
 --- src/lib/libc/gen/initgroups.c.orig  2003-11-19 18:51:26.000000000 +0300
 +++ src/lib/libc/gen/initgroups.c   2008-02-27 22:59:13.000000000 +0300
 @@ -42,6 +42,7 @@
  #include <stdio.h>
  #include "namespace.h"
  #include <err.h>
 +#include <errno.h>
  #include "un-namespace.h"
  #include <unistd.h>
 
 @@ -51,6 +52,7 @@
     gid_t agroup;
  {
     int ngroups;
 +    int result;
     /*
      * Provide space for one group more than NGROUPS to allow
      * setgroups to fail and set errno.
 @@ -59,5 +61,10 @@
 
     ngroups = NGROUPS + 1;
     getgrouplist(uname, agroup, groups, &ngroups);
 -   return (setgroups(ngroups, groups));
 +
 +    do
 +      result = setgroups (ngroups, groups);
 +    while (result == -1 && errno == EINVAL && --ngroups > 0);
 +
 +   return result;
  }
 
 
 

From: ttw+bsd@cobbled.net
To: bug-followup@FreeBSD.org, dean.povey@quest.com
Cc: current@freebsd.org
Subject: Re: kern/113398: [libc] initgroups fails rather than
	truncates if number of groups &gt; NGROUPS_MAX meaning the user can no
	longer login
Date: Fri, 08 Aug 2008 14:12:44 +0100

 i'm using group accounts for restricting access to client's data and
 have been paralysed by this limitation for some time.  as such i've been
 looking to implement a more complete solution to this problem.  here are
 my issues with the current patch
 
 	1. doesn't actually solve the problem, only increases the
 	ceiling
 
 	2. increases kernel memory usage significantly as there are
 	a number of buffers initialised from these variables.  i.e.
 	if i want to increase it to 65534 then i'm using an additional
 	128k in each ki_info (per thread?) and ucred (per process?)
 	as well as other knock on effects
 
 	3. the above increase occurs even if i don't actually need
 	the space and i cannot therefore be conservative about needs
 	without impacting usage
 
 	4. mangles the current ki_info size and thus breaks binary
 	compatability
 
 	5. other unseen impacts e.g. will xucred still work as it's
 	now too big (appears not as the IPC stuff defines a CMGROUPS_MAX
 	for it's own purposes)
 
 	6. still no explict handling of IPC/NFS limit within an
 	application
 
 so i've done the following and would appreciate some feedback before i
 go completing these changes and submitting a complete patch.
 
 	1. defined two group limits SYS_NGROUPS which is the system
 	limit, currently this is 65534 (minus one for egid) as most
 	'ngroup' are 'int' and IPC_NGROUPS which will be fixed at 16
 
 	2. statically defined NGROUPS as 16 for compatibility.
 	applications should use sysconf/syscall to get
 	NGROUPS_MAX/KERN_NGROUPS instead
 
 	3. removed NGROUPS_MAX to force the use of sysconf/syscall or
 	sensible use of application dependant defaults
 
 	4. defined KERN_NGROUPS as a changable sysctl variable and
 	defined the initial value as IPC_NGROUPS
 
 	5. renamed the 'cr_groups' array to be 'cr_ipc_groups' and
 	defined it's length at IPC_NGROUPS.  then added a new pointer
 	after 'endcopy' with the name 'cr_groups' such that re-compiled
 	code will find the new group structure.  when the number of
 	groups is less than IPC_GROUPS the 'cr_groups' simply points
 	to the 'cr_ipc_groups'.
 
 	6. altered the 'kinfo' initialisation to copy the 'ipc_groups'
 	instead.  (may also use one of the 'kinfo' spare pointers to
 	copy the extended group)
 
 	7. removed KI_NGROUPS and defined ki_groups length as IPC_NGROUPS
 
 	8. removed CMGROUPS_MAX, using IPC_NGROUPS instead
 
 here are my current issues and need for feed back.
 
 	1. will the common use of 'cr_ngroups' defeat any attempt to
 	increase the use beyond 16?  it appears that "a lot"[tm] of
 	the code will 'MAX(cr_ngroups,NGROUPS)' correctly but i'm not
 	experienced enough to guess at the bigger picture
 
 	2. does the above juggling actually do anything productive;
 	i'm doing it to be conservative (i.e. keeping kinfo stuct
 	static) but will it actually save anything; considering the
 	alteration to the 'cred' stuct my guess is no.  i was thinking
 	to add the extended groups using the spare pointers in kinfo
 	but again, with this much change does it actually save anything?
 
 	3. i'm thinking that keeping the current group list and morphing
 	it into an ipc_group list so that we can manage the 'clipping'
 	of the group list in a consistent manner. e.g. add another
 	sysctl variable to clip the ipc_group list to just the egid
 	or do "smart shuffling" of the visible ipc_groups but again,
 	i'm less than confident this will positively impact the
 	situation.  is the definition of an 'ipc_groups' list of any
 	long term benefit?
 
 	4. as far as i'm concerned the use of NGROUPS at compile time
 	is wrong as it implies a limit to the number of groups below
 	that of available memory constraints.  i see no actual reason
 	to do that.  i do understand the argument of "restricting
 	access via group usage" but unless we stick to 16 groups that
 	is an issue we have to confront on NFS anyway.   am i failing
 	to understand NGROUPS fully?
 
 in summary, i'm pretty happy with the changes but i'm not confident
 that they are actually what's required.  perhaps i'd be better just
 redefining 'cr_groups' as a pointer and MALLOC'ing the space dynamically,
 at least then the use of 'cr_ngroups' is consistent.
 
 thanks for any feedback and apologies for the rambling considerations
 of this message.

From: dfilter@FreeBSD.ORG (dfilter service)
To: bug-followup@FreeBSD.org
Cc:  
Subject: Re: bin/113398: commit references a PR
Date: Fri, 19 Jun 2009 17:10:53 +0000 (UTC)

 Author: brooks
 Date: Fri Jun 19 17:10:35 2009
 New Revision: 194498
 URL: http://svn.freebsd.org/changeset/base/194498
 
 Log:
   Rework the credential code to support larger values of NGROUPS and
   NGROUPS_MAX, eliminate ABI dependencies on them, and raise the to 1024
   and 1023 respectively.  (Previously they were equal, but under a close
   reading of POSIX, NGROUPS_MAX was defined to be too large by 1 since it
   is the number of supplemental groups, not total number of groups.)
   
   The bulk of the change consists of converting the struct ucred member
   cr_groups from a static array to a pointer.  Do the equivalent in
   kinfo_proc.
   
   Introduce new interfaces crcopysafe() and crsetgroups() for duplicating
   a process credential before modifying it and for setting group lists
   respectively.  Both interfaces take care for the details of allocating
   groups array. crsetgroups() takes care of truncating the group list
   to the current maximum (NGROUPS) if necessary.  In the future,
   crsetgroups() may be responsible for insuring invariants such as sorting
   the supplemental groups to allow groupmember() to be implemented as a
   binary search.
   
   Because we can not change struct xucred without breaking application
   ABIs, we leave it alone and introduce a new XU_NGROUPS value which is
   always 16 and is to be used or NGRPS as appropriate for things such as
   NFS which need to use no more than 16 groups.  When feasible, truncate
   the group list rather than generating an error.
   
   Minor changes:
     - Reduce the number of hand rolled versions of groupmember().
     - Do not assign to both cr_gid and cr_groups[0].
     - Modify ipfw to cache ucreds instead of part of their contents since
       they are immutable once referenced by more than one entity.
   
   Submitted by:	Isilon Systems (initial implementation)
   X-MFC after:	never
   PR:		bin/113398 kern/133867
 
 Modified:
   head/UPDATING
   head/lib/libc/rpc/netname.c
   head/lib/libc/rpc/netnamer.c
   head/lib/libkvm/kvm_proc.c
   head/sys/compat/linux/linux_misc.c
   head/sys/compat/linux/linux_uid16.c
   head/sys/fs/nfs/nfs_commonport.c
   head/sys/fs/nfsclient/nfs_clport.c
   head/sys/fs/nfsserver/nfs_nfsdport.c
   head/sys/fs/nfsserver/nfs_nfsdstate.c
   head/sys/fs/portalfs/portal.h
   head/sys/fs/portalfs/portal_vnops.c
   head/sys/fs/unionfs/union_vnops.c
   head/sys/i386/ibcs2/ibcs2_misc.c
   head/sys/kern/kern_exec.c
   head/sys/kern/kern_proc.c
   head/sys/kern/kern_prot.c
   head/sys/kern/vfs_export.c
   head/sys/netinet/ipfw/ip_fw2.c
   head/sys/nfsserver/nfs_srvsock.c
   head/sys/nfsserver/nfs_srvsubs.c
   head/sys/rpc/rpcsec_gss/svc_rpcsec_gss.c
   head/sys/rpc/svc_auth.c
   head/sys/rpc/svc_auth_unix.c
   head/sys/sys/param.h
   head/sys/sys/syslimits.h
   head/sys/sys/ucred.h
   head/sys/sys/user.h
   head/sys/ufs/ufs/ufs_vnops.c
   head/usr.sbin/mount_portalfs/portald.h
   head/usr.sbin/mountd/mountd.c
 
 Modified: head/UPDATING
 ==============================================================================
 --- head/UPDATING	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/UPDATING	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -22,6 +22,23 @@ NOTE TO PEOPLE WHO THINK THAT FreeBSD 8.
  	to maximize performance.  (To disable malloc debugging, run
  	ln -s aj /etc/malloc.conf.)
  
 +20090619:
 +	NGROUPS_MAX and NGROUPS have been increased from 16 to 1023
 +	and 1024 respectively.  As long as no more than 16 groups per
 +	process are used, no changes should be visible.  When more
 +	than 16 groups are used, old binaries may fail if they call
 +	getgroups() or getgrouplist() with statically sized storage.
 +	Recompiling will work around this, but applications should be
 +	modified to use dynamically allocated storage for group arrays
 +	as POSIX.1-2008 does not cap an implementation's number of
 +	supported groups at NGROUPS_MAX+1 as previous versions did.
 +
 +	NFS and portalfs mounts may also be affected as the list of
 +	groups is truncated to 16.  Users of NFS who use more than 16
 +	groups, should take care that negative group permissions are not
 +	used on the exported file systems as they will not be reliable
 +	unless a GSSAPI based authentication method is used.
 +
  20090616:
  	The compiling option ADAPTIVE_LOCKMGRS has been introduced.
  	This option compiles in the support for adaptive spinning for lockmgrs
 
 Modified: head/lib/libc/rpc/netname.c
 ==============================================================================
 --- head/lib/libc/rpc/netname.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/lib/libc/rpc/netname.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -61,9 +61,6 @@ __FBSDID("$FreeBSD$");
  #ifndef MAXHOSTNAMELEN
  #define MAXHOSTNAMELEN 256
  #endif
 -#ifndef NGROUPS
 -#define NGROUPS 16
 -#endif
  
  #define TYPE_BIT(type)  (sizeof (type) * CHAR_BIT)
  
 
 Modified: head/lib/libc/rpc/netnamer.c
 ==============================================================================
 --- head/lib/libc/rpc/netnamer.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/lib/libc/rpc/netnamer.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -66,10 +66,6 @@ static char    *NETIDFILE = "/etc/netid"
  static int getnetid( char *, char * );
  static int _getgroups( char *, gid_t * );
  
 -#ifndef NGROUPS
 -#define NGROUPS 16
 -#endif
 -
  /*
   * Convert network-name into unix credential
   */
 @@ -104,7 +100,7 @@ netname2user(netname, uidp, gidp, gidlen
  			return (0);
  		}
  		*gidp = (gid_t) atol(p);
 -		for (gidlen = 0; gidlen < NGROUPS; gidlen++) {
 +		for (gidlen = 0; gidlen < NGRPS; gidlen++) {
  			p = strsep(&res, "\n,");
  			if (p == NULL)
  				break;
 @@ -157,7 +153,7 @@ netname2user(netname, uidp, gidp, gidlen
  static int
  _getgroups(uname, groups)
  	char           *uname;
 -	gid_t          groups[NGROUPS];
 +	gid_t          groups[NGRPS];
  {
  	gid_t           ngroups = 0;
  	struct group *grp;
 @@ -169,7 +165,7 @@ _getgroups(uname, groups)
  	while ((grp = getgrent())) {
  		for (i = 0; grp->gr_mem[i]; i++)
  			if (!strcmp(grp->gr_mem[i], uname)) {
 -				if (ngroups == NGROUPS) {
 +				if (ngroups == NGRPS) {
  #ifdef DEBUG
  					fprintf(stderr,
  				"initgroups: %s is in too many groups\n", uname);
 
 Modified: head/lib/libkvm/kvm_proc.c
 ==============================================================================
 --- head/lib/libkvm/kvm_proc.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/lib/libkvm/kvm_proc.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -146,8 +146,7 @@ kvm_proclist(kd, what, arg, p, bp, maxcn
  			kp->ki_rgid = ucred.cr_rgid;
  			kp->ki_svgid = ucred.cr_svgid;
  			kp->ki_ngroups = ucred.cr_ngroups;
 -			bcopy(ucred.cr_groups, kp->ki_groups,
 -			    NGROUPS * sizeof(gid_t));
 +			kp->ki_groups = ucred.cr_groups;
  			kp->ki_uid = ucred.cr_uid;
  			if (ucred.cr_prison != NULL) {
  				if (KREAD(kd, (u_long)ucred.cr_prison, &pr)) {
 
 Modified: head/sys/compat/linux/linux_misc.c
 ==============================================================================
 --- head/sys/compat/linux/linux_misc.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/compat/linux/linux_misc.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -1132,7 +1132,7 @@ int
  linux_setgroups(struct thread *td, struct linux_setgroups_args *args)
  {
  	struct ucred *newcred, *oldcred;
 -	l_gid_t linux_gidset[NGROUPS];
 +	l_gid_t *linux_gidset;
  	gid_t *bsd_gidset;
  	int ngrp, error;
  	struct proc *p;
 @@ -1140,13 +1140,14 @@ linux_setgroups(struct thread *td, struc
  	ngrp = args->gidsetsize;
  	if (ngrp < 0 || ngrp >= NGROUPS)
  		return (EINVAL);
 +	linux_gidset = malloc(ngrp * sizeof(*linux_gidset), M_TEMP, M_WAITOK);
  	error = copyin(args->grouplist, linux_gidset, ngrp * sizeof(l_gid_t));
  	if (error)
 -		return (error);
 +		goto out;
  	newcred = crget();
  	p = td->td_proc;
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  	/*
  	 * cr_groups[0] holds egid. Setting the whole set from
 @@ -1157,10 +1158,9 @@ linux_setgroups(struct thread *td, struc
  	if ((error = priv_check_cred(oldcred, PRIV_CRED_SETGROUPS, 0)) != 0) {
  		PROC_UNLOCK(p);
  		crfree(newcred);
 -		return (error);
 +		goto out;
  	}
  
 -	crcopy(newcred, oldcred);
  	if (ngrp > 0) {
  		newcred->cr_ngroups = ngrp + 1;
  
 @@ -1177,14 +1177,17 @@ linux_setgroups(struct thread *td, struc
  	p->p_ucred = newcred;
  	PROC_UNLOCK(p);
  	crfree(oldcred);
 -	return (0);
 +	error = 0;
 +out:
 +	free(linux_gidset, M_TEMP);
 +	return (error);
  }
  
  int
  linux_getgroups(struct thread *td, struct linux_getgroups_args *args)
  {
  	struct ucred *cred;
 -	l_gid_t linux_gidset[NGROUPS];
 +	l_gid_t *linux_gidset;
  	gid_t *bsd_gidset;
  	int bsd_gidsetsz, ngrp, error;
  
 @@ -1207,13 +1210,16 @@ linux_getgroups(struct thread *td, struc
  		return (EINVAL);
  
  	ngrp = 0;
 +	linux_gidset = malloc(bsd_gidsetsz * sizeof(*linux_gidset),
 +	    M_TEMP, M_WAITOK);
  	while (ngrp < bsd_gidsetsz) {
  		linux_gidset[ngrp] = bsd_gidset[ngrp + 1];
  		ngrp++;
  	}
  
 -	if ((error = copyout(linux_gidset, args->grouplist,
 -	    ngrp * sizeof(l_gid_t))))
 +	error = copyout(linux_gidset, args->grouplist, ngrp * sizeof(l_gid_t));
 +	free(linux_gidset, M_TEMP);
 +	if (error)
  		return (error);
  
  	td->td_retval[0] = ngrp;
 
 Modified: head/sys/compat/linux/linux_uid16.c
 ==============================================================================
 --- head/sys/compat/linux/linux_uid16.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/compat/linux/linux_uid16.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -98,7 +98,7 @@ int
  linux_setgroups16(struct thread *td, struct linux_setgroups16_args *args)
  {
  	struct ucred *newcred, *oldcred;
 -	l_gid16_t linux_gidset[NGROUPS];
 +	l_gid16_t *linux_gidset;
  	gid_t *bsd_gidset;
  	int ngrp, error;
  	struct proc *p;
 @@ -111,13 +111,14 @@ linux_setgroups16(struct thread *td, str
  	ngrp = args->gidsetsize;
  	if (ngrp < 0 || ngrp >= NGROUPS)
  		return (EINVAL);
 +	linux_gidset = malloc(ngrp * sizeof(*linux_gidset), M_TEMP, M_WAITOK);
  	error = copyin(args->gidset, linux_gidset, ngrp * sizeof(l_gid16_t));
  	if (error)
  		return (error);
  	newcred = crget();
  	p = td->td_proc;
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  	/*
  	 * cr_groups[0] holds egid. Setting the whole set from
 @@ -128,10 +129,9 @@ linux_setgroups16(struct thread *td, str
  	if ((error = priv_check_cred(oldcred, PRIV_CRED_SETGROUPS, 0)) != 0) {
  		PROC_UNLOCK(p);
  		crfree(newcred);
 -		return (error);
 +		goto out;
  	}
  
 -	crcopy(newcred, oldcred);
  	if (ngrp > 0) {
  		newcred->cr_ngroups = ngrp + 1;
  
 @@ -149,14 +149,17 @@ linux_setgroups16(struct thread *td, str
  	p->p_ucred = newcred;
  	PROC_UNLOCK(p);
  	crfree(oldcred);
 -	return (0);
 +	error = 0;
 +out:
 +	free(linux_gidset, M_TEMP);
 +	return (error);
  }
  
  int
  linux_getgroups16(struct thread *td, struct linux_getgroups16_args *args)
  {
  	struct ucred *cred;
 -	l_gid16_t linux_gidset[NGROUPS];
 +	l_gid16_t *linux_gidset;
  	gid_t *bsd_gidset;
  	int bsd_gidsetsz, ngrp, error;
  
 @@ -184,12 +187,15 @@ linux_getgroups16(struct thread *td, str
  		return (EINVAL);
  
  	ngrp = 0;
 +	linux_gidset = malloc(bsd_gidsetsz * sizeof(*linux_gidset),
 +	    M_TEMP, M_WAITOK);
  	while (ngrp < bsd_gidsetsz) {
  		linux_gidset[ngrp] = bsd_gidset[ngrp + 1];
  		ngrp++;
  	}
  
  	error = copyout(linux_gidset, args->gidset, ngrp * sizeof(l_gid16_t));
 +	free(linux_gidset, M_TEMP);
  	if (error)
  		return (error);
  
 
 Modified: head/sys/fs/nfs/nfs_commonport.c
 ==============================================================================
 --- head/sys/fs/nfs/nfs_commonport.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/fs/nfs/nfs_commonport.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -220,14 +220,9 @@ nfsrv_lookupfilename(struct nameidata *n
  void
  newnfs_copycred(struct nfscred *nfscr, struct ucred *cr)
  {
 -	int ngroups, i;
  
  	cr->cr_uid = nfscr->nfsc_uid;
 -	ngroups = (nfscr->nfsc_ngroups < NGROUPS) ?
 -	    nfscr->nfsc_ngroups : NGROUPS;
 -	for (i = 0; i < ngroups; i++)
 -		cr->cr_groups[i] = nfscr->nfsc_groups[i];
 -	cr->cr_ngroups = ngroups;
 +	crsetgroups(cr, nfscr->nfsc_ngroups, nfscr->nfsc_groups);
  }
  
  /*
 
 Modified: head/sys/fs/nfsclient/nfs_clport.c
 ==============================================================================
 --- head/sys/fs/nfsclient/nfs_clport.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/fs/nfsclient/nfs_clport.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -976,14 +976,12 @@ nfscl_getmyip(struct nfsmount *nmp, int 
  void
  newnfs_copyincred(struct ucred *cr, struct nfscred *nfscr)
  {
 -	int ngroups, i;
 +	int i;
  
  	nfscr->nfsc_uid = cr->cr_uid;
 -	ngroups = (cr->cr_ngroups > NGROUPS) ? NGROUPS :
 -	    cr->cr_ngroups;
 -	for (i = 0; i < ngroups; i++)
 +	nfscr->nfsc_ngroups = MIN(cr->cr_ngroups, XU_NGROUPS);
 +	for (i = 0; i < nfscr->nfsc_ngroups; i++)
  		nfscr->nfsc_groups[i] = cr->cr_groups[i];
 -	nfscr->nfsc_ngroups = ngroups;
  }
  
  
 
 Modified: head/sys/fs/nfsserver/nfs_nfsdport.c
 ==============================================================================
 --- head/sys/fs/nfsserver/nfs_nfsdport.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/fs/nfsserver/nfs_nfsdport.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -2360,7 +2360,6 @@ int
  nfsd_excred(struct nfsrv_descript *nd, struct nfsexstuff *exp,
      struct ucred *credanon)
  {
 -	int i;
  	int error = 0;
  
  	/*
 @@ -2403,9 +2402,8 @@ nfsd_excred(struct nfsrv_descript *nd, s
  	     (nd->nd_flag & ND_AUTHNONE))) {
  		nd->nd_cred->cr_uid = credanon->cr_uid;
  		nd->nd_cred->cr_gid = credanon->cr_gid;
 -		for (i = 0; i < credanon->cr_ngroups && i < NGROUPS; i++)
 -			nd->nd_cred->cr_groups[i] = credanon->cr_groups[i];
 -		nd->nd_cred->cr_ngroups = i;
 +		crsetgroups(nd->nd_cred, credanon->cr_ngroups,
 +		    credanon->cr_groups);
  	}
  	return (0);
  }
 
 Modified: head/sys/fs/nfsserver/nfs_nfsdstate.c
 ==============================================================================
 --- head/sys/fs/nfsserver/nfs_nfsdstate.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/fs/nfsserver/nfs_nfsdstate.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -3577,7 +3577,6 @@ nfsrv_docallback(struct nfsclient *clp, 
  	nd->nd_repstat = 0;
  	cred->cr_uid = clp->lc_uid;
  	cred->cr_gid = clp->lc_gid;
 -	cred->cr_groups[0] = clp->lc_gid;
  	callback = clp->lc_callback;
  	NFSUNLOCKSTATE();
  	cred->cr_ngroups = 1;
 
 Modified: head/sys/fs/portalfs/portal.h
 ==============================================================================
 --- head/sys/fs/portalfs/portal.h	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/fs/portalfs/portal.h	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -43,7 +43,7 @@ struct portal_cred {
  	int		pcr_flag;		/* File open mode */
  	uid_t		pcr_uid;		/* From ucred */
  	short		pcr_ngroups;		/* From ucred */
 -	gid_t		pcr_groups[NGROUPS];	/* From ucred */
 +	gid_t		pcr_groups[XU_NGROUPS];	/* From ucred */
  };
  
  #ifdef _KERNEL
 
 Modified: head/sys/fs/portalfs/portal_vnops.c
 ==============================================================================
 --- head/sys/fs/portalfs/portal_vnops.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/fs/portalfs/portal_vnops.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -311,8 +311,9 @@ portal_open(ap)
  
  	pcred.pcr_flag = ap->a_mode;
  	pcred.pcr_uid = ap->a_cred->cr_uid;
 -	pcred.pcr_ngroups = ap->a_cred->cr_ngroups;
 -	bcopy(ap->a_cred->cr_groups, pcred.pcr_groups, NGROUPS * sizeof(gid_t));
 +	pcred.pcr_ngroups = MIN(ap->a_cred->cr_ngroups, XU_NGROUPS);
 +	bcopy(ap->a_cred->cr_groups, pcred.pcr_groups,
 +	    pcred.pcr_ngroups * sizeof(gid_t));
  	aiov[0].iov_base = (caddr_t) &pcred;
  	aiov[0].iov_len = sizeof(pcred);
  	aiov[1].iov_base = pt->pt_arg;
 
 Modified: head/sys/fs/unionfs/union_vnops.c
 ==============================================================================
 --- head/sys/fs/unionfs/union_vnops.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/fs/unionfs/union_vnops.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -638,7 +638,6 @@ unionfs_check_corrected_access(accmode_t
  	uid_t		uid;	/* upper side vnode's uid */
  	gid_t		gid;	/* upper side vnode's gid */
  	u_short		vmode;	/* upper side vnode's mode */
 -	gid_t          *gp;
  	u_short		mask;
  
  	mask = 0;
 @@ -659,17 +658,14 @@ unionfs_check_corrected_access(accmode_t
  
  	/* check group */
  	count = 0;
 -	gp = cred->cr_groups;
 -	for (; count < cred->cr_ngroups; count++, gp++) {
 -		if (gid == *gp) {
 -			if (accmode & VEXEC)
 -				mask |= S_IXGRP;
 -			if (accmode & VREAD)
 -				mask |= S_IRGRP;
 -			if (accmode & VWRITE)
 -				mask |= S_IWGRP;
 -			return ((vmode & mask) == mask ? 0 : EACCES);
 -		}
 +	if (groupmember(gid, cred)) {
 +		if (accmode & VEXEC)
 +			mask |= S_IXGRP;
 +		if (accmode & VREAD)
 +			mask |= S_IRGRP;
 +		if (accmode & VWRITE)
 +			mask |= S_IWGRP;
 +		return ((vmode & mask) == mask ? 0 : EACCES);
  	}
  
  	/* check other */
 
 Modified: head/sys/i386/ibcs2/ibcs2_misc.c
 ==============================================================================
 --- head/sys/i386/ibcs2/ibcs2_misc.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/i386/ibcs2/ibcs2_misc.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -657,24 +657,29 @@ ibcs2_getgroups(td, uap)
  	struct thread *td;
  	struct ibcs2_getgroups_args *uap;
  {
 -	ibcs2_gid_t iset[NGROUPS_MAX];
 -	gid_t gp[NGROUPS_MAX];
 +	ibcs2_gid_t *iset;
 +	gid_t *gp;
  	u_int i, ngrp;
  	int error;
  
  	if (uap->gidsetsize < 0)
  		return (EINVAL);
  	ngrp = MIN(uap->gidsetsize, NGROUPS_MAX);
 +	gp = malloc(ngrp * sizeof(*gp), M_TEMP, M_WAITOK);
  	error = kern_getgroups(td, &ngrp, gp);
  	if (error)
 -		return (error);
 +		goto out;
  	if (uap->gidsetsize > 0) {
 +		iset = malloc(ngrp * sizeof(*iset), M_TEMP, M_WAITOK);
  		for (i = 0; i < ngrp; i++)
  			iset[i] = (ibcs2_gid_t)gp[i];
  		error = copyout(iset, uap->gidset, ngrp * sizeof(ibcs2_gid_t));
 +		free(iset, M_TEMP);
  	}
  	if (error == 0)
  		td->td_retval[0] = ngrp;
 +out:
 +	free(gp, M_TEMP);
  	return (error);
  }
  
 @@ -683,21 +688,31 @@ ibcs2_setgroups(td, uap)
  	struct thread *td;
  	struct ibcs2_setgroups_args *uap;
  {
 -	ibcs2_gid_t iset[NGROUPS_MAX];
 -	gid_t gp[NGROUPS_MAX];
 +	ibcs2_gid_t *iset;
 +	gid_t *gp;
  	int error, i;
  
  	if (uap->gidsetsize < 0 || uap->gidsetsize > NGROUPS_MAX)
  		return (EINVAL);
 -	if (uap->gidsetsize && uap->gidset) {
 +	if (uap->gidsetsize && uap->gidset == NULL)
 +		return (EINVAL);
 +	gp = malloc(uap->gidsetsize * sizeof(*gp), M_TEMP, M_WAITOK);
 +	if (uap->gidsetsize) {
 +		iset = malloc(uap->gidsetsize * sizeof(*iset), M_TEMP, M_WAITOK);
  		error = copyin(uap->gidset, iset, sizeof(ibcs2_gid_t) *
  		    uap->gidsetsize);
 -		if (error)
 -			return (error);
 +		if (error) {
 +			free(iset, M_TEMP);
 +			goto out;
 +		}
  		for (i = 0; i < uap->gidsetsize; i++)
  			gp[i] = (gid_t)iset[i];
  	}
 -	return (kern_setgroups(td, uap->gidsetsize, gp));
 +
 +	error = kern_setgroups(td, uap->gidsetsize, gp);
 +out:
 +	free(gp, M_TEMP);
 +	return (error);
  }
  
  int
 
 Modified: head/sys/kern/kern_exec.c
 ==============================================================================
 --- head/sys/kern/kern_exec.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/kern/kern_exec.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -579,6 +579,7 @@ interpret:
  	 * reset.
  	 */
  	PROC_LOCK(p);
 +	oldcred = crcopysafe(p, newcred);
  	if (sigacts_shared(p->p_sigacts)) {
  		oldsigacts = p->p_sigacts;
  		PROC_UNLOCK(p);
 @@ -629,7 +630,6 @@ interpret:
  	 * XXXMAC: For the time being, use NOSUID to also prohibit
  	 * transitions on the file system.
  	 */
 -	oldcred = p->p_ucred;
  	credential_changing = 0;
  	credential_changing |= (attr.va_mode & S_ISUID) && oldcred->cr_uid !=
  	    attr.va_uid;
 @@ -683,7 +683,6 @@ interpret:
  		/*
  		 * Set the new credentials.
  		 */
 -		crcopy(newcred, oldcred);
  		if (attr.va_mode & S_ISUID)
  			change_euid(newcred, euip);
  		if (attr.va_mode & S_ISGID)
 @@ -723,7 +722,6 @@ interpret:
  		 */
  		if (oldcred->cr_svuid != oldcred->cr_uid ||
  		    oldcred->cr_svgid != oldcred->cr_gid) {
 -			crcopy(newcred, oldcred);
  			change_svuid(newcred, newcred->cr_uid);
  			change_svgid(newcred, newcred->cr_gid);
  			p->p_ucred = newcred;
 
 Modified: head/sys/kern/kern_proc.c
 ==============================================================================
 --- head/sys/kern/kern_proc.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/kern/kern_proc.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -730,10 +730,8 @@ fill_kinfo_proc_only(struct proc *p, str
  		kp->ki_uid = cred->cr_uid;
  		kp->ki_ruid = cred->cr_ruid;
  		kp->ki_svuid = cred->cr_svuid;
 -		/* XXX bde doesn't like KI_NGROUPS */
 -		kp->ki_ngroups = min(cred->cr_ngroups, KI_NGROUPS);
 -		bcopy(cred->cr_groups, kp->ki_groups,
 -		    kp->ki_ngroups * sizeof(gid_t));
 +		kp->ki_ngroups = cred->cr_ngroups;
 +		kp->ki_groups = cred->cr_groups;
  		kp->ki_rgid = cred->cr_rgid;
  		kp->ki_svgid = cred->cr_svgid;
  		kp->ki_cr_flags = cred->cr_flags;
 
 Modified: head/sys/kern/kern_prot.c
 ==============================================================================
 --- head/sys/kern/kern_prot.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/kern/kern_prot.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -82,6 +82,11 @@ static MALLOC_DEFINE(M_CRED, "cred", "cr
  
  SYSCTL_NODE(_security, OID_AUTO, bsd, CTLFLAG_RW, 0, "BSD security policy");
  
 +static void crextend(struct ucred *cr, int n);
 +static void crsetgroups_locked(struct ucred *cr, int ngrp,
 +    gid_t *groups);
 +
 +
  #ifndef _SYS_SYSPROTO_H_
  struct getpid_args {
  	int	dummy;
 @@ -276,18 +281,21 @@ struct getgroups_args {
  int
  getgroups(struct thread *td, register struct getgroups_args *uap)
  {
 -	gid_t groups[NGROUPS];
 +	gid_t *groups;
  	u_int ngrp;
  	int error;
  
  	ngrp = MIN(uap->gidsetsize, NGROUPS);
 +	groups = malloc(ngrp * sizeof(*groups), M_TEMP, M_WAITOK);
  	error = kern_getgroups(td, &ngrp, groups);
  	if (error)
 -		return (error);
 +		goto out;
  	if (uap->gidsetsize > 0)
  		error = copyout(groups, uap->gidset, ngrp * sizeof(gid_t));
  	if (error == 0)
  		td->td_retval[0] = ngrp;
 +out:
 +	free(groups, M_TEMP);
  	return (error);
  }
  
 @@ -486,7 +494,10 @@ setuid(struct thread *td, struct setuid_
  	newcred = crget();
  	uip = uifind(uid);
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	/*
 +	 * Copy credentials so other references do not see our changes.
 +	 */
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_setuid(oldcred, uid);
 @@ -521,10 +532,6 @@ setuid(struct thread *td, struct setuid_
  	    (error = priv_check_cred(oldcred, PRIV_CRED_SETUID, 0)) != 0)
  		goto fail;
  
 -	/*
 -	 * Copy credentials so other references do not see our changes.
 -	 */
 -	crcopy(newcred, oldcred);
  #ifdef _POSIX_SAVED_IDS
  	/*
  	 * Do we have "appropriate privileges" (are we root or uid == euid)
 @@ -598,7 +605,10 @@ seteuid(struct thread *td, struct seteui
  	newcred = crget();
  	euip = uifind(euid);
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	/*
 +	 * Copy credentials so other references do not see our changes.
 +	 */
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_seteuid(oldcred, euid);
 @@ -612,8 +622,7 @@ seteuid(struct thread *td, struct seteui
  		goto fail;
  
  	/*
 -	 * Everything's okay, do it.  Copy credentials so other references do
 -	 * not see our changes.
 +	 * Everything's okay, do it.
  	 */
  	crcopy(newcred, oldcred);
  	if (oldcred->cr_uid != euid) {
 @@ -651,7 +660,7 @@ setgid(struct thread *td, struct setgid_
  	AUDIT_ARG(gid, gid);
  	newcred = crget();
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_setgid(oldcred, gid);
 @@ -680,7 +689,6 @@ setgid(struct thread *td, struct setgid_
  	    (error = priv_check_cred(oldcred, PRIV_CRED_SETGID, 0)) != 0)
  		goto fail;
  
 -	crcopy(newcred, oldcred);
  #ifdef _POSIX_SAVED_IDS
  	/*
  	 * Do we have "appropriate privileges" (are we root or gid == egid)
 @@ -750,7 +758,7 @@ setegid(struct thread *td, struct setegi
  	AUDIT_ARG(egid, egid);
  	newcred = crget();
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_setegid(oldcred, egid);
 @@ -763,7 +771,6 @@ setegid(struct thread *td, struct setegi
  	    (error = priv_check_cred(oldcred, PRIV_CRED_SETEGID, 0)) != 0)
  		goto fail;
  
 -	crcopy(newcred, oldcred);
  	if (oldcred->cr_groups[0] != egid) {
  		change_egid(newcred, egid);
  		setsugid(p);
 @@ -789,15 +796,19 @@ struct setgroups_args {
  int
  setgroups(struct thread *td, struct setgroups_args *uap)
  {
 -	gid_t groups[NGROUPS];
 +	gid_t *groups = NULL;
  	int error;
  
  	if (uap->gidsetsize > NGROUPS)
  		return (EINVAL);
 +	groups = malloc(uap->gidsetsize * sizeof(gid_t), M_TEMP, M_WAITOK);
  	error = copyin(uap->gidset, groups, uap->gidsetsize * sizeof(gid_t));
  	if (error)
 -		return (error);
 -	return (kern_setgroups(td, uap->gidsetsize, groups));
 +		goto out;
 +	error = kern_setgroups(td, uap->gidsetsize, groups);
 +out:
 +	free(groups, M_TEMP);
 +	return (error);
  }
  
  int
 @@ -811,8 +822,9 @@ kern_setgroups(struct thread *td, u_int 
  		return (EINVAL);
  	AUDIT_ARG(groupset, groups, ngrp);
  	newcred = crget();
 +	crextend(newcred, ngrp);
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_setgroups(oldcred, ngrp, groups);
 @@ -824,11 +836,6 @@ kern_setgroups(struct thread *td, u_int 
  	if (error)
  		goto fail;
  
 -	/*
 -	 * XXX A little bit lazy here.  We could test if anything has
 -	 * changed before crcopy() and setting P_SUGID.
 -	 */
 -	crcopy(newcred, oldcred);
  	if (ngrp < 1) {
  		/*
  		 * setgroups(0, NULL) is a legitimate way of clearing the
 @@ -838,8 +845,7 @@ kern_setgroups(struct thread *td, u_int 
  		 */
  		newcred->cr_ngroups = 1;
  	} else {
 -		bcopy(groups, newcred->cr_groups, ngrp * sizeof(gid_t));
 -		newcred->cr_ngroups = ngrp;
 +		crsetgroups_locked(newcred, ngrp, groups);
  	}
  	setsugid(p);
  	p->p_ucred = newcred;
 @@ -877,7 +883,7 @@ setreuid(register struct thread *td, str
  	euip = uifind(euid);
  	ruip = uifind(ruid);
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_setreuid(oldcred, ruid, euid);
 @@ -892,7 +898,6 @@ setreuid(register struct thread *td, str
  	    (error = priv_check_cred(oldcred, PRIV_CRED_SETREUID, 0)) != 0)
  		goto fail;
  
 -	crcopy(newcred, oldcred);
  	if (euid != (uid_t)-1 && oldcred->cr_uid != euid) {
  		change_euid(newcred, euip);
  		setsugid(p);
 @@ -942,7 +947,7 @@ setregid(register struct thread *td, str
  	AUDIT_ARG(rgid, rgid);
  	newcred = crget();
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_setregid(oldcred, rgid, egid);
 @@ -957,7 +962,6 @@ setregid(register struct thread *td, str
  	    (error = priv_check_cred(oldcred, PRIV_CRED_SETREGID, 0)) != 0)
  		goto fail;
  
 -	crcopy(newcred, oldcred);
  	if (egid != (gid_t)-1 && oldcred->cr_groups[0] != egid) {
  		change_egid(newcred, egid);
  		setsugid(p);
 @@ -1013,7 +1017,7 @@ setresuid(register struct thread *td, st
  	euip = uifind(euid);
  	ruip = uifind(ruid);
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_setresuid(oldcred, ruid, euid, suid);
 @@ -1033,7 +1037,6 @@ setresuid(register struct thread *td, st
  	    (error = priv_check_cred(oldcred, PRIV_CRED_SETRESUID, 0)) != 0)
  		goto fail;
  
 -	crcopy(newcred, oldcred);
  	if (euid != (uid_t)-1 && oldcred->cr_uid != euid) {
  		change_euid(newcred, euip);
  		setsugid(p);
 @@ -1090,7 +1093,7 @@ setresgid(register struct thread *td, st
  	AUDIT_ARG(sgid, sgid);
  	newcred = crget();
  	PROC_LOCK(p);
 -	oldcred = p->p_ucred;
 +	oldcred = crcopysafe(p, newcred);
  
  #ifdef MAC
  	error = mac_cred_check_setresgid(oldcred, rgid, egid, sgid);
 @@ -1110,7 +1113,6 @@ setresgid(register struct thread *td, st
  	    (error = priv_check_cred(oldcred, PRIV_CRED_SETRESGID, 0)) != 0)
  		goto fail;
  
 -	crcopy(newcred, oldcred);
  	if (egid != (gid_t)-1 && oldcred->cr_groups[0] != egid) {
  		change_egid(newcred, egid);
  		setsugid(p);
 @@ -1780,6 +1782,7 @@ crget(void)
  #ifdef MAC
  	mac_cred_init(cr);
  #endif
 +	crextend(cr, XU_NGROUPS);
  	return (cr);
  }
  
 @@ -1829,6 +1832,7 @@ crfree(struct ucred *cr)
  #ifdef MAC
  		mac_cred_destroy(cr);
  #endif
 +		free(cr->cr_groups, M_CRED);
  		free(cr, M_CRED);
  	}
  }
 @@ -1854,6 +1858,7 @@ crcopy(struct ucred *dest, struct ucred 
  	bcopy(&src->cr_startcopy, &dest->cr_startcopy,
  	    (unsigned)((caddr_t)&src->cr_endcopy -
  		(caddr_t)&src->cr_startcopy));
 +	crsetgroups(dest, src->cr_ngroups, src->cr_groups);
  	uihold(dest->cr_uidinfo);
  	uihold(dest->cr_ruidinfo);
  	prison_hold(dest->cr_prison);
 @@ -1888,12 +1893,16 @@ crdup(struct ucred *cr)
  void
  cru2x(struct ucred *cr, struct xucred *xcr)
  {
 +	int ngroups;
  
  	bzero(xcr, sizeof(*xcr));
  	xcr->cr_version = XUCRED_VERSION;
  	xcr->cr_uid = cr->cr_uid;
 -	xcr->cr_ngroups = cr->cr_ngroups;
 -	bcopy(cr->cr_groups, xcr->cr_groups, sizeof(cr->cr_groups));
 +
 +	ngroups = MIN(cr->cr_ngroups, XU_NGROUPS);
 +	xcr->cr_ngroups = ngroups;
 +	bcopy(cr->cr_groups, xcr->cr_groups,
 +	    ngroups * sizeof(*cr->cr_groups));
  }
  
  /*
 @@ -1915,6 +1924,97 @@ cred_update_thread(struct thread *td)
  		crfree(cred);
  }
  
 +struct ucred *
 +crcopysafe(struct proc *p, struct ucred *cr)
 +{
 +	struct ucred *oldcred;
 +	int groups;
 +
 +	PROC_LOCK_ASSERT(p, MA_OWNED);
 +
 +	oldcred = p->p_ucred;
 +	while (cr->cr_agroups < oldcred->cr_agroups) {
 +		groups = oldcred->cr_agroups;
 +		PROC_UNLOCK(p);
 +		crextend(cr, groups);
 +		PROC_LOCK(p);
 +		oldcred = p->p_ucred;
 +	}
 +	crcopy(cr, oldcred);
 +
 +	return (oldcred);
 +}
 +
 +/*
 + * Extend the passed in credential to hold n items.
 + */
 +static void
 +crextend(struct ucred *cr, int n)
 +{
 +	int cnt;
 +
 +	/* Truncate? */
 +	if (n <= cr->cr_agroups)
 +		return;
 +
 +	/*
 +	 * We extend by 2 each time since we're using a power of two
 +	 * allocator until we need enough groups to fill a page.
 +	 * Once we're allocating multiple pages, only allocate as many
 +	 * as we actually need.  The case of processes needing a
 +	 * non-power of two number of pages seems more likely than
 +	 * a real world process that adds thousands of groups one at a
 +	 * time.
 +	 */
 +	if ( n < PAGE_SIZE / sizeof(gid_t) ) {
 +		if (cr->cr_agroups == 0)
 +			cnt = MINALLOCSIZE / sizeof(gid_t);
 +		else
 +			cnt = cr->cr_agroups * 2;
 +
 +		while (cnt < n)
 +			cnt *= 2;
 +	} else
 +		cnt = roundup2(n, PAGE_SIZE / sizeof(gid_t));
 +
 +	/* Free the old array. */
 +	if (cr->cr_groups)
 +		free(cr->cr_groups, M_CRED);
 +
 +	cr->cr_groups = malloc(cnt * sizeof(gid_t), M_CRED, M_WAITOK | M_ZERO);
 +	cr->cr_agroups = cnt;
 +}
 +
 +/*
 + * Copy groups in to a credential, preserving any necessicary invariants
 + * (i.e. sorting in the future).  crextend() must have been called
 + * before hand to ensure sufficient space is available.  If 
 + */
 +static void
 +crsetgroups_locked(struct ucred *cr, int ngrp, gid_t *groups)
 +{
 +	
 +	KASSERT(cr->cr_agroups >= ngrp, ("cr_ngroups is too small"));
 +
 +	bcopy(groups, cr->cr_groups, ngrp * sizeof(gid_t));
 +	cr->cr_ngroups = ngrp;
 +}
 +
 +/*
 + * Copy groups in to a credential after expanding it if required.
 + * Truncate the list to NGROUPS if it is too large.
 + */
 +void
 +crsetgroups(struct ucred *cr, int ngrp, gid_t *groups)
 +{
 +
 +	if (ngrp > NGROUPS)
 +		ngrp = NGROUPS;
 +
 +	crextend(cr, ngrp);
 +	crsetgroups_locked(cr, ngrp, groups);
 +}
 +
  /*
   * Get login name, if available.
   */
 
 Modified: head/sys/kern/vfs_export.c
 ==============================================================================
 --- head/sys/kern/vfs_export.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/kern/vfs_export.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -120,9 +120,8 @@ vfs_hang_addrlist(struct mount *mp, stru
  		np->netc_exflags = argp->ex_flags;
  		np->netc_anon = crget();
  		np->netc_anon->cr_uid = argp->ex_anon.cr_uid;
 -		np->netc_anon->cr_ngroups = argp->ex_anon.cr_ngroups;
 -		bcopy(argp->ex_anon.cr_groups, np->netc_anon->cr_groups,
 -		    sizeof(np->netc_anon->cr_groups));
 +		crsetgroups(np->netc_anon, argp->ex_anon.cr_ngroups,
 +		    argp->ex_anon.cr_groups);
  		np->netc_numsecflavors = argp->ex_numsecflavors;
  		bcopy(argp->ex_secflavors, np->netc_secflavors,
  		    sizeof(np->netc_secflavors));
 @@ -205,9 +204,8 @@ vfs_hang_addrlist(struct mount *mp, stru
  	np->netc_exflags = argp->ex_flags;
  	np->netc_anon = crget();
  	np->netc_anon->cr_uid = argp->ex_anon.cr_uid;
 -	np->netc_anon->cr_ngroups = argp->ex_anon.cr_ngroups;
 -	bcopy(argp->ex_anon.cr_groups, np->netc_anon->cr_groups,
 -	    sizeof(np->netc_anon->cr_groups));
 +	crsetgroups(np->netc_anon, argp->ex_anon.cr_ngroups,
 +	    np->netc_anon->cr_groups);
  	np->netc_numsecflavors = argp->ex_numsecflavors;
  	bcopy(argp->ex_secflavors, np->netc_secflavors,
  	    sizeof(np->netc_secflavors));
 
 Modified: head/sys/netinet/ipfw/ip_fw2.c
 ==============================================================================
 --- head/sys/netinet/ipfw/ip_fw2.c	Fri Jun 19 17:07:38 2009	(r194497)
 +++ head/sys/netinet/ipfw/ip_fw2.c	Fri Jun 19 17:10:35 2009	(r194498)
 @@ -135,19 +135,6 @@ static uma_zone_t ipfw_dyn_rule_zone;
  struct ip_fw *ip_fw_default_rule;
  
  /*
 - * Data structure to cache our ucred related
 - * information. This structure only gets used if
 - * the user specified UID/GID based constraints in
 - * a firewall rule.
 - */
 -struct ip_fw_ugid {
 -	gid_t		fw_groups[NGROUPS];
 -	int		fw_ngroups;
 -	uid_t		fw_uid;
 -	int		fw_prid;
 -};
 -
 -/*
   * list of rules for layer 3
   */
  #ifdef VIMAGE_GLOBALS
 @@ -2009,22 +1996,10 @@ dump_table(struct ip_fw_chain *ch, ipfw_
  	return (0);
  }
  
 -static void
 -fill_ugid_cache(struct inpcb *inp, struct ip_fw_ugid *ugp)
 -{
 -	struct ucred *cr;
 -
 -	cr = inp->inp_cred;
 -	ugp->fw_prid = jailed(cr) ? cr->cr_prison->pr_id : -1;
 -	ugp->fw_uid = cr->cr_uid;
 -	ugp->fw_ngroups = cr->cr_ngroups;
 -	bcopy(cr->cr_groups, ugp->fw_groups, sizeof(ugp->fw_groups));
 -}
 -
  static int
  check_uidgid(ipfw_insn_u32 *insn, int proto, struct ifnet *oif,
      struct in_addr dst_ip, u_int16_t dst_port, struct in_addr src_ip,
 -    u_int16_t src_port, struct ip_fw_ugid *ugp, int *ugid_lookupp,
 +    u_int16_t src_port, struct ucred **uc, int *ugid_lookupp,
      struct inpcb *inp)
  {
  	INIT_VNET_INET(curvnet);
 @@ -2032,7 +2007,6 @@ check_uidgid(ipfw_insn_u32 *insn, int pr
  	int wildcard;
  	struct inpcb *pcb;
  	int match;
 -	gid_t *gp;
  
  	/*
  	 * Check to see if the UDP or TCP stack supplied us with
 @@ -2042,7 +2016,7 @@ check_uidgid(ipfw_insn_u32 *insn, int pr
  	if (inp && *ugid_lookupp == 0) {
  		INP_LOCK_ASSERT(inp);
  		if (inp->inp_socket != NULL) {
 -			fill_ugid_cache(inp, ugp);
 +			*uc = crhold(inp->inp_cred);
  			*ugid_lookupp = 1;
  		} else
  			*ugid_lookupp = -1;
 @@ -2075,7 +2049,7 @@ check_uidgid(ipfw_insn_u32 *insn, int pr
  				dst_ip, htons(dst_port),
  				wildcard, NULL);
  		if (pcb != NULL) {
 -			fill_ugid_cache(pcb, ugp);
 +			*uc = crhold(inp->inp_cred);
  			*ugid_lookupp = 1;
  		}
  		INP_INFO_RUNLOCK(pi);
 @@ -2091,16 +2065,11 @@ check_uidgid(ipfw_insn_u32 *insn, int pr
  		}
  	} 
  	if (insn->o.opcode == O_UID)
 -		match = (ugp->fw_uid == (uid_t)insn->d[0]);
 -	else if (insn->o.opcode == O_GID) {
 -		for (gp = ugp->fw_groups;
 -			gp < &ugp->fw_groups[ugp->fw_ngroups]; gp++)
 -			if (*gp == (gid_t)insn->d[0]) {
 -				match = 1;
 -				break;
 -			}
 -	} else if (insn->o.opcode == O_JAIL)
 -		match = (ugp->fw_prid == (int)insn->d[0]);
 +		match = ((*uc)->cr_uid == (uid_t)insn->d[0]);
 +	else if (insn->o.opcode == O_GID)
 +		match = groupmember((gid_t)insn->d[0], *uc);
 +	else if (insn->o.opcode == O_JAIL)
 +		match = ((*uc)->cr_prison->pr_id == (int)insn->d[0]);
  	return match;
  }
  
 @@ -2178,8 +2147,8 @@ ipfw_chk(struct ip_fw_args *args)
 
 *** DIFF OUTPUT TRUNCATED AT 1000 LINES ***
 _______________________________________________
 svn-src-all@freebsd.org mailing list
 http://lists.freebsd.org/mailman/listinfo/svn-src-all
 To unsubscribe, send any mail to "svn-src-all-unsubscribe@freebsd.org"
 
State-Changed-From-To: open->closed 
State-Changed-By: eadler 
State-Changed-When: Tue Mar 1 10:14:38 EST 2011 
State-Changed-Why:  
committed in head (r194498) but will never be MFCed 

http://www.freebsd.org/cgi/query-pr.cgi?pr=113398 
>Unformatted:
