1
2
3
4#include <linux/cred.h>
5#include <linux/export.h>
6#include <linux/slab.h>
7#include <linux/security.h>
8#include <linux/syscalls.h>
9#include <linux/user_namespace.h>
10#include <asm/uaccess.h>
11
12
13struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
14
15struct group_info *groups_alloc(int gidsetsize)
16{
17 struct group_info *group_info;
18 int nblocks;
19 int i;
20
21 nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
22
23 nblocks = nblocks ? : 1;
24 group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
25 if (!group_info)
26 return NULL;
27 group_info->ngroups = gidsetsize;
28 group_info->nblocks = nblocks;
29 atomic_set(&group_info->usage, 1);
30
31 if (gidsetsize <= NGROUPS_SMALL)
32 group_info->blocks[0] = group_info->small_block;
33 else {
34 for (i = 0; i < nblocks; i++) {
35 kgid_t *b;
36 b = (void *)__get_free_page(GFP_USER);
37 if (!b)
38 goto out_undo_partial_alloc;
39 group_info->blocks[i] = b;
40 }
41 }
42 return group_info;
43
44out_undo_partial_alloc:
45 while (--i >= 0) {
46 free_page((unsigned long)group_info->blocks[i]);
47 }
48 kfree(group_info);
49 return NULL;
50}
51
52EXPORT_SYMBOL(groups_alloc);
53
54void groups_free(struct group_info *group_info)
55{
56 if (group_info->blocks[0] != group_info->small_block) {
57 int i;
58 for (i = 0; i < group_info->nblocks; i++)
59 free_page((unsigned long)group_info->blocks[i]);
60 }
61 kfree(group_info);
62}
63
64EXPORT_SYMBOL(groups_free);
65
66
67static int groups_to_user(gid_t __user *grouplist,
68 const struct group_info *group_info)
69{
70 struct user_namespace *user_ns = current_user_ns();
71 int i;
72 unsigned int count = group_info->ngroups;
73
74 for (i = 0; i < count; i++) {
75 gid_t gid;
76 gid = from_kgid_munged(user_ns, GROUP_AT(group_info, i));
77 if (put_user(gid, grouplist+i))
78 return -EFAULT;
79 }
80 return 0;
81}
82
83
84static int groups_from_user(struct group_info *group_info,
85 gid_t __user *grouplist)
86{
87 struct user_namespace *user_ns = current_user_ns();
88 int i;
89 unsigned int count = group_info->ngroups;
90
91 for (i = 0; i < count; i++) {
92 gid_t gid;
93 kgid_t kgid;
94 if (get_user(gid, grouplist+i))
95 return -EFAULT;
96
97 kgid = make_kgid(user_ns, gid);
98 if (!gid_valid(kgid))
99 return -EINVAL;
100
101 GROUP_AT(group_info, i) = kgid;
102 }
103 return 0;
104}
105
106
107void groups_sort(struct group_info *group_info)
108{
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124 int base, max, stride;
125 int gidsetsize = group_info->ngroups;
126
127 for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
128 ;
129 stride /= 3;
130
131 while (stride) {
132 max = gidsetsize - stride;
133 for (base = 0; base < max; base++) {
134 int left = base;
135 int right = left + stride;
136 kgid_t tmp = GROUP_AT(group_info, right);
137
138 while (left >= 0 && gid_gt(GROUP_AT(group_info, left), tmp)) {
139 GROUP_AT(group_info, right) =
140 GROUP_AT(group_info, left);
141 right = left;
142 left -= stride;
143 }
144 GROUP_AT(group_info, right) = tmp;
145 }
146 stride /= 3;
147 }
148}
149EXPORT_SYMBOL(groups_sort);
150
151
152int groups_search(const struct group_info *group_info, kgid_t grp)
153{
154 unsigned int left, right;
155
156 if (!group_info)
157 return 0;
158
159 left = 0;
160 right = group_info->ngroups;
161 while (left < right) {
162 unsigned int mid = (left+right)/2;
163 if (gid_gt(grp, GROUP_AT(group_info, mid)))
164 left = mid + 1;
165 else if (gid_lt(grp, GROUP_AT(group_info, mid)))
166 right = mid;
167 else
168 return 1;
169 }
170 return 0;
171}
172
173
174
175
176
177
178
179
180
181int set_groups(struct cred *new, struct group_info *group_info)
182{
183 put_group_info(new->group_info);
184 groups_sort(group_info);
185 get_group_info(group_info);
186 new->group_info = group_info;
187 return 0;
188}
189
190EXPORT_SYMBOL(set_groups);
191
192
193
194
195
196
197
198
199int set_current_groups(struct group_info *group_info)
200{
201 struct cred *new;
202 int ret;
203
204 new = prepare_creds();
205 if (!new)
206 return -ENOMEM;
207
208 ret = set_groups(new, group_info);
209 if (ret < 0) {
210 abort_creds(new);
211 return ret;
212 }
213
214 return commit_creds(new);
215}
216
217EXPORT_SYMBOL(set_current_groups);
218
219SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t __user *, grouplist)
220{
221 const struct cred *cred = current_cred();
222 int i;
223
224 if (gidsetsize < 0)
225 return -EINVAL;
226
227
228 i = cred->group_info->ngroups;
229 if (gidsetsize) {
230 if (i > gidsetsize) {
231 i = -EINVAL;
232 goto out;
233 }
234 if (groups_to_user(grouplist, cred->group_info)) {
235 i = -EFAULT;
236 goto out;
237 }
238 }
239out:
240 return i;
241}
242
243bool may_setgroups(void)
244{
245 struct user_namespace *user_ns = current_user_ns();
246
247 return ns_capable(user_ns, CAP_SETGID) &&
248 userns_may_setgroups(user_ns);
249}
250
251
252
253
254
255
256SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
257{
258 struct group_info *group_info;
259 int retval;
260
261 if (!may_setgroups())
262 return -EPERM;
263 if ((unsigned)gidsetsize > NGROUPS_MAX)
264 return -EINVAL;
265
266 group_info = groups_alloc(gidsetsize);
267 if (!group_info)
268 return -ENOMEM;
269 retval = groups_from_user(group_info, grouplist);
270 if (retval) {
271 put_group_info(group_info);
272 return retval;
273 }
274
275 groups_sort(group_info);
276 retval = set_current_groups(group_info);
277 put_group_info(group_info);
278
279 return retval;
280}
281
282
283
284
285int in_group_p(kgid_t grp)
286{
287 const struct cred *cred = current_cred();
288 int retval = 1;
289
290 if (!gid_eq(grp, cred->fsgid))
291 retval = groups_search(cred->group_info, grp);
292 return retval;
293}
294
295EXPORT_SYMBOL(in_group_p);
296
297int in_egroup_p(kgid_t grp)
298{
299 const struct cred *cred = current_cred();
300 int retval = 1;
301
302 if (!gid_eq(grp, cred->egid))
303 retval = groups_search(cred->group_info, grp);
304 return retval;
305}
306
307EXPORT_SYMBOL(in_egroup_p);
308