xref: /openbmc/linux/kernel/groups.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
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