a83bcb9154d0bc88b0512da8b749a7774d1781d0
[openafs.git] / src / afs / LINUX / osi_groups.c
1 /*
2  * Copyright 2000, International Business Machines Corporation and others.
3  * All Rights Reserved.
4  * 
5  * This software has been released under the terms of the IBM Public
6  * License.  For details, see the LICENSE file in the top-level source
7  * directory or online at http://www.openafs.org/dl/license10.html
8  */
9
10 /*
11  * Implements:
12  * setgroups (syscall)
13  * setpag
14  *
15  */
16 #include <afsconfig.h>
17 #include "afs/param.h"
18 #ifdef LINUX_KEYRING_SUPPORT
19 #include <linux/seq_file.h>
20 #endif
21
22
23 #include "afs/sysincludes.h"
24 #include "afsincludes.h"
25 #include "afs/afs_stats.h"      /* statistics */
26 #include "afs/nfsclient.h"
27 #include "osi_compat.h"
28
29 #ifdef AFS_LINUX26_ONEGROUP_ENV
30 # define NUMPAGGROUPS 1
31
32 static afs_uint32
33 afs_linux_pag_from_groups(struct group_info *group_info) {
34     afs_uint32 g0 = 0;
35     afs_uint32 i;
36
37     if (group_info->ngroups < NUMPAGGROUPS)
38         return NOPAG;
39
40     for (i = 0; i < group_info->ngroups; i++) {
41         g0 = afs_from_kgid(GROUP_AT(group_info, i));
42         if (((g0 >> 24) & 0xff) == 'A')
43             return g0;
44     }
45     return NOPAG;
46 }
47
48 static inline void
49 afs_linux_pag_to_groups(afs_uint32 newpag,
50                         struct group_info *old, struct group_info **new) {
51     int need_space = 0;
52     int i;
53     int j;
54     afs_kgid_t newkgid = afs_make_kgid(newpag);
55
56     if (afs_linux_pag_from_groups(old) == NOPAG)
57         need_space = NUMPAGGROUPS;
58
59     *new = groups_alloc(old->ngroups + need_space);
60
61     for (i = 0, j = 0; i < old->ngroups; ++i) {
62         afs_kgid_t ths = GROUP_AT(old, i);
63         if ((afs_from_kgid(ths) >> 24) == 'A')
64             continue;
65         if ((i == 0 || !gid_lt(newkgid, GROUP_AT(old, i-1))) &&
66             gid_lt(newkgid, ths)) {
67            GROUP_AT(*new, j) = newkgid;
68            j++;
69         }
70         GROUP_AT(*new, j) = ths;
71         j++;
72     }
73     if (j != i + need_space)
74         GROUP_AT(*new, j) = newkgid;
75 }
76
77 #else
78 # define NUMPAGGROUPS 2
79
80 static inline afs_uint32
81 afs_linux_pag_from_groups(struct group_info *group_info) {
82
83     if (group_info->ngroups < NUMPAGGROUPS)
84         return NOPAG;
85
86     return afs_get_pag_from_groups(GROUP_AT(group_info, 0), GROUP_AT(group_info, 1));
87 }
88
89 static inline void
90 afs_linux_pag_to_groups(afs_uint32 newpag,
91                         struct group_info *old, struct group_info **new) {
92     int need_space = 0;
93     int i;
94     gid_t g0;
95     gid_t g1;
96
97     if (afs_linux_pag_from_groups(old) == NOPAG)
98         need_space = NUMPAGGGROUPS;
99
100     *new = groups_alloc(old->ngroups + need_space);
101
102     for (i = 0; i < old->ngroups; ++i)
103           GROUP_AT(new, i + need_space) = GROUP_AT(old, i);
104
105     afs_get_groups_from_pag(newpag, &g0, g1);
106     GROUP_AT(new, 0) = g0;
107     GROUP_AT(new, 1) = g1;
108 }
109 #endif
110
111 afs_int32
112 osi_get_group_pag(afs_ucred_t *cred) {
113     return afs_linux_pag_from_groups(afs_cr_group_info(cred));
114 }
115
116
117 static int
118 afs_setgroups(cred_t **cr, struct group_info *group_info, int change_parent)
119 {
120     struct group_info *old_info;
121
122     AFS_STATCNT(afs_setgroups);
123
124     old_info = afs_cr_group_info(*cr);
125     get_group_info(group_info);
126     afs_set_cr_group_info(*cr, group_info);
127     put_group_info(old_info);
128
129     crset(*cr);
130
131 #if defined(STRUCT_TASK_STRUCT_HAS_PARENT) && !defined(STRUCT_TASK_STRUCT_HAS_CRED)
132     if (change_parent) {
133         old_info = current->parent->group_info;
134         get_group_info(group_info);
135         current->parent->group_info = group_info;
136         put_group_info(old_info);
137     }
138 #endif
139
140     return (0);
141 }
142
143 int
144 __setpag(cred_t **cr, afs_uint32 pagvalue, afs_uint32 *newpag,
145          int change_parent, struct group_info **old_groups)
146 {
147     struct group_info *group_info;
148     struct group_info *tmp;
149
150     get_group_info(afs_cr_group_info(*cr));
151     group_info = afs_cr_group_info(*cr);
152
153     *newpag = (pagvalue == -1 ? genpag() : pagvalue);
154     afs_linux_pag_to_groups(*newpag, group_info, &tmp);
155
156     if (old_groups) {
157         *old_groups = group_info;
158     } else {
159         put_group_info(group_info);
160         group_info = NULL;
161     }
162
163     afs_setgroups(cr, tmp, change_parent);
164
165     put_group_info(tmp);
166
167     return 0;
168 }
169
170 #ifdef LINUX_KEYRING_SUPPORT
171 extern struct key_type key_type_keyring __attribute__((weak));
172 static struct key_type *__key_type_keyring = &key_type_keyring;
173
174 /* install_session_keyring returns negative error values */
175 static int
176 install_session_keyring(struct key *keyring)
177 {
178     struct key *old;
179     char desc[20];
180     int code = -EINVAL;
181     unsigned long flags;
182
183     if (!__key_type_keyring)
184         return code;
185
186     if (!keyring) {
187
188         /* create an empty session keyring */
189         sprintf(desc, "_ses.%u", current->tgid);
190
191         /* if we're root, don't count the keyring against our quota. This
192          * avoids starvation issues when dealing with PAM modules that always
193          * setpag() as root */
194         if (capable(CAP_SYS_ADMIN))
195             flags = KEY_ALLOC_NOT_IN_QUOTA;
196         else
197             flags = KEY_ALLOC_IN_QUOTA;
198
199         keyring = afs_linux_key_alloc(
200                             __key_type_keyring, desc,
201                             current_uid(), current_gid(),
202                             (KEY_POS_ALL & ~KEY_POS_SETATTR) | KEY_USR_ALL,
203                             flags);
204
205         if (IS_ERR(keyring)) {
206             code = PTR_ERR(keyring);
207             goto out;
208         }
209     }
210
211     code = key_instantiate_and_link(keyring, NULL, 0, NULL, NULL);
212     if (code < 0) {
213         key_put(keyring);
214         goto out;
215     }
216
217     /* install the keyring */
218     old = afs_set_session_keyring(keyring);
219     if (old)
220         key_put(old);
221
222 out:
223     return code;
224 }
225 #endif /* LINUX_KEYRING_SUPPORT */
226
227 /* Error codes from setpag must be positive, otherwise they don't
228  * make it back into userspace properly. Error codes from the
229  * Linux keyring utilities, and from install_session_keyring()
230  * are negative. So we need to be careful to convert them correctly
231  * here
232  */
233 int
234 setpag(cred_t **cr, afs_uint32 pagvalue, afs_uint32 *newpag,
235        int change_parent)
236 {
237     int code;
238     struct group_info *old_groups = NULL;
239
240     AFS_STATCNT(setpag);
241
242     code = __setpag(cr, pagvalue, newpag, change_parent, &old_groups);
243
244 #ifdef LINUX_KEYRING_SUPPORT
245     if (code == 0 && afs_cr_rgid(*cr) != NFSXLATOR_CRED) {
246         code = install_session_keyring(NULL);
247         if (code == 0 && current_session_keyring()) {
248             struct key *key;
249             key_perm_t perm;
250
251             perm = KEY_POS_VIEW | KEY_POS_SEARCH;
252             perm |= KEY_USR_VIEW | KEY_USR_SEARCH;
253
254             key = afs_linux_key_alloc(&key_type_afs_pag, "_pag", GLOBAL_ROOT_UID, GLOBAL_ROOT_GID, perm, KEY_ALLOC_NOT_IN_QUOTA);
255
256             if (!IS_ERR(key)) {
257                 code = key_instantiate_and_link(key, (void *) newpag, sizeof(afs_uint32),
258                                          current_session_keyring(), NULL);
259                 key_put(key);
260             } else {
261                 code = PTR_ERR(key);
262             }
263         }
264         if (code)
265             code = -code;
266     }
267 #endif /* LINUX_KEYRING_SUPPORT */
268
269     if (code) {
270         if (old_groups) {
271             afs_setgroups(cr, old_groups, change_parent);
272             put_group_info(old_groups);
273             old_groups = NULL;
274         }
275         if (*newpag > -1) {
276             afs_MarkUserExpired(*newpag);
277             *newpag = -1;
278         }
279     }
280
281     return code;
282 }
283
284
285 /* Intercept the standard system call. */
286 extern asmlinkage long (*sys_setgroupsp) (int gidsetsize, gid_t * grouplist);
287 asmlinkage long
288 afs_xsetgroups(int gidsetsize, gid_t * grouplist)
289 {
290     long code;
291     cred_t *cr = crref();
292     afs_uint32 junk;
293     int old_pag;
294
295     old_pag = PagInCred(cr);
296     crfree(cr);
297
298     code = (*sys_setgroupsp) (gidsetsize, grouplist);
299     if (code) {
300         return code;
301     }
302
303     cr = crref();
304     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
305         /* re-install old pag if there's room. */
306         code = __setpag(&cr, old_pag, &junk, 0, NULL);
307     }
308     crfree(cr);
309
310     /* Linux syscall ABI returns errno as negative */
311     return (-code);
312 }
313
314 /* Intercept the standard uid32 system call. */
315 extern asmlinkage int (*sys_setgroups32p) (int gidsetsize,
316                                            __kernel_gid32_t * grouplist);
317 asmlinkage long
318 afs_xsetgroups32(int gidsetsize, gid_t * grouplist)
319 {
320     long code;
321     cred_t *cr = crref();
322     afs_uint32 junk;
323     int old_pag;
324
325     old_pag = PagInCred(cr);
326     crfree(cr);
327
328     code = (*sys_setgroups32p) (gidsetsize, grouplist);
329
330     if (code) {
331         return code;
332     }
333
334     cr = crref();
335     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
336         /* re-install old pag if there's room. */
337         code = __setpag(&cr, old_pag, &junk, 0, NULL);
338     }
339     crfree(cr);
340
341     /* Linux syscall ABI returns errno as negative */
342     return (-code);
343 }
344
345 #if defined(AFS_PPC64_LINUX20_ENV)
346 /* Intercept the uid16 system call as used by 32bit programs. */
347 extern asmlinkage long (*sys32_setgroupsp)(int gidsetsize, gid_t *grouplist);
348 asmlinkage long afs32_xsetgroups(int gidsetsize, gid_t *grouplist)
349 {
350     long code;
351     cred_t *cr = crref();
352     afs_uint32 junk;
353     int old_pag;
354     
355     old_pag = PagInCred(cr);
356     crfree(cr);
357     
358     code = (*sys32_setgroupsp)(gidsetsize, grouplist);
359     if (code) {
360         return code;
361     }
362     
363     cr = crref();
364     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
365         /* re-install old pag if there's room. */
366         code = __setpag(&cr, old_pag, &junk, 0, NULL);
367     }
368     crfree(cr);
369     
370     /* Linux syscall ABI returns errno as negative */
371     return (-code);
372 }
373 #endif
374
375 #if defined(AFS_SPARC64_LINUX20_ENV) || defined(AFS_AMD64_LINUX20_ENV)
376 /* Intercept the uid16 system call as used by 32bit programs. */
377 #ifdef AFS_AMD64_LINUX20_ENV
378 extern asmlinkage long (*sys32_setgroupsp) (int gidsetsize, u16 * grouplist);
379 #endif /* AFS_AMD64_LINUX20_ENV */
380 #ifdef AFS_SPARC64_LINUX26_ENV
381 extern asmlinkage int (*sys32_setgroupsp) (int gidsetsize,
382                                            __kernel_gid32_t * grouplist);
383 #endif /* AFS_SPARC64_LINUX26_ENV */
384 asmlinkage long
385 afs32_xsetgroups(int gidsetsize, u16 * grouplist)
386 {
387     long code;
388     cred_t *cr = crref();
389     afs_uint32 junk;
390     int old_pag;
391     
392     old_pag = PagInCred(cr);
393     crfree(cr);
394     
395     code = (*sys32_setgroupsp) (gidsetsize, grouplist);
396     if (code) {
397         return code;
398     }
399     
400     cr = crref();
401     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
402         /* re-install old pag if there's room. */
403         code = __setpag(&cr, old_pag, &junk, 0, NULL);
404     }
405     crfree(cr);
406     
407     /* Linux syscall ABI returns errno as negative */
408     return (-code);
409 }
410
411 /* Intercept the uid32 system call as used by 32bit programs. */
412 #ifdef AFS_AMD64_LINUX20_ENV
413 extern asmlinkage long (*sys32_setgroups32p) (int gidsetsize, gid_t * grouplist);
414 #endif /* AFS_AMD64_LINUX20_ENV */
415 #ifdef AFS_SPARC64_LINUX26_ENV
416 extern asmlinkage int (*sys32_setgroups32p) (int gidsetsize,
417                                              __kernel_gid32_t * grouplist);
418 #endif /* AFS_SPARC64_LINUX26_ENV */
419 asmlinkage long
420 afs32_xsetgroups32(int gidsetsize, gid_t * grouplist)
421 {
422     long code;
423     cred_t *cr = crref();
424     afs_uint32 junk;
425     int old_pag;
426
427     old_pag = PagInCred(cr);
428     crfree(cr);
429
430     code = (*sys32_setgroups32p) (gidsetsize, grouplist);
431     if (code) {
432         return code;
433     }
434
435     cr = crref();
436     if (old_pag != NOPAG && PagInCred(cr) == NOPAG) {
437         /* re-install old pag if there's room. */
438         code = __setpag(&cr, old_pag, &junk, 0, NULL);
439     }
440     crfree(cr);
441
442     /* Linux syscall ABI returns errno as negative */
443     return (-code);
444 }
445 #endif
446
447
448 #ifdef LINUX_KEYRING_SUPPORT
449 static void afs_pag_describe(const struct key *key, struct seq_file *m)
450 {
451     seq_puts(m, key->description);
452
453     seq_printf(m, ": %u", key->datalen);
454 }
455
456 #if defined(STRUCT_KEY_TYPE_HAS_PREPARSE)
457 static int afs_pag_instantiate(struct key *key, struct key_preparsed_payload *prep)
458 #else
459 static int afs_pag_instantiate(struct key *key, const void *data, size_t datalen)
460 #endif
461 {
462     int code;
463     afs_uint32 *userpag, pag = NOPAG;
464
465     if (!uid_eq(key->uid, GLOBAL_ROOT_UID) || !gid_eq(key->gid, GLOBAL_ROOT_GID))
466         return -EPERM;
467
468     code = -EINVAL;
469     get_group_info(current_group_info());
470
471 #if defined(STRUCT_KEY_TYPE_HAS_PREPARSE)
472     if (prep->datalen != sizeof(afs_uint32) || !prep->data)
473 #else
474     if (datalen != sizeof(afs_uint32) || !data)
475 #endif
476         goto error;
477
478     /* ensure key being set matches current pag */
479     pag = afs_linux_pag_from_groups(current_group_info());
480
481     if (pag == NOPAG)
482         goto error;
483
484 #if defined(STRUCT_KEY_TYPE_HAS_PREPARSE)
485     userpag = (afs_uint32 *)prep->data;
486 #else
487     userpag = (afs_uint32 *)data;
488 #endif
489     if (*userpag != pag)
490         goto error;
491
492 #if defined(STRUCT_KEY_HAS_PAYLOAD_VALUE)
493     key->payload.value = (unsigned long) *userpag;
494 #else
495     memcpy(&key->payload, userpag, sizeof(afs_uint32));
496 #endif
497     key->datalen = sizeof(afs_uint32);
498     code = 0;
499
500 error:
501     put_group_info(current_group_info());
502     return code;
503 }
504
505 #if !defined(STRUCT_KEY_TYPE_HAS_MATCH_PREPARSE)
506 /* Note that we only define a ->match function if struct
507  * key_type.match_preparse does _not_ exist. If key_type.match_preparse does
508  * exist, we would use that to specify an alternative comparison function; but
509  * since we just rely on default behavior, we don't need to actually specify
510  * one. But for kernels with no such match_preparse function, we need to
511  * specify a 'match' function, since there is no default. */
512 static int afs_pag_match(const struct key *key, const void *description)
513 {
514         return strcmp(key->description, description) == 0;
515 }
516 #endif
517
518 static void afs_pag_destroy(struct key *key)
519 {
520     afs_uint32 pag;
521     int locked = ISAFS_GLOCK();
522
523 #if defined(STRUCT_KEY_HAS_PAYLOAD_VALUE)
524     pag = key->payload.value;
525 #else
526     memcpy(&pag, &key->payload, sizeof(afs_uint32));
527 #endif
528
529     if (!locked)
530         AFS_GLOCK();
531
532     afs_MarkUserExpired(pag);
533
534     if (!locked)
535         AFS_GUNLOCK();
536 }
537
538 struct key_type key_type_afs_pag =
539 {
540     .name        = "afs_pag",
541     .describe    = afs_pag_describe,
542 #if defined(STRUCT_KEY_TYPE_HAS_INSTANTIATE_PREP)
543     .instantiate_prep = afs_pag_instantiate,
544     .instantiate = NULL,
545 #else
546     .instantiate = afs_pag_instantiate,
547 #endif
548 #if !defined(STRUCT_KEY_TYPE_HAS_MATCH_PREPARSE)
549     .match       = afs_pag_match,
550 #endif
551     .destroy     = afs_pag_destroy,
552 };
553
554 #ifdef EXPORTED_TASKLIST_LOCK
555 extern rwlock_t tasklist_lock __attribute__((weak));
556 #endif
557
558 void osi_keyring_init(void)
559 {
560 #if !defined(EXPORTED_KEY_TYPE_KEYRING)
561     struct task_struct *p;
562
563     /* If we can't lock the tasklist, either with its explicit lock,
564      * or by using the RCU lock, then we can't safely work out the 
565      * type of a keyring. So, we have to rely on the weak reference. 
566      * If that's not available, then keyring based PAGs won't work.
567      */
568     
569 #if defined(EXPORTED_TASKLIST_LOCK) || (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) && defined(HAVE_LINUX_RCU_READ_LOCK))
570     if (__key_type_keyring == NULL) {
571 # ifdef EXPORTED_TASKLIST_LOCK
572         if (&tasklist_lock)
573             read_lock(&tasklist_lock);
574 # endif
575 # if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) && defined(HAVE_LINUX_RCU_READ_LOCK))
576 #  ifdef EXPORTED_TASKLIST_LOCK
577         else
578 #  endif
579             rcu_read_lock();
580 # endif
581 #if defined(HAVE_LINUX_FIND_TASK_BY_PID)
582         p = find_task_by_pid(1);
583 #else
584         p = find_task_by_vpid(1);
585 #endif
586         if (p && task_user(p)->session_keyring)
587             __key_type_keyring = task_user(p)->session_keyring->type;
588 # ifdef EXPORTED_TASKLIST_LOCK
589         if (&tasklist_lock)
590             read_unlock(&tasklist_lock);
591 # endif
592 # if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,16) && defined(HAVE_LINUX_RCU_READ_LOCK))
593 #  ifdef EXPORTED_TASKLIST_LOCK
594         else
595 #  endif
596             rcu_read_unlock();
597 # endif
598     }
599 #endif
600 #endif
601
602     register_key_type(&key_type_afs_pag);
603 }
604
605 void osi_keyring_shutdown(void)
606 {
607     unregister_key_type(&key_type_afs_pag);
608 }
609
610 afs_int32
611 osi_get_keyring_pag(afs_ucred_t *cred)
612 {
613     struct key *key;
614     afs_uint32 newpag;
615     afs_int32 keyring_pag = NOPAG;
616
617     if (afs_cr_rgid(cred) != NFSXLATOR_CRED) {
618         key = afs_linux_search_keyring(cred, &key_type_afs_pag);
619
620         if (!IS_ERR(key)) {
621             if (key_validate(key) == 0 && uid_eq(key->uid, GLOBAL_ROOT_UID)) {      /* also verify in the session keyring? */
622 #if defined(STRUCT_KEY_HAS_PAYLOAD_VALUE)
623                 keyring_pag = key->payload.value;
624 #else
625                 memcpy(&keyring_pag, &key->payload, sizeof(afs_int32));
626 #endif
627                 /* Only set PAG in groups if needed,
628                  * and the creds are from the current process */
629                 if (afs_linux_cred_is_current(cred) &&
630                     ((keyring_pag >> 24) & 0xff) == 'A' &&
631                     keyring_pag != afs_linux_pag_from_groups(current_group_info())) {
632                         __setpag(&cred, keyring_pag, &newpag, 0, NULL);
633                 }
634             }
635             key_put(key);
636         }
637     }
638     return keyring_pag;
639 }
640
641 #else
642 void osi_keyring_init(void)
643 {
644         return;
645 }
646
647 void osi_keyring_shutdown(void)
648 {
649         return;
650 }
651 #endif