Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
awilliam
GitHub Repository: awilliam/linux-vfio
Path: blob/master/kernel/groups.c
10814 views
1
/*
2
* Supplementary group IDs
3
*/
4
#include <linux/cred.h>
5
#include <linux/module.h>
6
#include <linux/slab.h>
7
#include <linux/security.h>
8
#include <linux/syscalls.h>
9
#include <asm/uaccess.h>
10
11
/* init to 2 - one for init_task, one to ensure it is never freed */
12
struct group_info init_groups = { .usage = ATOMIC_INIT(2) };
13
14
struct group_info *groups_alloc(int gidsetsize)
15
{
16
struct group_info *group_info;
17
int nblocks;
18
int i;
19
20
nblocks = (gidsetsize + NGROUPS_PER_BLOCK - 1) / NGROUPS_PER_BLOCK;
21
/* Make sure we always allocate at least one indirect block pointer */
22
nblocks = nblocks ? : 1;
23
group_info = kmalloc(sizeof(*group_info) + nblocks*sizeof(gid_t *), GFP_USER);
24
if (!group_info)
25
return NULL;
26
group_info->ngroups = gidsetsize;
27
group_info->nblocks = nblocks;
28
atomic_set(&group_info->usage, 1);
29
30
if (gidsetsize <= NGROUPS_SMALL)
31
group_info->blocks[0] = group_info->small_block;
32
else {
33
for (i = 0; i < nblocks; i++) {
34
gid_t *b;
35
b = (void *)__get_free_page(GFP_USER);
36
if (!b)
37
goto out_undo_partial_alloc;
38
group_info->blocks[i] = b;
39
}
40
}
41
return group_info;
42
43
out_undo_partial_alloc:
44
while (--i >= 0) {
45
free_page((unsigned long)group_info->blocks[i]);
46
}
47
kfree(group_info);
48
return NULL;
49
}
50
51
EXPORT_SYMBOL(groups_alloc);
52
53
void groups_free(struct group_info *group_info)
54
{
55
if (group_info->blocks[0] != group_info->small_block) {
56
int i;
57
for (i = 0; i < group_info->nblocks; i++)
58
free_page((unsigned long)group_info->blocks[i]);
59
}
60
kfree(group_info);
61
}
62
63
EXPORT_SYMBOL(groups_free);
64
65
/* export the group_info to a user-space array */
66
static int groups_to_user(gid_t __user *grouplist,
67
const struct group_info *group_info)
68
{
69
int i;
70
unsigned int count = group_info->ngroups;
71
72
for (i = 0; i < group_info->nblocks; i++) {
73
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
74
unsigned int len = cp_count * sizeof(*grouplist);
75
76
if (copy_to_user(grouplist, group_info->blocks[i], len))
77
return -EFAULT;
78
79
grouplist += NGROUPS_PER_BLOCK;
80
count -= cp_count;
81
}
82
return 0;
83
}
84
85
/* fill a group_info from a user-space array - it must be allocated already */
86
static int groups_from_user(struct group_info *group_info,
87
gid_t __user *grouplist)
88
{
89
int i;
90
unsigned int count = group_info->ngroups;
91
92
for (i = 0; i < group_info->nblocks; i++) {
93
unsigned int cp_count = min(NGROUPS_PER_BLOCK, count);
94
unsigned int len = cp_count * sizeof(*grouplist);
95
96
if (copy_from_user(group_info->blocks[i], grouplist, len))
97
return -EFAULT;
98
99
grouplist += NGROUPS_PER_BLOCK;
100
count -= cp_count;
101
}
102
return 0;
103
}
104
105
/* a simple Shell sort */
106
static void groups_sort(struct group_info *group_info)
107
{
108
int base, max, stride;
109
int gidsetsize = group_info->ngroups;
110
111
for (stride = 1; stride < gidsetsize; stride = 3 * stride + 1)
112
; /* nothing */
113
stride /= 3;
114
115
while (stride) {
116
max = gidsetsize - stride;
117
for (base = 0; base < max; base++) {
118
int left = base;
119
int right = left + stride;
120
gid_t tmp = GROUP_AT(group_info, right);
121
122
while (left >= 0 && GROUP_AT(group_info, left) > tmp) {
123
GROUP_AT(group_info, right) =
124
GROUP_AT(group_info, left);
125
right = left;
126
left -= stride;
127
}
128
GROUP_AT(group_info, right) = tmp;
129
}
130
stride /= 3;
131
}
132
}
133
134
/* a simple bsearch */
135
int groups_search(const struct group_info *group_info, gid_t grp)
136
{
137
unsigned int left, right;
138
139
if (!group_info)
140
return 0;
141
142
left = 0;
143
right = group_info->ngroups;
144
while (left < right) {
145
unsigned int mid = (left+right)/2;
146
if (grp > GROUP_AT(group_info, mid))
147
left = mid + 1;
148
else if (grp < GROUP_AT(group_info, mid))
149
right = mid;
150
else
151
return 1;
152
}
153
return 0;
154
}
155
156
/**
157
* set_groups - Change a group subscription in a set of credentials
158
* @new: The newly prepared set of credentials to alter
159
* @group_info: The group list to install
160
*
161
* Validate a group subscription and, if valid, insert it into a set
162
* of credentials.
163
*/
164
int set_groups(struct cred *new, struct group_info *group_info)
165
{
166
put_group_info(new->group_info);
167
groups_sort(group_info);
168
get_group_info(group_info);
169
new->group_info = group_info;
170
return 0;
171
}
172
173
EXPORT_SYMBOL(set_groups);
174
175
/**
176
* set_current_groups - Change current's group subscription
177
* @group_info: The group list to impose
178
*
179
* Validate a group subscription and, if valid, impose it upon current's task
180
* security record.
181
*/
182
int set_current_groups(struct group_info *group_info)
183
{
184
struct cred *new;
185
int ret;
186
187
new = prepare_creds();
188
if (!new)
189
return -ENOMEM;
190
191
ret = set_groups(new, group_info);
192
if (ret < 0) {
193
abort_creds(new);
194
return ret;
195
}
196
197
return commit_creds(new);
198
}
199
200
EXPORT_SYMBOL(set_current_groups);
201
202
SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t __user *, grouplist)
203
{
204
const struct cred *cred = current_cred();
205
int i;
206
207
if (gidsetsize < 0)
208
return -EINVAL;
209
210
/* no need to grab task_lock here; it cannot change */
211
i = cred->group_info->ngroups;
212
if (gidsetsize) {
213
if (i > gidsetsize) {
214
i = -EINVAL;
215
goto out;
216
}
217
if (groups_to_user(grouplist, cred->group_info)) {
218
i = -EFAULT;
219
goto out;
220
}
221
}
222
out:
223
return i;
224
}
225
226
/*
227
* SMP: Our groups are copy-on-write. We can set them safely
228
* without another task interfering.
229
*/
230
231
SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
232
{
233
struct group_info *group_info;
234
int retval;
235
236
if (!nsown_capable(CAP_SETGID))
237
return -EPERM;
238
if ((unsigned)gidsetsize > NGROUPS_MAX)
239
return -EINVAL;
240
241
group_info = groups_alloc(gidsetsize);
242
if (!group_info)
243
return -ENOMEM;
244
retval = groups_from_user(group_info, grouplist);
245
if (retval) {
246
put_group_info(group_info);
247
return retval;
248
}
249
250
retval = set_current_groups(group_info);
251
put_group_info(group_info);
252
253
return retval;
254
}
255
256
/*
257
* Check whether we're fsgid/egid or in the supplemental group..
258
*/
259
int in_group_p(gid_t grp)
260
{
261
const struct cred *cred = current_cred();
262
int retval = 1;
263
264
if (grp != cred->fsgid)
265
retval = groups_search(cred->group_info, grp);
266
return retval;
267
}
268
269
EXPORT_SYMBOL(in_group_p);
270
271
int in_egroup_p(gid_t grp)
272
{
273
const struct cred *cred = current_cred();
274
int retval = 1;
275
276
if (grp != cred->egid)
277
retval = groups_search(cred->group_info, grp);
278
return retval;
279
}
280
281
EXPORT_SYMBOL(in_egroup_p);
282
283