xref: /linux/kernel/groups.c (revision 8c04bddc27d60df8ca5cb5bea40374c3ca1d75fc)
1  // SPDX-License-Identifier: GPL-2.0
2  /*
3   * Supplementary group IDs
4   */
5  #include <linux/cred.h>
6  #include <linux/export.h>
7  #include <linux/slab.h>
8  #include <linux/security.h>
9  #include <linux/sort.h>
10  #include <linux/syscalls.h>
11  #include <linux/user_namespace.h>
12  #include <linux/vmalloc.h>
13  #include <linux/uaccess.h>
14  
15  struct group_info *groups_alloc(int gidsetsize)
16  {
17  	struct group_info *gi;
18  	gi = kvmalloc(struct_size(gi, gid, gidsetsize), GFP_KERNEL_ACCOUNT);
19  	if (!gi)
20  		return NULL;
21  
22  	refcount_set(&gi->usage, 1);
23  	gi->ngroups = gidsetsize;
24  	return gi;
25  }
26  
27  EXPORT_SYMBOL(groups_alloc);
28  
29  void groups_free(struct group_info *group_info)
30  {
31  	kvfree(group_info);
32  }
33  
34  EXPORT_SYMBOL(groups_free);
35  
36  /* export the group_info to a user-space array */
37  static int groups_to_user(gid_t __user *grouplist,
38  			  const struct group_info *group_info)
39  {
40  	struct user_namespace *user_ns = current_user_ns();
41  	int i;
42  	unsigned int count = group_info->ngroups;
43  
44  	for (i = 0; i < count; i++) {
45  		gid_t gid;
46  		gid = from_kgid_munged(user_ns, group_info->gid[i]);
47  		if (put_user(gid, grouplist+i))
48  			return -EFAULT;
49  	}
50  	return 0;
51  }
52  
53  /* fill a group_info from a user-space array - it must be allocated already */
54  static int groups_from_user(struct group_info *group_info,
55      gid_t __user *grouplist)
56  {
57  	struct user_namespace *user_ns = current_user_ns();
58  	int i;
59  	unsigned int count = group_info->ngroups;
60  
61  	for (i = 0; i < count; i++) {
62  		gid_t gid;
63  		kgid_t kgid;
64  		if (get_user(gid, grouplist+i))
65  			return -EFAULT;
66  
67  		kgid = make_kgid(user_ns, gid);
68  		if (!gid_valid(kgid))
69  			return -EINVAL;
70  
71  		group_info->gid[i] = kgid;
72  	}
73  	return 0;
74  }
75  
76  static int gid_cmp(const void *_a, const void *_b)
77  {
78  	kgid_t a = *(kgid_t *)_a;
79  	kgid_t b = *(kgid_t *)_b;
80  
81  	return gid_gt(a, b) - gid_lt(a, b);
82  }
83  
84  void groups_sort(struct group_info *group_info)
85  {
86  	sort(group_info->gid, group_info->ngroups, sizeof(*group_info->gid),
87  	     gid_cmp, NULL);
88  }
89  EXPORT_SYMBOL(groups_sort);
90  
91  /* a simple bsearch */
92  int groups_search(const struct group_info *group_info, kgid_t grp)
93  {
94  	unsigned int left, right;
95  
96  	if (!group_info)
97  		return 0;
98  
99  	left = 0;
100  	right = group_info->ngroups;
101  	while (left < right) {
102  		unsigned int mid = (left+right)/2;
103  		if (gid_gt(grp, group_info->gid[mid]))
104  			left = mid + 1;
105  		else if (gid_lt(grp, group_info->gid[mid]))
106  			right = mid;
107  		else
108  			return 1;
109  	}
110  	return 0;
111  }
112  
113  /**
114   * set_groups - Change a group subscription in a set of credentials
115   * @new: The newly prepared set of credentials to alter
116   * @group_info: The group list to install
117   */
118  void set_groups(struct cred *new, struct group_info *group_info)
119  {
120  	put_group_info(new->group_info);
121  	get_group_info(group_info);
122  	new->group_info = group_info;
123  }
124  
125  EXPORT_SYMBOL(set_groups);
126  
127  /**
128   * set_current_groups - Change current's group subscription
129   * @group_info: The group list to impose
130   *
131   * Validate a group subscription and, if valid, impose it upon current's task
132   * security record.
133   */
134  int set_current_groups(struct group_info *group_info)
135  {
136  	struct cred *new;
137  	const struct cred *old;
138  	int retval;
139  
140  	new = prepare_creds();
141  	if (!new)
142  		return -ENOMEM;
143  
144  	old = current_cred();
145  
146  	set_groups(new, group_info);
147  
148  	retval = security_task_fix_setgroups(new, old);
149  	if (retval < 0)
150  		goto error;
151  
152  	return commit_creds(new);
153  
154  error:
155  	abort_creds(new);
156  	return retval;
157  }
158  
159  EXPORT_SYMBOL(set_current_groups);
160  
161  SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t __user *, grouplist)
162  {
163  	const struct cred *cred = current_cred();
164  	int i;
165  
166  	if (gidsetsize < 0)
167  		return -EINVAL;
168  
169  	/* no need to grab task_lock here; it cannot change */
170  	i = cred->group_info->ngroups;
171  	if (gidsetsize) {
172  		if (i > gidsetsize) {
173  			i = -EINVAL;
174  			goto out;
175  		}
176  		if (groups_to_user(grouplist, cred->group_info)) {
177  			i = -EFAULT;
178  			goto out;
179  		}
180  	}
181  out:
182  	return i;
183  }
184  
185  bool may_setgroups(void)
186  {
187  	struct user_namespace *user_ns = current_user_ns();
188  
189  	return ns_capable_setid(user_ns, CAP_SETGID) &&
190  		userns_may_setgroups(user_ns);
191  }
192  
193  /*
194   *	SMP: Our groups are copy-on-write. We can set them safely
195   *	without another task interfering.
196   */
197  
198  SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
199  {
200  	struct group_info *group_info;
201  	int retval;
202  
203  	if (!may_setgroups())
204  		return -EPERM;
205  	if ((unsigned)gidsetsize > NGROUPS_MAX)
206  		return -EINVAL;
207  
208  	group_info = groups_alloc(gidsetsize);
209  	if (!group_info)
210  		return -ENOMEM;
211  	retval = groups_from_user(group_info, grouplist);
212  	if (retval) {
213  		put_group_info(group_info);
214  		return retval;
215  	}
216  
217  	groups_sort(group_info);
218  	retval = set_current_groups(group_info);
219  	put_group_info(group_info);
220  
221  	return retval;
222  }
223  
224  /*
225   * Check whether we're fsgid/egid or in the supplemental group..
226   */
227  int in_group_p(kgid_t grp)
228  {
229  	const struct cred *cred = current_cred();
230  	int retval = 1;
231  
232  	if (!gid_eq(grp, cred->fsgid))
233  		retval = groups_search(cred->group_info, grp);
234  	return retval;
235  }
236  
237  EXPORT_SYMBOL(in_group_p);
238  
239  int in_egroup_p(kgid_t grp)
240  {
241  	const struct cred *cred = current_cred();
242  	int retval = 1;
243  
244  	if (!gid_eq(grp, cred->egid))
245  		retval = groups_search(cred->group_info, grp);
246  	return retval;
247  }
248  
249  EXPORT_SYMBOL(in_egroup_p);
250