Name: Dynamic Allocation of Groups Array When Required Author: Rusty Russell Status: Booted on 2.6.0-test5-bk5 Depends: Misc/groups_32.patch.gz D: This is a naive approach to larger groups: use maximal external D: allocation when internal array exceeded, never free until task D: struct freed. D: D: Importantly, this patch also gets rid of [NGROUPS] arrays on the D: stack in case NGROUPS is v. large. diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .22128-linux-2.6.0-test5-bk5/include/linux/sched.h .22128-linux-2.6.0-test5-bk5.updated/include/linux/sched.h --- .22128-linux-2.6.0-test5-bk5/include/linux/sched.h 2003-09-09 10:35:03.000000000 +1000 +++ .22128-linux-2.6.0-test5-bk5.updated/include/linux/sched.h 2003-09-20 12:01:35.000000000 +1000 @@ -326,6 +326,8 @@ struct k_itimer { struct io_context; /* See blkdev.h */ void exit_io_context(void); +#define NGROUPS_INTERNAL 1 /* For testing. */ + struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ struct thread_info *thread_info; @@ -399,7 +401,8 @@ struct task_struct { uid_t uid,euid,suid,fsuid; gid_t gid,egid,sgid,fsgid; int ngroups; - gid_t groups[NGROUPS]; + gid_t *groups; + gid_t internal_groups[NGROUPS_INTERNAL]; kernel_cap_t cap_effective, cap_inheritable, cap_permitted; int keep_capabilities:1; struct user_struct *user; @@ -667,6 +670,9 @@ 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 *); +/* Enlarge current groups to this size, if possible. 0 or -err. */ +extern int enlarge_current_groups(unsigned int ngroups); + #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 .22128-linux-2.6.0-test5-bk5/kernel/fork.c .22128-linux-2.6.0-test5-bk5.updated/kernel/fork.c --- .22128-linux-2.6.0-test5-bk5/kernel/fork.c 2003-09-09 10:35:05.000000000 +1000 +++ .22128-linux-2.6.0-test5-bk5.updated/kernel/fork.c 2003-09-20 12:03:45.000000000 +1000 @@ -73,6 +73,8 @@ static kmem_cache_t *task_struct_cachep; static void free_task(struct task_struct *tsk) { + if (unlikely(tsk->groups != tsk->internal_groups)) + kfree(tsk->groups); free_thread_info(tsk->thread_info); free_task_struct(tsk); } @@ -195,6 +197,7 @@ static struct task_struct *dup_task_stru { struct task_struct *tsk; struct thread_info *ti; + gid_t *groups = NULL; prepare_to_copy(orig); @@ -207,12 +210,29 @@ static struct task_struct *dup_task_stru free_task_struct(tsk); return NULL; } + if (unlikely(orig->ngroups > NGROUPS_INTERNAL)) { + groups = kmalloc(sizeof(groups[0]) * NGROUPS, GFP_KERNEL); + if (!groups) { + free_task_struct(tsk); + free_thread_info(ti); + return NULL; + } + } *ti = *orig->thread_info; *tsk = *orig; tsk->thread_info = ti; ti->task = tsk; + if (unlikely(orig->groups != orig->internal_groups)) { + /* They might be using external array because they + * once had more groups, but no longer. */ + if (!groups) + groups = tsk->internal_groups; + memcpy(groups, orig->groups, sizeof(groups[0]) * tsk->ngroups); + tsk->groups = groups; + } + /* One for us, one for whoever does the "release_task()" (usually parent) */ atomic_set(&tsk->usage,2); return tsk; diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .22128-linux-2.6.0-test5-bk5/kernel/sched.c .22128-linux-2.6.0-test5-bk5.updated/kernel/sched.c --- .22128-linux-2.6.0-test5-bk5/kernel/sched.c 2003-09-19 10:52:43.000000000 +1000 +++ .22128-linux-2.6.0-test5-bk5.updated/kernel/sched.c 2003-09-20 12:01:35.000000000 +1000 @@ -2617,3 +2617,20 @@ void __preempt_write_lock(rwlock_t *lock } while (!_raw_write_trylock(lock)); } #endif + +/* Enlarge current groups to this size, if possible. 0 or -err. */ +int enlarge_current_groups(unsigned int ngroups) +{ + if (ngroups > NGROUPS) + return -EINVAL; + + if (ngroups > NGROUPS_INTERNAL + && current->groups == current->internal_groups) { + gid_t *groups; + groups = kmalloc(sizeof(groups[0]) * NGROUPS, GFP_KERNEL); + if (!groups) + return -ENOMEM; + current->groups = groups; + } + return 0; +} diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .22128-linux-2.6.0-test5-bk5/kernel/sys.c .22128-linux-2.6.0-test5-bk5.updated/kernel/sys.c --- .22128-linux-2.6.0-test5-bk5/kernel/sys.c 2003-09-09 10:35:05.000000000 +1000 +++ .22128-linux-2.6.0-test5-bk5.updated/kernel/sys.c 2003-09-20 12:01:35.000000000 +1000 @@ -1101,8 +1101,9 @@ asmlinkage long sys_setgroups(int gidset if (!capable(CAP_SETGID)) return -EPERM; - if ((unsigned) gidsetsize > NGROUPS) - return -EINVAL; + retval = enlarge_current_groups(gidsetsize); + if (retval < 0) + return retval; if (copy_from_user(groups, grouplist, gidsetsize * sizeof(gid_t))) return -EFAULT; retval = security_task_setgroups(gidsetsize, groups); diff -urpN --exclude TAGS -X /home/rusty/devel/kernel/kernel-patches/current-dontdiff --minimal .22128-linux-2.6.0-test5-bk5/kernel/uid16.c .22128-linux-2.6.0-test5-bk5.updated/kernel/uid16.c --- .22128-linux-2.6.0-test5-bk5/kernel/uid16.c 2003-04-20 18:05:16.000000000 +1000 +++ .22128-linux-2.6.0-test5-bk5.updated/kernel/uid16.c 2003-09-20 12:01:35.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;jgroups[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;jgroups[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 = enlarge_current_groups(gidsetsize)) < 0) + return ret; + ret = -ENOMEM; + groups = kmalloc(sizeof(groups[0]) * NGROUPS, GFP_KERNEL); + new_groups = kmalloc(sizeof(new_groups[0]) * NGROUPS, 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)