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 /* init to 2 - one for init_task, one to ensure it is never freed */
13 struct group_info init_groups
= { .usage
= ATOMIC_INIT(2) };
15 struct group_info
*groups_alloc(int gidsetsize
)
17 struct group_info
*group_info
;
21 nblocks
= (gidsetsize
+ NGROUPS_PER_BLOCK
- 1) / NGROUPS_PER_BLOCK
;
22 /* Make sure we always allocate at least one indirect block pointer */
23 nblocks
= nblocks
? : 1;
24 group_info
= kmalloc(sizeof(*group_info
) + nblocks
*sizeof(gid_t
*), GFP_USER
);
27 group_info
->ngroups
= gidsetsize
;
28 group_info
->nblocks
= nblocks
;
29 atomic_set(&group_info
->usage
, 1);
31 if (gidsetsize
<= NGROUPS_SMALL
)
32 group_info
->blocks
[0] = group_info
->small_block
;
34 for (i
= 0; i
< nblocks
; i
++) {
36 b
= (void *)__get_free_page(GFP_USER
);
38 goto out_undo_partial_alloc
;
39 group_info
->blocks
[i
] = b
;
44 out_undo_partial_alloc
:
46 free_page((unsigned long)group_info
->blocks
[i
]);
52 EXPORT_SYMBOL(groups_alloc
);
54 void groups_free(struct group_info
*group_info
)
56 if (group_info
->blocks
[0] != group_info
->small_block
) {
58 for (i
= 0; i
< group_info
->nblocks
; i
++)
59 free_page((unsigned long)group_info
->blocks
[i
]);
64 EXPORT_SYMBOL(groups_free
);
66 /* export the group_info to a user-space array */
67 static int groups_to_user(gid_t __user
*grouplist
,
68 const struct group_info
*group_info
)
70 struct user_namespace
*user_ns
= current_user_ns();
72 unsigned int count
= group_info
->ngroups
;
74 for (i
= 0; i
< count
; i
++) {
76 gid
= from_kgid_munged(user_ns
, GROUP_AT(group_info
, i
));
77 if (put_user(gid
, grouplist
+i
))
83 /* fill a group_info from a user-space array - it must be allocated already */
84 static int groups_from_user(struct group_info
*group_info
,
85 gid_t __user
*grouplist
)
87 struct user_namespace
*user_ns
= current_user_ns();
89 unsigned int count
= group_info
->ngroups
;
91 for (i
= 0; i
< count
; i
++) {
94 if (get_user(gid
, grouplist
+i
))
97 kgid
= make_kgid(user_ns
, gid
);
101 GROUP_AT(group_info
, i
) = kgid
;
106 /* a simple Shell sort */
107 static void groups_sort(struct group_info
*group_info
)
109 int base
, max
, stride
;
110 int gidsetsize
= group_info
->ngroups
;
112 for (stride
= 1; stride
< gidsetsize
; stride
= 3 * stride
+ 1)
117 max
= gidsetsize
- stride
;
118 for (base
= 0; base
< max
; base
++) {
120 int right
= left
+ stride
;
121 kgid_t tmp
= GROUP_AT(group_info
, right
);
123 while (left
>= 0 && gid_gt(GROUP_AT(group_info
, left
), tmp
)) {
124 GROUP_AT(group_info
, right
) =
125 GROUP_AT(group_info
, left
);
129 GROUP_AT(group_info
, right
) = tmp
;
135 /* a simple bsearch */
136 int groups_search(const struct group_info
*group_info
, kgid_t grp
)
138 unsigned int left
, right
;
144 right
= group_info
->ngroups
;
145 while (left
< right
) {
146 unsigned int mid
= (left
+right
)/2;
147 if (gid_gt(grp
, GROUP_AT(group_info
, mid
)))
149 else if (gid_lt(grp
, GROUP_AT(group_info
, mid
)))
158 * set_groups - Change a group subscription in a set of credentials
159 * @new: The newly prepared set of credentials to alter
160 * @group_info: The group list to install
162 void set_groups(struct cred
*new, struct group_info
*group_info
)
164 put_group_info(new->group_info
);
165 groups_sort(group_info
);
166 get_group_info(group_info
);
167 new->group_info
= group_info
;
170 EXPORT_SYMBOL(set_groups
);
173 * set_current_groups - Change current's group subscription
174 * @group_info: The group list to impose
176 * Validate a group subscription and, if valid, impose it upon current's task
179 int set_current_groups(struct group_info
*group_info
)
183 new = prepare_creds();
187 set_groups(new, group_info
);
188 return commit_creds(new);
191 EXPORT_SYMBOL(set_current_groups
);
193 SYSCALL_DEFINE2(getgroups
, int, gidsetsize
, gid_t __user
*, grouplist
)
195 const struct cred
*cred
= current_cred();
201 /* no need to grab task_lock here; it cannot change */
202 i
= cred
->group_info
->ngroups
;
204 if (i
> gidsetsize
) {
208 if (groups_to_user(grouplist
, cred
->group_info
)) {
217 bool may_setgroups(void)
219 struct user_namespace
*user_ns
= current_user_ns();
221 return ns_capable(user_ns
, CAP_SETGID
) &&
222 userns_may_setgroups(user_ns
);
226 * SMP: Our groups are copy-on-write. We can set them safely
227 * without another task interfering.
230 SYSCALL_DEFINE2(setgroups
, int, gidsetsize
, gid_t __user
*, grouplist
)
232 struct group_info
*group_info
;
235 if (!may_setgroups())
237 if ((unsigned)gidsetsize
> NGROUPS_MAX
)
240 group_info
= groups_alloc(gidsetsize
);
243 retval
= groups_from_user(group_info
, grouplist
);
245 put_group_info(group_info
);
249 retval
= set_current_groups(group_info
);
250 put_group_info(group_info
);
256 * Check whether we're fsgid/egid or in the supplemental group..
258 int in_group_p(kgid_t grp
)
260 const struct cred
*cred
= current_cred();
263 if (!gid_eq(grp
, cred
->fsgid
))
264 retval
= groups_search(cred
->group_info
, grp
);
268 EXPORT_SYMBOL(in_group_p
);
270 int in_egroup_p(kgid_t grp
)
272 const struct cred
*cred
= current_cred();
275 if (!gid_eq(grp
, cred
->egid
))
276 retval
= groups_search(cred
->group_info
, grp
);
280 EXPORT_SYMBOL(in_egroup_p
);