Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
torvalds
GitHub Repository: torvalds/linux
Path: blob/master/kernel/groups.c
26243 views
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
251