linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] Many groups patch.
@ 2003-09-29  7:19 Rusty Russell
  2003-09-29 17:25 ` Linus Torvalds
  0 siblings, 1 reply; 21+ messages in thread
From: Rusty Russell @ 2003-09-29  7:19 UTC (permalink / raw)
  To: Tim Hockin, linux-kernel; +Cc: torvalds, braam, neilb

As stated before, SAMBA customers want > 200 groups.  Special thanks
to Tim Hockin for feedback based on his patch.

This version drops the internal groups array (it's so often shared
that it's not worth it, and the logic becomes a bit neater), and does
vmalloc fallback in case someone has massive number of groups.

Rusty.
--
  Anyone who quotes me in their sig is an idiot. -- Rusty Russell.

Name: Dynamic Allocation of Groups Array When Required: With Refcounting
Author: Rusty Russell
Status: Tested on 2.6.0-test6
Depends: Misc/qemu-page-offset.patch.gz

D: This patch allows the maximum number of groups to be varied using
D: sysctl.  Since sharing is so common, we use a refcounted external
D: array for groups.
D: 
D: Changes:
D: 1) Remove the NGROUPS define from archs.
D: 2) Fixup the few places which declare [NGROUPS] arrays on the stack.
D: 3) The ia64, s390 and sparc64 ports have their own setgroups/getgroups
D:    implementations: unify them on the ia64 one, which calls the core
D:    functions.
D: 4) Change the task_struct's groups to a pointer to inside a refcounted
D:    external array, fix up fork() to inc refcount.
D: 5) Introduce max_groups and use it instead of NGROUPS.
D: 6) Add a sysctl to vary max_groups.
D: 
D: This patch scars nfs: artificially restrict the groups there to 
D: SVC_CRED_NGROUPS (32), which is probably wrong, but won't break if
D: they don't change the default.
D: 
D: This patch breaks intermezzo: I'm not sure how they want to deal
D: with it.

diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/ia64/ia32/sys_ia32.c .12070-2.6.0-test6-more_groups_refcount/arch/ia64/ia32/sys_ia32.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/ia64/ia32/sys_ia32.c	2003-09-29 10:25:17.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/ia64/ia32/sys_ia32.c	2003-09-29 17:18:46.000000000 +1000
@@ -2432,17 +2432,24 @@ asmlinkage long
 sys32_getgroups16 (int gidsetsize, short *grouplist)
 {
 	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
+	gid_t *gl;
 	int ret, i;
 
+	gl = kmalloc(sizeof(gl[0]) * current->ngroups, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+
 	set_fs(KERNEL_DS);
 	ret = sys_getgroups(gidsetsize, gl);
 	set_fs(old_fs);
 
-	if (gidsetsize && ret > 0 && ret <= NGROUPS)
+	if (gidsetsize && ret > 0)
 		for (i = 0; i < ret; i++, grouplist++)
-			if (put_user(gl[i], grouplist))
-				return -EFAULT;
+			if (put_user(gl[i], grouplist)) {
+				ret = -EFAULT;
+				break;
+			}
+	kfree(gl);
 	return ret;
 }
 
@@ -2452,17 +2459,23 @@ asmlinkage long
 sys32_setgroups16 (int gidsetsize, short *grouplist)
 {
 	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
+	gid_t *gl;
 	int ret, i;
 
-	if ((unsigned) gidsetsize > NGROUPS)
+	if ((unsigned) gidsetsize > max_groups)
 		return -EINVAL;
+	gl = kmalloc(sizeof(gl[0]) * gidsetsize, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
 	for (i = 0; i < gidsetsize; i++, grouplist++)
-		if (get_user(gl[i], grouplist))
+		if (get_user(gl[i], grouplist)) {
+			kfree(gl);
 			return -EFAULT;
+		}
 	set_fs(KERNEL_DS);
 	ret = sys_setgroups(gidsetsize, gl);
 	set_fs(old_fs);
+	kfree(gl);
 	return ret;
 }
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/mips/kernel/sysirix.c .12070-2.6.0-test6-more_groups_refcount/arch/mips/kernel/sysirix.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/mips/kernel/sysirix.c	2003-09-29 10:25:18.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/mips/kernel/sysirix.c	2003-09-29 17:18:46.000000000 +1000
@@ -368,7 +368,7 @@ asmlinkage int irix_syssgi(struct pt_reg
 			retval = HZ;
 			goto out;
 		case 4:
-			retval = NGROUPS;
+			retval = max_groups;
 			goto out;
 		case 5:
 			retval = NR_OPEN;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/s390/kernel/compat_linux.c .12070-2.6.0-test6-more_groups_refcount/arch/s390/kernel/compat_linux.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/s390/kernel/compat_linux.c	2003-09-29 10:25:21.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/s390/kernel/compat_linux.c	2003-09-29 17:18:46.000000000 +1000
@@ -189,40 +189,57 @@ asmlinkage long sys32_setfsgid16(u16 gid
 	return sys_setfsgid((gid_t)gid);
 }
 
-asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
+extern asmlinkage long sys_getgroups (int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+sys32_getgroups16 (int gidsetsize, short *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	mm_segment_t old_fs = get_fs();
+	gid_t *gl;
+	int ret, i;
 
-	if (gidsetsize < 0)
-		return -EINVAL;
-	i = current->ngroups;
-	if (gidsetsize) {
-		if (i > gidsetsize)
-			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
-			return -EFAULT;
-	}
-	return i;
+	gl = kmalloc(sizeof(gl[0]) * current->ngroups, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+
+	set_fs(KERNEL_DS);
+	ret = sys_getgroups(gidsetsize, gl);
+	set_fs(old_fs);
+
+	if (gidsetsize && ret > 0)
+		for (i = 0; i < ret; i++, grouplist++)
+			if (put_user(gl[i], grouplist)) {
+				ret = -EFAULT;
+				break;
+			}
+	kfree(gl);
+	return ret;
 }
 
-asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
+extern asmlinkage long sys_setgroups (int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+sys32_setgroups16 (int gidsetsize, short *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	mm_segment_t old_fs = get_fs();
+	gid_t *gl;
+	int ret, i;
 
-	if (!capable(CAP_SETGID))
-		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
+	if ((unsigned) gidsetsize > max_groups)
 		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	gl = kmalloc(sizeof(gl[0]) * gidsetsize, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+	for (i = 0; i < gidsetsize; i++, grouplist++)
+		if (get_user(gl[i], grouplist)) {
+			kfree(gl);
+			return -EFAULT;
+		}
+	set_fs(KERNEL_DS);
+	ret = sys_setgroups(gidsetsize, gl);
+	set_fs(old_fs);
+	kfree(gl);
+	return ret;
 }
 
 asmlinkage long sys32_getuid16(void)
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc/kernel/sys_sunos.c .12070-2.6.0-test6-more_groups_refcount/arch/sparc/kernel/sys_sunos.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc/kernel/sys_sunos.c	2003-09-22 10:27:56.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/sparc/kernel/sys_sunos.c	2003-09-29 17:18:46.000000000 +1000
@@ -896,7 +896,7 @@ extern asmlinkage long sunos_sysconf (in
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = max_groups;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc64/kernel/sys_sparc32.c .12070-2.6.0-test6-more_groups_refcount/arch/sparc64/kernel/sys_sparc32.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc64/kernel/sys_sparc32.c	2003-09-29 10:25:22.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/sparc64/kernel/sys_sparc32.c	2003-09-29 17:18:46.000000000 +1000
@@ -206,40 +206,57 @@ asmlinkage long sys32_setfsgid16(u16 gid
 	return sys_setfsgid((gid_t)gid);
 }
 
-asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
+extern asmlinkage long sys_getgroups (int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+sys32_getgroups16 (int gidsetsize, short *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	mm_segment_t old_fs = get_fs();
+	gid_t *gl;
+	int ret, i;
 
-	if (gidsetsize < 0)
-		return -EINVAL;
-	i = current->ngroups;
-	if (gidsetsize) {
-		if (i > gidsetsize)
-			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
-			return -EFAULT;
-	}
-	return i;
+	gl = kmalloc(sizeof(gl[0]) * current->ngroups, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+
+	set_fs(KERNEL_DS);
+	ret = sys_getgroups(gidsetsize, gl);
+	set_fs(old_fs);
+
+	if (gidsetsize && ret > 0)
+		for (i = 0; i < ret; i++, grouplist++)
+			if (put_user(gl[i], grouplist)) {
+				ret = -EFAULT;
+				break;
+			}
+	kfree(gl);
+	return ret;
 }
 
-asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
+extern asmlinkage long sys_setgroups (int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+sys32_setgroups16 (int gidsetsize, short *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	mm_segment_t old_fs = get_fs();
+	gid_t *gl;
+	int ret, i;
 
-	if (!capable(CAP_SETGID))
-		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
+	if ((unsigned) gidsetsize > max_groups)
 		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	gl = kmalloc(sizeof(gl[0]) * gidsetsize, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+	for (i = 0; i < gidsetsize; i++, grouplist++)
+		if (get_user(gl[i], grouplist)) {
+			kfree(gl);
+			return -EFAULT;
+		}
+	set_fs(KERNEL_DS);
+	ret = sys_setgroups(gidsetsize, gl);
+	set_fs(old_fs);
+	kfree(gl);
+	return ret;
 }
 
 asmlinkage long sys32_getuid16(void)
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc64/kernel/sys_sunos32.c .12070-2.6.0-test6-more_groups_refcount/arch/sparc64/kernel/sys_sunos32.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc64/kernel/sys_sunos32.c	2003-09-22 10:27:56.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/sparc64/kernel/sys_sunos32.c	2003-09-29 17:18:46.000000000 +1000
@@ -859,7 +859,7 @@ extern asmlinkage s32 sunos_sysconf (int
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = max_groups;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/auth.c .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/auth.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/auth.c	2003-09-22 10:21:34.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/auth.c	2003-09-29 17:18:46.000000000 +1000
@@ -26,7 +26,7 @@ nfsd_setuser(struct svc_rqst *rqstp, str
 			cred->cr_uid = exp->ex_anon_uid;
 		if (!cred->cr_gid)
 			cred->cr_gid = exp->ex_anon_gid;
-		for (i = 0; i < NGROUPS; i++)
+		for (i = 0; i < SVC_CRED_NGROUPS; i++)
 			if (!cred->cr_groups[i])
 				cred->cr_groups[i] = exp->ex_anon_gid;
 	}
@@ -39,7 +39,9 @@ nfsd_setuser(struct svc_rqst *rqstp, str
 		current->fsgid = cred->cr_gid;
 	else
 		current->fsgid = exp->ex_anon_gid;
-	for (i = 0; i < NGROUPS; i++) {
+
+	/* We can do this because cow_current_groups() was done at birth. */
+	for (i = 0; i < SVC_CRED_NGROUPS; i++) {
 		gid_t group = cred->cr_groups[i];
 		if (group == (gid_t) NOGROUP)
 			break;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/nfs4state.c .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/nfs4state.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/nfs4state.c	2003-09-29 10:25:52.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/nfs4state.c	2003-09-29 17:18:46.000000000 +1000
@@ -244,7 +244,7 @@ copy_cred(struct svc_cred *target, struc
 
 	target->cr_uid = source->cr_uid;
 	target->cr_gid = source->cr_gid;
-	for(i = 0; i < NGROUPS; i++)
+	for(i = 0; i < SVC_CREDS_NGROUPS; i++)
 		target->cr_groups[i] = source->cr_groups[i];
 }
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/nfssvc.c .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/nfssvc.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/nfssvc.c	2003-09-22 10:26:12.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/nfssvc.c	2003-09-29 17:18:46.000000000 +1000
@@ -182,6 +182,11 @@ nfsd(struct svc_rqst *rqstp)
 	daemonize("nfsd");
 	current->rlim[RLIMIT_FSIZE].rlim_cur = RLIM_INFINITY;
 
+	if (cow_current_groups(SVC_CRED_NGROUPS) < 0) {
+		printk("Unable to start nfsd thread: can't set groups\n");
+		goto out;
+	}
+
 	/* After daemonize() this kernel thread shares current->fs
 	 * with the init process. We need to create files with a
 	 * umask of 0 instead of init's umask. */
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-alpha/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-alpha/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-alpha/param.h	2003-09-21 17:27:17.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-alpha/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -19,10 +19,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-arm/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-arm/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-arm/param.h	2003-09-22 10:28:10.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-arm/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -26,10 +26,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-arm26/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-arm26/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-arm26/param.h	2003-09-22 10:09:07.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-arm26/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -22,10 +22,6 @@
 # define HZ		100
 #endif
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-cris/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-cris/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-cris/param.h	2003-09-22 10:23:13.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-cris/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-h8300/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-h8300/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-h8300/param.h	2003-09-22 10:07:04.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-h8300/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-i386/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-i386/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-i386/param.h	2003-09-29 17:18:46.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-i386/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -18,10 +18,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ia64/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-ia64/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ia64/param.h	2003-09-29 10:25:58.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-ia64/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -10,10 +10,6 @@
 
 #define EXEC_PAGESIZE	65536
 
-#ifndef NGROUPS
-# define NGROUPS	32
-#endif
-
 #ifndef NOGROUP
 # define NOGROUP	(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-m68k/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-m68k/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-m68k/param.h	2003-09-21 17:26:43.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-m68k/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-m68knommu/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-m68knommu/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-m68knommu/param.h	2003-09-21 17:31:31.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-m68knommu/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -44,10 +44,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-mips/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-mips/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-mips/param.h	2003-09-22 10:22:44.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-mips/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -33,10 +33,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-parisc/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-parisc/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-parisc/param.h	2003-09-21 17:31:10.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-parisc/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ppc/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-ppc/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ppc/param.h	2003-09-22 09:47:27.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-ppc/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ppc64/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-ppc64/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ppc64/param.h	2003-09-21 17:26:44.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-ppc64/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -20,10 +20,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-s390/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-s390/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-s390/param.h	2003-09-21 17:29:29.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-s390/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -21,10 +21,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sh/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-sh/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sh/param.h	2003-09-22 10:23:00.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-sh/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sparc/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-sparc/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sparc/param.h	2003-09-21 17:26:18.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-sparc/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sparc64/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-sparc64/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sparc64/param.h	2003-09-21 17:26:18.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-sparc64/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-um/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-um/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-um/param.h	2003-09-21 17:28:16.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-um/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -3,10 +3,6 @@
 
 #define EXEC_PAGESIZE   4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-v850/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-v850/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-v850/param.h	2003-09-21 17:31:32.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-v850/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -18,10 +18,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-x86_64/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-x86_64/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-x86_64/param.h	2003-09-21 17:30:30.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-x86_64/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/init_task.h .12070-2.6.0-test6-more_groups_refcount/include/linux/init_task.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/init_task.h	2003-09-22 10:27:37.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/linux/init_task.h	2003-09-29 17:18:47.000000000 +1000
@@ -56,6 +56,8 @@
 	.siglock	= SPIN_LOCK_UNLOCKED, 		\
 }
 
+extern struct task_groups init_groups;
+
 /*
  *  INIT_TASK is used to set up the first task table, touch at
  * your own risk!. Base=0, limit=0x1fffff (=2MB)
@@ -108,6 +110,7 @@
 	.proc_lock	= SPIN_LOCK_UNLOCKED,				\
 	.switch_lock	= SPIN_LOCK_UNLOCKED,				\
 	.journal_info	= NULL,						\
+	.groups		= init_groups.groups,				\
 }
 
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sched.h .12070-2.6.0-test6-more_groups_refcount/include/linux/sched.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sched.h	2003-09-29 10:26:05.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/linux/sched.h	2003-09-29 17:18:47.000000000 +1000
@@ -328,6 +328,16 @@ struct k_itimer {
 struct io_context;			/* See blkdev.h */
 void exit_io_context(void);
 
+/* Size is determined by task_struct's ngroups. */
+struct task_groups
+{
+	atomic_t usage;
+	void (*free)(const void *);
+	gid_t groups[0];
+};
+
+extern int max_groups;
+
 struct task_struct {
 	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
 	struct thread_info *thread_info;
@@ -403,7 +413,7 @@ struct task_struct {
 	uid_t uid,euid,suid,fsuid;
 	gid_t gid,egid,sgid,fsgid;
 	int ngroups;
-	gid_t	groups[NGROUPS];
+	gid_t *groups; /* task_groups->groups */
 	kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
 	int keep_capabilities:1;
 	struct user_struct *user;
@@ -602,6 +612,12 @@ extern int send_group_sigqueue(int, stru
 extern int do_sigaction(int, const struct k_sigaction *, struct k_sigaction *);
 extern int do_sigaltstack(const stack_t __user *, stack_t __user *, unsigned long);
 
+/* container_of doesn't like arrays. */
+static inline struct task_groups *task_groups(gid_t *groups)
+{
+	return (void *)groups - offsetof(struct task_groups, groups);
+}
+
 /* These can be the second arg to send_sig_info/send_group_sig_info.  */
 #define SEND_SIG_NOINFO ((struct siginfo *) 0)
 #define SEND_SIG_PRIV	((struct siginfo *) 1)
@@ -678,6 +694,10 @@ extern int do_execve(char *, char __user
 extern long do_fork(unsigned long, unsigned long, struct pt_regs *, unsigned long, int __user *, int __user *);
 extern struct task_struct * copy_process(unsigned long, unsigned long, struct pt_regs *, unsigned long, int __user *, int __user *);
 
+/* Copy-on-write current groups to this size, if possible.  0 or -err. */
+extern int cow_current_groups(unsigned int ngroups);
+extern void release_groups(gid_t *groups);
+
 #ifdef CONFIG_SMP
 extern void wait_task_inactive(task_t * p);
 #else
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sunrpc/svcauth.h .12070-2.6.0-test6-more_groups_refcount/include/linux/sunrpc/svcauth.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sunrpc/svcauth.h	2003-09-22 09:47:41.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/linux/sunrpc/svcauth.h	2003-09-29 17:18:47.000000000 +1000
@@ -16,10 +16,12 @@
 #include <linux/sunrpc/cache.h>
 #include <linux/hash.h>
 
+#define SVC_CRED_NGROUPS	32
+
 struct svc_cred {
 	uid_t			cr_uid;
 	gid_t			cr_gid;
-	gid_t			cr_groups[NGROUPS];
+	gid_t			cr_groups[SVC_CRED_NGROUPS];
 };
 
 struct svc_rqst;		/* forward decl */
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sysctl.h .12070-2.6.0-test6-more_groups_refcount/include/linux/sysctl.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sysctl.h	2003-09-22 10:28:13.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/linux/sysctl.h	2003-09-29 17:18:47.000000000 +1000
@@ -127,6 +127,7 @@ enum
 	KERN_PANIC_ON_OOPS=57,  /* int: whether we will panic on an oops */
 	KERN_HPPA_PWRSW=58,	/* int: hppa soft-power enable */
 	KERN_HPPA_UNALIGNED=59,	/* int: hppa unaligned-trap enable */
+	KERN_MAX_GROUPS=60,	/* int: setgroups limit */
 };
 
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/init/main.c .12070-2.6.0-test6-more_groups_refcount/init/main.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/init/main.c	2003-09-29 10:26:06.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/init/main.c	2003-09-29 17:18:47.000000000 +1000
@@ -594,7 +594,6 @@ static int init(void * unused)
 	 * The Bourne shell can be used instead of init if we are 
 	 * trying to recover a really broken machine.
 	 */
-
 	if (execute_command)
 		run_init_process(execute_command);
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/kernel/fork.c .12070-2.6.0-test6-more_groups_refcount/kernel/fork.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/kernel/fork.c	2003-09-29 10:26:06.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/kernel/fork.c	2003-09-29 17:18:47.000000000 +1000
@@ -85,6 +85,7 @@ void __put_task_struct(struct task_struc
 
 	security_task_free(tsk);
 	free_uid(tsk->user);
+	release_groups(tsk->groups);
 	free_task(tsk);
 }
 
@@ -817,6 +818,7 @@ struct task_struct *copy_process(unsigne
 
 	atomic_inc(&p->user->__count);
 	atomic_inc(&p->user->processes);
+	atomic_inc(&task_groups(p->groups)->usage);
 
 	/*
 	 * If multiple threads are within copy_process(), then this check
@@ -1063,6 +1065,7 @@ bad_fork_cleanup_put_domain:
 bad_fork_cleanup_count:
 	atomic_dec(&p->user->processes);
 	free_uid(p->user);
+	release_groups(p->groups);
 bad_fork_free:
 	free_task(p);
 	goto fork_out;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/kernel/sys.c .12070-2.6.0-test6-more_groups_refcount/kernel/sys.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/kernel/sys.c	2003-09-29 10:26:06.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/kernel/sys.c	2003-09-29 17:18:47.000000000 +1000
@@ -1069,6 +1069,62 @@ out:
 	return err;
 }
 
+/* Never freed */
+struct task_groups init_groups = { .usage = ATOMIC_INIT(2) };
+
+int max_groups = 32;
+
+void release_groups(gid_t *groups)
+{
+	struct task_groups *tgrp = task_groups(groups);
+
+	if (atomic_dec_and_test(&tgrp->usage))
+		tgrp->free(tgrp);
+}
+
+/* This does the actual copy and changeover.  Caller sets
+ * current->ngroups and changes group array if everything else goes ok. */
+static inline int alloc_new_groups(unsigned int ngroups)
+{
+	struct task_groups *tgrp;
+	unsigned int size;
+
+	if (ngroups < current->ngroups)
+		ngroups = current->ngroups;
+	size = sizeof(*tgrp) + sizeof(tgrp->groups[0])*ngroups;
+	
+	if (!(tgrp = kmalloc(size, GFP_KERNEL))) {
+		if (!(tgrp = vmalloc(size)))
+			return -ENOMEM;
+		tgrp->free = (void (*)(const void *))vfree;
+	} else
+		tgrp->free = kfree;
+	atomic_set(&tgrp->usage, 1);
+
+	memcpy(tgrp->groups, current->groups,
+	       sizeof(tgrp->groups[0]) * current->ngroups);
+
+	release_groups(current->groups);
+	current->groups = tgrp->groups;
+	return 0;
+}
+
+/* Unshare and maybe enlarge current groups to this size, if possible.
+ * 0 or -err. */
+int cow_current_groups(unsigned int ngroups)
+{
+	if (ngroups > max_groups)
+		return -EINVAL;
+
+	printk("cow to %u groups\n", ngroups);
+	/* Shared, or needs expansion? */
+	if (atomic_read(&task_groups(current->groups)->usage) > 1
+	    || ngroups > current->ngroups)
+		return alloc_new_groups(ngroups);
+
+	return 0;
+}
+
 /*
  * Supplementary group IDs
  */
@@ -1094,27 +1150,36 @@ asmlinkage long sys_getgroups(int gidset
 }
 
 /*
- *	SMP: Our groups are not shared. We can copy to/from them safely
+ *	SMP: Our groups are copy-on-write. We can copy to/from them safely
  *	without another task interfering.
  */
  
 asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist)
 {
-	gid_t groups[NGROUPS];
+	gid_t *groups;
 	int retval;
 
+	printk("sys_setgroups %u groups\n", gidsetsize);
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(gid_t)))
-		return -EFAULT;
+	retval = cow_current_groups(gidsetsize);
+	if (retval < 0)
+		return retval;
+	groups = kmalloc(sizeof(groups[0]) * gidsetsize, GFP_KERNEL);
+	if (!groups)
+		return -ENOMEM;
+	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(gid_t))) {
+		retval = -EFAULT;
+		goto out;
+	}
 	retval = security_task_setgroups(gidsetsize, groups);
 	if (retval)
-		return retval;
+		goto out;
 	memcpy(current->groups, groups, gidsetsize * sizeof(gid_t));
 	current->ngroups = gidsetsize;
-	return 0;
+out:
+	kfree(groups);
+	return retval;
 }
 
 static int supplemental_group_member(gid_t grp)
@@ -1434,6 +1499,7 @@ asmlinkage long sys_prctl(int option, un
 	return error;
 }
 
+EXPORT_SYMBOL(cow_current_groups);
 EXPORT_SYMBOL(notifier_chain_register);
 EXPORT_SYMBOL(notifier_chain_unregister);
 EXPORT_SYMBOL(notifier_call_chain);
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/kernel/sysctl.c .12070-2.6.0-test6-more_groups_refcount/kernel/sysctl.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/kernel/sysctl.c	2003-09-29 10:26:06.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/kernel/sysctl.c	2003-09-29 17:18:47.000000000 +1000
@@ -581,6 +581,14 @@ static ctl_table kern_table[] = {
 		.mode		= 0644,
 		.proc_handler	= &proc_dointvec,
 	},
+	{
+		.ctl_name	= KERN_MAX_GROUPS,
+		.procname	= "max_groups",
+		.data		= &max_groups,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec,
+	},
 	{ .ctl_name = 0 }
 };
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/kernel/uid16.c .12070-2.6.0-test6-more_groups_refcount/kernel/uid16.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/kernel/uid16.c	2003-09-22 10:07:19.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/kernel/uid16.c	2003-09-29 17:18:47.000000000 +1000
@@ -109,7 +109,6 @@ asmlinkage long sys_setfsgid16(old_gid_t
 
 asmlinkage long sys_getgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
 	int i,j;
 
 	if (gidsetsize < 0)
@@ -118,34 +117,48 @@ asmlinkage long sys_getgroups16(int gids
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(old_gid_t)*i))
+		if (!access_ok(VERIFY_WRITE, grouplist, sizeof(old_gid_t)*i))
 			return -EFAULT;
+		for(j=0;j<i;j++) {
+			old_gid_t group;
+			group = current->groups[j];
+			if (copy_to_user(grouplist+j, &group, sizeof(group)))
+				return -EFAULT;
+		}
 	}
 	return i;
 }
 
 asmlinkage long sys_setgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
-	gid_t new_groups[NGROUPS];
-	int i;
+	old_gid_t *groups;
+	gid_t *new_groups;
+	int i, ret;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
+
+	if ((ret = cow_current_groups(gidsetsize)) < 0)
+		return ret;
+	ret = -ENOMEM;
+	groups = kmalloc(sizeof(groups[0]) * gidsetsize, GFP_KERNEL);
+	new_groups = kmalloc(sizeof(new_groups[0]) * gidsetsize, GFP_KERNEL);
+	if (!groups || !new_groups)
+		goto out;
+	ret = -EFAULT;
 	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(old_gid_t)))
-		return -EFAULT;
+		goto out;
 	for (i = 0 ; i < gidsetsize ; i++)
 		new_groups[i] = (gid_t)groups[i];
-	i = security_task_setgroups(gidsetsize, new_groups);
-	if (i)
-		return i;
+	ret = security_task_setgroups(gidsetsize, new_groups);
+	if (ret)
+		goto out;
 	memcpy(current->groups, new_groups, gidsetsize * sizeof(gid_t));
 	current->ngroups = gidsetsize;
-	return 0;
+out:
+	kfree(groups);
+	kfree(groups);
+	return ret;
 }
 
 asmlinkage long sys_getuid16(void)
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/net/sunrpc/svcauth_unix.c .12070-2.6.0-test6-more_groups_refcount/net/sunrpc/svcauth_unix.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/net/sunrpc/svcauth_unix.c	2003-09-22 10:23:05.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/net/sunrpc/svcauth_unix.c	2003-09-29 17:18:47.000000000 +1000
@@ -434,11 +434,11 @@ svcauth_unix_accept(struct svc_rqst *rqs
 	if (slen > 16 || (len -= (slen + 2)*4) < 0)
 		goto badcred;
 	for (i = 0; i < slen; i++)
-		if (i < NGROUPS)
+		if (i < SVC_CRED_NGROUPS)
 			cred->cr_groups[i] = ntohl(svc_getu32(argv));
 		else
 			svc_getu32(argv);
-	if (i < NGROUPS)
+	if (i < SVC_CRED_NGROUPS)
 		cred->cr_groups[i] = NOGROUP;
 
 	if (svc_getu32(argv) != RPC_AUTH_NULL || svc_getu32(argv) != 0) {

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-09-29  7:19 [PATCH] Many groups patch Rusty Russell
@ 2003-09-29 17:25 ` Linus Torvalds
  2003-09-29 23:30   ` Rusty Russell
  0 siblings, 1 reply; 21+ messages in thread
From: Linus Torvalds @ 2003-09-29 17:25 UTC (permalink / raw)
  To: Rusty Russell; +Cc: Tim Hockin, linux-kernel, torvalds, braam, neilb


On Mon, 29 Sep 2003, Rusty Russell wrote:
> 
> This version drops the internal groups array (it's so often shared
> that it's not worth it, and the logic becomes a bit neater), and does
> vmalloc fallback in case someone has massive number of groups.

Why?

kmalloc() works fine. Anybody who needs 200 groups may be sane, but 
anybody who needs more than fits in a kmalloc() is definitely so far out 
that there is no point. 

The vmalloc space is limited, and the code just gets uglier.

Have you been looking at glibc sources lately, or why do you believe that 
we should encourage insane usage?

		Linus


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-09-29 17:25 ` Linus Torvalds
@ 2003-09-29 23:30   ` Rusty Russell
  2003-09-30  4:11     ` Tim Hockin
  0 siblings, 1 reply; 21+ messages in thread
From: Rusty Russell @ 2003-09-29 23:30 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Tim Hockin, linux-kernel, torvalds, braam, neilb, David Meybohm

In message <Pine.LNX.4.44.0309291024040.28114-100000@home.osdl.org> you write:
> 
> On Mon, 29 Sep 2003, Rusty Russell wrote:
> > 
> > This version drops the internal groups array (it's so often shared
> > that it's not worth it, and the logic becomes a bit neater), and does
> > vmalloc fallback in case someone has massive number of groups.
> 
> Why?

(Rusty points at Tim).

He has 10,000 groups.  Now me, I'm happy with the minimal fix.

> kmalloc() works fine. Anybody who needs 200 groups may be sane, but 
> anybody who needs more than fits in a kmalloc() is definitely so far out 
> that there is no point. 

And worse, there are the intermediate kmallocs which would need to be
fixed (thanks to Stephen Rothwell for pointing this out).  Fixing this
would make it even uglier.

Here's an updated one (with David Meybohm's fix, too -- Thanks!),
Rusty.
--
  Anyone who quotes me in their sig is an idiot. -- Rusty Russell.

Name: Dynamic Allocation of Groups Array When Required: With Refcounting
Author: Rusty Russell
Status: Booted on 2.6.0-test6-bk1
Depends: Misc/qemu-page-offset.patch.gz

D: This patch allows the maximum number of groups to be varied using
D: sysctl.  Since sharing is so common, we use a refcounted external
D: array for groups.
D: 
D: Changes:
D: 1) Remove the NGROUPS define from archs.
D: 2) Fixup the few places which declare [NGROUPS] arrays on the stack.
D: 3) The ia64, s390 and sparc64 ports have their own setgroups/getgroups
D:    implementations: unify them on the ia64 one, which calls the core
D:    functions.
D: 4) Change the task_struct's groups to a pointer to inside a refcounted
D:    external array, fix up fork() to inc refcount.
D: 5) Introduce max_groups and use it instead of NGROUPS.
D: 6) Add a sysctl to vary max_groups.
D: 
D: This patch scars nfs: artificially restrict the groups there to 
D: SVC_CRED_NGROUPS (32), which is probably wrong, but won't break if
D: they don't change the default.
D: 
D: This patch breaks intermezzo: I'm not sure how they want to deal
D: with it.

diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/ia64/ia32/sys_ia32.c .12070-2.6.0-test6-more_groups_refcount/arch/ia64/ia32/sys_ia32.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/ia64/ia32/sys_ia32.c	2003-09-29 10:25:17.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/ia64/ia32/sys_ia32.c	2003-09-29 17:18:46.000000000 +1000
@@ -2432,17 +2432,24 @@ asmlinkage long
 sys32_getgroups16 (int gidsetsize, short *grouplist)
 {
 	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
+	gid_t *gl;
 	int ret, i;
 
+	gl = kmalloc(sizeof(gl[0]) * current->ngroups, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+
 	set_fs(KERNEL_DS);
 	ret = sys_getgroups(gidsetsize, gl);
 	set_fs(old_fs);
 
-	if (gidsetsize && ret > 0 && ret <= NGROUPS)
+	if (gidsetsize && ret > 0)
 		for (i = 0; i < ret; i++, grouplist++)
-			if (put_user(gl[i], grouplist))
-				return -EFAULT;
+			if (put_user(gl[i], grouplist)) {
+				ret = -EFAULT;
+				break;
+			}
+	kfree(gl);
 	return ret;
 }
 
@@ -2452,17 +2459,23 @@ asmlinkage long
 sys32_setgroups16 (int gidsetsize, short *grouplist)
 {
 	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
+	gid_t *gl;
 	int ret, i;
 
-	if ((unsigned) gidsetsize > NGROUPS)
+	if ((unsigned) gidsetsize > max_groups)
 		return -EINVAL;
+	gl = kmalloc(sizeof(gl[0]) * gidsetsize, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
 	for (i = 0; i < gidsetsize; i++, grouplist++)
-		if (get_user(gl[i], grouplist))
+		if (get_user(gl[i], grouplist)) {
+			kfree(gl);
 			return -EFAULT;
+		}
 	set_fs(KERNEL_DS);
 	ret = sys_setgroups(gidsetsize, gl);
 	set_fs(old_fs);
+	kfree(gl);
 	return ret;
 }
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/mips/kernel/sysirix.c .12070-2.6.0-test6-more_groups_refcount/arch/mips/kernel/sysirix.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/mips/kernel/sysirix.c	2003-09-29 10:25:18.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/mips/kernel/sysirix.c	2003-09-29 17:18:46.000000000 +1000
@@ -368,7 +368,7 @@ asmlinkage int irix_syssgi(struct pt_reg
 			retval = HZ;
 			goto out;
 		case 4:
-			retval = NGROUPS;
+			retval = max_groups;
 			goto out;
 		case 5:
 			retval = NR_OPEN;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/s390/kernel/compat_linux.c .12070-2.6.0-test6-more_groups_refcount/arch/s390/kernel/compat_linux.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/s390/kernel/compat_linux.c	2003-09-29 10:25:21.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/s390/kernel/compat_linux.c	2003-09-29 17:18:46.000000000 +1000
@@ -189,40 +189,57 @@ asmlinkage long sys32_setfsgid16(u16 gid
 	return sys_setfsgid((gid_t)gid);
 }
 
-asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
+extern asmlinkage long sys_getgroups (int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+sys32_getgroups16 (int gidsetsize, short *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	mm_segment_t old_fs = get_fs();
+	gid_t *gl;
+	int ret, i;
 
-	if (gidsetsize < 0)
-		return -EINVAL;
-	i = current->ngroups;
-	if (gidsetsize) {
-		if (i > gidsetsize)
-			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
-			return -EFAULT;
-	}
-	return i;
+	gl = kmalloc(sizeof(gl[0]) * current->ngroups, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+
+	set_fs(KERNEL_DS);
+	ret = sys_getgroups(gidsetsize, gl);
+	set_fs(old_fs);
+
+	if (gidsetsize && ret > 0)
+		for (i = 0; i < ret; i++, grouplist++)
+			if (put_user(gl[i], grouplist)) {
+				ret = -EFAULT;
+				break;
+			}
+	kfree(gl);
+	return ret;
 }
 
-asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
+extern asmlinkage long sys_setgroups (int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+sys32_setgroups16 (int gidsetsize, short *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	mm_segment_t old_fs = get_fs();
+	gid_t *gl;
+	int ret, i;
 
-	if (!capable(CAP_SETGID))
-		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
+	if ((unsigned) gidsetsize > max_groups)
 		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	gl = kmalloc(sizeof(gl[0]) * gidsetsize, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+	for (i = 0; i < gidsetsize; i++, grouplist++)
+		if (get_user(gl[i], grouplist)) {
+			kfree(gl);
+			return -EFAULT;
+		}
+	set_fs(KERNEL_DS);
+	ret = sys_setgroups(gidsetsize, gl);
+	set_fs(old_fs);
+	kfree(gl);
+	return ret;
 }
 
 asmlinkage long sys32_getuid16(void)
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc/kernel/sys_sunos.c .12070-2.6.0-test6-more_groups_refcount/arch/sparc/kernel/sys_sunos.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc/kernel/sys_sunos.c	2003-09-22 10:27:56.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/sparc/kernel/sys_sunos.c	2003-09-29 17:18:46.000000000 +1000
@@ -896,7 +896,7 @@ extern asmlinkage long sunos_sysconf (in
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = max_groups;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc64/kernel/sys_sparc32.c .12070-2.6.0-test6-more_groups_refcount/arch/sparc64/kernel/sys_sparc32.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc64/kernel/sys_sparc32.c	2003-09-29 10:25:22.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/sparc64/kernel/sys_sparc32.c	2003-09-29 17:18:46.000000000 +1000
@@ -206,40 +206,57 @@ asmlinkage long sys32_setfsgid16(u16 gid
 	return sys_setfsgid((gid_t)gid);
 }
 
-asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
+extern asmlinkage long sys_getgroups (int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+sys32_getgroups16 (int gidsetsize, short *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	mm_segment_t old_fs = get_fs();
+	gid_t *gl;
+	int ret, i;
 
-	if (gidsetsize < 0)
-		return -EINVAL;
-	i = current->ngroups;
-	if (gidsetsize) {
-		if (i > gidsetsize)
-			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
-			return -EFAULT;
-	}
-	return i;
+	gl = kmalloc(sizeof(gl[0]) * current->ngroups, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+
+	set_fs(KERNEL_DS);
+	ret = sys_getgroups(gidsetsize, gl);
+	set_fs(old_fs);
+
+	if (gidsetsize && ret > 0)
+		for (i = 0; i < ret; i++, grouplist++)
+			if (put_user(gl[i], grouplist)) {
+				ret = -EFAULT;
+				break;
+			}
+	kfree(gl);
+	return ret;
 }
 
-asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
+extern asmlinkage long sys_setgroups (int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+sys32_setgroups16 (int gidsetsize, short *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	mm_segment_t old_fs = get_fs();
+	gid_t *gl;
+	int ret, i;
 
-	if (!capable(CAP_SETGID))
-		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
+	if ((unsigned) gidsetsize > max_groups)
 		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	gl = kmalloc(sizeof(gl[0]) * gidsetsize, GFP_KERNEL);
+	if (!gl)
+		return -ENOMEM;
+	for (i = 0; i < gidsetsize; i++, grouplist++)
+		if (get_user(gl[i], grouplist)) {
+			kfree(gl);
+			return -EFAULT;
+		}
+	set_fs(KERNEL_DS);
+	ret = sys_setgroups(gidsetsize, gl);
+	set_fs(old_fs);
+	kfree(gl);
+	return ret;
 }
 
 asmlinkage long sys32_getuid16(void)
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc64/kernel/sys_sunos32.c .12070-2.6.0-test6-more_groups_refcount/arch/sparc64/kernel/sys_sunos32.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/arch/sparc64/kernel/sys_sunos32.c	2003-09-22 10:27:56.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/arch/sparc64/kernel/sys_sunos32.c	2003-09-29 17:18:46.000000000 +1000
@@ -859,7 +859,7 @@ extern asmlinkage s32 sunos_sysconf (int
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = max_groups;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/auth.c .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/auth.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/auth.c	2003-09-22 10:21:34.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/auth.c	2003-09-29 17:18:46.000000000 +1000
@@ -26,7 +26,7 @@ nfsd_setuser(struct svc_rqst *rqstp, str
 			cred->cr_uid = exp->ex_anon_uid;
 		if (!cred->cr_gid)
 			cred->cr_gid = exp->ex_anon_gid;
-		for (i = 0; i < NGROUPS; i++)
+		for (i = 0; i < SVC_CRED_NGROUPS; i++)
 			if (!cred->cr_groups[i])
 				cred->cr_groups[i] = exp->ex_anon_gid;
 	}
@@ -39,7 +39,9 @@ nfsd_setuser(struct svc_rqst *rqstp, str
 		current->fsgid = cred->cr_gid;
 	else
 		current->fsgid = exp->ex_anon_gid;
-	for (i = 0; i < NGROUPS; i++) {
+
+	/* We can do this because cow_current_groups() was done at birth. */
+	for (i = 0; i < SVC_CRED_NGROUPS; i++) {
 		gid_t group = cred->cr_groups[i];
 		if (group == (gid_t) NOGROUP)
 			break;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/nfs4state.c .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/nfs4state.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/nfs4state.c	2003-09-29 10:25:52.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/nfs4state.c	2003-09-29 17:18:46.000000000 +1000
@@ -244,7 +244,7 @@ copy_cred(struct svc_cred *target, struc
 
 	target->cr_uid = source->cr_uid;
 	target->cr_gid = source->cr_gid;
-	for(i = 0; i < NGROUPS; i++)
+	for(i = 0; i < SVC_CREDS_NGROUPS; i++)
 		target->cr_groups[i] = source->cr_groups[i];
 }
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/nfssvc.c .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/nfssvc.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/fs/nfsd/nfssvc.c	2003-09-22 10:26:12.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/fs/nfsd/nfssvc.c	2003-09-29 17:18:46.000000000 +1000
@@ -182,6 +182,11 @@ nfsd(struct svc_rqst *rqstp)
 	daemonize("nfsd");
 	current->rlim[RLIMIT_FSIZE].rlim_cur = RLIM_INFINITY;
 
+	if (cow_current_groups(SVC_CRED_NGROUPS) < 0) {
+		printk("Unable to start nfsd thread: can't set groups\n");
+		goto out;
+	}
+
 	/* After daemonize() this kernel thread shares current->fs
 	 * with the init process. We need to create files with a
 	 * umask of 0 instead of init's umask. */
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-alpha/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-alpha/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-alpha/param.h	2003-09-21 17:27:17.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-alpha/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -19,10 +19,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-arm/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-arm/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-arm/param.h	2003-09-22 10:28:10.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-arm/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -26,10 +26,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-arm26/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-arm26/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-arm26/param.h	2003-09-22 10:09:07.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-arm26/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -22,10 +22,6 @@
 # define HZ		100
 #endif
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-cris/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-cris/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-cris/param.h	2003-09-22 10:23:13.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-cris/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-h8300/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-h8300/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-h8300/param.h	2003-09-22 10:07:04.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-h8300/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-i386/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-i386/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-i386/param.h	2003-09-29 17:18:46.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-i386/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -18,10 +18,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ia64/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-ia64/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ia64/param.h	2003-09-29 10:25:58.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-ia64/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -10,10 +10,6 @@
 
 #define EXEC_PAGESIZE	65536
 
-#ifndef NGROUPS
-# define NGROUPS	32
-#endif
-
 #ifndef NOGROUP
 # define NOGROUP	(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-m68k/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-m68k/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-m68k/param.h	2003-09-21 17:26:43.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-m68k/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-m68knommu/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-m68knommu/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-m68knommu/param.h	2003-09-21 17:31:31.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-m68knommu/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -44,10 +44,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-mips/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-mips/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-mips/param.h	2003-09-22 10:22:44.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-mips/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -33,10 +33,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-parisc/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-parisc/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-parisc/param.h	2003-09-21 17:31:10.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-parisc/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ppc/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-ppc/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ppc/param.h	2003-09-22 09:47:27.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-ppc/param.h	2003-09-29 17:18:46.000000000 +1000
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ppc64/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-ppc64/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-ppc64/param.h	2003-09-21 17:26:44.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-ppc64/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -20,10 +20,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-s390/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-s390/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-s390/param.h	2003-09-21 17:29:29.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-s390/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -21,10 +21,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sh/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-sh/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sh/param.h	2003-09-22 10:23:00.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-sh/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sparc/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-sparc/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sparc/param.h	2003-09-21 17:26:18.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-sparc/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sparc64/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-sparc64/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-sparc64/param.h	2003-09-21 17:26:18.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-sparc64/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-um/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-um/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-um/param.h	2003-09-21 17:28:16.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-um/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -3,10 +3,6 @@
 
 #define EXEC_PAGESIZE   4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-v850/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-v850/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-v850/param.h	2003-09-21 17:31:32.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-v850/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -18,10 +18,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-x86_64/param.h .12070-2.6.0-test6-more_groups_refcount/include/asm-x86_64/param.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/asm-x86_64/param.h	2003-09-21 17:30:30.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/asm-x86_64/param.h	2003-09-29 17:18:47.000000000 +1000
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/init_task.h .12070-2.6.0-test6-more_groups_refcount/include/linux/init_task.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/init_task.h	2003-09-22 10:27:37.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/linux/init_task.h	2003-09-29 17:18:47.000000000 +1000
@@ -56,6 +56,8 @@
 	.siglock	= SPIN_LOCK_UNLOCKED, 		\
 }
 
+extern struct task_groups init_groups;
+
 /*
  *  INIT_TASK is used to set up the first task table, touch at
  * your own risk!. Base=0, limit=0x1fffff (=2MB)
@@ -108,6 +110,7 @@
 	.proc_lock	= SPIN_LOCK_UNLOCKED,				\
 	.switch_lock	= SPIN_LOCK_UNLOCKED,				\
 	.journal_info	= NULL,						\
+	.groups		= init_groups.groups,				\
 }
 
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sched.h .12070-2.6.0-test6-more_groups_refcount/include/linux/sched.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sched.h	2003-09-29 10:26:05.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/linux/sched.h	2003-09-29 17:18:47.000000000 +1000
@@ -328,6 +328,15 @@ struct k_itimer {
 struct io_context;			/* See blkdev.h */
 void exit_io_context(void);
 
+/* Size is determined by task_struct's ngroups. */
+struct task_groups
+{
+	atomic_t usage;
+	gid_t groups[0];
+};
+
+extern int max_groups;
+
 struct task_struct {
 	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
 	struct thread_info *thread_info;
@@ -403,7 +413,7 @@ struct task_struct {
 	uid_t uid,euid,suid,fsuid;
 	gid_t gid,egid,sgid,fsgid;
 	int ngroups;
-	gid_t	groups[NGROUPS];
+	gid_t *groups; /* task_groups->groups */
 	kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
 	int keep_capabilities:1;
 	struct user_struct *user;
@@ -602,6 +612,12 @@ extern int send_group_sigqueue(int, stru
 extern int do_sigaction(int, const struct k_sigaction *, struct k_sigaction *);
 extern int do_sigaltstack(const stack_t __user *, stack_t __user *, unsigned long);
 
+/* container_of doesn't like arrays. */
+static inline struct task_groups *task_groups(gid_t *groups)
+{
+	return (void *)groups - offsetof(struct task_groups, groups);
+}
+
 /* These can be the second arg to send_sig_info/send_group_sig_info.  */
 #define SEND_SIG_NOINFO ((struct siginfo *) 0)
 #define SEND_SIG_PRIV	((struct siginfo *) 1)
@@ -678,6 +694,10 @@ extern int do_execve(char *, char __user
 extern long do_fork(unsigned long, unsigned long, struct pt_regs *, unsigned long, int __user *, int __user *);
 extern struct task_struct * copy_process(unsigned long, unsigned long, struct pt_regs *, unsigned long, int __user *, int __user *);
 
+/* Copy-on-write current groups to this size, if possible.  0 or -err. */
+extern int cow_current_groups(unsigned int ngroups);
+extern void release_groups(gid_t *groups);
+
 #ifdef CONFIG_SMP
 extern void wait_task_inactive(task_t * p);
 #else
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sunrpc/svcauth.h .12070-2.6.0-test6-more_groups_refcount/include/linux/sunrpc/svcauth.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sunrpc/svcauth.h	2003-09-22 09:47:41.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/linux/sunrpc/svcauth.h	2003-09-29 17:18:47.000000000 +1000
@@ -16,10 +16,12 @@
 #include <linux/sunrpc/cache.h>
 #include <linux/hash.h>
 
+#define SVC_CRED_NGROUPS	32
+
 struct svc_cred {
 	uid_t			cr_uid;
 	gid_t			cr_gid;
-	gid_t			cr_groups[NGROUPS];
+	gid_t			cr_groups[SVC_CRED_NGROUPS];
 };
 
 struct svc_rqst;		/* forward decl */
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sysctl.h .12070-2.6.0-test6-more_groups_refcount/include/linux/sysctl.h
--- .12070-2.6.0-test6-more_groups_refcount.pre/include/linux/sysctl.h	2003-09-22 10:28:13.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/include/linux/sysctl.h	2003-09-29 17:18:47.000000000 +1000
@@ -127,6 +127,7 @@ enum
 	KERN_PANIC_ON_OOPS=57,  /* int: whether we will panic on an oops */
 	KERN_HPPA_PWRSW=58,	/* int: hppa soft-power enable */
 	KERN_HPPA_UNALIGNED=59,	/* int: hppa unaligned-trap enable */
+	KERN_MAX_GROUPS=60,	/* int: setgroups limit */
 };
 
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/init/main.c .12070-2.6.0-test6-more_groups_refcount/init/main.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/init/main.c	2003-09-29 10:26:06.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/init/main.c	2003-09-29 17:18:47.000000000 +1000
@@ -594,7 +594,6 @@ static int init(void * unused)
 	 * The Bourne shell can be used instead of init if we are 
 	 * trying to recover a really broken machine.
 	 */
-
 	if (execute_command)
 		run_init_process(execute_command);
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/kernel/fork.c .12070-2.6.0-test6-more_groups_refcount/kernel/fork.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/kernel/fork.c	2003-09-29 10:26:06.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/kernel/fork.c	2003-09-29 17:18:47.000000000 +1000
@@ -85,6 +85,7 @@ void __put_task_struct(struct task_struc
 
 	security_task_free(tsk);
 	free_uid(tsk->user);
+	release_groups(tsk->groups);
 	free_task(tsk);
 }
 
@@ -817,6 +818,7 @@ struct task_struct *copy_process(unsigne
 
 	atomic_inc(&p->user->__count);
 	atomic_inc(&p->user->processes);
+	atomic_inc(&task_groups(p->groups)->usage);
 
 	/*
 	 * If multiple threads are within copy_process(), then this check
@@ -1063,6 +1065,7 @@ bad_fork_cleanup_put_domain:
 bad_fork_cleanup_count:
 	atomic_dec(&p->user->processes);
 	free_uid(p->user);
+	release_groups(p->groups);
 bad_fork_free:
 	free_task(p);
 	goto fork_out;
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/kernel/sys.c .12070-2.6.0-test6-more_groups_refcount/kernel/sys.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/kernel/sys.c	2003-09-29 10:26:06.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/kernel/sys.c	2003-09-29 17:18:47.000000000 +1000
@@ -1069,6 +1069,56 @@ out:
 	return err;
 }
 
+/* Never freed */
+struct task_groups init_groups = { .usage = ATOMIC_INIT(2) };
+
+int max_groups = 32;
+
+void release_groups(gid_t *groups)
+{
+	struct task_groups *tgrp = task_groups(groups);
+
+	if (atomic_dec_and_test(&tgrp->usage))
+		kfree(tgrp);
+}
+
+/* This does the actual copy and changeover.  Caller sets
+ * current->ngroups and changes group array if everything else goes ok. */
+static inline int alloc_new_groups(unsigned int ngroups)
+{
+	struct task_groups *tgrp;
+
+	if (ngroups < current->ngroups)
+		ngroups = current->ngroups;
+
+	tgrp = kmalloc(sizeof(*tgrp)+sizeof(tgrp->groups[0])*ngroups,GFP_KERNEL);
+	if (!tgrp)
+		return -ENOMEM;
+	atomic_set(&tgrp->usage, 1);
+
+	memcpy(tgrp->groups, current->groups,
+	       sizeof(tgrp->groups[0]) * current->ngroups);
+
+	release_groups(current->groups);
+	current->groups = tgrp->groups;
+	return 0;
+}
+
+/* Unshare and maybe enlarge current groups to this size, if possible.
+ * 0 or -err. */
+int cow_current_groups(unsigned int ngroups)
+{
+	if (ngroups > max_groups)
+		return -EINVAL;
+
+	/* Shared, or needs expansion? */
+	if (atomic_read(&task_groups(current->groups)->usage) > 1
+	    || ngroups > current->ngroups)
+		return alloc_new_groups(ngroups);
+
+	return 0;
+}
+
 /*
  * Supplementary group IDs
  */
@@ -1094,27 +1150,36 @@ asmlinkage long sys_getgroups(int gidset
 }
 
 /*
- *	SMP: Our groups are not shared. We can copy to/from them safely
+ *	SMP: Our groups are copy-on-write. We can copy to/from them safely
  *	without another task interfering.
  */
  
 asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist)
 {
-	gid_t groups[NGROUPS];
+	gid_t *groups;
 	int retval;
 
+	printk("sys_setgroups %u groups\n", gidsetsize);
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(gid_t)))
-		return -EFAULT;
+	retval = cow_current_groups(gidsetsize);
+	if (retval < 0)
+		return retval;
+	groups = kmalloc(sizeof(groups[0]) * gidsetsize, GFP_KERNEL);
+	if (!groups)
+		return -ENOMEM;
+	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(gid_t))) {
+		retval = -EFAULT;
+		goto out;
+	}
 	retval = security_task_setgroups(gidsetsize, groups);
 	if (retval)
-		return retval;
+		goto out;
 	memcpy(current->groups, groups, gidsetsize * sizeof(gid_t));
 	current->ngroups = gidsetsize;
-	return 0;
+out:
+	kfree(groups);
+	return retval;
 }
 
 static int supplemental_group_member(gid_t grp)
@@ -1434,6 +1499,7 @@ asmlinkage long sys_prctl(int option, un
 	return error;
 }
 
+EXPORT_SYMBOL(cow_current_groups);
 EXPORT_SYMBOL(notifier_chain_register);
 EXPORT_SYMBOL(notifier_chain_unregister);
 EXPORT_SYMBOL(notifier_call_chain);
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/kernel/sysctl.c .12070-2.6.0-test6-more_groups_refcount/kernel/sysctl.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/kernel/sysctl.c	2003-09-29 10:26:06.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/kernel/sysctl.c	2003-09-29 17:18:47.000000000 +1000
@@ -581,6 +581,14 @@ static ctl_table kern_table[] = {
 		.mode		= 0644,
 		.proc_handler	= &proc_dointvec,
 	},
+	{
+		.ctl_name	= KERN_MAX_GROUPS,
+		.procname	= "max_groups",
+		.data		= &max_groups,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= &proc_dointvec,
+	},
 	{ .ctl_name = 0 }
 };
 
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/kernel/uid16.c .12070-2.6.0-test6-more_groups_refcount/kernel/uid16.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/kernel/uid16.c	2003-09-22 10:07:19.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/kernel/uid16.c	2003-09-29 17:18:47.000000000 +1000
@@ -109,7 +109,6 @@ asmlinkage long sys_setfsgid16(old_gid_t
 
 asmlinkage long sys_getgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
 	int i,j;
 
 	if (gidsetsize < 0)
@@ -118,34 +117,48 @@ asmlinkage long sys_getgroups16(int gids
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(old_gid_t)*i))
+		if (!access_ok(VERIFY_WRITE, grouplist, sizeof(old_gid_t)*i))
 			return -EFAULT;
+		for(j=0;j<i;j++) {
+			old_gid_t group;
+			group = current->groups[j];
+			if (copy_to_user(grouplist+j, &group, sizeof(group)))
+				return -EFAULT;
+		}
 	}
 	return i;
 }
 
 asmlinkage long sys_setgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
-	gid_t new_groups[NGROUPS];
-	int i;
+	old_gid_t *groups;
+	gid_t *new_groups;
+	int i, ret;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
+
+	if ((ret = cow_current_groups(gidsetsize)) < 0)
+		return ret;
+	ret = -ENOMEM;
+	groups = kmalloc(sizeof(groups[0]) * gidsetsize, GFP_KERNEL);
+	new_groups = kmalloc(sizeof(new_groups[0]) * gidsetsize, GFP_KERNEL);
+	if (!groups || !new_groups)
+		goto out;
+	ret = -EFAULT;
 	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(old_gid_t)))
-		return -EFAULT;
+		goto out;
 	for (i = 0 ; i < gidsetsize ; i++)
 		new_groups[i] = (gid_t)groups[i];
-	i = security_task_setgroups(gidsetsize, new_groups);
-	if (i)
-		return i;
+	ret = security_task_setgroups(gidsetsize, new_groups);
+	if (ret)
+		goto out;
 	memcpy(current->groups, new_groups, gidsetsize * sizeof(gid_t));
 	current->ngroups = gidsetsize;
-	return 0;
+out:
+	kfree(groups);
+	kfree(new_groups);
+	return ret;
 }
 
 asmlinkage long sys_getuid16(void)
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .12070-2.6.0-test6-more_groups_refcount.pre/net/sunrpc/svcauth_unix.c .12070-2.6.0-test6-more_groups_refcount/net/sunrpc/svcauth_unix.c
--- .12070-2.6.0-test6-more_groups_refcount.pre/net/sunrpc/svcauth_unix.c	2003-09-22 10:23:05.000000000 +1000
+++ .12070-2.6.0-test6-more_groups_refcount/net/sunrpc/svcauth_unix.c	2003-09-29 17:18:47.000000000 +1000
@@ -434,11 +434,11 @@ svcauth_unix_accept(struct svc_rqst *rqs
 	if (slen > 16 || (len -= (slen + 2)*4) < 0)
 		goto badcred;
 	for (i = 0; i < slen; i++)
-		if (i < NGROUPS)
+		if (i < SVC_CRED_NGROUPS)
 			cred->cr_groups[i] = ntohl(svc_getu32(argv));
 		else
 			svc_getu32(argv);
-	if (i < NGROUPS)
+	if (i < SVC_CRED_NGROUPS)
 		cred->cr_groups[i] = NOGROUP;
 
 	if (svc_getu32(argv) != RPC_AUTH_NULL || svc_getu32(argv) != 0) {

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-09-29 23:30   ` Rusty Russell
@ 2003-09-30  4:11     ` Tim Hockin
  2003-10-01  7:29       ` Rusty Russell
  0 siblings, 1 reply; 21+ messages in thread
From: Tim Hockin @ 2003-09-30  4:11 UTC (permalink / raw)
  To: Rusty Russell
  Cc: Linus Torvalds, linux-kernel, torvalds, braam, neilb, David Meybohm

On Tue, Sep 30, 2003 at 09:30:07AM +1000, Rusty Russell wrote:
> > Why?
> 
> (Rusty points at Tim).
> 
> He has 10,000 groups.  Now me, I'm happy with the minimal fix.

I'm going to merge your thoughts and mine tomorrow and send it out.  Linus
suggested the array of pages approah is more sane, so I'm going to try for
it.  I'm going to comb through the diffs between your patch and mine.

> And worse, there are the intermediate kmallocs which would need to be
> fixed (thanks to Stephen Rothwell for pointing this out).  Fixing this
> would make it even uglier.

Specifically?  I think my patch gets all of those.  At least all the ones I
found.

> Here's an updated one (with David Meybohm's fix, too -- Thanks!),
> Rusty.

Can you elaborate on what this extra fix is?

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-09-30  4:11     ` Tim Hockin
@ 2003-10-01  7:29       ` Rusty Russell
  0 siblings, 0 replies; 21+ messages in thread
From: Rusty Russell @ 2003-10-01  7:29 UTC (permalink / raw)
  To: Tim Hockin
  Cc: Linus Torvalds, linux-kernel, torvalds, braam, neilb, David Meybohm, sfr

In message <20030929211155.A28089@hockin.org> you write:
> On Tue, Sep 30, 2003 at 09:30:07AM +1000, Rusty Russell wrote:
> > > Why?
> > 
> > (Rusty points at Tim).
> > 
> > He has 10,000 groups.  Now me, I'm happy with the minimal fix.
> 
> I'm going to merge your thoughts and mine tomorrow and send it out.  Linus
> suggested the array of pages approah is more sane, so I'm going to try for
> it.  I'm going to comb through the diffs between your patch and mine.

Keeping the groups array as an array is a feature.  IMHO, if there are
too many for a kmalloc, vmalloc fallback makes sense: it's
conceptually simple and not that much code.

Introducing an almost-vmalloc because Linus didn't like the vmalloc
just doesn't make sense, IMHO.

> > And worse, there are the intermediate kmallocs which would need to be
> > fixed (thanks to Stephen Rothwell for pointing this out).  Fixing this
> > would make it even uglier.
> 
> Specifically?  I think my patch gets all of those.  At least all the ones I
> found.

Yep, you got them, I didn't.  I am still hoping Stephen (CC'd) will
move the IA64, S390 and Sparc64 code into kernel/compat.c which will
shrink out patches.

> > Here's an updated one (with David Meybohm's fix, too -- Thanks!),
> > Rusty.
> 
> Can you elaborate on what this extra fix is?

I screwed up a free.

Cheers,
Rusty.
--
  Anyone who quotes me in their sig is an idiot. -- Rusty Russell.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-02  2:09               ` Rusty Russell
  2003-10-02  4:39                 ` Tim Hockin
@ 2003-10-02  9:10                 ` David S. Miller
  1 sibling, 0 replies; 21+ messages in thread
From: David S. Miller @ 2003-10-02  9:10 UTC (permalink / raw)
  To: Rusty Russell
  Cc: torvalds, zaitcev, braam, thockin, linux-kernel, schwidefsky, davidm

On Thu, 02 Oct 2003 12:09:19 +1000
Rusty Russell <rusty@rustcorp.com.au> wrote:

> Sure.  First step is to put this function in kernel/compat.c where it
> belongs.  The identical function is already in kernel/uid16.c, but
> defining CONFIG_UID16 does not work for these platforms (which only
> want 16-bit uids for the 32-bit syscalls).

This is correct.  To be more precise, we also need it for the compat
ABI binary format handlers too (elf32, etc.)

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-02  2:09               ` Rusty Russell
@ 2003-10-02  4:39                 ` Tim Hockin
  2003-10-02  9:10                 ` David S. Miller
  1 sibling, 0 replies; 21+ messages in thread
From: Tim Hockin @ 2003-10-02  4:39 UTC (permalink / raw)
  To: Rusty Russell
  Cc: Linus Torvalds, Pete Zaitcev, braam, Linux Kernel mailing list,
	davem, schwidefsky, davidm

On Thu, Oct 02, 2003 at 12:09:19PM +1000, Rusty Russell wrote:
> > Augh. It also makes code even uglier than it used to be:
> 
> Sure.  First step is to put this function in kernel/compat.c where it
> belongs.  The identical function is already in kernel/uid16.c, but
> defining CONFIG_UID16 does not work for these platforms (which only
> want 16-bit uids for the 32-bit syscalls).
 

That works, I guess.  See my other message today (sorry, I forgot to CC: you
Rusty) with a different approach.  It makes uid16.c actually always be used.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-02  2:25             ` Rusty Russell
@ 2003-10-02  2:46               ` Pete Zaitcev
  0 siblings, 0 replies; 21+ messages in thread
From: Pete Zaitcev @ 2003-10-02  2:46 UTC (permalink / raw)
  To: Rusty Russell
  Cc: Tim Hockin, Linus Torvalds, Pete Zaitcev, braam,
	Linux Kernel mailing list

> > Groups are sorted and b-searched for efficiency.
> 
> That's where I feel Linus' comment about catering to stupidity comes
> in 8)

I'm in this discussion for NFS groups only. The difficulty
is that NFS needs to sort groups differently, LRU by usage
(instead of numerical value). Therefore, two ways to index
that data set are unavoidable. Two b-trees, perhaps.

-- Pete

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-01 18:46           ` Tim Hockin
  2003-10-01 19:22             ` Linus Torvalds
@ 2003-10-02  2:25             ` Rusty Russell
  2003-10-02  2:46               ` Pete Zaitcev
  1 sibling, 1 reply; 21+ messages in thread
From: Rusty Russell @ 2003-10-02  2:25 UTC (permalink / raw)
  To: Tim Hockin; +Cc: Linus Torvalds, Pete Zaitcev, braam, Linux Kernel mailing list

In message <20031001184610.GA25716@hockin.org> you write:
> Because groups are stored in a 2-D array, the GRP_AT()
> macro was added to allow simple 1-D style indexing.

I respectfully disagree with this approach.  I don't understand why
Linus would think that 4 lines of code to fallback to vmalloc is
"ugly", when doing your own array of pages means you have to change
all the iterators and do magic when copying to and from userspace,
since it's no longer a simple array.

Tim and I have had this discussion, but I would benifit from Linus'
wisdom here.

> Groups are sorted and b-searched for efficiency.

That's where I feel Linus' comment about catering to stupidity comes
in 8)

> This patch modifies /proc/pid/status to only display the first 32 groups.

Ouch, good catch, but this is broken in both our patches 8( I think we
should bump the refcount before reading it to avoid accessing freed
memory.  Now, they have to be root to access that anyway, but it just
makes me uncomfortable.

> This patch removes the NGROUPS define from all architectures as well as
> NGROUPS_MAX.

This will break glibc build (trivial to fix), but I agree: leaving
NGROUPS_MAX is too likely to cause people to use it inside the kernel
like intermezzo does.

> This patch changes the security API to check a struct group_info, rather
> than an array of gid_t.

This is why I prefer the "fall back to vmalloc" approach.

> This patch totally horks Intermezzo.

That's a feature 8)

Thanks,
Rusty.
--
  Anyone who quotes me in their sig is an idiot. -- Rusty Russell.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-01 19:22             ` Linus Torvalds
  2003-10-01 20:29               ` Tim Hockin
@ 2003-10-02  2:09               ` Rusty Russell
  2003-10-02  4:39                 ` Tim Hockin
  2003-10-02  9:10                 ` David S. Miller
  1 sibling, 2 replies; 21+ messages in thread
From: Rusty Russell @ 2003-10-02  2:09 UTC (permalink / raw)
  To: Linus Torvalds
  Cc: Pete Zaitcev, braam, Tim Hockin, Linux Kernel mailing list,
	davem, schwidefsky, davidm

In message <Pine.LNX.4.44.0310011216530.24564-100000@home.osdl.org> you write:
> 
> On Wed, 1 Oct 2003, Tim Hockin wrote:
> > 
> > This patch touches all the compat code in the 64-bit architectures.
> > These files have a LOT of duplicated code from uid16.c.  I did not try to
> > reduce duplicated code, and instead followed suit.
> 
> Augh. It also makes code even uglier than it used to be:

Sure.  First step is to put this function in kernel/compat.c where it
belongs.  The identical function is already in kernel/uid16.c, but
defining CONFIG_UID16 does not work for these platforms (which only
want 16-bit uids for the 32-bit syscalls).

I discussed this with DaveM and David Mosberger last week.

Naturally, untested (damn obscure archs!), but ideally this changes
nothing, just makes a groups patch simpler.

Cheers,
Rusty.
--
  Anyone who quotes me in their sig is an idiot. -- Rusty Russell.

Name: Put 16-bit GID code in kernel/compat.c
Author: Rusty Russell
Status: Trivial

D: IA64, S390 and Sparc64 need 16-bit setgroups and getgroups calls,
D: but can't define CONFIG_UID16 since they only want 16-bit gid/uid
D: compatibility for 32-bit system calls.  Centralize their versions
D: of setgroups16 and getgroups16 into kernel/compat.c, under a
D: NEED_COMPAT_GROUPS16 define.

diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/arch/ia64/ia32/ia32_entry.S .4448-linux-2.6.0-test6-bk3.updated/arch/ia64/ia32/ia32_entry.S
--- .4448-linux-2.6.0-test6-bk3/arch/ia64/ia32/ia32_entry.S	2003-09-22 10:26:21.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/arch/ia64/ia32/ia32_entry.S	2003-10-02 12:00:03.000000000 +1000
@@ -266,8 +266,8 @@ ia32_syscall_table:
 	data8 compat_sys_getrusage
 	data8 sys32_gettimeofday
 	data8 sys32_settimeofday
-	data8 sys32_getgroups16	  /* 80 */
-	data8 sys32_setgroups16
+	data8 compat_getgroups16  /* 80 */
+	data8 compat_setgroups16
 	data8 sys32_old_select
 	data8 sys_symlink
 	data8 sys32_ni_syscall
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/arch/ia64/ia32/sys_ia32.c .4448-linux-2.6.0-test6-bk3.updated/arch/ia64/ia32/sys_ia32.c
--- .4448-linux-2.6.0-test6-bk3/arch/ia64/ia32/sys_ia32.c	2003-09-29 10:25:17.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/arch/ia64/ia32/sys_ia32.c	2003-10-02 11:56:42.000000000 +1000
@@ -2426,46 +2426,6 @@ sys32_lseek (unsigned int fd, int offset
 	return sys_lseek(fd, offset, whence);
 }
 
-extern asmlinkage long sys_getgroups (int gidsetsize, gid_t *grouplist);
-
-asmlinkage long
-sys32_getgroups16 (int gidsetsize, short *grouplist)
-{
-	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
-	int ret, i;
-
-	set_fs(KERNEL_DS);
-	ret = sys_getgroups(gidsetsize, gl);
-	set_fs(old_fs);
-
-	if (gidsetsize && ret > 0 && ret <= NGROUPS)
-		for (i = 0; i < ret; i++, grouplist++)
-			if (put_user(gl[i], grouplist))
-				return -EFAULT;
-	return ret;
-}
-
-extern asmlinkage long sys_setgroups (int gidsetsize, gid_t *grouplist);
-
-asmlinkage long
-sys32_setgroups16 (int gidsetsize, short *grouplist)
-{
-	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
-	int ret, i;
-
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	for (i = 0; i < gidsetsize; i++, grouplist++)
-		if (get_user(gl[i], grouplist))
-			return -EFAULT;
-	set_fs(KERNEL_DS);
-	ret = sys_setgroups(gidsetsize, gl);
-	set_fs(old_fs);
-	return ret;
-}
-
 asmlinkage long
 sys32_truncate64 (unsigned int path, unsigned int len_lo, unsigned int len_hi)
 {
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/arch/s390/kernel/compat_linux.c .4448-linux-2.6.0-test6-bk3.updated/arch/s390/kernel/compat_linux.c
--- .4448-linux-2.6.0-test6-bk3/arch/s390/kernel/compat_linux.c	2003-09-29 10:25:21.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/arch/s390/kernel/compat_linux.c	2003-10-02 12:04:34.000000000 +1000
@@ -189,42 +189,6 @@ asmlinkage long sys32_setfsgid16(u16 gid
 	return sys_setfsgid((gid_t)gid);
 }
 
-asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
-{
-	u16 groups[NGROUPS];
-	int i,j;
-
-	if (gidsetsize < 0)
-		return -EINVAL;
-	i = current->ngroups;
-	if (gidsetsize) {
-		if (i > gidsetsize)
-			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
-			return -EFAULT;
-	}
-	return i;
-}
-
-asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
-{
-	u16 groups[NGROUPS];
-	int i;
-
-	if (!capable(CAP_SETGID))
-		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
-}
-
 asmlinkage long sys32_getuid16(void)
 {
 	return high2lowuid(current->uid);
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/arch/s390/kernel/compat_wrapper.S .4448-linux-2.6.0-test6-bk3.updated/arch/s390/kernel/compat_wrapper.S
--- .4448-linux-2.6.0-test6-bk3/arch/s390/kernel/compat_wrapper.S	2003-09-22 10:08:17.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/arch/s390/kernel/compat_wrapper.S	2003-10-02 12:04:48.000000000 +1000
@@ -344,13 +344,13 @@ sys32_settimeofday_wrapper:
 sys32_getgroups16_wrapper:
 	lgfr	%r2,%r2			# int
 	llgtr	%r3,%r3			# __kernel_old_gid_emu31_t *
-	jg	sys32_getgroups16	# branch to system call
+	jg	compat_getgroups16	# branch to system call
 
 	.globl  sys32_setgroups16_wrapper 
 sys32_setgroups16_wrapper:
 	lgfr	%r2,%r2			# int
 	llgtr	%r3,%r3			# __kernel_old_gid_emu31_t *
-	jg	sys32_setgroups16	# branch to system call
+	jg	compat_setgroups16	# branch to system call
 
 	.globl  sys32_symlink_wrapper 
 sys32_symlink_wrapper:
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/arch/sparc64/kernel/sys_sparc32.c .4448-linux-2.6.0-test6-bk3.updated/arch/sparc64/kernel/sys_sparc32.c
--- .4448-linux-2.6.0-test6-bk3/arch/sparc64/kernel/sys_sparc32.c	2003-09-29 10:25:22.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/arch/sparc64/kernel/sys_sparc32.c	2003-10-02 12:03:54.000000000 +1000
@@ -206,42 +206,6 @@ asmlinkage long sys32_setfsgid16(u16 gid
 	return sys_setfsgid((gid_t)gid);
 }
 
-asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
-{
-	u16 groups[NGROUPS];
-	int i,j;
-
-	if (gidsetsize < 0)
-		return -EINVAL;
-	i = current->ngroups;
-	if (gidsetsize) {
-		if (i > gidsetsize)
-			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
-			return -EFAULT;
-	}
-	return i;
-}
-
-asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
-{
-	u16 groups[NGROUPS];
-	int i;
-
-	if (!capable(CAP_SETGID))
-		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
-}
-
 asmlinkage long sys32_getuid16(void)
 {
 	return high2lowuid(current->uid);
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/arch/sparc64/kernel/systbls.S .4448-linux-2.6.0-test6-bk3.updated/arch/sparc64/kernel/systbls.S
--- .4448-linux-2.6.0-test6-bk3/arch/sparc64/kernel/systbls.S	2003-09-22 10:27:56.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/arch/sparc64/kernel/systbls.S	2003-10-02 12:03:40.000000000 +1000
@@ -34,8 +34,8 @@ sys_call_table32:
 /*60*/	.word sys_umask, sys_chroot, compat_sys_newfstat, sys_fstat64, sys_getpagesize
 	.word sys_msync, sys_vfork, sys32_pread64, sys32_pwrite64, sys_geteuid
 /*70*/	.word sys_getegid, sys32_mmap, sys_setreuid, sys_munmap, sys_mprotect
-	.word sys_madvise, sys_vhangup, sys32_truncate64, sys_mincore, sys32_getgroups16
-/*80*/	.word sys32_setgroups16, sys_getpgrp, sys_setgroups, compat_sys_setitimer, sys32_ftruncate64
+	.word sys_madvise, sys_vhangup, sys32_truncate64, sys_mincore, compat_getgroups16
+/*80*/	.word compat_setgroups16, sys_getpgrp, sys_setgroups, compat_sys_setitimer, sys32_ftruncate64
 	.word sys_swapon, compat_sys_getitimer, sys_setuid, sys_sethostname, sys_setgid
 /*90*/	.word sys_dup2, sys_setfsuid, compat_sys_fcntl, sys32_select, sys_setfsgid
 	.word sys_fsync, sys_setpriority32, sys_nis_syscall, sys_nis_syscall, sys_nis_syscall
@@ -168,8 +168,8 @@ sunos_sys_table:
 	.word sunos_nosys, sunos_sbrk, sunos_sstk
 	.word sunos_mmap, sunos_vadvise, sys_munmap
 	.word sys_mprotect, sys_madvise, sys_vhangup
-	.word sunos_nosys, sys_mincore, sys32_getgroups16
-	.word sys32_setgroups16, sys_getpgrp, sunos_setpgrp
+	.word sunos_nosys, sys_mincore, compat_getgroups16
+	.word compat_setgroups16, sys_getpgrp, sunos_setpgrp
 	.word compat_sys_setitimer, sunos_nosys, sys_swapon
 	.word compat_sys_getitimer, sys_gethostname, sys_sethostname
 	.word sunos_getdtablesize, sys_dup2, sunos_nop
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/include/asm-ia64/compat.h .4448-linux-2.6.0-test6-bk3.updated/include/asm-ia64/compat.h
--- .4448-linux-2.6.0-test6-bk3/include/asm-ia64/compat.h	2003-09-22 10:26:44.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/include/asm-ia64/compat.h	2003-10-02 11:58:13.000000000 +1000
@@ -136,4 +136,6 @@ compat_alloc_user_space (long len)
 	return (void *) (((regs->r12 & 0xffffffff) & -16) - len);
 }
 
+#define NEED_COMPAT_GROUPS16
+
 #endif /* _ASM_IA64_COMPAT_H */
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/include/asm-s390/compat.h .4448-linux-2.6.0-test6-bk3.updated/include/asm-s390/compat.h
--- .4448-linux-2.6.0-test6-bk3/include/asm-s390/compat.h	2003-09-22 10:22:59.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/include/asm-s390/compat.h	2003-10-02 12:06:26.000000000 +1000
@@ -134,4 +134,6 @@ static inline void *compat_alloc_user_sp
 	return (void *) (stack - len);
 }
 
+#define NEED_COMPAT_GROUPS16
+
 #endif /* _ASM_S390X_COMPAT_H */
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/include/asm-sparc64/compat.h .4448-linux-2.6.0-test6-bk3.updated/include/asm-sparc64/compat.h
--- .4448-linux-2.6.0-test6-bk3/include/asm-sparc64/compat.h	2003-09-22 10:23:00.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/include/asm-sparc64/compat.h	2003-10-02 12:04:23.000000000 +1000
@@ -132,4 +132,5 @@ static __inline__ void *compat_alloc_use
 	return (void *) (usp - len);
 }
 
+#define NEED_COMPAT_GROUPS16
 #endif /* _ASM_SPARC64_COMPAT_H */
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/include/linux/compat.h .4448-linux-2.6.0-test6-bk3.updated/include/linux/compat.h
--- .4448-linux-2.6.0-test6-bk3/include/linux/compat.h	2003-09-22 10:27:37.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/include/linux/compat.h	2003-10-02 11:59:39.000000000 +1000
@@ -90,5 +90,10 @@ struct compat_statfs64 {
 	__u32 f_spare[5];
 };
 
+#ifdef NEED_COMPAT_GROUPS16
+asmlinkage long compat_getgroups16(int gidsetsize, short *grouplist);
+asmlinkage long compat_setgroups16(int gidsetsize, short *grouplist);
+#endif /* NEED_COMPAT_GROUPS16 */
+
 #endif /* CONFIG_COMPAT */
 #endif /* _LINUX_COMPAT_H */
diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .4448-linux-2.6.0-test6-bk3/kernel/compat.c .4448-linux-2.6.0-test6-bk3.updated/kernel/compat.c
--- .4448-linux-2.6.0-test6-bk3/kernel/compat.c	2003-09-22 10:28:13.000000000 +1000
+++ .4448-linux-2.6.0-test6-bk3.updated/kernel/compat.c	2003-10-02 11:58:00.000000000 +1000
@@ -563,3 +563,44 @@ long compat_clock_nanosleep(clockid_t wh
 
 /* timer_create is architecture specific because it needs sigevent conversion */
 
+#ifdef NEED_COMPAT_GROUPS16
+extern asmlinkage long sys_getgroups(int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+compat_getgroups16(int gidsetsize, short *grouplist)
+{
+	mm_segment_t old_fs = get_fs();
+	gid_t gl[NGROUPS];
+	int ret, i;
+
+	set_fs(KERNEL_DS);
+	ret = sys_getgroups(gidsetsize, gl);
+	set_fs(old_fs);
+
+	if (gidsetsize && ret > 0 && ret <= NGROUPS)
+		for (i = 0; i < ret; i++, grouplist++)
+			if (put_user(gl[i], grouplist))
+				return -EFAULT;
+	return ret;
+}
+
+extern asmlinkage long sys_setgroups(int gidsetsize, gid_t *grouplist);
+
+asmlinkage long
+compat_setgroups16(int gidsetsize, short *grouplist)
+{
+	mm_segment_t old_fs = get_fs();
+	gid_t gl[NGROUPS];
+	int ret, i;
+
+	if ((unsigned) gidsetsize > NGROUPS)
+		return -EINVAL;
+	for (i = 0; i < gidsetsize; i++, grouplist++)
+		if (get_user(gl[i], grouplist))
+			return -EFAULT;
+	set_fs(KERNEL_DS);
+	ret = sys_setgroups(gidsetsize, gl);
+	set_fs(old_fs);
+	return ret;
+}
+#endif /* NEED_COMPAT_GROUPS16 */

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-01 20:46                 ` Linus Torvalds
  2003-10-01 21:12                   ` Tim Hockin
@ 2003-10-01 21:16                   ` Tim Hockin
  1 sibling, 0 replies; 21+ messages in thread
From: Tim Hockin @ 2003-10-01 21:16 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Linux Kernel mailing list

[-- Attachment #1: Type: text/plain, Size: 42 bytes --]

forgot the patch again.  Bad week for me.

[-- Attachment #2: ngroups-2.6.0-test6-3.diff --]
[-- Type: text/plain, Size: 34371 bytes --]

===== arch/ia64/ia32/sys_ia32.c 1.78 vs edited =====
--- 1.78/arch/ia64/ia32/sys_ia32.c	Mon Sep 22 21:16:30 2003
+++ edited/arch/ia64/ia32/sys_ia32.c	Wed Oct  1 13:58:23 2003
@@ -2426,44 +2426,85 @@
 	return sys_lseek(fd, offset, whence);
 }
 
-extern asmlinkage long sys_getgroups (int gidsetsize, gid_t *grouplist);
+static int
+groups16_to_user(short *grouplist, struct group_info *info)
+{
+	int i;
+	short group;
+
+	if (info->ngroups > TASK_SIZE/sizeof(group))
+		return -EFAULT;
+	if (!access_ok(grouplist, info->ngroups*sizeof(group)))
+		return -EFAULT;
+
+	for (i = 0; i < info->ngroups; i++) {
+		group = (short)GROUP_AT(info, i);
+		if (__put_user(group, grouplist+i))
+			return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int
+groups16_from_user(struct group_info *info, short *grouplist)
+{
+	int i;
+	short group;
+
+	if (info->ngroups > TASK_SIZE/sizeof(group))
+		return -EFAULT;
+	if (!access_ok(grouplist, info->ngroups*sizeof(group)))
+		return -EFAULT;
+
+	for (i = 0; i < info->ngroups; i++) {
+		if (__get_user(group, grouplist+i))
+			return  -EFAULT;
+		GROUP_AT(info, i) = (gid_t)group;
+	}
+
+	return 0;
+}
 
 asmlinkage long
 sys32_getgroups16 (int gidsetsize, short *grouplist)
 {
-	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
-	int ret, i;
-
-	set_fs(KERNEL_DS);
-	ret = sys_getgroups(gidsetsize, gl);
-	set_fs(old_fs);
-
-	if (gidsetsize && ret > 0 && ret <= NGROUPS)
-		for (i = 0; i < ret; i++, grouplist++)
-			if (put_user(gl[i], grouplist))
-				return -EFAULT;
-	return ret;
-}
+	int i;
 
-extern asmlinkage long sys_setgroups (int gidsetsize, gid_t *grouplist);
+	if (gidsetsize < 0)
+		return -EINVAL;
+	i = current->group_info->ngroups;
+	if (gidsetsize) {
+		if (i > gidsetsize)
+			return -EINVAL;
+		if (groups16_to_user(grouplist, current->group_info))
+			return -EFAULT;
+	}
+	return i;
+}
 
 asmlinkage long
 sys32_setgroups16 (int gidsetsize, short *grouplist)
 {
-	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
-	int ret, i;
+	struct group_info *new_info;
+	int retval;
 
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	for (i = 0; i < gidsetsize; i++, grouplist++)
-		if (get_user(gl[i], grouplist))
-			return -EFAULT;
-	set_fs(KERNEL_DS);
-	ret = sys_setgroups(gidsetsize, gl);
-	set_fs(old_fs);
-	return ret;
+	if (!capable(CAP_SETGID))
+		return -EPERM;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
+
+	return retval;
 }
 
 asmlinkage long
===== arch/mips/kernel/sysirix.c 1.18 vs edited =====
--- 1.18/arch/mips/kernel/sysirix.c	Tue Sep 23 19:34:27 2003
+++ edited/arch/mips/kernel/sysirix.c	Tue Sep 30 18:32:00 2003
@@ -368,7 +368,7 @@
 			retval = HZ;
 			goto out;
 		case 4:
-			retval = NGROUPS;
+			retval = INT_MAX;
 			goto out;
 		case 5:
 			retval = NR_OPEN;
===== arch/s390/kernel/compat_linux.c 1.9 vs edited =====
--- 1.9/arch/s390/kernel/compat_linux.c	Thu Sep 25 11:33:23 2003
+++ edited/arch/s390/kernel/compat_linux.c	Wed Oct  1 13:58:28 2003
@@ -189,20 +189,55 @@
 	return sys_setfsgid((gid_t)gid);
 }
 
+static int groups16_to_user(u16 *grouplist, struct group_info *info)
+{
+	int i;
+	u16 group;
+
+	if (info->ngroups > TASK_SIZE/sizeof(group))
+		return -EFAULT;
+	if (!access_ok(grouplist, info->ngroups*sizeof(group)))
+		return -EFAULT;
+
+	for (i = 0; i < info->ngroups; i++) {
+		group = (u16)GROUP_AT(info, i);
+		if (__put_user(group, grouplist+i))
+			return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int groups16_from_user(struct group_info *info, u16 *grouplist)
+{
+	int i;
+	u16 group;
+
+	if (info->ngroups > TASK_SIZE/sizeof(group))
+		return -EFAULT;
+	if (!access_ok(grouplist, info->ngroups*sizeof(group)))
+		return -EFAULT;
+
+	for (i = 0; i < info->ngroups; i++) {
+		if (__get_user(group, grouplist+i))
+			return  -EFAULT;
+		GROUP_AT(info, i) = (gid_t)group;
+	}
+
+	return 0;
+}
+
 asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	int i;
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
+		if (groups16_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
@@ -210,19 +245,25 @@
 
 asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	struct group_info *new_info;
+	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
+
+	return retval;
 }
 
 asmlinkage long sys32_getuid16(void)
===== arch/sparc/kernel/sys_sunos.c 1.26 vs edited =====
--- 1.26/arch/sparc/kernel/sys_sunos.c	Tue Aug 26 09:25:41 2003
+++ edited/arch/sparc/kernel/sys_sunos.c	Tue Sep 30 18:32:00 2003
@@ -896,7 +896,7 @@
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = INT_MAX;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
===== arch/sparc64/kernel/sys_sparc32.c 1.83 vs edited =====
--- 1.83/arch/sparc64/kernel/sys_sparc32.c	Mon Sep 22 21:16:30 2003
+++ edited/arch/sparc64/kernel/sys_sparc32.c	Wed Oct  1 13:58:32 2003
@@ -206,20 +206,55 @@
 	return sys_setfsgid((gid_t)gid);
 }
 
+static int groups16_to_user(u16 *grouplist, struct group_info *info)
+{
+	int i;
+	u16 group;
+
+	if (info->ngroups > TASK_SIZE/sizeof(group))
+		return -EFAULT;
+	if (!access_ok(grouplist, info->ngroups*sizeof(group)))
+		return -EFAULT;
+
+	for (i = 0; i < info->ngroups; i++) {
+		group = (u16)GROUP_AT(info, i);
+		if (__put_user(group, grouplist+i))
+			return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int groups16_from_user(struct group_info *info, u16 *grouplist)
+{
+	int i;
+	u16 group;
+
+	if (info->ngroups > TASK_SIZE/sizeof(group))
+		return -EFAULT;
+	if (!access_ok(grouplist, info->ngroups*sizeof(group)))
+		return -EFAULT;
+
+	for (i = 0; i < info->ngroups; i++) {
+		if (__get_user(group, grouplist+i))
+			return  -EFAULT;
+		GROUP_AT(info, i) = (gid_t)group;
+	}
+
+	return 0;
+}
+
 asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	int i;
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
+		if (groups16_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
@@ -227,19 +262,25 @@
 
 asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	struct group_info *new_info;
+	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
+
+	return retval;
 }
 
 asmlinkage long sys32_getuid16(void)
===== arch/sparc64/kernel/sys_sunos32.c 1.35 vs edited =====
--- 1.35/arch/sparc64/kernel/sys_sunos32.c	Tue Aug 26 09:25:41 2003
+++ edited/arch/sparc64/kernel/sys_sunos32.c	Tue Sep 30 18:32:00 2003
@@ -859,7 +859,7 @@
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = INT_MAX;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
===== arch/sparc64/solaris/misc.c 1.18 vs edited =====
--- 1.18/arch/sparc64/solaris/misc.c	Sun Sep 21 14:49:52 2003
+++ edited/arch/sparc64/solaris/misc.c	Tue Sep 30 18:32:00 2003
@@ -341,7 +341,7 @@
 asmlinkage int solaris_sysconf(int id)
 {
 	switch (id) {
-	case SOLARIS_CONFIG_NGROUPS:	return NGROUPS_MAX;
+	case SOLARIS_CONFIG_NGROUPS:	return INT_MAX;
 	case SOLARIS_CONFIG_CHILD_MAX:	return CHILD_MAX;
 	case SOLARIS_CONFIG_OPEN_FILES:	return OPEN_MAX;
 	case SOLARIS_CONFIG_POSIX_VER:	return 199309;
===== fs/nfsd/auth.c 1.2 vs edited =====
--- 1.2/fs/nfsd/auth.c	Tue Jun 17 16:31:29 2003
+++ edited/fs/nfsd/auth.c	Tue Sep 30 18:32:00 2003
@@ -10,12 +10,15 @@
 #include <linux/sunrpc/svcauth.h>
 #include <linux/nfsd/nfsd.h>
 
+extern asmlinkage long sys_setgroups(int gidsetsize, gid_t *grouplist);
+
 #define	CAP_NFSD_MASK (CAP_FS_MASK|CAP_TO_MASK(CAP_SYS_RESOURCE))
 void
 nfsd_setuser(struct svc_rqst *rqstp, struct svc_export *exp)
 {
 	struct svc_cred	*cred = &rqstp->rq_cred;
 	int		i;
+	gid_t		groups[SVC_CRED_NGROUPS];
 
 	if (exp->ex_flags & NFSEXP_ALLSQUASH) {
 		cred->cr_uid = exp->ex_anon_uid;
@@ -26,7 +29,7 @@
 			cred->cr_uid = exp->ex_anon_uid;
 		if (!cred->cr_gid)
 			cred->cr_gid = exp->ex_anon_gid;
-		for (i = 0; i < NGROUPS; i++)
+		for (i = 0; i < SVC_CRED_NGROUPS; i++)
 			if (!cred->cr_groups[i])
 				cred->cr_groups[i] = exp->ex_anon_gid;
 	}
@@ -39,13 +42,13 @@
 		current->fsgid = cred->cr_gid;
 	else
 		current->fsgid = exp->ex_anon_gid;
-	for (i = 0; i < NGROUPS; i++) {
+	for (i = 0; i < SVC_CRED_NGROUPS; i++) {
 		gid_t group = cred->cr_groups[i];
 		if (group == (gid_t) NOGROUP)
 			break;
-		current->groups[i] = group;
+		groups[i] = group;
 	}
-	current->ngroups = i;
+	sys_setgroups(i, groups);
 
 	if ((cred->cr_uid)) {
 		cap_t(current->cap_effective) &= ~CAP_NFSD_MASK;
===== fs/nfsd/nfs4state.c 1.20 vs edited =====
--- 1.20/fs/nfsd/nfs4state.c	Mon Sep 22 16:11:52 2003
+++ edited/fs/nfsd/nfs4state.c	Tue Sep 30 18:32:00 2003
@@ -244,7 +244,7 @@
 
 	target->cr_uid = source->cr_uid;
 	target->cr_gid = source->cr_gid;
-	for(i = 0; i < NGROUPS; i++)
+	for(i = 0; i < SVC_CRED_NGROUPS; i++)
 		target->cr_groups[i] = source->cr_groups[i];
 }
 
===== fs/proc/array.c 1.52 vs edited =====
--- 1.52/fs/proc/array.c	Tue Sep 23 18:39:53 2003
+++ edited/fs/proc/array.c	Wed Oct  1 13:58:37 2003
@@ -176,8 +176,8 @@
 		p->files ? p->files->max_fds : 0);
 	task_unlock(p);
 
-	for (g = 0; g < p->ngroups; g++)
-		buffer += sprintf(buffer, "%d ", p->groups[g]);
+	for (g = 0; g < min(p->group_info->ngroups,NGROUPS_SMALL); g++)
+		buffer += sprintf(buffer, "%d ", GROUP_AT(p->group_info,g));
 
 	buffer += sprintf(buffer, "\n");
 	return buffer;
===== include/asm-alpha/param.h 1.2 vs edited =====
--- 1.2/include/asm-alpha/param.h	Thu Aug  8 12:28:02 2002
+++ edited/include/asm-alpha/param.h	Tue Sep 30 18:32:00 2003
@@ -19,10 +19,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-arm/param.h 1.5 vs edited =====
--- 1.5/include/asm-arm/param.h	Wed Sep  3 10:17:57 2003
+++ edited/include/asm-arm/param.h	Tue Sep 30 18:32:00 2003
@@ -26,10 +26,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-arm26/param.h 1.1 vs edited =====
--- 1.1/include/asm-arm26/param.h	Wed Jun  4 04:14:10 2003
+++ edited/include/asm-arm26/param.h	Tue Sep 30 18:32:00 2003
@@ -22,10 +22,6 @@
 # define HZ		100
 #endif
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-cris/param.h 1.2 vs edited =====
--- 1.2/include/asm-cris/param.h	Thu Nov  7 01:29:17 2002
+++ edited/include/asm-cris/param.h	Tue Sep 30 18:32:00 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-h8300/param.h 1.1 vs edited =====
--- 1.1/include/asm-h8300/param.h	Sun Feb 16 16:01:58 2003
+++ edited/include/asm-h8300/param.h	Tue Sep 30 18:32:00 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-i386/param.h 1.2 vs edited =====
--- 1.2/include/asm-i386/param.h	Mon Jul  1 14:41:36 2002
+++ edited/include/asm-i386/param.h	Tue Sep 30 18:32:00 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ia64/param.h 1.4 vs edited =====
--- 1.4/include/asm-ia64/param.h	Wed Sep 17 18:59:37 2003
+++ edited/include/asm-ia64/param.h	Tue Sep 30 18:32:00 2003
@@ -10,10 +10,6 @@
 
 #define EXEC_PAGESIZE	65536
 
-#ifndef NGROUPS
-# define NGROUPS	32
-#endif
-
 #ifndef NOGROUP
 # define NOGROUP	(-1)
 #endif
===== include/asm-m68k/param.h 1.2 vs edited =====
--- 1.2/include/asm-m68k/param.h	Mon Jul  8 05:53:12 2002
+++ edited/include/asm-m68k/param.h	Tue Sep 30 18:32:00 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-m68knommu/param.h 1.1 vs edited =====
--- 1.1/include/asm-m68knommu/param.h	Fri Nov  1 08:37:46 2002
+++ edited/include/asm-m68knommu/param.h	Tue Sep 30 18:32:00 2003
@@ -44,10 +44,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-mips/param.h 1.3 vs edited =====
--- 1.3/include/asm-mips/param.h	Mon Apr 14 20:10:06 2003
+++ edited/include/asm-mips/param.h	Tue Sep 30 18:32:00 2003
@@ -33,10 +33,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-parisc/param.h 1.2 vs edited =====
--- 1.2/include/asm-parisc/param.h	Mon Oct 28 02:33:42 2002
+++ edited/include/asm-parisc/param.h	Tue Sep 30 18:32:00 2003
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ppc/param.h 1.6 vs edited =====
--- 1.6/include/asm-ppc/param.h	Tue Jan  7 11:45:19 2003
+++ edited/include/asm-ppc/param.h	Tue Sep 30 18:32:00 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ppc64/param.h 1.2 vs edited =====
--- 1.2/include/asm-ppc64/param.h	Wed Jul 17 23:18:40 2002
+++ edited/include/asm-ppc64/param.h	Tue Sep 30 18:32:00 2003
@@ -20,10 +20,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-s390/param.h 1.3 vs edited =====
--- 1.3/include/asm-s390/param.h	Fri Oct  4 09:14:42 2002
+++ edited/include/asm-s390/param.h	Tue Sep 30 18:32:00 2003
@@ -21,10 +21,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sh/param.h 1.2 vs edited =====
--- 1.2/include/asm-sh/param.h	Tue May 27 15:48:59 2003
+++ edited/include/asm-sh/param.h	Tue Sep 30 18:32:00 2003
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sparc/param.h 1.2 vs edited =====
--- 1.2/include/asm-sparc/param.h	Fri Jul 12 15:54:40 2002
+++ edited/include/asm-sparc/param.h	Tue Sep 30 18:32:00 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sparc64/param.h 1.2 vs edited =====
--- 1.2/include/asm-sparc64/param.h	Fri Jul 12 15:54:40 2002
+++ edited/include/asm-sparc64/param.h	Tue Sep 30 18:32:00 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-um/param.h 1.1 vs edited =====
--- 1.1/include/asm-um/param.h	Fri Sep  6 10:29:29 2002
+++ edited/include/asm-um/param.h	Tue Sep 30 18:32:00 2003
@@ -3,10 +3,6 @@
 
 #define EXEC_PAGESIZE   4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-v850/param.h 1.1 vs edited =====
--- 1.1/include/asm-v850/param.h	Fri Nov  1 08:38:12 2002
+++ edited/include/asm-v850/param.h	Tue Sep 30 18:32:00 2003
@@ -18,10 +18,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-x86_64/param.h 1.3 vs edited =====
--- 1.3/include/asm-x86_64/param.h	Fri Oct 18 18:36:59 2002
+++ edited/include/asm-x86_64/param.h	Tue Sep 30 18:32:00 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/linux/init_task.h 1.27 vs edited =====
--- 1.27/include/linux/init_task.h	Mon Aug 18 19:46:23 2003
+++ edited/include/linux/init_task.h	Tue Sep 30 18:32:00 2003
@@ -56,6 +56,8 @@
 	.siglock	= SPIN_LOCK_UNLOCKED, 		\
 }
 
+extern struct group_info init_groups;
+
 /*
  *  INIT_TASK is used to set up the first task table, touch at
  * your own risk!. Base=0, limit=0x1fffff (=2MB)
@@ -87,6 +89,7 @@
 	.real_timer	= {						\
 		.function	= it_real_fn				\
 	},								\
+	.group_info	= &init_groups,					\
 	.cap_effective	= CAP_INIT_EFF_SET,				\
 	.cap_inheritable = CAP_INIT_INH_SET,				\
 	.cap_permitted	= CAP_FULL_SET,					\
===== include/linux/limits.h 1.3 vs edited =====
--- 1.3/include/linux/limits.h	Tue Feb  5 07:28:33 2002
+++ edited/include/linux/limits.h	Tue Sep 30 18:32:00 2003
@@ -3,7 +3,6 @@
 
 #define NR_OPEN	        1024
 
-#define NGROUPS_MAX       32	/* supplemental group IDs are available */
 #define ARG_MAX       131072	/* # bytes of args + environ for exec() */
 #define CHILD_MAX        999    /* no limit :-) */
 #define OPEN_MAX         256	/* # open files a process may have */
===== include/linux/sched.h 1.167 vs edited =====
--- 1.167/include/linux/sched.h	Sun Sep 21 14:50:04 2003
+++ edited/include/linux/sched.h	Wed Oct  1 13:57:23 2003
@@ -328,6 +328,32 @@
 struct io_context;			/* See blkdev.h */
 void exit_io_context(void);
 
+#define NGROUPS_SMALL		32
+#define NGROUPS_BLOCK		((int)(EXEC_PAGESIZE / sizeof(gid_t)))
+struct group_info {
+	int ngroups;
+	atomic_t usage;
+	gid_t small_block[NGROUPS_SMALL];
+	int nblocks;
+	gid_t *blocks[0];
+};
+
+#define get_group_info(info) do { \
+	atomic_inc(&(info)->usage); \
+} while (0);
+
+#define put_group_info(info) do { \
+	if (atomic_dec_and_test(&(info)->usage)) \
+		groups_free(info); \
+} while (0)
+
+struct group_info *groups_alloc(int gidsetsize);
+void groups_free(struct group_info *info);
+int set_current_groups(struct group_info *info);
+/* access the groups "array" with this macro */
+#define GROUP_AT(gi, i) ((gi)->blocks[(i)/NGROUPS_BLOCK][(i)%NGROUPS_BLOCK])
+
+
 struct task_struct {
 	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
 	struct thread_info *thread_info;
@@ -402,8 +428,7 @@
 /* process credentials */
 	uid_t uid,euid,suid,fsuid;
 	gid_t gid,egid,sgid,fsgid;
-	int ngroups;
-	gid_t	groups[NGROUPS];
+	struct group_info *group_info;
 	kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
 	int keep_capabilities:1;
 	struct user_struct *user;
===== include/linux/security.h 1.25 vs edited =====
--- 1.25/include/linux/security.h	Wed Jul  2 21:22:38 2003
+++ edited/include/linux/security.h	Tue Sep 30 18:32:00 2003
@@ -551,9 +551,8 @@
  *	Return 0 if permission is granted.
  * @task_setgroups:
  *	Check permission before setting the supplementary group set of the
- *	current process to @grouplist.
- *	@gidsetsize contains the number of elements in @grouplist.
- *	@grouplist contains the array of gids.
+ *	current process.
+ *	@group_info contains the new group information.
  *	Return 0 if permission is granted.
  * @task_setnice:
  *	Check permission before setting the nice value of @p to @nice.
@@ -1097,7 +1096,7 @@
 	int (*task_setpgid) (struct task_struct * p, pid_t pgid);
 	int (*task_getpgid) (struct task_struct * p);
 	int (*task_getsid) (struct task_struct * p);
-	int (*task_setgroups) (int gidsetsize, gid_t * grouplist);
+	int (*task_setgroups) (struct group_info *group_info);
 	int (*task_setnice) (struct task_struct * p, int nice);
 	int (*task_setrlimit) (unsigned int resource, struct rlimit * new_rlim);
 	int (*task_setscheduler) (struct task_struct * p, int policy,
@@ -1647,9 +1646,9 @@
 	return security_ops->task_getsid (p);
 }
 
-static inline int security_task_setgroups (int gidsetsize, gid_t *grouplist)
+static inline int security_task_setgroups (struct group_info *group_info)
 {
-	return security_ops->task_setgroups (gidsetsize, grouplist);
+	return security_ops->task_setgroups (group_info);
 }
 
 static inline int security_task_setnice (struct task_struct *p, int nice)
@@ -2275,7 +2274,7 @@
 	return 0;
 }
 
-static inline int security_task_setgroups (int gidsetsize, gid_t *grouplist)
+static inline int security_task_setgroups (struct group_info *group_info)
 {
 	return 0;
 }
===== include/linux/sunrpc/auth.h 1.9 vs edited =====
--- 1.9/include/linux/sunrpc/auth.h	Wed Jun 11 19:22:40 2003
+++ edited/include/linux/sunrpc/auth.h	Tue Sep 30 18:32:00 2003
@@ -28,8 +28,7 @@
 struct auth_cred {
 	uid_t	uid;
 	gid_t	gid;
-	int	ngroups;
-	gid_t	*groups;
+	struct group_info *group_info;
 };
 
 /*
===== include/linux/sunrpc/svcauth.h 1.9 vs edited =====
--- 1.9/include/linux/sunrpc/svcauth.h	Fri Jan 10 17:55:15 2003
+++ edited/include/linux/sunrpc/svcauth.h	Tue Sep 30 18:32:00 2003
@@ -16,10 +16,11 @@
 #include <linux/sunrpc/cache.h>
 #include <linux/hash.h>
 
+#define SVC_CRED_NGROUPS	32
 struct svc_cred {
 	uid_t			cr_uid;
 	gid_t			cr_gid;
-	gid_t			cr_groups[NGROUPS];
+	gid_t			cr_groups[SVC_CRED_NGROUPS];
 };
 
 struct svc_rqst;		/* forward decl */
===== kernel/fork.c 1.142 vs edited =====
--- 1.142/kernel/fork.c	Mon Sep 22 12:32:04 2003
+++ edited/kernel/fork.c	Wed Oct  1 11:22:59 2003
@@ -85,6 +85,7 @@
 
 	security_task_free(tsk);
 	free_uid(tsk->user);
+	put_group_info(tsk->group_info);
 	free_task(tsk);
 }
 
@@ -817,6 +818,7 @@
 
 	atomic_inc(&p->user->__count);
 	atomic_inc(&p->user->processes);
+	get_group_info(p->group_info);
 
 	/*
 	 * If multiple threads are within copy_process(), then this check
@@ -1061,6 +1063,7 @@
 bad_fork_cleanup_put_domain:
 	module_put(p->thread_info->exec_domain->module);
 bad_fork_cleanup_count:
+	put_group_info(p->group_info);
 	atomic_dec(&p->user->processes);
 	free_uid(p->user);
 bad_fork_free:
===== kernel/sys.c 1.61 vs edited =====
--- 1.61/kernel/sys.c	Mon Sep 22 13:49:26 2003
+++ edited/kernel/sys.c	Wed Oct  1 13:59:35 2003
@@ -1072,9 +1072,160 @@
 /*
  * Supplementary group IDs
  */
-asmlinkage long sys_getgroups(int gidsetsize, gid_t __user *grouplist)
+
+/* init to 2 - one for init_task, one to ensure it is never freed */
+struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
+
+struct group_info *groups_alloc(int gidsetsize)
+{
+	struct group_info *info;
+	int nblocks;
+
+	nblocks = (gidsetsize/NGROUPS_BLOCK) + (gidsetsize%NGROUPS_BLOCK?1:0);
+	info = kmalloc(sizeof(*info) + nblocks*sizeof(gid_t *), GFP_USER);
+	if (!info)
+		return NULL;
+	info->ngroups = gidsetsize;
+	info->nblocks = nblocks;
+	atomic_set(&info->usage, 1);
+
+	if (gidsetsize <= NGROUPS_SMALL) {
+		info->blocks[0] = info->small_block;
+	} else {
+		int i;
+		for (i = 0; i < nblocks; i++) {
+			gid_t *b;
+			b = (void *)__get_free_page(GFP_USER);
+			if (!b) {
+				int j;
+				for (j = 0; j < i; j++)
+					free_page((unsigned long)info->blocks[j]);
+				kfree(info);
+				return NULL;
+			}
+			info->blocks[i] = b;
+		}
+	}
+	return info;
+}
+
+void groups_free(struct group_info *info)
 {
+	if (info->ngroups > NGROUPS_SMALL) {
+		int i;
+		for (i = 0; i < info->nblocks; i++)
+			free_page((unsigned long)info->blocks[i]);
+	}
+	kfree(info);
+}
+
+/* export the group_info to a user-space array */
+static int groups_to_user(gid_t *grouplist, struct group_info __user *info)
+{
+	int i;
+	int count = info->ngroups;
+
+	for (i = 0; i < info->nblocks; i++) {
+		int cp_count = min(NGROUPS_BLOCK, count);
+		int off = i * NGROUPS_BLOCK;
+		int len = cp_count * sizeof(*grouplist);
+
+		if (copy_to_user(grouplist+off, info->blocks[i], len))
+			return -EFAULT;
+
+		count -= cp_count;
+	}
+	return 0;
+}
+
+/* fill a group_info from a user-space array - it must be allocated already */
+static int groups_from_user(struct group_info *info, gid_t __user *grouplist)
+ {
 	int i;
+	int count = info->ngroups;
+
+	for (i = 0; i < info->nblocks; i++) {
+		int cp_count = min(NGROUPS_BLOCK, count);
+		int off = i * NGROUPS_BLOCK;
+		int len = cp_count * sizeof(*grouplist);
+
+		if (copy_from_user(info->blocks[i], grouplist+off, len))
+			return -EFAULT;
+
+		count -= cp_count;
+	}
+	return 0;
+}
+
+/* a simple shell-metzner sort */
+static void groups_sort(struct group_info *info)
+{
+	int base, max, stride;
+	int gidsetsize = info->ngroups;
+
+	for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
+		; /* nothing */
+	stride /= 3;
+
+	while (stride) {
+		max = gidsetsize - stride;
+		for (base = 0; base < max; base++) {
+			int left = base;
+			gid_t tmp = GROUP_AT(info, base + stride);
+			while (left >= 0 && tmp < GROUP_AT(info, left)) {
+				GROUP_AT(info,left)=GROUP_AT(info,left+stride);
+				left -= stride;
+			}
+			GROUP_AT(info, left + stride) = tmp;
+		}
+		stride /= 3;
+	}
+}
+
+/* a simple bsearch */
+static int groups_search(struct group_info *info, gid_t grp)
+{
+	int left, right;
+
+	if (!info)
+		return 0;
+
+	left = 0;
+	right = info->ngroups;
+	while (left < right) {
+		int mid = (left+right)/2;
+		int cmp = grp - GROUP_AT(info, mid);
+		if (cmp > 0)
+			left = mid + 1;
+		else if (cmp < 0)
+			right = mid;
+		else
+			return 1;
+	}
+	return 0;
+}
+
+/* validate and set current->group_info */
+int set_current_groups(struct group_info *info)
+{
+	int retval;
+	struct group_info *old_info;
+
+	retval = security_task_setgroups(info);
+	if (retval)
+		return retval;
+
+	groups_sort(info);
+	old_info = current->group_info;
+	current->group_info = info;
+	put_group_info(old_info);
+
+	return 0;
+}
+
+asmlinkage long sys_getgroups(int gidsetsize, gid_t __user *grouplist)
+{
+	int i = 0;
 	
 	/*
 	 *	SMP: Nobody else can change our grouplist. Thus we are
@@ -1083,54 +1234,43 @@
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		if (copy_to_user(grouplist, current->groups, sizeof(gid_t)*i))
+		if (groups_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
 }
 
 /*
- *	SMP: Our groups are not shared. We can copy to/from them safely
+ *	SMP: Our groups are copy-on-write. We can set them safely
  *	without another task interfering.
  */
  
 asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist)
 {
-	gid_t groups[NGROUPS];
+	struct group_info *new_info;
 	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(gid_t)))
-		return -EFAULT;
-	retval = security_task_setgroups(gidsetsize, groups);
-	if (retval)
+
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
 		return retval;
-	memcpy(current->groups, groups, gidsetsize * sizeof(gid_t));
-	current->ngroups = gidsetsize;
-	return 0;
-}
+	}
 
-static int supplemental_group_member(gid_t grp)
-{
-	int i = current->ngroups;
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
 
-	if (i) {
-		gid_t *groups = current->groups;
-		do {
-			if (*groups == grp)
-				return 1;
-			groups++;
-			i--;
-		} while (i);
-	}
-	return 0;
+	return retval;
 }
 
 /*
@@ -1140,7 +1280,7 @@
 {
 	int retval = 1;
 	if (grp != current->fsgid)
-		retval = supplemental_group_member(grp);
+		retval = groups_search(current->group_info, grp);
 	return retval;
 }
 
@@ -1148,7 +1288,7 @@
 {
 	int retval = 1;
 	if (grp != current->egid)
-		retval = supplemental_group_member(grp);
+		retval = groups_search(current->group_info, grp);
 	return retval;
 }
 
===== kernel/uid16.c 1.5 vs edited =====
--- 1.5/kernel/uid16.c	Wed Apr  9 20:51:27 2003
+++ edited/kernel/uid16.c	Wed Oct  1 13:59:45 2003
@@ -107,20 +107,57 @@
 	return sys_setfsgid((gid_t)gid);
 }
 
+static int groups16_to_user(old_gid_t __user *grouplist,
+    struct group_info *info)
+{
+	int i;
+	old_gid_t group;
+
+	if (info->ngroups > TASK_SIZE/sizeof(group))
+		return -EFAULT;
+	if (!access_ok(grouplist, info->ngroups*sizeof(group)))
+		return -EFAULT;
+
+	for (i = 0; i < info->ngroups; i++) {
+		group = (old_gid_t)GROUP_AT(info, i);
+		if (__put_user(group, grouplist+i))
+			return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int groups16_from_user(struct group_info *info,
+    old_gid_t __user *grouplist)
+{
+	int i;
+	old_gid_t group;
+
+	if (info->ngroups > TASK_SIZE/sizeof(group))
+		return -EFAULT;
+	if (!access_ok(grouplist, info->ngroups*sizeof(group)))
+		return -EFAULT;
+
+	for (i = 0; i < info->ngroups; i++) {
+		if (__get_user(group, grouplist+i))
+			return  -EFAULT;
+		GROUP_AT(info, i) = (gid_t)group;
+	}
+
+	return 0;
+}
+
 asmlinkage long sys_getgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
-	int i,j;
+	int i = 0;
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(old_gid_t)*i))
+		if (groups16_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
@@ -128,24 +165,25 @@
 
 asmlinkage long sys_setgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
-	gid_t new_groups[NGROUPS];
-	int i;
+	struct group_info *new_info;
+	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(old_gid_t)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		new_groups[i] = (gid_t)groups[i];
-	i = security_task_setgroups(gidsetsize, new_groups);
-	if (i)
-		return i;
-	memcpy(current->groups, new_groups, gidsetsize * sizeof(gid_t));
-	current->ngroups = gidsetsize;
-	return 0;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
+
+	return retval;
 }
 
 asmlinkage long sys_getuid16(void)
===== net/sunrpc/auth.c 1.12 vs edited =====
--- 1.12/net/sunrpc/auth.c	Wed Jun 11 19:22:40 2003
+++ edited/net/sunrpc/auth.c	Tue Sep 30 18:32:00 2003
@@ -249,8 +249,7 @@
 	struct auth_cred acred = {
 		.uid = current->fsuid,
 		.gid = current->fsgid,
-		.ngroups = current->ngroups,
-		.groups = current->groups,
+		.group_info = current->group_info,
 	};
 	dprintk("RPC:     looking up %s cred\n",
 		auth->au_ops->au_name);
@@ -264,8 +263,7 @@
 	struct auth_cred acred = {
 		.uid = current->fsuid,
 		.gid = current->fsgid,
-		.ngroups = current->ngroups,
-		.groups = current->groups,
+		.group_info = current->group_info,
 	};
 
 	dprintk("RPC: %4d looking up %s cred\n",
===== net/sunrpc/auth_unix.c 1.11 vs edited =====
--- 1.11/net/sunrpc/auth_unix.c	Mon Feb 24 08:08:37 2003
+++ edited/net/sunrpc/auth_unix.c	Wed Oct  1 13:59:50 2003
@@ -82,7 +82,7 @@
 		cred->uc_gid = cred->uc_pgid = 0;
 		cred->uc_gids[0] = NOGROUP;
 	} else {
-		int groups = acred->ngroups;
+		int groups = acred->group_info->ngroups;
 		if (groups > NFS_NGROUPS)
 			groups = NFS_NGROUPS;
 
@@ -91,7 +91,7 @@
 		cred->uc_puid = current->uid;
 		cred->uc_pgid = current->gid;
 		for (i = 0; i < groups; i++)
-			cred->uc_gids[i] = (gid_t) acred->groups[i];
+			cred->uc_gids[i] = GROUP_AT(acred->group_info, i);
 		if (i < NFS_NGROUPS)
 		  cred->uc_gids[i] = NOGROUP;
 	}
@@ -126,11 +126,11 @@
 		 || cred->uc_pgid != current->gid)
 			return 0;
 
-		groups = acred->ngroups;
+		groups = acred->group_info->ngroups;
 		if (groups > NFS_NGROUPS)
 			groups = NFS_NGROUPS;
 		for (i = 0; i < groups ; i++)
-			if (cred->uc_gids[i] != (gid_t) acred->groups[i])
+			if (cred->uc_gids[i] != GROUP_AT(acred->group_info, i))
 				return 0;
 		return 1;
 	}
===== net/sunrpc/svcauth_unix.c 1.20 vs edited =====
--- 1.20/net/sunrpc/svcauth_unix.c	Thu Jun 26 21:21:42 2003
+++ edited/net/sunrpc/svcauth_unix.c	Tue Sep 30 18:32:00 2003
@@ -434,11 +434,11 @@
 	if (slen > 16 || (len -= (slen + 2)*4) < 0)
 		goto badcred;
 	for (i = 0; i < slen; i++)
-		if (i < NGROUPS)
+		if (i < SVC_CRED_NGROUPS)
 			cred->cr_groups[i] = ntohl(svc_getu32(argv));
 		else
 			svc_getu32(argv);
-	if (i < NGROUPS)
+	if (i < SVC_CRED_NGROUPS)
 		cred->cr_groups[i] = NOGROUP;
 
 	if (svc_getu32(argv) != RPC_AUTH_NULL || svc_getu32(argv) != 0) {
===== security/dummy.c 1.27 vs edited =====
--- 1.27/security/dummy.c	Wed Jul  2 21:22:38 2003
+++ edited/security/dummy.c	Tue Sep 30 18:32:00 2003
@@ -530,7 +530,7 @@
 	return 0;
 }
 
-static int dummy_task_setgroups (int gidsetsize, gid_t * grouplist)
+static int dummy_task_setgroups (struct group_info *group_info)
 {
 	return 0;
 }

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-01 20:46                 ` Linus Torvalds
@ 2003-10-01 21:12                   ` Tim Hockin
  2003-10-01 21:16                   ` Tim Hockin
  1 sibling, 0 replies; 21+ messages in thread
From: Tim Hockin @ 2003-10-01 21:12 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Linux Kernel mailing list

On Wed, Oct 01, 2003 at 01:46:55PM -0700, Linus Torvalds wrote:
> How about just putting it in "gid16.c" and then adding a CONFIG_GID16
> config variable. Then architectures that want it (pretty much all, no?)  
> can then obviously just do the
> 
> 	config GID16
> 		bool
> 		default y

uid16 has this same code in it, which could be moved to gid16.  The problem
is that architectures that currently use CONFIG_UID16 expect to use
old_gid_t and get all the highuid.h magic.  The 64-bit architectures want to
use stuff from uid16 (and by extension gid16) but they don't want it ALL the
time, just in their compat code.  Therefore, they don't have old_gid_t as 16
bits.

You end up with tons of duplicated code in the 64-bit architectures (most
copies straight from uid16.c  s/old_gid_t/short/).

You end up with places that include highuid.h and then undef functions and
redefine them exactly the same  s/old_gid_t/short/.

The gid16 way would work, but doesn't solve the real problem, which is all
the duplicated code and differing types.  Some use old_gid_t, some use
short, some use u16.

What if I broke CONFIG_UID16 into CONFIG_UID16_SYSCALLS and CONFIG_OLDUID?
UID16_SYSCALLS would make the build compile uid16.c.  CONFIG_OLDUID would
activate high2lowuid() and friends.  It would involve copying some of
highuid.h into uid16.c to make uid16.c always do high2lowuid().  But what
types?

Your call - I'll make gid16.c if that's good enough for you.  Or if you
prefer, I'll do a separate patch in which we find a way to REALLY solve it.

Here's the prior fixups but not the gid16 duplicated code cleanups.  If the
rest of this patch meets approval, I'll be happy to go ahead on whatever you
prefer for gid16 stuff.
o
-- 
Notice that as computers are becoming easier and easier to use,
suddenly there's a big market for "Dummies" books.  Cause and effect,
or merely an ironic juxtaposition of unrelated facts?


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-01 20:29               ` Tim Hockin
@ 2003-10-01 20:46                 ` Linus Torvalds
  2003-10-01 21:12                   ` Tim Hockin
  2003-10-01 21:16                   ` Tim Hockin
  0 siblings, 2 replies; 21+ messages in thread
From: Linus Torvalds @ 2003-10-01 20:46 UTC (permalink / raw)
  To: Tim Hockin; +Cc: Linux Kernel mailing list


On Wed, 1 Oct 2003, Tim Hockin wrote:
> 
> I'd love to put it in uid16.c, but uid16.c is not used by the 64-bit
> architectures.

How about just putting it in "gid16.c" and then adding a CONFIG_GID16
config variable. Then architectures that want it (pretty much all, no?)  
can then obviously just do the

	config GID16
		bool
		default y

in their Kconfig files and be happy. Add the obvious

	obj-$(CONFIG_GID16) += gid16.o

to the kernel makefile and you're done. Looks surgically clean, and 
follows existing practice wrt uid16.

Ok?

		Linus


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-01 19:22             ` Linus Torvalds
@ 2003-10-01 20:29               ` Tim Hockin
  2003-10-01 20:46                 ` Linus Torvalds
  2003-10-02  2:09               ` Rusty Russell
  1 sibling, 1 reply; 21+ messages in thread
From: Tim Hockin @ 2003-10-01 20:29 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Linux Kernel mailing list

On Wed, Oct 01, 2003 at 12:22:03PM -0700, Linus Torvalds wrote:
> Augh. It also makes code even uglier than it used to be:
> 
> 	...
> 
> 	+               u16 group;
> 	+               if (copy_from_user(&group, grouplist+i, sizeof(group)))
> 	+                       return  -EFAULT;

I can change it to do a copy_from_user one block at a time, if you prefer...

> 	if (nr > TASK_SIZE / sizeof(group))
> 		return -EFAULT;
> 	if (!access_ok(grouplist, nr*sizeof(group))
> 		return -EFAULT;
> 	...
> 
> 		if (__get_user(group, grouplist + i))
> 			return -EFAULT;
> 	...

Or change it to this, which is the same 1-gid-at-a-time copy.  This code is
definitely SIMPLER than the 1-block-at-a-time copy.  I'll go with that.

> which really is so common that it _really_ should be in kernel/uid16.c
> (or, actually create a new kernel/gid16.c file) rather than copied 
> (incorrectly) to a lot of architectures. Then things like the above can be 
> done right once, rather than merging this that does the nasty thing over 
> and over.

I'd love to put it in uid16.c, but uid16.c is not used by the 64-bit
architectures.  I remember proposing a simple fix at one point and being
shot down.  I'd love to fix that up, but it's a separate patch.  If I
fix it up as suggested above, will you take it with the promise that I'll
find some way to do uid16 properly for the 64-bit arches and clean it up in
a followup patch?

> Sorry to just complain all the time,

I'm just glad it's getting attention - I'm dying to take this off my todo
list.

Tim

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-01 18:46           ` Tim Hockin
@ 2003-10-01 19:22             ` Linus Torvalds
  2003-10-01 20:29               ` Tim Hockin
  2003-10-02  2:09               ` Rusty Russell
  2003-10-02  2:25             ` Rusty Russell
  1 sibling, 2 replies; 21+ messages in thread
From: Linus Torvalds @ 2003-10-01 19:22 UTC (permalink / raw)
  To: Tim Hockin; +Cc: Pete Zaitcev, braam, rusty, Linux Kernel mailing list


On Wed, 1 Oct 2003, Tim Hockin wrote:
> 
> This patch touches all the compat code in the 64-bit architectures.
> These files have a LOT of duplicated code from uid16.c.  I did not try to
> reduce duplicated code, and instead followed suit.

Augh. It also makes code even uglier than it used to be:

	...

	+               u16 group;
	+               if (copy_from_user(&group, grouplist+i, sizeof(group)))
	+                       return  -EFAULT;

which is really a memcpy() of two bytes at a time. That's disgusting. It
really should be

	u16 group;

	if (nr > TASK_SIZE / sizeof(group))
		return -EFAULT;
	if (!access_ok(grouplist, nr*sizeof(group))
		return -EFAULT;
	...

		if (__get_user(group, grouplist + i))
			return -EFAULT;
	...

which really is so common that it _really_ should be in kernel/uid16.c
(or, actually create a new kernel/gid16.c file) rather than copied 
(incorrectly) to a lot of architectures. Then things like the above can be 
done right once, rather than merging this that does the nasty thing over 
and over.

Sorry to just complain all the time,

			Linus


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-10-01  7:10         ` Tim Hockin
@ 2003-10-01 18:46           ` Tim Hockin
  2003-10-01 19:22             ` Linus Torvalds
  2003-10-02  2:25             ` Rusty Russell
  0 siblings, 2 replies; 21+ messages in thread
From: Tim Hockin @ 2003-10-01 18:46 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Pete Zaitcev, braam, rusty, Linux Kernel mailing list

[-- Attachment #1: Type: text/plain, Size: 1353 bytes --]

Summary: Get rid of the NGROUPS hard limit.

This patch removes all fixed-size arrays which depend on NGROUPS, and
replaces them with struct group_info, which is refcounted, and holds an
array of pages in which to store groups.  groups_alloc() and groups_free()
are used to allocate and free struct group_info, and set_group_info is used
to actually put a group_info into a task.  Groups are sorted and b-searched
for efficiency.  Because groups are stored in a 2-D array, the GRP_AT()
macro was added to allow simple 1-D style indexing.

This patch touches all the compat code in the 64-bit architectures.
These files have a LOT of duplicated code from uid16.c.  I did not try to
reduce duplicated code, and instead followed suit.  A proper cleanup of
those architectures code-bases would be fun.  Any sysconf() which used to
return NGROUPS now returns INT_MAX - there is no hard limit.

This patch also touches nfsd by imposing a limit on the number of groups in
an svc_cred struct.

This patch modifies /proc/pid/status to only display the first 32 groups.

This patch removes the NGROUPS define from all architectures as well as
NGROUPS_MAX.

This patch changes the security API to check a struct group_info, rather
than an array of gid_t.

This patch totally horks Intermezzo.

This was built and tested against 2.6.0-test6 (BK today) on an i386.


[-- Attachment #2: ngroups-2.6.0-test6-2.diff --]
[-- Type: text/plain, Size: 33307 bytes --]

===== arch/ia64/ia32/sys_ia32.c 1.78 vs edited =====
--- 1.78/arch/ia64/ia32/sys_ia32.c	Mon Sep 22 21:16:30 2003
+++ edited/arch/ia64/ia32/sys_ia32.c	Wed Oct  1 11:35:04 2003
@@ -2426,44 +2426,71 @@
 	return sys_lseek(fd, offset, whence);
 }
 
-extern asmlinkage long sys_getgroups (int gidsetsize, gid_t *grouplist);
+static int
+groups16_to_user(short *grouplist, struct group_info *info)
+{
+	int i;
+	for (i = 0; i < info->ngroups; i++) {
+		short group;
+		group = (short)GRP_AT(info, i);
+		if (copy_to_user(grouplist+i, &group, sizeof(group)))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static int
+groups16_from_user(struct group_info *info, short *grouplist)
+{
+	int i;
+	for (i = 0; i < info->ngroups; i++) {
+		short group;
+		if (copy_from_user(&group, grouplist+i, sizeof(group)))
+			return  -EFAULT;
+		GRP_AT(info, i) = (gid_t)group;
+	}
+	return 0;
+}
 
 asmlinkage long
 sys32_getgroups16 (int gidsetsize, short *grouplist)
 {
-	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
-	int ret, i;
-
-	set_fs(KERNEL_DS);
-	ret = sys_getgroups(gidsetsize, gl);
-	set_fs(old_fs);
-
-	if (gidsetsize && ret > 0 && ret <= NGROUPS)
-		for (i = 0; i < ret; i++, grouplist++)
-			if (put_user(gl[i], grouplist))
-				return -EFAULT;
-	return ret;
-}
+	int i;
 
-extern asmlinkage long sys_setgroups (int gidsetsize, gid_t *grouplist);
+	if (gidsetsize < 0)
+		return -EINVAL;
+	i = current->group_info->ngroups;
+	if (gidsetsize) {
+		if (i > gidsetsize)
+			return -EINVAL;
+		if (groups16_to_user(grouplist, current->group_info))
+			return -EFAULT;
+	}
+	return i;
+}
 
 asmlinkage long
 sys32_setgroups16 (int gidsetsize, short *grouplist)
 {
-	mm_segment_t old_fs = get_fs();
-	gid_t gl[NGROUPS];
-	int ret, i;
+	struct group_info *new_info;
+	int retval;
 
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	for (i = 0; i < gidsetsize; i++, grouplist++)
-		if (get_user(gl[i], grouplist))
-			return -EFAULT;
-	set_fs(KERNEL_DS);
-	ret = sys_setgroups(gidsetsize, gl);
-	set_fs(old_fs);
-	return ret;
+	if (!capable(CAP_SETGID))
+		return -EPERM;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
+
+	return retval;
 }
 
 asmlinkage long
===== arch/mips/kernel/sysirix.c 1.18 vs edited =====
--- 1.18/arch/mips/kernel/sysirix.c	Tue Sep 23 19:34:27 2003
+++ edited/arch/mips/kernel/sysirix.c	Tue Sep 30 18:32:00 2003
@@ -368,7 +368,7 @@
 			retval = HZ;
 			goto out;
 		case 4:
-			retval = NGROUPS;
+			retval = INT_MAX;
 			goto out;
 		case 5:
 			retval = NR_OPEN;
===== arch/s390/kernel/compat_linux.c 1.9 vs edited =====
--- 1.9/arch/s390/kernel/compat_linux.c	Thu Sep 25 11:33:23 2003
+++ edited/arch/s390/kernel/compat_linux.c	Wed Oct  1 11:35:09 2003
@@ -189,20 +189,41 @@
 	return sys_setfsgid((gid_t)gid);
 }
 
+static int groups16_to_user(u16 *grouplist, struct group_info *info)
+{
+	int i;
+	for (i = 0; i < info->ngroups; i++) {
+		u16 group;
+		group = (u16)GRP_AT(info, i);
+		if (copy_to_user(grouplist+i, &group, sizeof(group)))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static int groups16_from_user(struct group_info *info, u16 *grouplist)
+{
+	int i;
+	for (i = 0; i < info->ngroups; i++) {
+		u16 group;
+		if (copy_from_user(&group, grouplist+i, sizeof(group)))
+			return  -EFAULT;
+		GRP_AT(info, i) = (gid_t)group;
+	}
+	return 0;
+}
+
 asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	int i;
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
+		if (groups16_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
@@ -210,19 +231,25 @@
 
 asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	struct group_info *new_info;
+	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
+
+	return retval;
 }
 
 asmlinkage long sys32_getuid16(void)
===== arch/sparc/kernel/sys_sunos.c 1.26 vs edited =====
--- 1.26/arch/sparc/kernel/sys_sunos.c	Tue Aug 26 09:25:41 2003
+++ edited/arch/sparc/kernel/sys_sunos.c	Tue Sep 30 18:32:00 2003
@@ -896,7 +896,7 @@
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = INT_MAX;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
===== arch/sparc64/kernel/sys_sparc32.c 1.83 vs edited =====
--- 1.83/arch/sparc64/kernel/sys_sparc32.c	Mon Sep 22 21:16:30 2003
+++ edited/arch/sparc64/kernel/sys_sparc32.c	Wed Oct  1 11:35:18 2003
@@ -206,20 +206,41 @@
 	return sys_setfsgid((gid_t)gid);
 }
 
+static int groups16_to_user(u16 *grouplist, struct group_info *info)
+{
+	int i;
+	for (i = 0; i < info->ngroups; i++) {
+		u16 group;
+		group = (u16)GRP_AT(info, i);
+		if (copy_to_user(grouplist+i, &group, sizeof(group)))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static int groups16_from_user(struct group_info *info, u16 *grouplist)
+{
+	int i;
+	for (i = 0; i < info->ngroups; i++) {
+		u16 group;
+		if (copy_from_user(&group, grouplist+i, sizeof(group)))
+			return  -EFAULT;
+		GRP_AT(info, i) = (gid_t)group;
+	}
+	return 0;
+}
+
 asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	int i;
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
+		if (groups16_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
@@ -227,19 +248,25 @@
 
 asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	struct group_info *new_info;
+	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
+
+	return retval;
 }
 
 asmlinkage long sys32_getuid16(void)
===== arch/sparc64/kernel/sys_sunos32.c 1.35 vs edited =====
--- 1.35/arch/sparc64/kernel/sys_sunos32.c	Tue Aug 26 09:25:41 2003
+++ edited/arch/sparc64/kernel/sys_sunos32.c	Tue Sep 30 18:32:00 2003
@@ -859,7 +859,7 @@
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = INT_MAX;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
===== arch/sparc64/solaris/misc.c 1.18 vs edited =====
--- 1.18/arch/sparc64/solaris/misc.c	Sun Sep 21 14:49:52 2003
+++ edited/arch/sparc64/solaris/misc.c	Tue Sep 30 18:32:00 2003
@@ -341,7 +341,7 @@
 asmlinkage int solaris_sysconf(int id)
 {
 	switch (id) {
-	case SOLARIS_CONFIG_NGROUPS:	return NGROUPS_MAX;
+	case SOLARIS_CONFIG_NGROUPS:	return INT_MAX;
 	case SOLARIS_CONFIG_CHILD_MAX:	return CHILD_MAX;
 	case SOLARIS_CONFIG_OPEN_FILES:	return OPEN_MAX;
 	case SOLARIS_CONFIG_POSIX_VER:	return 199309;
===== fs/nfsd/auth.c 1.2 vs edited =====
--- 1.2/fs/nfsd/auth.c	Tue Jun 17 16:31:29 2003
+++ edited/fs/nfsd/auth.c	Tue Sep 30 18:32:00 2003
@@ -10,12 +10,15 @@
 #include <linux/sunrpc/svcauth.h>
 #include <linux/nfsd/nfsd.h>
 
+extern asmlinkage long sys_setgroups(int gidsetsize, gid_t *grouplist);
+
 #define	CAP_NFSD_MASK (CAP_FS_MASK|CAP_TO_MASK(CAP_SYS_RESOURCE))
 void
 nfsd_setuser(struct svc_rqst *rqstp, struct svc_export *exp)
 {
 	struct svc_cred	*cred = &rqstp->rq_cred;
 	int		i;
+	gid_t		groups[SVC_CRED_NGROUPS];
 
 	if (exp->ex_flags & NFSEXP_ALLSQUASH) {
 		cred->cr_uid = exp->ex_anon_uid;
@@ -26,7 +29,7 @@
 			cred->cr_uid = exp->ex_anon_uid;
 		if (!cred->cr_gid)
 			cred->cr_gid = exp->ex_anon_gid;
-		for (i = 0; i < NGROUPS; i++)
+		for (i = 0; i < SVC_CRED_NGROUPS; i++)
 			if (!cred->cr_groups[i])
 				cred->cr_groups[i] = exp->ex_anon_gid;
 	}
@@ -39,13 +42,13 @@
 		current->fsgid = cred->cr_gid;
 	else
 		current->fsgid = exp->ex_anon_gid;
-	for (i = 0; i < NGROUPS; i++) {
+	for (i = 0; i < SVC_CRED_NGROUPS; i++) {
 		gid_t group = cred->cr_groups[i];
 		if (group == (gid_t) NOGROUP)
 			break;
-		current->groups[i] = group;
+		groups[i] = group;
 	}
-	current->ngroups = i;
+	sys_setgroups(i, groups);
 
 	if ((cred->cr_uid)) {
 		cap_t(current->cap_effective) &= ~CAP_NFSD_MASK;
===== fs/nfsd/nfs4state.c 1.20 vs edited =====
--- 1.20/fs/nfsd/nfs4state.c	Mon Sep 22 16:11:52 2003
+++ edited/fs/nfsd/nfs4state.c	Tue Sep 30 18:32:00 2003
@@ -244,7 +244,7 @@
 
 	target->cr_uid = source->cr_uid;
 	target->cr_gid = source->cr_gid;
-	for(i = 0; i < NGROUPS; i++)
+	for(i = 0; i < SVC_CRED_NGROUPS; i++)
 		target->cr_groups[i] = source->cr_groups[i];
 }
 
===== fs/proc/array.c 1.52 vs edited =====
--- 1.52/fs/proc/array.c	Tue Sep 23 18:39:53 2003
+++ edited/fs/proc/array.c	Tue Sep 30 18:32:00 2003
@@ -176,8 +176,8 @@
 		p->files ? p->files->max_fds : 0);
 	task_unlock(p);
 
-	for (g = 0; g < p->ngroups; g++)
-		buffer += sprintf(buffer, "%d ", p->groups[g]);
+	for (g = 0; g < min(p->group_info->ngroups,NGROUPS_SMALL); g++)
+		buffer += sprintf(buffer, "%d ", GRP_AT(p->group_info,g));
 
 	buffer += sprintf(buffer, "\n");
 	return buffer;
===== include/asm-alpha/param.h 1.2 vs edited =====
--- 1.2/include/asm-alpha/param.h	Thu Aug  8 12:28:02 2002
+++ edited/include/asm-alpha/param.h	Tue Sep 30 18:32:00 2003
@@ -19,10 +19,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-arm/param.h 1.5 vs edited =====
--- 1.5/include/asm-arm/param.h	Wed Sep  3 10:17:57 2003
+++ edited/include/asm-arm/param.h	Tue Sep 30 18:32:00 2003
@@ -26,10 +26,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-arm26/param.h 1.1 vs edited =====
--- 1.1/include/asm-arm26/param.h	Wed Jun  4 04:14:10 2003
+++ edited/include/asm-arm26/param.h	Tue Sep 30 18:32:00 2003
@@ -22,10 +22,6 @@
 # define HZ		100
 #endif
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-cris/param.h 1.2 vs edited =====
--- 1.2/include/asm-cris/param.h	Thu Nov  7 01:29:17 2002
+++ edited/include/asm-cris/param.h	Tue Sep 30 18:32:00 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-h8300/param.h 1.1 vs edited =====
--- 1.1/include/asm-h8300/param.h	Sun Feb 16 16:01:58 2003
+++ edited/include/asm-h8300/param.h	Tue Sep 30 18:32:00 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-i386/param.h 1.2 vs edited =====
--- 1.2/include/asm-i386/param.h	Mon Jul  1 14:41:36 2002
+++ edited/include/asm-i386/param.h	Tue Sep 30 18:32:00 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ia64/param.h 1.4 vs edited =====
--- 1.4/include/asm-ia64/param.h	Wed Sep 17 18:59:37 2003
+++ edited/include/asm-ia64/param.h	Tue Sep 30 18:32:00 2003
@@ -10,10 +10,6 @@
 
 #define EXEC_PAGESIZE	65536
 
-#ifndef NGROUPS
-# define NGROUPS	32
-#endif
-
 #ifndef NOGROUP
 # define NOGROUP	(-1)
 #endif
===== include/asm-m68k/param.h 1.2 vs edited =====
--- 1.2/include/asm-m68k/param.h	Mon Jul  8 05:53:12 2002
+++ edited/include/asm-m68k/param.h	Tue Sep 30 18:32:00 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-m68knommu/param.h 1.1 vs edited =====
--- 1.1/include/asm-m68knommu/param.h	Fri Nov  1 08:37:46 2002
+++ edited/include/asm-m68knommu/param.h	Tue Sep 30 18:32:00 2003
@@ -44,10 +44,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-mips/param.h 1.3 vs edited =====
--- 1.3/include/asm-mips/param.h	Mon Apr 14 20:10:06 2003
+++ edited/include/asm-mips/param.h	Tue Sep 30 18:32:00 2003
@@ -33,10 +33,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-parisc/param.h 1.2 vs edited =====
--- 1.2/include/asm-parisc/param.h	Mon Oct 28 02:33:42 2002
+++ edited/include/asm-parisc/param.h	Tue Sep 30 18:32:00 2003
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ppc/param.h 1.6 vs edited =====
--- 1.6/include/asm-ppc/param.h	Tue Jan  7 11:45:19 2003
+++ edited/include/asm-ppc/param.h	Tue Sep 30 18:32:00 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ppc64/param.h 1.2 vs edited =====
--- 1.2/include/asm-ppc64/param.h	Wed Jul 17 23:18:40 2002
+++ edited/include/asm-ppc64/param.h	Tue Sep 30 18:32:00 2003
@@ -20,10 +20,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-s390/param.h 1.3 vs edited =====
--- 1.3/include/asm-s390/param.h	Fri Oct  4 09:14:42 2002
+++ edited/include/asm-s390/param.h	Tue Sep 30 18:32:00 2003
@@ -21,10 +21,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sh/param.h 1.2 vs edited =====
--- 1.2/include/asm-sh/param.h	Tue May 27 15:48:59 2003
+++ edited/include/asm-sh/param.h	Tue Sep 30 18:32:00 2003
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sparc/param.h 1.2 vs edited =====
--- 1.2/include/asm-sparc/param.h	Fri Jul 12 15:54:40 2002
+++ edited/include/asm-sparc/param.h	Tue Sep 30 18:32:00 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sparc64/param.h 1.2 vs edited =====
--- 1.2/include/asm-sparc64/param.h	Fri Jul 12 15:54:40 2002
+++ edited/include/asm-sparc64/param.h	Tue Sep 30 18:32:00 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-um/param.h 1.1 vs edited =====
--- 1.1/include/asm-um/param.h	Fri Sep  6 10:29:29 2002
+++ edited/include/asm-um/param.h	Tue Sep 30 18:32:00 2003
@@ -3,10 +3,6 @@
 
 #define EXEC_PAGESIZE   4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-v850/param.h 1.1 vs edited =====
--- 1.1/include/asm-v850/param.h	Fri Nov  1 08:38:12 2002
+++ edited/include/asm-v850/param.h	Tue Sep 30 18:32:00 2003
@@ -18,10 +18,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-x86_64/param.h 1.3 vs edited =====
--- 1.3/include/asm-x86_64/param.h	Fri Oct 18 18:36:59 2002
+++ edited/include/asm-x86_64/param.h	Tue Sep 30 18:32:00 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/linux/init_task.h 1.27 vs edited =====
--- 1.27/include/linux/init_task.h	Mon Aug 18 19:46:23 2003
+++ edited/include/linux/init_task.h	Tue Sep 30 18:32:00 2003
@@ -56,6 +56,8 @@
 	.siglock	= SPIN_LOCK_UNLOCKED, 		\
 }
 
+extern struct group_info init_groups;
+
 /*
  *  INIT_TASK is used to set up the first task table, touch at
  * your own risk!. Base=0, limit=0x1fffff (=2MB)
@@ -87,6 +89,7 @@
 	.real_timer	= {						\
 		.function	= it_real_fn				\
 	},								\
+	.group_info	= &init_groups,					\
 	.cap_effective	= CAP_INIT_EFF_SET,				\
 	.cap_inheritable = CAP_INIT_INH_SET,				\
 	.cap_permitted	= CAP_FULL_SET,					\
===== include/linux/limits.h 1.3 vs edited =====
--- 1.3/include/linux/limits.h	Tue Feb  5 07:28:33 2002
+++ edited/include/linux/limits.h	Tue Sep 30 18:32:00 2003
@@ -3,7 +3,6 @@
 
 #define NR_OPEN	        1024
 
-#define NGROUPS_MAX       32	/* supplemental group IDs are available */
 #define ARG_MAX       131072	/* # bytes of args + environ for exec() */
 #define CHILD_MAX        999    /* no limit :-) */
 #define OPEN_MAX         256	/* # open files a process may have */
===== include/linux/sched.h 1.167 vs edited =====
--- 1.167/include/linux/sched.h	Sun Sep 21 14:50:04 2003
+++ edited/include/linux/sched.h	Wed Oct  1 11:33:02 2003
@@ -328,6 +328,32 @@
 struct io_context;			/* See blkdev.h */
 void exit_io_context(void);
 
+#define NGROUPS_SMALL		32
+#define NGROUPS_BLOCK		((int)(EXEC_PAGESIZE / sizeof(gid_t)))
+struct group_info {
+	int ngroups;
+	atomic_t usage;
+	gid_t small_block[NGROUPS_SMALL];
+	int nblocks;
+	gid_t *blocks[0];
+};
+
+#define get_group_info(info) do { \
+	atomic_inc(&(info)->usage); \
+} while (0);
+
+#define put_group_info(info) do { \
+	if (atomic_dec_and_test(&(info)->usage)) \
+		groups_free(info); \
+} while (0)
+
+struct group_info *groups_alloc(int gidsetsize);
+void groups_free(struct group_info *info);
+int set_current_groups(struct group_info *info);
+/* access the groups "array" with this macro */
+#define GRP_AT(gi, i) ((gi)->blocks[(i)/NGROUPS_BLOCK][(i)%NGROUPS_BLOCK])
+
+
 struct task_struct {
 	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
 	struct thread_info *thread_info;
@@ -402,8 +428,7 @@
 /* process credentials */
 	uid_t uid,euid,suid,fsuid;
 	gid_t gid,egid,sgid,fsgid;
-	int ngroups;
-	gid_t	groups[NGROUPS];
+	struct group_info *group_info;
 	kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
 	int keep_capabilities:1;
 	struct user_struct *user;
===== include/linux/security.h 1.25 vs edited =====
--- 1.25/include/linux/security.h	Wed Jul  2 21:22:38 2003
+++ edited/include/linux/security.h	Tue Sep 30 18:32:00 2003
@@ -551,9 +551,8 @@
  *	Return 0 if permission is granted.
  * @task_setgroups:
  *	Check permission before setting the supplementary group set of the
- *	current process to @grouplist.
- *	@gidsetsize contains the number of elements in @grouplist.
- *	@grouplist contains the array of gids.
+ *	current process.
+ *	@group_info contains the new group information.
  *	Return 0 if permission is granted.
  * @task_setnice:
  *	Check permission before setting the nice value of @p to @nice.
@@ -1097,7 +1096,7 @@
 	int (*task_setpgid) (struct task_struct * p, pid_t pgid);
 	int (*task_getpgid) (struct task_struct * p);
 	int (*task_getsid) (struct task_struct * p);
-	int (*task_setgroups) (int gidsetsize, gid_t * grouplist);
+	int (*task_setgroups) (struct group_info *group_info);
 	int (*task_setnice) (struct task_struct * p, int nice);
 	int (*task_setrlimit) (unsigned int resource, struct rlimit * new_rlim);
 	int (*task_setscheduler) (struct task_struct * p, int policy,
@@ -1647,9 +1646,9 @@
 	return security_ops->task_getsid (p);
 }
 
-static inline int security_task_setgroups (int gidsetsize, gid_t *grouplist)
+static inline int security_task_setgroups (struct group_info *group_info)
 {
-	return security_ops->task_setgroups (gidsetsize, grouplist);
+	return security_ops->task_setgroups (group_info);
 }
 
 static inline int security_task_setnice (struct task_struct *p, int nice)
@@ -2275,7 +2274,7 @@
 	return 0;
 }
 
-static inline int security_task_setgroups (int gidsetsize, gid_t *grouplist)
+static inline int security_task_setgroups (struct group_info *group_info)
 {
 	return 0;
 }
===== include/linux/sunrpc/auth.h 1.9 vs edited =====
--- 1.9/include/linux/sunrpc/auth.h	Wed Jun 11 19:22:40 2003
+++ edited/include/linux/sunrpc/auth.h	Tue Sep 30 18:32:00 2003
@@ -28,8 +28,7 @@
 struct auth_cred {
 	uid_t	uid;
 	gid_t	gid;
-	int	ngroups;
-	gid_t	*groups;
+	struct group_info *group_info;
 };
 
 /*
===== include/linux/sunrpc/svcauth.h 1.9 vs edited =====
--- 1.9/include/linux/sunrpc/svcauth.h	Fri Jan 10 17:55:15 2003
+++ edited/include/linux/sunrpc/svcauth.h	Tue Sep 30 18:32:00 2003
@@ -16,10 +16,11 @@
 #include <linux/sunrpc/cache.h>
 #include <linux/hash.h>
 
+#define SVC_CRED_NGROUPS	32
 struct svc_cred {
 	uid_t			cr_uid;
 	gid_t			cr_gid;
-	gid_t			cr_groups[NGROUPS];
+	gid_t			cr_groups[SVC_CRED_NGROUPS];
 };
 
 struct svc_rqst;		/* forward decl */
===== kernel/fork.c 1.142 vs edited =====
--- 1.142/kernel/fork.c	Mon Sep 22 12:32:04 2003
+++ edited/kernel/fork.c	Wed Oct  1 11:22:59 2003
@@ -85,6 +85,7 @@
 
 	security_task_free(tsk);
 	free_uid(tsk->user);
+	put_group_info(tsk->group_info);
 	free_task(tsk);
 }
 
@@ -817,6 +818,7 @@
 
 	atomic_inc(&p->user->__count);
 	atomic_inc(&p->user->processes);
+	get_group_info(p->group_info);
 
 	/*
 	 * If multiple threads are within copy_process(), then this check
@@ -1061,6 +1063,7 @@
 bad_fork_cleanup_put_domain:
 	module_put(p->thread_info->exec_domain->module);
 bad_fork_cleanup_count:
+	put_group_info(p->group_info);
 	atomic_dec(&p->user->processes);
 	free_uid(p->user);
 bad_fork_free:
===== kernel/sys.c 1.61 vs edited =====
--- 1.61/kernel/sys.c	Mon Sep 22 13:49:26 2003
+++ edited/kernel/sys.c	Wed Oct  1 11:33:28 2003
@@ -1072,9 +1072,160 @@
 /*
  * Supplementary group IDs
  */
-asmlinkage long sys_getgroups(int gidsetsize, gid_t __user *grouplist)
+
+/* init to 2 - one for init_task, one to ensure it is never freed */
+struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
+
+struct group_info *groups_alloc(int gidsetsize)
+{
+	struct group_info *info;
+	int nblocks;
+
+	nblocks = (gidsetsize/NGROUPS_BLOCK) + (gidsetsize%NGROUPS_BLOCK?1:0);
+	info = kmalloc(sizeof(*info) + nblocks*sizeof(gid_t *), GFP_USER);
+	if (!info)
+		return NULL;
+	info->ngroups = gidsetsize;
+	info->nblocks = nblocks;
+	atomic_set(&info->usage, 1);
+
+	if (gidsetsize <= NGROUPS_SMALL) {
+		info->blocks[0] = info->small_block;
+	} else {
+		int i;
+		for (i = 0; i < nblocks; i++) {
+			gid_t *b;
+			b = (void *)__get_free_page(GFP_USER);
+			if (!b) {
+				int j;
+				for (j = 0; j < i; j++)
+					free_page((unsigned long)info->blocks[j]);
+				kfree(info);
+				return NULL;
+			}
+			info->blocks[i] = b;
+		}
+	}
+	return info;
+}
+
+void groups_free(struct group_info *info)
 {
+	if (info->ngroups > NGROUPS_SMALL) {
+		int i;
+		for (i = 0; i < info->nblocks; i++)
+			free_page((unsigned long)info->blocks[i]);
+	}
+	kfree(info);
+}
+
+/* export the group_info to a user-space array */
+static int groups_to_user(gid_t *grouplist, struct group_info __user *info)
+{
+	int i;
+	int count = info->ngroups;
+
+	for (i = 0; i < info->nblocks; i++) {
+		int cp_count = min(NGROUPS_BLOCK, count);
+		int off = i * NGROUPS_BLOCK;
+		int len = cp_count * sizeof(*grouplist);
+
+		if (copy_to_user(grouplist+off, info->blocks[i], len))
+			return -EFAULT;
+
+		count -= cp_count;
+	}
+	return 0;
+}
+
+/* fill a group_info from a user-space array - it must be allocated already */
+static int groups_from_user(struct group_info *info, gid_t __user *grouplist)
+ {
 	int i;
+	int count = info->ngroups;
+
+	for (i = 0; i < info->nblocks; i++) {
+		int cp_count = min(NGROUPS_BLOCK, count);
+		int off = i * NGROUPS_BLOCK;
+		int len = cp_count * sizeof(*grouplist);
+
+		if (copy_from_user(info->blocks[i], grouplist+off, len))
+			return -EFAULT;
+
+		count -= cp_count;
+	}
+	return 0;
+}
+
+/* a simple shell-metzner sort */
+static void groups_sort(struct group_info *info)
+{
+	int base, max, stride;
+	int gidsetsize = info->ngroups;
+
+	for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
+		; /* nothing */
+	stride /= 3;
+
+	while (stride) {
+		max = gidsetsize - stride;
+		for (base = 0; base < max; base++) {
+			int left = base;
+			gid_t tmp = GRP_AT(info, base + stride);
+			while (left >= 0 && tmp < GRP_AT(info, left)) {
+				GRP_AT(info, left) = GRP_AT(info, left+stride);
+				left -= stride;
+			}
+			GRP_AT(info, left + stride) = tmp;
+		}
+		stride /= 3;
+	}
+}
+
+/* a simple bsearch */
+static int groups_search(struct group_info *info, gid_t grp)
+{
+	int left, right;
+
+	if (!info)
+		return 0;
+
+	left = 0;
+	right = info->ngroups;
+	while (left < right) {
+		int mid = (left+right)/2;
+		int cmp = grp - GRP_AT(info, mid);
+		if (cmp > 0)
+			left = mid + 1;
+		else if (cmp < 0)
+			right = mid;
+		else
+			return 1;
+	}
+	return 0;
+}
+
+/* validate and set current->group_info */
+int set_current_groups(struct group_info *info)
+{
+	int retval;
+	struct group_info *old_info;
+
+	retval = security_task_setgroups(info);
+	if (retval)
+		return retval;
+
+	groups_sort(info);
+	old_info = current->group_info;
+	current->group_info = info;
+	put_group_info(old_info);
+
+	return 0;
+}
+
+asmlinkage long sys_getgroups(int gidsetsize, gid_t __user *grouplist)
+{
+	int i = 0;
 	
 	/*
 	 *	SMP: Nobody else can change our grouplist. Thus we are
@@ -1083,54 +1234,43 @@
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		if (copy_to_user(grouplist, current->groups, sizeof(gid_t)*i))
+		if (groups_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
 }
 
 /*
- *	SMP: Our groups are not shared. We can copy to/from them safely
+ *	SMP: Our groups are copy-on-write. We can set them safely
  *	without another task interfering.
  */
  
 asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist)
 {
-	gid_t groups[NGROUPS];
+	struct group_info *new_info;
 	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(gid_t)))
-		return -EFAULT;
-	retval = security_task_setgroups(gidsetsize, groups);
-	if (retval)
+
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
 		return retval;
-	memcpy(current->groups, groups, gidsetsize * sizeof(gid_t));
-	current->ngroups = gidsetsize;
-	return 0;
-}
+	}
 
-static int supplemental_group_member(gid_t grp)
-{
-	int i = current->ngroups;
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
 
-	if (i) {
-		gid_t *groups = current->groups;
-		do {
-			if (*groups == grp)
-				return 1;
-			groups++;
-			i--;
-		} while (i);
-	}
-	return 0;
+	return retval;
 }
 
 /*
@@ -1140,7 +1280,7 @@
 {
 	int retval = 1;
 	if (grp != current->fsgid)
-		retval = supplemental_group_member(grp);
+		retval = groups_search(current->group_info, grp);
 	return retval;
 }
 
@@ -1148,7 +1288,7 @@
 {
 	int retval = 1;
 	if (grp != current->egid)
-		retval = supplemental_group_member(grp);
+		retval = groups_search(current->group_info, grp);
 	return retval;
 }
 
===== kernel/uid16.c 1.5 vs edited =====
--- 1.5/kernel/uid16.c	Wed Apr  9 20:51:27 2003
+++ edited/kernel/uid16.c	Wed Oct  1 11:35:25 2003
@@ -107,20 +107,43 @@
 	return sys_setfsgid((gid_t)gid);
 }
 
+static int groups16_to_user(old_gid_t __user *grouplist,
+    struct group_info *info)
+{
+	int i;
+	for (i = 0; i < info->ngroups; i++) {
+		old_gid_t group;
+		group = (old_gid_t)GRP_AT(info, i);
+		if (copy_to_user(grouplist+i, &group, sizeof(group)))
+			return -EFAULT;
+	}
+	return 0;
+}
+
+static int groups16_from_user(struct group_info *info,
+    old_gid_t __user *grouplist)
+{
+	int i;
+	for (i = 0; i < info->ngroups; i++) {
+		old_gid_t group;
+		if (copy_from_user(&group, grouplist+i, sizeof(group)))
+			return  -EFAULT;
+		GRP_AT(info, i) = (gid_t)group;
+	}
+	return 0;
+}
+
 asmlinkage long sys_getgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
-	int i,j;
+	int i = 0;
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(old_gid_t)*i))
+		if (groups16_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
@@ -128,24 +151,25 @@
 
 asmlinkage long sys_setgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
-	gid_t new_groups[NGROUPS];
-	int i;
+	struct group_info *new_info;
+	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(old_gid_t)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		new_groups[i] = (gid_t)groups[i];
-	i = security_task_setgroups(gidsetsize, new_groups);
-	if (i)
-		return i;
-	memcpy(current->groups, new_groups, gidsetsize * sizeof(gid_t));
-	current->ngroups = gidsetsize;
-	return 0;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	retval = set_current_groups(new_info);
+	if (retval)
+		groups_free(new_info);
+
+	return retval;
 }
 
 asmlinkage long sys_getuid16(void)
===== net/sunrpc/auth.c 1.12 vs edited =====
--- 1.12/net/sunrpc/auth.c	Wed Jun 11 19:22:40 2003
+++ edited/net/sunrpc/auth.c	Tue Sep 30 18:32:00 2003
@@ -249,8 +249,7 @@
 	struct auth_cred acred = {
 		.uid = current->fsuid,
 		.gid = current->fsgid,
-		.ngroups = current->ngroups,
-		.groups = current->groups,
+		.group_info = current->group_info,
 	};
 	dprintk("RPC:     looking up %s cred\n",
 		auth->au_ops->au_name);
@@ -264,8 +263,7 @@
 	struct auth_cred acred = {
 		.uid = current->fsuid,
 		.gid = current->fsgid,
-		.ngroups = current->ngroups,
-		.groups = current->groups,
+		.group_info = current->group_info,
 	};
 
 	dprintk("RPC: %4d looking up %s cred\n",
===== net/sunrpc/auth_unix.c 1.11 vs edited =====
--- 1.11/net/sunrpc/auth_unix.c	Mon Feb 24 08:08:37 2003
+++ edited/net/sunrpc/auth_unix.c	Tue Sep 30 18:32:00 2003
@@ -82,7 +82,7 @@
 		cred->uc_gid = cred->uc_pgid = 0;
 		cred->uc_gids[0] = NOGROUP;
 	} else {
-		int groups = acred->ngroups;
+		int groups = acred->group_info->ngroups;
 		if (groups > NFS_NGROUPS)
 			groups = NFS_NGROUPS;
 
@@ -91,7 +91,7 @@
 		cred->uc_puid = current->uid;
 		cred->uc_pgid = current->gid;
 		for (i = 0; i < groups; i++)
-			cred->uc_gids[i] = (gid_t) acred->groups[i];
+			cred->uc_gids[i] = GRP_AT(acred->group_info, i);
 		if (i < NFS_NGROUPS)
 		  cred->uc_gids[i] = NOGROUP;
 	}
@@ -126,11 +126,11 @@
 		 || cred->uc_pgid != current->gid)
 			return 0;
 
-		groups = acred->ngroups;
+		groups = acred->group_info->ngroups;
 		if (groups > NFS_NGROUPS)
 			groups = NFS_NGROUPS;
 		for (i = 0; i < groups ; i++)
-			if (cred->uc_gids[i] != (gid_t) acred->groups[i])
+			if (cred->uc_gids[i] != GRP_AT(acred->group_info, i))
 				return 0;
 		return 1;
 	}
===== net/sunrpc/svcauth_unix.c 1.20 vs edited =====
--- 1.20/net/sunrpc/svcauth_unix.c	Thu Jun 26 21:21:42 2003
+++ edited/net/sunrpc/svcauth_unix.c	Tue Sep 30 18:32:00 2003
@@ -434,11 +434,11 @@
 	if (slen > 16 || (len -= (slen + 2)*4) < 0)
 		goto badcred;
 	for (i = 0; i < slen; i++)
-		if (i < NGROUPS)
+		if (i < SVC_CRED_NGROUPS)
 			cred->cr_groups[i] = ntohl(svc_getu32(argv));
 		else
 			svc_getu32(argv);
-	if (i < NGROUPS)
+	if (i < SVC_CRED_NGROUPS)
 		cred->cr_groups[i] = NOGROUP;
 
 	if (svc_getu32(argv) != RPC_AUTH_NULL || svc_getu32(argv) != 0) {
===== security/dummy.c 1.27 vs edited =====
--- 1.27/security/dummy.c	Wed Jul  2 21:22:38 2003
+++ edited/security/dummy.c	Tue Sep 30 18:32:00 2003
@@ -530,7 +530,7 @@
 	return 0;
 }
 
-static int dummy_task_setgroups (int gidsetsize, gid_t * grouplist)
+static int dummy_task_setgroups (struct group_info *group_info)
 {
 	return 0;
 }

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-09-29 23:10       ` Linus Torvalds
@ 2003-10-01  7:10         ` Tim Hockin
  2003-10-01 18:46           ` Tim Hockin
  0 siblings, 1 reply; 21+ messages in thread
From: Tim Hockin @ 2003-10-01  7:10 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: Pete Zaitcev, braam, rusty, Linux Kernel mailing list

On Mon, Sep 29, 2003 at 04:10:23PM -0700, Linus Torvalds wrote:
> > On Mon, Sep 29, 2003 at 03:43:43PM -0700, Tim Hockin wrote:
> > > My version uses a struct group_info which has an array of pages.  The groups
> 
> I'm definitely happier about this one. 
> 
> Not that I'm any more thrilled about users using thousands of groups. But 
> this looks a bit saner.

OK, then.  Here's a brushed up version against 2.6.0-test6.  Linus, if you
like this, I can get it into a public BK for you.  If not, please indicate
the issues.



Summary: Get rid of the NGROUPS hard limit.

This patch removes all fixed-size arrays which depend on NGROUPS, and
replaces them with struct group_info, which is refcounted, and holds an
array of pages in which to store groups.  groups_alloc() and groups_free()
are used to allocate and free struct group_info, and set_group_info is used
to actually put a group_info into a task.  Groups are sorted and b-searched
for efficiency.  Because groups are stored in a 2-D array, the GRP_AT()
macro was added to allow simple 1-D style indexing.

This patch touches all the compat code in the 64-bit architectures.
These files have a LOT of duplicated code from uid16.c.  I did not try to
reduce duplicated code, and instead followed suit.  A proper cleanup of
those architectures code-bases would be fun.  Any sysconf() which used to
return NGROUPS now returns INT_MAX - there is no hard limit.

This patch also touches nfsd by imposing a limit on the number of groups in
an svc_cred struct.

This patch modifies /proc/pid/status to only display the first 32 groups.

This patch removes the NGROUPS define from all architectures as well as
NGROUPS_MAX.

This patch changes the security API to check a struct group_info, rather
than an array of gid_t.

This patch totally horks Intermezzo.

This was built and tested against 2.6.0-test6 (BK today) on an i386.

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-09-29 22:55     ` Tim Hockin
@ 2003-09-29 23:10       ` Linus Torvalds
  2003-10-01  7:10         ` Tim Hockin
  0 siblings, 1 reply; 21+ messages in thread
From: Linus Torvalds @ 2003-09-29 23:10 UTC (permalink / raw)
  To: Tim Hockin; +Cc: Pete Zaitcev, linux-kernel


On Mon, 29 Sep 2003, Tim Hockin wrote:
>
> On Mon, Sep 29, 2003 at 03:43:43PM -0700, Tim Hockin wrote:
> > My version uses a struct group_info which has an array of pages.  The groups
> 
> Woops, patch is here

I'm definitely happier about this one. 

Not that I'm any more thrilled about users using thousands of groups. But 
this looks a bit saner.

		Linus


^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-09-29 22:43   ` Tim Hockin
@ 2003-09-29 22:55     ` Tim Hockin
  2003-09-29 23:10       ` Linus Torvalds
  0 siblings, 1 reply; 21+ messages in thread
From: Tim Hockin @ 2003-09-29 22:55 UTC (permalink / raw)
  To: Pete Zaitcev; +Cc: Linus Torvalds, linux-kernel

[-- Attachment #1: Type: text/plain, Size: 161 bytes --]

On Mon, Sep 29, 2003 at 03:43:43PM -0700, Tim Hockin wrote:
> My version uses a struct group_info which has an array of pages.  The groups

Woops, patch is here

[-- Attachment #2: diff-09-25.diff --]
[-- Type: text/plain, Size: 28875 bytes --]

===== arch/mips/kernel/sysirix.c 1.14 vs edited =====
--- 1.14/arch/mips/kernel/sysirix.c	Thu Aug  7 10:29:18 2003
+++ edited/arch/mips/kernel/sysirix.c	Fri Aug 22 16:40:41 2003
@@ -368,7 +368,7 @@
 			retval = HZ;
 			goto out;
 		case 4:
-			retval = NGROUPS;
+			retval = INT_MAX;
 			goto out;
 		case 5:
 			retval = NR_OPEN;
===== arch/s390/kernel/compat_linux.c 1.6 vs edited =====
--- 1.6/arch/s390/kernel/compat_linux.c	Fri Aug  1 22:01:17 2003
+++ edited/arch/s390/kernel/compat_linux.c	Fri Aug 22 16:58:34 2003
@@ -191,18 +191,18 @@
 
 asmlinkage long sys32_getgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i,j;
+	int i;
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	if (!current->group_info)
+		return 0;
+
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(u16)*i))
+		if (groups16_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
@@ -210,19 +210,21 @@
 
 asmlinkage long sys32_setgroups16(int gidsetsize, u16 *grouplist)
 {
-	u16 groups[NGROUPS];
-	int i;
+	struct group_info *new_info;
+	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(u16)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		current->groups[i] = (gid_t)groups[i];
-	current->ngroups = gidsetsize;
-	return 0;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	return set_group_info(new_info);
 }
 
 asmlinkage long sys32_getuid16(void)
===== arch/sparc/kernel/sys_sunos.c 1.24 vs edited =====
--- 1.24/arch/sparc/kernel/sys_sunos.c	Sun May 25 17:00:00 2003
+++ edited/arch/sparc/kernel/sys_sunos.c	Fri Aug 22 16:43:43 2003
@@ -896,7 +896,7 @@
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = INT_MAX;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
===== arch/sparc64/kernel/sys_sunos32.c 1.33 vs edited =====
--- 1.33/arch/sparc64/kernel/sys_sunos32.c	Sun May 25 17:00:00 2003
+++ edited/arch/sparc64/kernel/sys_sunos32.c	Fri Aug 22 16:44:01 2003
@@ -859,7 +859,7 @@
 		ret = HZ;
 		break;
 	case _SC_NGROUPS_MAX:
-		ret = NGROUPS_MAX;
+		ret = INT_MAX;
 		break;
 	case _SC_OPEN_MAX:
 		ret = OPEN_MAX;
===== arch/sparc64/solaris/misc.c 1.14 vs edited =====
--- 1.14/arch/sparc64/solaris/misc.c	Mon Apr 21 23:09:31 2003
+++ edited/arch/sparc64/solaris/misc.c	Fri Aug 22 16:44:32 2003
@@ -341,7 +341,7 @@
 asmlinkage int solaris_sysconf(int id)
 {
 	switch (id) {
-	case SOLARIS_CONFIG_NGROUPS:	return NGROUPS_MAX;
+	case SOLARIS_CONFIG_NGROUPS:	return INT_MAX;
 	case SOLARIS_CONFIG_CHILD_MAX:	return CHILD_MAX;
 	case SOLARIS_CONFIG_OPEN_FILES:	return OPEN_MAX;
 	case SOLARIS_CONFIG_POSIX_VER:	return 199309;
===== fs/nfsd/auth.c 1.2 vs edited =====
--- 1.2/fs/nfsd/auth.c	Tue Jun 17 16:31:29 2003
+++ edited/fs/nfsd/auth.c	Fri Aug 15 15:37:09 2003
@@ -10,12 +10,15 @@
 #include <linux/sunrpc/svcauth.h>
 #include <linux/nfsd/nfsd.h>
 
+extern asmlinkage long sys_setgroups(int gidsetsize, gid_t *grouplist);
+
 #define	CAP_NFSD_MASK (CAP_FS_MASK|CAP_TO_MASK(CAP_SYS_RESOURCE))
 void
 nfsd_setuser(struct svc_rqst *rqstp, struct svc_export *exp)
 {
 	struct svc_cred	*cred = &rqstp->rq_cred;
 	int		i;
+	gid_t		groups[SVC_CRED_NGROUPS];
 
 	if (exp->ex_flags & NFSEXP_ALLSQUASH) {
 		cred->cr_uid = exp->ex_anon_uid;
@@ -26,7 +29,7 @@
 			cred->cr_uid = exp->ex_anon_uid;
 		if (!cred->cr_gid)
 			cred->cr_gid = exp->ex_anon_gid;
-		for (i = 0; i < NGROUPS; i++)
+		for (i = 0; i < SVC_CRED_NGROUPS; i++)
 			if (!cred->cr_groups[i])
 				cred->cr_groups[i] = exp->ex_anon_gid;
 	}
@@ -39,13 +42,13 @@
 		current->fsgid = cred->cr_gid;
 	else
 		current->fsgid = exp->ex_anon_gid;
-	for (i = 0; i < NGROUPS; i++) {
+	for (i = 0; i < SVC_CRED_NGROUPS; i++) {
 		gid_t group = cred->cr_groups[i];
 		if (group == (gid_t) NOGROUP)
 			break;
-		current->groups[i] = group;
+		groups[i] = group;
 	}
-	current->ngroups = i;
+	sys_setgroups(i, groups);
 
 	if ((cred->cr_uid)) {
 		cap_t(current->cap_effective) &= ~CAP_NFSD_MASK;
===== fs/nfsd/nfs4state.c 1.11 vs edited =====
--- 1.11/fs/nfsd/nfs4state.c	Thu Jul 31 16:53:11 2003
+++ edited/fs/nfsd/nfs4state.c	Fri Aug 22 16:45:49 2003
@@ -241,7 +241,7 @@
 
 	target->cr_uid = source->cr_uid;
 	target->cr_gid = source->cr_gid;
-	for(i = 0; i < NGROUPS; i++)
+	for(i = 0; i < SVC_CRED_NGROUPS; i++)
 		target->cr_groups[i] = source->cr_groups[i];
 }
 
===== fs/proc/array.c 1.47 vs edited =====
--- 1.47/fs/proc/array.c	Mon Apr 21 20:58:43 2003
+++ edited/fs/proc/array.c	Fri Aug 15 15:37:13 2003
@@ -173,8 +173,11 @@
 		p->files ? p->files->max_fds : 0);
 	task_unlock(p);
 
-	for (g = 0; g < p->ngroups; g++)
-		buffer += sprintf(buffer, "%d ", p->groups[g]);
+	if (p->group_info) {
+		for (g = 0; g < min(p->group_info->ngroups,NGROUPS_SMALL); g++)
+			buffer += sprintf(buffer, "%d ",
+			    GRP_AT(p->group_info,g));
+	}
 
 	buffer += sprintf(buffer, "\n");
 	return buffer;
===== include/asm-alpha/param.h 1.2 vs edited =====
--- 1.2/include/asm-alpha/param.h	Thu Aug  8 12:28:02 2002
+++ edited/include/asm-alpha/param.h	Wed Aug 20 15:56:00 2003
@@ -19,10 +19,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-arm/param.h 1.4 vs edited =====
--- 1.4/include/asm-arm/param.h	Tue Jul  9 12:05:39 2002
+++ edited/include/asm-arm/param.h	Wed Aug 20 15:56:05 2003
@@ -25,10 +25,6 @@
 # define HZ		100
 #endif
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-arm26/param.h 1.1 vs edited =====
--- 1.1/include/asm-arm26/param.h	Wed Jun  4 04:14:10 2003
+++ edited/include/asm-arm26/param.h	Wed Aug 20 15:56:09 2003
@@ -22,10 +22,6 @@
 # define HZ		100
 #endif
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-cris/param.h 1.2 vs edited =====
--- 1.2/include/asm-cris/param.h	Thu Nov  7 01:29:17 2002
+++ edited/include/asm-cris/param.h	Wed Aug 20 15:56:13 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-h8300/param.h 1.1 vs edited =====
--- 1.1/include/asm-h8300/param.h	Sun Feb 16 16:01:58 2003
+++ edited/include/asm-h8300/param.h	Wed Aug 20 15:56:16 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-i386/param.h 1.2 vs edited =====
--- 1.2/include/asm-i386/param.h	Mon Jul  1 14:41:36 2002
+++ edited/include/asm-i386/param.h	Fri Aug 15 17:09:34 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ia64/param.h 1.3 vs edited =====
--- 1.3/include/asm-ia64/param.h	Thu Aug  1 00:44:11 2002
+++ edited/include/asm-ia64/param.h	Wed Aug 20 15:56:22 2003
@@ -22,10 +22,6 @@
 
 #define EXEC_PAGESIZE	65536
 
-#ifndef NGROUPS
-# define NGROUPS	32
-#endif
-
 #ifndef NOGROUP
 # define NOGROUP	(-1)
 #endif
===== include/asm-m68k/param.h 1.2 vs edited =====
--- 1.2/include/asm-m68k/param.h	Mon Jul  8 05:53:12 2002
+++ edited/include/asm-m68k/param.h	Wed Aug 20 15:56:26 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	8192
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-m68knommu/param.h 1.1 vs edited =====
--- 1.1/include/asm-m68knommu/param.h	Fri Nov  1 08:37:46 2002
+++ edited/include/asm-m68knommu/param.h	Wed Aug 20 15:56:29 2003
@@ -44,10 +44,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-mips/param.h 1.3 vs edited =====
--- 1.3/include/asm-mips/param.h	Mon Apr 14 20:10:06 2003
+++ edited/include/asm-mips/param.h	Wed Aug 20 15:56:32 2003
@@ -33,10 +33,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-parisc/param.h 1.2 vs edited =====
--- 1.2/include/asm-parisc/param.h	Mon Oct 28 02:33:42 2002
+++ edited/include/asm-parisc/param.h	Wed Aug 20 15:56:35 2003
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ppc/param.h 1.6 vs edited =====
--- 1.6/include/asm-ppc/param.h	Tue Jan  7 11:45:19 2003
+++ edited/include/asm-ppc/param.h	Wed Aug 20 15:56:39 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-ppc64/param.h 1.2 vs edited =====
--- 1.2/include/asm-ppc64/param.h	Wed Jul 17 23:18:40 2002
+++ edited/include/asm-ppc64/param.h	Wed Aug 20 15:56:42 2003
@@ -20,10 +20,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-s390/param.h 1.3 vs edited =====
--- 1.3/include/asm-s390/param.h	Fri Oct  4 09:14:42 2002
+++ edited/include/asm-s390/param.h	Wed Aug 20 15:56:45 2003
@@ -21,10 +21,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sh/param.h 1.2 vs edited =====
--- 1.2/include/asm-sh/param.h	Tue May 27 15:48:59 2003
+++ edited/include/asm-sh/param.h	Wed Aug 20 15:56:47 2003
@@ -17,10 +17,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sparc/param.h 1.2 vs edited =====
--- 1.2/include/asm-sparc/param.h	Fri Jul 12 15:54:40 2002
+++ edited/include/asm-sparc/param.h	Wed Aug 20 15:56:50 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-sparc64/param.h 1.2 vs edited =====
--- 1.2/include/asm-sparc64/param.h	Fri Jul 12 15:54:40 2002
+++ edited/include/asm-sparc64/param.h	Wed Aug 20 15:56:52 2003
@@ -14,10 +14,6 @@
 
 #define EXEC_PAGESIZE	8192    /* Thanks for sun4's we carry baggage... */
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-um/param.h 1.1 vs edited =====
--- 1.1/include/asm-um/param.h	Fri Sep  6 10:29:29 2002
+++ edited/include/asm-um/param.h	Wed Aug 20 15:56:55 2003
@@ -3,10 +3,6 @@
 
 #define EXEC_PAGESIZE   4096
 
-#ifndef NGROUPS
-#define NGROUPS         32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP         (-1)
 #endif
===== include/asm-v850/param.h 1.1 vs edited =====
--- 1.1/include/asm-v850/param.h	Fri Nov  1 08:38:12 2002
+++ edited/include/asm-v850/param.h	Wed Aug 20 15:56:58 2003
@@ -18,10 +18,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/asm-x86_64/param.h 1.3 vs edited =====
--- 1.3/include/asm-x86_64/param.h	Fri Oct 18 18:36:59 2002
+++ edited/include/asm-x86_64/param.h	Wed Aug 20 15:57:01 2003
@@ -13,10 +13,6 @@
 
 #define EXEC_PAGESIZE	4096
 
-#ifndef NGROUPS
-#define NGROUPS		32
-#endif
-
 #ifndef NOGROUP
 #define NOGROUP		(-1)
 #endif
===== include/linux/init_task.h 1.26 vs edited =====
--- 1.26/include/linux/init_task.h	Sat May 31 12:18:12 2003
+++ edited/include/linux/init_task.h	Fri Aug 15 15:35:35 2003
@@ -87,6 +87,7 @@
 	.real_timer	= {						\
 		.function	= it_real_fn				\
 	},								\
+	.group_info	= NULL,						\
 	.cap_effective	= CAP_INIT_EFF_SET,				\
 	.cap_inheritable = CAP_INIT_INH_SET,				\
 	.cap_permitted	= CAP_FULL_SET,					\
===== include/linux/limits.h 1.3 vs edited =====
--- 1.3/include/linux/limits.h	Tue Feb  5 07:28:33 2002
+++ edited/include/linux/limits.h	Fri Aug 15 17:09:41 2003
@@ -3,7 +3,6 @@
 
 #define NR_OPEN	        1024
 
-#define NGROUPS_MAX       32	/* supplemental group IDs are available */
 #define ARG_MAX       131072	/* # bytes of args + environ for exec() */
 #define CHILD_MAX        999    /* no limit :-) */
 #define OPEN_MAX         256	/* # open files a process may have */
===== include/linux/sched.h 1.160 vs edited =====
--- 1.160/include/linux/sched.h	Thu Aug 14 11:11:35 2003
+++ edited/include/linux/sched.h	Fri Aug 15 15:29:38 2003
@@ -325,6 +325,23 @@
 struct io_context;			/* See blkdev.h */
 void exit_io_context(void);
 
+#define NGROUPS_SMALL		32
+#define NGROUPS_BLOCK		((int)(EXEC_PAGESIZE / sizeof(gid_t)))
+struct group_info {
+	int ngroups;
+	atomic_t refcount;
+	gid_t small_block[NGROUPS_SMALL];
+	int nblocks;
+	gid_t *blocks[0];
+};
+struct group_info *groups_alloc(int gidsetsize);
+void groups_free(struct group_info *info);
+int set_group_info(struct group_info *info);
+/* access the group array through this */
+#define GRP_AT(info, index) \
+	((info)->blocks[(index)/NGROUPS_BLOCK][(index)%NGROUPS_BLOCK])
+
+
 struct task_struct {
 	volatile long state;	/* -1 unrunnable, 0 runnable, >0 stopped */
 	struct thread_info *thread_info;
@@ -396,8 +413,7 @@
 /* process credentials */
 	uid_t uid,euid,suid,fsuid;
 	gid_t gid,egid,sgid,fsgid;
-	int ngroups;
-	gid_t	groups[NGROUPS];
+	struct group_info *group_info;
 	kernel_cap_t   cap_effective, cap_inheritable, cap_permitted;
 	int keep_capabilities:1;
 	struct user_struct *user;
===== include/linux/security.h 1.25 vs edited =====
--- 1.25/include/linux/security.h	Wed Jul  2 21:22:38 2003
+++ edited/include/linux/security.h	Fri Aug 15 15:35:46 2003
@@ -551,9 +551,8 @@
  *	Return 0 if permission is granted.
  * @task_setgroups:
  *	Check permission before setting the supplementary group set of the
- *	current process to @grouplist.
- *	@gidsetsize contains the number of elements in @grouplist.
- *	@grouplist contains the array of gids.
+ *	current process.
+ *	@group_info contains the new group information.
  *	Return 0 if permission is granted.
  * @task_setnice:
  *	Check permission before setting the nice value of @p to @nice.
@@ -1097,7 +1096,7 @@
 	int (*task_setpgid) (struct task_struct * p, pid_t pgid);
 	int (*task_getpgid) (struct task_struct * p);
 	int (*task_getsid) (struct task_struct * p);
-	int (*task_setgroups) (int gidsetsize, gid_t * grouplist);
+	int (*task_setgroups) (struct group_info *group_info);
 	int (*task_setnice) (struct task_struct * p, int nice);
 	int (*task_setrlimit) (unsigned int resource, struct rlimit * new_rlim);
 	int (*task_setscheduler) (struct task_struct * p, int policy,
@@ -1647,9 +1646,9 @@
 	return security_ops->task_getsid (p);
 }
 
-static inline int security_task_setgroups (int gidsetsize, gid_t *grouplist)
+static inline int security_task_setgroups (struct group_info *group_info)
 {
-	return security_ops->task_setgroups (gidsetsize, grouplist);
+	return security_ops->task_setgroups (group_info);
 }
 
 static inline int security_task_setnice (struct task_struct *p, int nice)
@@ -2275,7 +2274,7 @@
 	return 0;
 }
 
-static inline int security_task_setgroups (int gidsetsize, gid_t *grouplist)
+static inline int security_task_setgroups (struct group_info *group_info)
 {
 	return 0;
 }
===== include/linux/sunrpc/auth.h 1.9 vs edited =====
--- 1.9/include/linux/sunrpc/auth.h	Wed Jun 11 19:22:40 2003
+++ edited/include/linux/sunrpc/auth.h	Fri Aug 15 15:35:57 2003
@@ -28,8 +28,7 @@
 struct auth_cred {
 	uid_t	uid;
 	gid_t	gid;
-	int	ngroups;
-	gid_t	*groups;
+	struct group_info *group_info;
 };
 
 /*
===== include/linux/sunrpc/svcauth.h 1.9 vs edited =====
--- 1.9/include/linux/sunrpc/svcauth.h	Fri Jan 10 17:55:15 2003
+++ edited/include/linux/sunrpc/svcauth.h	Fri Aug 15 15:36:02 2003
@@ -16,10 +16,11 @@
 #include <linux/sunrpc/cache.h>
 #include <linux/hash.h>
 
+#define SVC_CRED_NGROUPS	32
 struct svc_cred {
 	uid_t			cr_uid;
 	gid_t			cr_gid;
-	gid_t			cr_groups[NGROUPS];
+	gid_t			cr_groups[SVC_CRED_NGROUPS];
 };
 
 struct svc_rqst;		/* forward decl */
===== kernel/exit.c 1.111 vs edited =====
--- 1.111/kernel/exit.c	Tue Aug  5 23:04:02 2003
+++ edited/kernel/exit.c	Fri Aug 15 15:27:43 2003
@@ -54,6 +54,9 @@
  
 	BUG_ON(p->state < TASK_ZOMBIE);
  
+	if (p->group_info && atomic_dec_and_test(&p->group_info->refcount))
+		groups_free(p->group_info);
+
 	atomic_dec(&p->user->processes);
 	spin_lock(&p->proc_lock);
 	proc_dentry = proc_pid_unhash(p);
===== kernel/fork.c 1.135 vs edited =====
--- 1.135/kernel/fork.c	Thu Aug 14 11:11:35 2003
+++ edited/kernel/fork.c	Fri Aug 15 15:27:39 2003
@@ -885,6 +885,10 @@
 	 */
 	clear_tsk_thread_flag(p, TIF_SYSCALL_TRACE);
 
+	/* increment the groups ref count */
+	if (p->group_info)
+		atomic_inc(&p->group_info->refcount);
+
 	/* Our parent execution domain becomes current domain
 	   These must match for thread signalling to apply */
 	   
===== kernel/sys.c 1.52 vs edited =====
--- 1.52/kernel/sys.c	Fri Jul 18 09:49:56 2003
+++ edited/kernel/sys.c	Fri Aug 15 15:27:14 2003
@@ -1071,9 +1071,161 @@
 /*
  * Supplementary group IDs
  */
-asmlinkage long sys_getgroups(int gidsetsize, gid_t __user *grouplist)
+struct group_info *groups_alloc(int gidsetsize)
+{
+	struct group_info *info;
+	int nblocks;
+
+	nblocks = (gidsetsize/NGROUPS_BLOCK) + (gidsetsize%NGROUPS_BLOCK?1:0);
+	info = kmalloc(sizeof(*info) + nblocks*sizeof(gid_t *), GFP_USER);
+	if (!info)
+		return NULL;
+	info->ngroups = gidsetsize;
+	info->nblocks = nblocks;
+	atomic_set(&info->refcount, 1);
+
+	if (gidsetsize <= NGROUPS_SMALL) {
+		info->blocks[0] = info->small_block;
+	} else {
+		int i;
+		for (i = 0; i < nblocks; i++) {
+			gid_t *b;
+			b = (void *)__get_free_page(GFP_USER);
+			if (!b) {
+				int j;
+				for (j = 0; j < i; j++)
+					free_page((unsigned long)info->blocks[j]);
+				kfree(info);
+				return NULL;
+			}
+			info->blocks[i] = b;
+		}
+	}
+	return info;
+}
+
+void groups_free(struct group_info *info)
+{
+	if (info->ngroups > NGROUPS_SMALL) {
+		int i;
+		for (i = 0; i < info->nblocks; i++)
+			free_page((unsigned long)info->blocks[i]);
+	}
+	kfree(info);
+}
+
+/* export the group_info to a user-space array */
+static int groups_to_user(gid_t *grouplist, struct group_info __user *info)
 {
 	int i;
+	int count = info->ngroups;
+
+	for (i = 0; i < info->nblocks; i++) {
+		int cp_count = min(NGROUPS_BLOCK, count);
+		int off = i * NGROUPS_BLOCK;
+		int len = cp_count * sizeof(*grouplist);
+
+		if (copy_to_user(grouplist+off, info->blocks[i], len))
+			return -EFAULT;
+
+		count -= cp_count;
+	}
+	return 0;
+}
+
+/* fill a group_info from a user-space array - it must be allocated already */
+static int groups_from_user(struct group_info *info, gid_t __user *grouplist)
+ {
+	int i;
+	int count = info->ngroups;
+
+	for (i = 0; i < info->nblocks; i++) {
+		int cp_count = min(NGROUPS_BLOCK, count);
+		int off = i * NGROUPS_BLOCK;
+		int len = cp_count * sizeof(*grouplist);
+
+		if (copy_from_user(info->blocks[i], grouplist+off, len))
+			return -EFAULT;
+
+		count -= cp_count;
+	}
+	return 0;
+}
+
+/* a simple shell-metzner sort */
+static void groups_sort(struct group_info *info)
+{
+	int base, max, stride;
+	int gidsetsize = info->ngroups;
+
+	for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
+		; /* nothing */
+	stride /= 3;
+
+	while (stride) {
+		max = gidsetsize - stride;
+		for (base = 0; base < max; base++) {
+			int left = base;
+			gid_t tmp = GRP_AT(info, base + stride);
+			while (left >= 0 && tmp < GRP_AT(info, left)) {
+				GRP_AT(info, left) = GRP_AT(info, left+stride);
+				left -= stride;
+			}
+			GRP_AT(info, left + stride) = tmp;
+		}
+		stride /= 3;
+	}
+}
+
+/* a simple bsearch */
+static int groups_search(struct group_info *info, gid_t grp)
+{
+	int left, right;
+
+	if (!info)
+		return 0;
+
+	left = 0;
+	right = info->ngroups;
+	while (left < right) {
+		int mid = (left+right)/2;
+		int cmp = grp - GRP_AT(info, mid);
+		if (cmp > 0)
+			left = mid + 1;
+		else if (cmp < 0)
+			right = mid;
+		else
+			return 1;
+	}
+	return 0;
+}
+
+/* validate and set current->group_info */
+int set_group_info(struct group_info *info)
+{
+	int retval;
+
+	retval = security_task_setgroups(info);
+	if (retval)
+		goto out;
+
+	if (current->group_info &&
+	    atomic_dec_and_test(&current->group_info->refcount))
+		groups_free(current->group_info);
+
+	groups_sort(info);
+	current->group_info = info;
+
+	return 0;
+
+out:
+	groups_free(info);
+	return retval;
+}
+
+asmlinkage long sys_getgroups(int gidsetsize, gid_t __user *grouplist)
+{
+	int i = 0;
 	
 	/*
 	 *	SMP: Nobody else can change our grouplist. Thus we are
@@ -1082,54 +1234,41 @@
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
-	if (gidsetsize) {
-		if (i > gidsetsize)
-			return -EINVAL;
-		if (copy_to_user(grouplist, current->groups, sizeof(gid_t)*i))
-			return -EFAULT;
+	if (current->group_info) {
+		i = current->group_info->ngroups;
+		if (gidsetsize) {
+			if (i > gidsetsize)
+				return -EINVAL;
+			if (groups_to_user(grouplist, current->group_info))
+				return -EFAULT;
+		}
 	}
 	return i;
 }
 
 /*
- *	SMP: Our groups are not shared. We can copy to/from them safely
+ *	SMP: Our groups are copy-on-write. We can set them safely
  *	without another task interfering.
  */
  
 asmlinkage long sys_setgroups(int gidsetsize, gid_t __user *grouplist)
 {
-	gid_t groups[NGROUPS];
+	struct group_info *new_info;
 	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(gid_t)))
-		return -EFAULT;
-	retval = security_task_setgroups(gidsetsize, groups);
-	if (retval)
-		return retval;
-	memcpy(current->groups, groups, gidsetsize * sizeof(gid_t));
-	current->ngroups = gidsetsize;
-	return 0;
-}
-
-static int supplemental_group_member(gid_t grp)
-{
-	int i = current->ngroups;
 
-	if (i) {
-		gid_t *groups = current->groups;
-		do {
-			if (*groups == grp)
-				return 1;
-			groups++;
-			i--;
-		} while (i);
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
 	}
-	return 0;
+
+	return set_group_info(new_info);
 }
 
 /*
@@ -1139,7 +1278,7 @@
 {
 	int retval = 1;
 	if (grp != current->fsgid)
-		retval = supplemental_group_member(grp);
+		retval = groups_search(current->group_info, grp);
 	return retval;
 }
 
@@ -1147,7 +1286,7 @@
 {
 	int retval = 1;
 	if (grp != current->egid)
-		retval = supplemental_group_member(grp);
+		retval = groups_search(current->group_info, grp);
 	return retval;
 }
 
===== kernel/uid16.c 1.5 vs edited =====
--- 1.5/kernel/uid16.c	Wed Apr  9 20:51:27 2003
+++ edited/kernel/uid16.c	Fri Aug 22 16:55:22 2003
@@ -107,20 +107,86 @@
 	return sys_setfsgid((gid_t)gid);
 }
 
+static int groups16_to_user(old_gid_t __user *grouplist,
+    struct group_info *info)
+{
+	int i;
+	int count = info->ngroups;
+	old_gid_t *groups;
+	int ret = 0;
+
+	/* temporary, but too large for the stack */
+	groups = kmalloc(NGROUPS_BLOCK * sizeof(*groups), GFP_KERNEL);
+	if (!groups)
+		return -ENOMEM;
+
+	for (i = 0; i < info->nblocks; i++) {
+		int cp_count = min(NGROUPS_BLOCK, count);
+		int off = i * NGROUPS_BLOCK;
+		int len = cp_count * sizeof(*grouplist);
+		int j;
+
+		for (j = 0; j < cp_count; j++)
+			groups[j] = (old_gid_t)GRP_AT(info, i*NGROUPS_BLOCK+j);
+		if (copy_to_user(grouplist+off, groups, len)) {
+			ret = -EFAULT;
+			goto out;
+		}
+
+		count -= cp_count;
+	}
+out:
+	kfree(groups);
+	return ret;
+}
+
+static int groups16_from_user(struct group_info *info,
+    old_gid_t __user *grouplist)
+{
+	int i;
+	int count = info->ngroups;
+	old_gid_t *groups;
+	int ret = 0;
+
+	/* too large for the stack? */
+	groups = kmalloc(NGROUPS_BLOCK * sizeof(*groups), GFP_KERNEL);
+	if (!groups)
+		return -ENOMEM;
+
+	for (i = 0; i < info->nblocks; i++) {
+		int cp_count = min(NGROUPS_BLOCK, count);
+		int off = i * NGROUPS_BLOCK;
+		int len = cp_count * sizeof(*grouplist);
+		int j;
+
+		if (copy_from_user(groups, grouplist+off, len)) {
+			ret = -EFAULT;
+			goto out;
+		}
+		for (j = 0; j < cp_count; j++)
+			GRP_AT(info, i*NGROUPS_BLOCK+j) = (gid_t)groups[j];
+
+		count -= cp_count;
+	}
+out:
+	kfree(groups);
+	return ret;
+}
+
 asmlinkage long sys_getgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
-	int i,j;
+	int i = 0;
 
 	if (gidsetsize < 0)
 		return -EINVAL;
-	i = current->ngroups;
+	if (!current->group_info)
+		return 0;
+
+	i = current->group_info->ngroups;
 	if (gidsetsize) {
 		if (i > gidsetsize)
 			return -EINVAL;
-		for(j=0;j<i;j++)
-			groups[j] = current->groups[j];
-		if (copy_to_user(grouplist, groups, sizeof(old_gid_t)*i))
+		if (groups16_to_user(grouplist, current->group_info))
 			return -EFAULT;
 	}
 	return i;
@@ -128,24 +194,21 @@
 
 asmlinkage long sys_setgroups16(int gidsetsize, old_gid_t __user *grouplist)
 {
-	old_gid_t groups[NGROUPS];
-	gid_t new_groups[NGROUPS];
-	int i;
+	struct group_info *new_info;
+	int retval;
 
 	if (!capable(CAP_SETGID))
 		return -EPERM;
-	if ((unsigned) gidsetsize > NGROUPS)
-		return -EINVAL;
-	if (copy_from_user(groups, grouplist, gidsetsize * sizeof(old_gid_t)))
-		return -EFAULT;
-	for (i = 0 ; i < gidsetsize ; i++)
-		new_groups[i] = (gid_t)groups[i];
-	i = security_task_setgroups(gidsetsize, new_groups);
-	if (i)
-		return i;
-	memcpy(current->groups, new_groups, gidsetsize * sizeof(gid_t));
-	current->ngroups = gidsetsize;
-	return 0;
+	new_info = groups_alloc(gidsetsize);
+	if (!new_info)
+		return -ENOMEM;
+	retval = groups16_from_user(new_info, grouplist);
+	if (retval) {
+		groups_free(new_info);
+		return retval;
+	}
+
+	return set_group_info(new_info);
 }
 
 asmlinkage long sys_getuid16(void)
===== net/sunrpc/auth.c 1.12 vs edited =====
--- 1.12/net/sunrpc/auth.c	Wed Jun 11 19:22:40 2003
+++ edited/net/sunrpc/auth.c	Fri Aug 15 15:37:25 2003
@@ -249,8 +249,7 @@
 	struct auth_cred acred = {
 		.uid = current->fsuid,
 		.gid = current->fsgid,
-		.ngroups = current->ngroups,
-		.groups = current->groups,
+		.group_info = current->group_info,
 	};
 	dprintk("RPC:     looking up %s cred\n",
 		auth->au_ops->au_name);
@@ -264,8 +263,7 @@
 	struct auth_cred acred = {
 		.uid = current->fsuid,
 		.gid = current->fsgid,
-		.ngroups = current->ngroups,
-		.groups = current->groups,
+		.group_info = current->group_info,
 	};
 
 	dprintk("RPC: %4d looking up %s cred\n",
===== net/sunrpc/auth_unix.c 1.11 vs edited =====
--- 1.11/net/sunrpc/auth_unix.c	Mon Feb 24 08:08:37 2003
+++ edited/net/sunrpc/auth_unix.c	Fri Aug 15 15:37:31 2003
@@ -82,7 +82,7 @@
 		cred->uc_gid = cred->uc_pgid = 0;
 		cred->uc_gids[0] = NOGROUP;
 	} else {
-		int groups = acred->ngroups;
+		int groups = acred->group_info ? acred->group_info->ngroups : 0;
 		if (groups > NFS_NGROUPS)
 			groups = NFS_NGROUPS;
 
@@ -91,7 +91,7 @@
 		cred->uc_puid = current->uid;
 		cred->uc_pgid = current->gid;
 		for (i = 0; i < groups; i++)
-			cred->uc_gids[i] = (gid_t) acred->groups[i];
+			cred->uc_gids[i] = GRP_AT(acred->group_info, i);
 		if (i < NFS_NGROUPS)
 		  cred->uc_gids[i] = NOGROUP;
 	}
@@ -126,11 +126,11 @@
 		 || cred->uc_pgid != current->gid)
 			return 0;
 
-		groups = acred->ngroups;
+		groups = acred->group_info ? acred->group_info->ngroups : 0;
 		if (groups > NFS_NGROUPS)
 			groups = NFS_NGROUPS;
 		for (i = 0; i < groups ; i++)
-			if (cred->uc_gids[i] != (gid_t) acred->groups[i])
+			if (cred->uc_gids[i] != GRP_AT(acred->group_info, i))
 				return 0;
 		return 1;
 	}
===== net/sunrpc/svcauth_unix.c 1.20 vs edited =====
--- 1.20/net/sunrpc/svcauth_unix.c	Thu Jun 26 21:21:42 2003
+++ edited/net/sunrpc/svcauth_unix.c	Fri Aug 15 15:37:38 2003
@@ -434,11 +434,11 @@
 	if (slen > 16 || (len -= (slen + 2)*4) < 0)
 		goto badcred;
 	for (i = 0; i < slen; i++)
-		if (i < NGROUPS)
+		if (i < SVC_CRED_NGROUPS)
 			cred->cr_groups[i] = ntohl(svc_getu32(argv));
 		else
 			svc_getu32(argv);
-	if (i < NGROUPS)
+	if (i < SVC_CRED_NGROUPS)
 		cred->cr_groups[i] = NOGROUP;
 
 	if (svc_getu32(argv) != RPC_AUTH_NULL || svc_getu32(argv) != 0) {
===== security/dummy.c 1.27 vs edited =====
--- 1.27/security/dummy.c	Wed Jul  2 21:22:38 2003
+++ edited/security/dummy.c	Fri Aug 15 15:37:43 2003
@@ -530,7 +530,7 @@
 	return 0;
 }
 
-static int dummy_task_setgroups (int gidsetsize, gid_t * grouplist)
+static int dummy_task_setgroups (struct group_info *group_info)
 {
 	return 0;
 }

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
  2003-09-29 22:29 ` Pete Zaitcev
@ 2003-09-29 22:43   ` Tim Hockin
  2003-09-29 22:55     ` Tim Hockin
  0 siblings, 1 reply; 21+ messages in thread
From: Tim Hockin @ 2003-09-29 22:43 UTC (permalink / raw)
  To: Pete Zaitcev; +Cc: Linus Torvalds, linux-kernel

On Mon, Sep 29, 2003 at 06:29:32PM -0400, Pete Zaitcev wrote:
> >> This version drops the internal groups array (it's so often shared
> >> that it's not worth it, and the logic becomes a bit neater), and does
> >> vmalloc fallback in case someone has massive number of groups.
> > 
> > Why?
> 
> > The vmalloc space is limited, and the code just gets uglier.
> 
> Tim was going to write a version that segments groups into
> smaller arrays. I reckon it was too difficult?

I posted it once or twice then got busy.  It's an array of pages.  Rusty has
it, but didn't believe me when I said Linus wouldn't let vmalloc() fly.

> > Have you been looking at glibc sources lately, or why do you believe that 
> > we should encourage insane usage?
> 
> We have some customers who run insane number of groups,
> with their own patches. This practice is popular in the
> Beowulf crowd for some reason. I should note this is not
> very mainstream.

I'd be ok with the simplest "kmalloc or too bad" version, but our customers
wouldn't.

My version uses a struct group_info which has an array of pages.  The groups
are sorted and bsearched, instead of linear.  The perfomance is quite good.
An older version against 2.6.0-test1 or something is attached.  If this
method will fly, I'll take some of Rusty's good ideas and finish this
version of it..

Linus?

^ permalink raw reply	[flat|nested] 21+ messages in thread

* Re: [PATCH] Many groups patch.
       [not found] <mailman.1064857032.26219.linux-kernel2news@redhat.com>
@ 2003-09-29 22:29 ` Pete Zaitcev
  2003-09-29 22:43   ` Tim Hockin
  0 siblings, 1 reply; 21+ messages in thread
From: Pete Zaitcev @ 2003-09-29 22:29 UTC (permalink / raw)
  To: Linus Torvalds; +Cc: linux-kernel

>> This version drops the internal groups array (it's so often shared
>> that it's not worth it, and the logic becomes a bit neater), and does
>> vmalloc fallback in case someone has massive number of groups.
> 
> Why?

> The vmalloc space is limited, and the code just gets uglier.

Tim was going to write a version that segments groups into
smaller arrays. I reckon it was too difficult?

> Have you been looking at glibc sources lately, or why do you believe that 
> we should encourage insane usage?

We have some customers who run insane number of groups,
with their own patches. This practice is popular in the
Beowulf crowd for some reason. I should note this is not
very mainstream.

-- Pete

^ permalink raw reply	[flat|nested] 21+ messages in thread

end of thread, other threads:[~2003-10-02  9:15 UTC | newest]

Thread overview: 21+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2003-09-29  7:19 [PATCH] Many groups patch Rusty Russell
2003-09-29 17:25 ` Linus Torvalds
2003-09-29 23:30   ` Rusty Russell
2003-09-30  4:11     ` Tim Hockin
2003-10-01  7:29       ` Rusty Russell
     [not found] <mailman.1064857032.26219.linux-kernel2news@redhat.com>
2003-09-29 22:29 ` Pete Zaitcev
2003-09-29 22:43   ` Tim Hockin
2003-09-29 22:55     ` Tim Hockin
2003-09-29 23:10       ` Linus Torvalds
2003-10-01  7:10         ` Tim Hockin
2003-10-01 18:46           ` Tim Hockin
2003-10-01 19:22             ` Linus Torvalds
2003-10-01 20:29               ` Tim Hockin
2003-10-01 20:46                 ` Linus Torvalds
2003-10-01 21:12                   ` Tim Hockin
2003-10-01 21:16                   ` Tim Hockin
2003-10-02  2:09               ` Rusty Russell
2003-10-02  4:39                 ` Tim Hockin
2003-10-02  9:10                 ` David S. Miller
2003-10-02  2:25             ` Rusty Russell
2003-10-02  2:46               ` Pete Zaitcev

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).