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