Linux Kernel  3.7.1
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Groups Pages
groups.c
Go to the documentation of this file.
1 /*
2  * Supplementary group IDs
3  */
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 <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  kgid_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 
52 
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 
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  struct user_namespace *user_ns = current_user_ns();
70  int i;
71  unsigned int count = group_info->ngroups;
72 
73  for (i = 0; i < count; i++) {
74  gid_t gid;
75  gid = from_kgid_munged(user_ns, GROUP_AT(group_info, i));
76  if (put_user(gid, grouplist+i))
77  return -EFAULT;
78  }
79  return 0;
80 }
81 
82 /* fill a group_info from a user-space array - it must be allocated already */
83 static int groups_from_user(struct group_info *group_info,
84  gid_t __user *grouplist)
85 {
86  struct user_namespace *user_ns = current_user_ns();
87  int i;
88  unsigned int count = group_info->ngroups;
89 
90  for (i = 0; i < count; i++) {
91  gid_t gid;
92  kgid_t kgid;
93  if (get_user(gid, grouplist+i))
94  return -EFAULT;
95 
96  kgid = make_kgid(user_ns, gid);
97  if (!gid_valid(kgid))
98  return -EINVAL;
99 
100  GROUP_AT(group_info, i) = kgid;
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  kgid_t tmp = GROUP_AT(group_info, right);
121 
122  while (left >= 0 && gid_gt(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, kgid_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 (gid_gt(grp, GROUP_AT(group_info, mid)))
147  left = mid + 1;
148  else if (gid_lt(grp, GROUP_AT(group_info, mid)))
149  right = mid;
150  else
151  return 1;
152  }
153  return 0;
154 }
155 
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 
174 
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 
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 
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  */
260 {
261  const struct cred *cred = current_cred();
262  int retval = 1;
263 
264  if (!gid_eq(grp, cred->fsgid))
265  retval = groups_search(cred->group_info, grp);
266  return retval;
267 }
268 
270 
272 {
273  const struct cred *cred = current_cred();
274  int retval = 1;
275 
276  if (!gid_eq(grp, cred->egid))
277  retval = groups_search(cred->group_info, grp);
278  return retval;
279 }
280