1b2441318SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
230639b6aSAlexey Dobriyan /*
330639b6aSAlexey Dobriyan * Supplementary group IDs
430639b6aSAlexey Dobriyan */
530639b6aSAlexey Dobriyan #include <linux/cred.h>
69984de1aSPaul Gortmaker #include <linux/export.h>
730639b6aSAlexey Dobriyan #include <linux/slab.h>
830639b6aSAlexey Dobriyan #include <linux/security.h>
9b7b2562fSRasmus Villemoes #include <linux/sort.h>
1030639b6aSAlexey Dobriyan #include <linux/syscalls.h>
11273d2c67SEric W. Biederman #include <linux/user_namespace.h>
1281243eacSAlexey Dobriyan #include <linux/vmalloc.h>
137c0f6ba6SLinus Torvalds #include <linux/uaccess.h>
1430639b6aSAlexey Dobriyan
groups_alloc(int gidsetsize)1530639b6aSAlexey Dobriyan struct group_info *groups_alloc(int gidsetsize)
1630639b6aSAlexey Dobriyan {
1781243eacSAlexey Dobriyan struct group_info *gi;
18e1e01411SHubert Jasudowicz gi = kvmalloc(struct_size(gi, gid, gidsetsize), GFP_KERNEL_ACCOUNT);
1981243eacSAlexey Dobriyan if (!gi)
2030639b6aSAlexey Dobriyan return NULL;
2130639b6aSAlexey Dobriyan
2281243eacSAlexey Dobriyan atomic_set(&gi->usage, 1);
2381243eacSAlexey Dobriyan gi->ngroups = gidsetsize;
2481243eacSAlexey Dobriyan return gi;
2530639b6aSAlexey Dobriyan }
2630639b6aSAlexey Dobriyan
2730639b6aSAlexey Dobriyan EXPORT_SYMBOL(groups_alloc);
2830639b6aSAlexey Dobriyan
groups_free(struct group_info * group_info)2930639b6aSAlexey Dobriyan void groups_free(struct group_info *group_info)
3030639b6aSAlexey Dobriyan {
3181243eacSAlexey Dobriyan kvfree(group_info);
3230639b6aSAlexey Dobriyan }
3330639b6aSAlexey Dobriyan
3430639b6aSAlexey Dobriyan EXPORT_SYMBOL(groups_free);
3530639b6aSAlexey Dobriyan
3630639b6aSAlexey Dobriyan /* export the group_info to a user-space array */
groups_to_user(gid_t __user * grouplist,const struct group_info * group_info)3730639b6aSAlexey Dobriyan static int groups_to_user(gid_t __user *grouplist,
3830639b6aSAlexey Dobriyan const struct group_info *group_info)
3930639b6aSAlexey Dobriyan {
40ae2975bcSEric W. Biederman struct user_namespace *user_ns = current_user_ns();
4130639b6aSAlexey Dobriyan int i;
4230639b6aSAlexey Dobriyan unsigned int count = group_info->ngroups;
4330639b6aSAlexey Dobriyan
44ae2975bcSEric W. Biederman for (i = 0; i < count; i++) {
45ae2975bcSEric W. Biederman gid_t gid;
4681243eacSAlexey Dobriyan gid = from_kgid_munged(user_ns, group_info->gid[i]);
47ae2975bcSEric W. Biederman if (put_user(gid, grouplist+i))
4830639b6aSAlexey Dobriyan return -EFAULT;
4930639b6aSAlexey Dobriyan }
5030639b6aSAlexey Dobriyan return 0;
5130639b6aSAlexey Dobriyan }
5230639b6aSAlexey Dobriyan
5330639b6aSAlexey Dobriyan /* fill a group_info from a user-space array - it must be allocated already */
groups_from_user(struct group_info * group_info,gid_t __user * grouplist)5430639b6aSAlexey Dobriyan static int groups_from_user(struct group_info *group_info,
5530639b6aSAlexey Dobriyan gid_t __user *grouplist)
5630639b6aSAlexey Dobriyan {
57ae2975bcSEric W. Biederman struct user_namespace *user_ns = current_user_ns();
5830639b6aSAlexey Dobriyan int i;
5930639b6aSAlexey Dobriyan unsigned int count = group_info->ngroups;
6030639b6aSAlexey Dobriyan
61ae2975bcSEric W. Biederman for (i = 0; i < count; i++) {
62ae2975bcSEric W. Biederman gid_t gid;
63ae2975bcSEric W. Biederman kgid_t kgid;
64ae2975bcSEric W. Biederman if (get_user(gid, grouplist+i))
6530639b6aSAlexey Dobriyan return -EFAULT;
6630639b6aSAlexey Dobriyan
67ae2975bcSEric W. Biederman kgid = make_kgid(user_ns, gid);
68ae2975bcSEric W. Biederman if (!gid_valid(kgid))
69ae2975bcSEric W. Biederman return -EINVAL;
70ae2975bcSEric W. Biederman
7181243eacSAlexey Dobriyan group_info->gid[i] = kgid;
7230639b6aSAlexey Dobriyan }
7330639b6aSAlexey Dobriyan return 0;
7430639b6aSAlexey Dobriyan }
7530639b6aSAlexey Dobriyan
gid_cmp(const void * _a,const void * _b)76b7b2562fSRasmus Villemoes static int gid_cmp(const void *_a, const void *_b)
77b7b2562fSRasmus Villemoes {
78b7b2562fSRasmus Villemoes kgid_t a = *(kgid_t *)_a;
79b7b2562fSRasmus Villemoes kgid_t b = *(kgid_t *)_b;
80b7b2562fSRasmus Villemoes
81b7b2562fSRasmus Villemoes return gid_gt(a, b) - gid_lt(a, b);
82b7b2562fSRasmus Villemoes }
83b7b2562fSRasmus Villemoes
groups_sort(struct group_info * group_info)84bdcf0a42SThiago Rafael Becker void groups_sort(struct group_info *group_info)
8530639b6aSAlexey Dobriyan {
86b7b2562fSRasmus Villemoes sort(group_info->gid, group_info->ngroups, sizeof(*group_info->gid),
87b7b2562fSRasmus Villemoes gid_cmp, NULL);
8830639b6aSAlexey Dobriyan }
89bdcf0a42SThiago Rafael Becker EXPORT_SYMBOL(groups_sort);
9030639b6aSAlexey Dobriyan
9130639b6aSAlexey Dobriyan /* a simple bsearch */
groups_search(const struct group_info * group_info,kgid_t grp)92ae2975bcSEric W. Biederman int groups_search(const struct group_info *group_info, kgid_t grp)
9330639b6aSAlexey Dobriyan {
9430639b6aSAlexey Dobriyan unsigned int left, right;
9530639b6aSAlexey Dobriyan
9630639b6aSAlexey Dobriyan if (!group_info)
9730639b6aSAlexey Dobriyan return 0;
9830639b6aSAlexey Dobriyan
9930639b6aSAlexey Dobriyan left = 0;
10030639b6aSAlexey Dobriyan right = group_info->ngroups;
10130639b6aSAlexey Dobriyan while (left < right) {
10230639b6aSAlexey Dobriyan unsigned int mid = (left+right)/2;
10381243eacSAlexey Dobriyan if (gid_gt(grp, group_info->gid[mid]))
10430639b6aSAlexey Dobriyan left = mid + 1;
10581243eacSAlexey Dobriyan else if (gid_lt(grp, group_info->gid[mid]))
10630639b6aSAlexey Dobriyan right = mid;
10730639b6aSAlexey Dobriyan else
10830639b6aSAlexey Dobriyan return 1;
10930639b6aSAlexey Dobriyan }
11030639b6aSAlexey Dobriyan return 0;
11130639b6aSAlexey Dobriyan }
11230639b6aSAlexey Dobriyan
11330639b6aSAlexey Dobriyan /**
11430639b6aSAlexey Dobriyan * set_groups - Change a group subscription in a set of credentials
11530639b6aSAlexey Dobriyan * @new: The newly prepared set of credentials to alter
11630639b6aSAlexey Dobriyan * @group_info: The group list to install
11730639b6aSAlexey Dobriyan */
set_groups(struct cred * new,struct group_info * group_info)1188f6c5ffcSWang YanQing void set_groups(struct cred *new, struct group_info *group_info)
11930639b6aSAlexey Dobriyan {
12030639b6aSAlexey Dobriyan put_group_info(new->group_info);
12130639b6aSAlexey Dobriyan get_group_info(group_info);
12230639b6aSAlexey Dobriyan new->group_info = group_info;
12330639b6aSAlexey Dobriyan }
12430639b6aSAlexey Dobriyan
12530639b6aSAlexey Dobriyan EXPORT_SYMBOL(set_groups);
12630639b6aSAlexey Dobriyan
12730639b6aSAlexey Dobriyan /**
12830639b6aSAlexey Dobriyan * set_current_groups - Change current's group subscription
12930639b6aSAlexey Dobriyan * @group_info: The group list to impose
13030639b6aSAlexey Dobriyan *
13130639b6aSAlexey Dobriyan * Validate a group subscription and, if valid, impose it upon current's task
13230639b6aSAlexey Dobriyan * security record.
13330639b6aSAlexey Dobriyan */
set_current_groups(struct group_info * group_info)13430639b6aSAlexey Dobriyan int set_current_groups(struct group_info *group_info)
13530639b6aSAlexey Dobriyan {
13630639b6aSAlexey Dobriyan struct cred *new;
137*fcfe0ac2SMicah Morton const struct cred *old;
138*fcfe0ac2SMicah Morton int retval;
13930639b6aSAlexey Dobriyan
14030639b6aSAlexey Dobriyan new = prepare_creds();
14130639b6aSAlexey Dobriyan if (!new)
14230639b6aSAlexey Dobriyan return -ENOMEM;
14330639b6aSAlexey Dobriyan
144*fcfe0ac2SMicah Morton old = current_cred();
145*fcfe0ac2SMicah Morton
1468f6c5ffcSWang YanQing set_groups(new, group_info);
147*fcfe0ac2SMicah Morton
148*fcfe0ac2SMicah Morton retval = security_task_fix_setgroups(new, old);
149*fcfe0ac2SMicah Morton if (retval < 0)
150*fcfe0ac2SMicah Morton goto error;
151*fcfe0ac2SMicah Morton
15230639b6aSAlexey Dobriyan return commit_creds(new);
153*fcfe0ac2SMicah Morton
154*fcfe0ac2SMicah Morton error:
155*fcfe0ac2SMicah Morton abort_creds(new);
156*fcfe0ac2SMicah Morton return retval;
15730639b6aSAlexey Dobriyan }
15830639b6aSAlexey Dobriyan
15930639b6aSAlexey Dobriyan EXPORT_SYMBOL(set_current_groups);
16030639b6aSAlexey Dobriyan
SYSCALL_DEFINE2(getgroups,int,gidsetsize,gid_t __user *,grouplist)16130639b6aSAlexey Dobriyan SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t __user *, grouplist)
16230639b6aSAlexey Dobriyan {
16330639b6aSAlexey Dobriyan const struct cred *cred = current_cred();
16430639b6aSAlexey Dobriyan int i;
16530639b6aSAlexey Dobriyan
16630639b6aSAlexey Dobriyan if (gidsetsize < 0)
16730639b6aSAlexey Dobriyan return -EINVAL;
16830639b6aSAlexey Dobriyan
16930639b6aSAlexey Dobriyan /* no need to grab task_lock here; it cannot change */
17030639b6aSAlexey Dobriyan i = cred->group_info->ngroups;
17130639b6aSAlexey Dobriyan if (gidsetsize) {
17230639b6aSAlexey Dobriyan if (i > gidsetsize) {
17330639b6aSAlexey Dobriyan i = -EINVAL;
17430639b6aSAlexey Dobriyan goto out;
17530639b6aSAlexey Dobriyan }
17630639b6aSAlexey Dobriyan if (groups_to_user(grouplist, cred->group_info)) {
17730639b6aSAlexey Dobriyan i = -EFAULT;
17830639b6aSAlexey Dobriyan goto out;
17930639b6aSAlexey Dobriyan }
18030639b6aSAlexey Dobriyan }
18130639b6aSAlexey Dobriyan out:
18230639b6aSAlexey Dobriyan return i;
18330639b6aSAlexey Dobriyan }
18430639b6aSAlexey Dobriyan
may_setgroups(void)1857ff4d90bSEric W. Biederman bool may_setgroups(void)
1867ff4d90bSEric W. Biederman {
1877ff4d90bSEric W. Biederman struct user_namespace *user_ns = current_user_ns();
1887ff4d90bSEric W. Biederman
189111767c1SThomas Cedeno return ns_capable_setid(user_ns, CAP_SETGID) &&
190273d2c67SEric W. Biederman userns_may_setgroups(user_ns);
1917ff4d90bSEric W. Biederman }
1927ff4d90bSEric W. Biederman
19330639b6aSAlexey Dobriyan /*
19430639b6aSAlexey Dobriyan * SMP: Our groups are copy-on-write. We can set them safely
19530639b6aSAlexey Dobriyan * without another task interfering.
19630639b6aSAlexey Dobriyan */
19730639b6aSAlexey Dobriyan
SYSCALL_DEFINE2(setgroups,int,gidsetsize,gid_t __user *,grouplist)19830639b6aSAlexey Dobriyan SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
19930639b6aSAlexey Dobriyan {
20030639b6aSAlexey Dobriyan struct group_info *group_info;
20130639b6aSAlexey Dobriyan int retval;
20230639b6aSAlexey Dobriyan
2037ff4d90bSEric W. Biederman if (!may_setgroups())
20430639b6aSAlexey Dobriyan return -EPERM;
20530639b6aSAlexey Dobriyan if ((unsigned)gidsetsize > NGROUPS_MAX)
20630639b6aSAlexey Dobriyan return -EINVAL;
20730639b6aSAlexey Dobriyan
20830639b6aSAlexey Dobriyan group_info = groups_alloc(gidsetsize);
20930639b6aSAlexey Dobriyan if (!group_info)
21030639b6aSAlexey Dobriyan return -ENOMEM;
21130639b6aSAlexey Dobriyan retval = groups_from_user(group_info, grouplist);
21230639b6aSAlexey Dobriyan if (retval) {
21330639b6aSAlexey Dobriyan put_group_info(group_info);
21430639b6aSAlexey Dobriyan return retval;
21530639b6aSAlexey Dobriyan }
21630639b6aSAlexey Dobriyan
217bdcf0a42SThiago Rafael Becker groups_sort(group_info);
21830639b6aSAlexey Dobriyan retval = set_current_groups(group_info);
21930639b6aSAlexey Dobriyan put_group_info(group_info);
22030639b6aSAlexey Dobriyan
22130639b6aSAlexey Dobriyan return retval;
22230639b6aSAlexey Dobriyan }
22330639b6aSAlexey Dobriyan
22430639b6aSAlexey Dobriyan /*
22530639b6aSAlexey Dobriyan * Check whether we're fsgid/egid or in the supplemental group..
22630639b6aSAlexey Dobriyan */
in_group_p(kgid_t grp)22772cda3d1SEric W. Biederman int in_group_p(kgid_t grp)
22830639b6aSAlexey Dobriyan {
22930639b6aSAlexey Dobriyan const struct cred *cred = current_cred();
23030639b6aSAlexey Dobriyan int retval = 1;
23130639b6aSAlexey Dobriyan
23272cda3d1SEric W. Biederman if (!gid_eq(grp, cred->fsgid))
23372cda3d1SEric W. Biederman retval = groups_search(cred->group_info, grp);
23430639b6aSAlexey Dobriyan return retval;
23530639b6aSAlexey Dobriyan }
23630639b6aSAlexey Dobriyan
23730639b6aSAlexey Dobriyan EXPORT_SYMBOL(in_group_p);
23830639b6aSAlexey Dobriyan
in_egroup_p(kgid_t grp)23972cda3d1SEric W. Biederman int in_egroup_p(kgid_t grp)
24030639b6aSAlexey Dobriyan {
24130639b6aSAlexey Dobriyan const struct cred *cred = current_cred();
24230639b6aSAlexey Dobriyan int retval = 1;
24330639b6aSAlexey Dobriyan
24472cda3d1SEric W. Biederman if (!gid_eq(grp, cred->egid))
24572cda3d1SEric W. Biederman retval = groups_search(cred->group_info, grp);
24630639b6aSAlexey Dobriyan return retval;
24730639b6aSAlexey Dobriyan }
24830639b6aSAlexey Dobriyan
24930639b6aSAlexey Dobriyan EXPORT_SYMBOL(in_egroup_p);
250