1 // SPDX-License-Identifier: GPL-2.0
3 * Supplementary group IDs
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>
15 struct group_info
*groups_alloc(int gidsetsize
)
17 struct group_info
*gi
;
20 len
= sizeof(struct group_info
) + sizeof(kgid_t
) * gidsetsize
;
21 gi
= kmalloc(len
, GFP_KERNEL_ACCOUNT
|__GFP_NOWARN
|__GFP_NORETRY
);
23 gi
= __vmalloc(len
, GFP_KERNEL_ACCOUNT
, PAGE_KERNEL
);
27 atomic_set(&gi
->usage
, 1);
28 gi
->ngroups
= gidsetsize
;
32 EXPORT_SYMBOL(groups_alloc
);
34 void groups_free(struct group_info
*group_info
)
39 EXPORT_SYMBOL(groups_free
);
41 /* export the group_info to a user-space array */
42 static int groups_to_user(gid_t __user
*grouplist
,
43 const struct group_info
*group_info
)
45 struct user_namespace
*user_ns
= current_user_ns();
47 unsigned int count
= group_info
->ngroups
;
49 for (i
= 0; i
< count
; i
++) {
51 gid
= from_kgid_munged(user_ns
, group_info
->gid
[i
]);
52 if (put_user(gid
, grouplist
+i
))
58 /* fill a group_info from a user-space array - it must be allocated already */
59 static int groups_from_user(struct group_info
*group_info
,
60 gid_t __user
*grouplist
)
62 struct user_namespace
*user_ns
= current_user_ns();
64 unsigned int count
= group_info
->ngroups
;
66 for (i
= 0; i
< count
; i
++) {
69 if (get_user(gid
, grouplist
+i
))
72 kgid
= make_kgid(user_ns
, gid
);
76 group_info
->gid
[i
] = kgid
;
81 static int gid_cmp(const void *_a
, const void *_b
)
83 kgid_t a
= *(kgid_t
*)_a
;
84 kgid_t b
= *(kgid_t
*)_b
;
86 return gid_gt(a
, b
) - gid_lt(a
, b
);
89 static void groups_sort(struct group_info
*group_info
)
91 sort(group_info
->gid
, group_info
->ngroups
, sizeof(*group_info
->gid
),
95 /* a simple bsearch */
96 int groups_search(const struct group_info
*group_info
, kgid_t grp
)
98 unsigned int left
, right
;
104 right
= group_info
->ngroups
;
105 while (left
< right
) {
106 unsigned int mid
= (left
+right
)/2;
107 if (gid_gt(grp
, group_info
->gid
[mid
]))
109 else if (gid_lt(grp
, group_info
->gid
[mid
]))
118 * set_groups - Change a group subscription in a set of credentials
119 * @new: The newly prepared set of credentials to alter
120 * @group_info: The group list to install
122 void set_groups(struct cred
*new, struct group_info
*group_info
)
124 put_group_info(new->group_info
);
125 groups_sort(group_info
);
126 get_group_info(group_info
);
127 new->group_info
= group_info
;
130 EXPORT_SYMBOL(set_groups
);
133 * set_current_groups - Change current's group subscription
134 * @group_info: The group list to impose
136 * Validate a group subscription and, if valid, impose it upon current's task
139 int set_current_groups(struct group_info
*group_info
)
143 new = prepare_creds();
147 set_groups(new, group_info
);
148 return commit_creds(new);
151 EXPORT_SYMBOL(set_current_groups
);
153 SYSCALL_DEFINE2(getgroups
, int, gidsetsize
, gid_t __user
*, grouplist
)
155 const struct cred
*cred
= current_cred();
161 /* no need to grab task_lock here; it cannot change */
162 i
= cred
->group_info
->ngroups
;
164 if (i
> gidsetsize
) {
168 if (groups_to_user(grouplist
, cred
->group_info
)) {
177 bool may_setgroups(void)
179 struct user_namespace
*user_ns
= current_user_ns();
181 return ns_capable(user_ns
, CAP_SETGID
) &&
182 userns_may_setgroups(user_ns
);
186 * SMP: Our groups are copy-on-write. We can set them safely
187 * without another task interfering.
190 SYSCALL_DEFINE2(setgroups
, int, gidsetsize
, gid_t __user
*, grouplist
)
192 struct group_info
*group_info
;
195 if (!may_setgroups())
197 if ((unsigned)gidsetsize
> NGROUPS_MAX
)
200 group_info
= groups_alloc(gidsetsize
);
203 retval
= groups_from_user(group_info
, grouplist
);
205 put_group_info(group_info
);
209 retval
= set_current_groups(group_info
);
210 put_group_info(group_info
);
216 * Check whether we're fsgid/egid or in the supplemental group..
218 int in_group_p(kgid_t grp
)
220 const struct cred
*cred
= current_cred();
223 if (!gid_eq(grp
, cred
->fsgid
))
224 retval
= groups_search(cred
->group_info
, grp
);
228 EXPORT_SYMBOL(in_group_p
);
230 int in_egroup_p(kgid_t grp
)
232 const struct cred
*cred
= current_cred();
235 if (!gid_eq(grp
, cred
->egid
))
236 retval
= groups_search(cred
->group_info
, grp
);
240 EXPORT_SYMBOL(in_egroup_p
);