| /* |
| * Supplementary group IDs |
| */ |
| #include <linux/cred.h> |
| #include <linux/export.h> |
| #include <linux/slab.h> |
| #include <linux/security.h> |
| #include <linux/syscalls.h> |
| #include <asm/uaccess.h> |
| |
| /* 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 *group_info; |
| int nblocks; |
| int i; |
| |
| nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK; |
| /* Make sure we always allocate at least one indirect block pointer */ |
| nblocks = nblocks ? : 1; |
| group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER); |
| if (!group_info) |
| return NULL; |
| group_info->ngroups = gidsetsize; |
| group_info->nblocks = nblocks; |
| atomic_set(&group_info->usage, 1); |
| |
| if (gidsetsize <= NGROUPS_SMALL) |
| group_info->blocks[0] = group_info->small_block; |
| else { |
| for (i = 0; i < nblocks; i++) { |
| gid_t *b; |
| b = (void *)__get_free_page(GFP_USER); |
| if (!b) |
| goto out_undo_partial_alloc; |
| group_info->blocks[i] = b; |
| } |
| } |
| return group_info; |
| |
| out_undo_partial_alloc: |
| while (--i >= 0) { |
| free_page((unsigned long)group_info->blocks[i]); |
| } |
| kfree(group_info); |
| return NULL; |
| } |
| |
| EXPORT_SYMBOL(groups_alloc); |
| |
| void groups_free(struct group_info *group_info) |
| { |
| if (group_info->blocks[0] != group_info->small_block) { |
| int i; |
| for (i = 0; i < group_info->nblocks; i++) |
| free_page((unsigned long)group_info->blocks[i]); |
| } |
| kfree(group_info); |
| } |
| |
| EXPORT_SYMBOL(groups_free); |
| |
| /* export the group_info to a user-space array */ |
| static int groups_to_user(gid_t __user *grouplist, |
| const struct group_info *group_info) |
| { |
| int i; |
| unsigned int count = group_info->ngroups; |
| |
| for (i = 0; i < group_info->nblocks; i++) { |
| unsigned int cp_count = min(NGROUPS_PER_BLOCK, count); |
| unsigned int len = cp_count * sizeof(*grouplist); |
| |
| if (copy_to_user(grouplist, group_info->blocks[i], len)) |
| return -EFAULT; |
| |
| grouplist += NGROUPS_PER_BLOCK; |
| 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 *group_info, |
| gid_t __user *grouplist) |
| { |
| int i; |
| unsigned int count = group_info->ngroups; |
| |
| for (i = 0; i < group_info->nblocks; i++) { |
| unsigned int cp_count = min(NGROUPS_PER_BLOCK, count); |
| unsigned int len = cp_count * sizeof(*grouplist); |
| |
| if (copy_from_user(group_info->blocks[i], grouplist, len)) |
| return -EFAULT; |
| |
| grouplist += NGROUPS_PER_BLOCK; |
| count -= cp_count; |
| } |
| return 0; |
| } |
| |
| /* a simple Shell sort */ |
| static void groups_sort(struct group_info *group_info) |
| { |
| int base, max, stride; |
| int gidsetsize = group_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; |
| int right = left + stride; |
| gid_t tmp = GROUP_AT(group_info, right); |
| |
| while (left >= 0 && GROUP_AT(group_info, left) > tmp) { |
| GROUP_AT(group_info, right) = |
| GROUP_AT(group_info, left); |
| right = left; |
| left -= stride; |
| } |
| GROUP_AT(group_info, right) = tmp; |
| } |
| stride /= 3; |
| } |
| } |
| |
| /* a simple bsearch */ |
| int groups_search(const struct group_info *group_info, gid_t grp) |
| { |
| unsigned int left, right; |
| |
| if (!group_info) |
| return 0; |
| |
| left = 0; |
| right = group_info->ngroups; |
| while (left < right) { |
| unsigned int mid = (left+right)/2; |
| if (grp > GROUP_AT(group_info, mid)) |
| left = mid + 1; |
| else if (grp < GROUP_AT(group_info, mid)) |
| right = mid; |
| else |
| return 1; |
| } |
| return 0; |
| } |
| |
| /** |
| * set_groups - Change a group subscription in a set of credentials |
| * @new: The newly prepared set of credentials to alter |
| * @group_info: The group list to install |
| * |
| * Validate a group subscription and, if valid, insert it into a set |
| * of credentials. |
| */ |
| int set_groups(struct cred *new, struct group_info *group_info) |
| { |
| put_group_info(new->group_info); |
| groups_sort(group_info); |
| get_group_info(group_info); |
| new->group_info = group_info; |
| return 0; |
| } |
| |
| EXPORT_SYMBOL(set_groups); |
| |
| /** |
| * set_current_groups - Change current's group subscription |
| * @group_info: The group list to impose |
| * |
| * Validate a group subscription and, if valid, impose it upon current's task |
| * security record. |
| */ |
| int set_current_groups(struct group_info *group_info) |
| { |
| struct cred *new; |
| int ret; |
| |
| new = prepare_creds(); |
| if (!new) |
| return -ENOMEM; |
| |
| ret = set_groups(new, group_info); |
| if (ret < 0) { |
| abort_creds(new); |
| return ret; |
| } |
| |
| return commit_creds(new); |
| } |
| |
| EXPORT_SYMBOL(set_current_groups); |
| |
| SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t __user *, grouplist) |
| { |
| const struct cred *cred = current_cred(); |
| int i; |
| |
| if (gidsetsize < 0) |
| return -EINVAL; |
| |
| /* no need to grab task_lock here; it cannot change */ |
| i = cred->group_info->ngroups; |
| if (gidsetsize) { |
| if (i > gidsetsize) { |
| i = -EINVAL; |
| goto out; |
| } |
| if (groups_to_user(grouplist, cred->group_info)) { |
| i = -EFAULT; |
| goto out; |
| } |
| } |
| out: |
| return i; |
| } |
| |
| /* |
| * SMP: Our groups are copy-on-write. We can set them safely |
| * without another task interfering. |
| */ |
| |
| SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist) |
| { |
| struct group_info *group_info; |
| int retval; |
| |
| if (!nsown_capable(CAP_SETGID)) |
| return -EPERM; |
| if ((unsigned)gidsetsize > NGROUPS_MAX) |
| return -EINVAL; |
| |
| group_info = groups_alloc(gidsetsize); |
| if (!group_info) |
| return -ENOMEM; |
| retval = groups_from_user(group_info, grouplist); |
| if (retval) { |
| put_group_info(group_info); |
| return retval; |
| } |
| |
| retval = set_current_groups(group_info); |
| put_group_info(group_info); |
| |
| return retval; |
| } |
| |
| /* |
| * Check whether we're fsgid/egid or in the supplemental group.. |
| */ |
| int in_group_p(gid_t grp) |
| { |
| const struct cred *cred = current_cred(); |
| int retval = 1; |
| |
| if (grp != cred->fsgid) |
| retval = groups_search(cred->group_info, grp); |
| return retval; |
| } |
| |
| EXPORT_SYMBOL(in_group_p); |
| |
| int in_egroup_p(gid_t grp) |
| { |
| const struct cred *cred = current_cred(); |
| int retval = 1; |
| |
| if (grp != cred->egid) |
| retval = groups_search(cred->group_info, grp); |
| return retval; |
| } |
| |
| EXPORT_SYMBOL(in_egroup_p); |