2 * Supplementary group IDs
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>
12 struct group_info
*groups_alloc(int gidsetsize
)
14 struct group_info
*group_info
;
18 nblocks
= (gidsetsize
+ NGROUPS_PER_BLOCK
- 1) / NGROUPS_PER_BLOCK
;
19 /* Make sure we always allocate at least one indirect block pointer */
20 nblocks
= nblocks
? : 1;
21 group_info
= kmalloc(sizeof(*group_info
) + nblocks
*sizeof(gid_t
*), GFP_USER
);
24 group_info
->ngroups
= gidsetsize
;
25 group_info
->nblocks
= nblocks
;
26 atomic_set(&group_info
->usage
, 1);
28 if (gidsetsize
<= NGROUPS_SMALL
)
29 group_info
->blocks
[0] = group_info
->small_block
;
31 for (i
= 0; i
< nblocks
; i
++) {
33 b
= (void *)__get_free_page(GFP_USER
);
35 goto out_undo_partial_alloc
;
36 group_info
->blocks
[i
] = b
;
41 out_undo_partial_alloc
:
43 free_page((unsigned long)group_info
->blocks
[i
]);
49 EXPORT_SYMBOL(groups_alloc
);
51 void groups_free(struct group_info
*group_info
)
53 if (group_info
->blocks
[0] != group_info
->small_block
) {
55 for (i
= 0; i
< group_info
->nblocks
; i
++)
56 free_page((unsigned long)group_info
->blocks
[i
]);
61 EXPORT_SYMBOL(groups_free
);
63 /* export the group_info to a user-space array */
64 static int groups_to_user(gid_t __user
*grouplist
,
65 const struct group_info
*group_info
)
67 struct user_namespace
*user_ns
= current_user_ns();
69 unsigned int count
= group_info
->ngroups
;
71 for (i
= 0; i
< count
; i
++) {
73 gid
= from_kgid_munged(user_ns
, GROUP_AT(group_info
, i
));
74 if (put_user(gid
, grouplist
+i
))
80 /* fill a group_info from a user-space array - it must be allocated already */
81 static int groups_from_user(struct group_info
*group_info
,
82 gid_t __user
*grouplist
)
84 struct user_namespace
*user_ns
= current_user_ns();
86 unsigned int count
= group_info
->ngroups
;
88 for (i
= 0; i
< count
; i
++) {
91 if (get_user(gid
, grouplist
+i
))
94 kgid
= make_kgid(user_ns
, gid
);
98 GROUP_AT(group_info
, i
) = kgid
;
103 /* a simple Shell sort */
104 static void groups_sort(struct group_info
*group_info
)
106 int base
, max
, stride
;
107 int gidsetsize
= group_info
->ngroups
;
109 for (stride
= 1; stride
< gidsetsize
; stride
= 3 * stride
+ 1)
114 max
= gidsetsize
- stride
;
115 for (base
= 0; base
< max
; base
++) {
117 int right
= left
+ stride
;
118 kgid_t tmp
= GROUP_AT(group_info
, right
);
120 while (left
>= 0 && gid_gt(GROUP_AT(group_info
, left
), tmp
)) {
121 GROUP_AT(group_info
, right
) =
122 GROUP_AT(group_info
, left
);
126 GROUP_AT(group_info
, right
) = tmp
;
132 /* a simple bsearch */
133 int groups_search(const struct group_info
*group_info
, kgid_t grp
)
135 unsigned int left
, right
;
141 right
= group_info
->ngroups
;
142 while (left
< right
) {
143 unsigned int mid
= (left
+right
)/2;
144 if (gid_gt(grp
, GROUP_AT(group_info
, mid
)))
146 else if (gid_lt(grp
, GROUP_AT(group_info
, mid
)))
155 * set_groups - Change a group subscription in a set of credentials
156 * @new: The newly prepared set of credentials to alter
157 * @group_info: The group list to install
159 void set_groups(struct cred
*new, struct group_info
*group_info
)
161 put_group_info(new->group_info
);
162 groups_sort(group_info
);
163 get_group_info(group_info
);
164 new->group_info
= group_info
;
167 EXPORT_SYMBOL(set_groups
);
170 * set_current_groups - Change current's group subscription
171 * @group_info: The group list to impose
173 * Validate a group subscription and, if valid, impose it upon current's task
176 int set_current_groups(struct group_info
*group_info
)
180 new = prepare_creds();
184 set_groups(new, group_info
);
185 return commit_creds(new);
188 EXPORT_SYMBOL(set_current_groups
);
190 SYSCALL_DEFINE2(getgroups
, int, gidsetsize
, gid_t __user
*, grouplist
)
192 const struct cred
*cred
= current_cred();
198 /* no need to grab task_lock here; it cannot change */
199 i
= cred
->group_info
->ngroups
;
201 if (i
> gidsetsize
) {
205 if (groups_to_user(grouplist
, cred
->group_info
)) {
214 bool may_setgroups(void)
216 struct user_namespace
*user_ns
= current_user_ns();
218 return ns_capable(user_ns
, CAP_SETGID
) &&
219 userns_may_setgroups(user_ns
);
223 * SMP: Our groups are copy-on-write. We can set them safely
224 * without another task interfering.
227 SYSCALL_DEFINE2(setgroups
, int, gidsetsize
, gid_t __user
*, grouplist
)
229 struct group_info
*group_info
;
232 if (!may_setgroups())
234 if ((unsigned)gidsetsize
> NGROUPS_MAX
)
237 group_info
= groups_alloc(gidsetsize
);
240 retval
= groups_from_user(group_info
, grouplist
);
242 put_group_info(group_info
);
246 retval
= set_current_groups(group_info
);
247 put_group_info(group_info
);
253 * Check whether we're fsgid/egid or in the supplemental group..
255 int in_group_p(kgid_t grp
)
257 const struct cred
*cred
= current_cred();
260 if (!gid_eq(grp
, cred
->fsgid
))
261 retval
= groups_search(cred
->group_info
, grp
);
265 EXPORT_SYMBOL(in_group_p
);
267 int in_egroup_p(kgid_t grp
)
269 const struct cred
*cred
= current_cred();
272 if (!gid_eq(grp
, cred
->egid
))
273 retval
= groups_search(cred
->group_info
, grp
);
277 EXPORT_SYMBOL(in_egroup_p
);