linux/kernel/groups.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Supplementary group IDs
   4 */
   5#include <linux/cred.h>
   6#include <linux/export.h>
   7#include <linux/slab.h>
   8#include <linux/security.h>
   9#include <linux/sort.h>
  10#include <linux/syscalls.h>
  11#include <linux/user_namespace.h>
  12#include <linux/vmalloc.h>
  13#include <linux/uaccess.h>
  14
  15struct group_info *groups_alloc(int gidsetsize)
  16{
  17        struct group_info *gi;
  18        unsigned int len;
  19
  20        len = sizeof(struct group_info) + sizeof(kgid_t) * gidsetsize;
  21        gi = kmalloc(len, GFP_KERNEL_ACCOUNT|__GFP_NOWARN|__GFP_NORETRY);
  22        if (!gi)
  23                gi = __vmalloc(len, GFP_KERNEL_ACCOUNT, PAGE_KERNEL);
  24        if (!gi)
  25                return NULL;
  26
  27        atomic_set(&gi->usage, 1);
  28        gi->ngroups = gidsetsize;
  29        return gi;
  30}
  31
  32EXPORT_SYMBOL(groups_alloc);
  33
  34void groups_free(struct group_info *group_info)
  35{
  36        kvfree(group_info);
  37}
  38
  39EXPORT_SYMBOL(groups_free);
  40
  41/* export the group_info to a user-space array */
  42static int groups_to_user(gid_t __user *grouplist,
  43                          const struct group_info *group_info)
  44{
  45        struct user_namespace *user_ns = current_user_ns();
  46        int i;
  47        unsigned int count = group_info->ngroups;
  48
  49        for (i = 0; i < count; i++) {
  50                gid_t gid;
  51                gid = from_kgid_munged(user_ns, group_info->gid[i]);
  52                if (put_user(gid, grouplist+i))
  53                        return -EFAULT;
  54        }
  55        return 0;
  56}
  57
  58/* fill a group_info from a user-space array - it must be allocated already */
  59static int groups_from_user(struct group_info *group_info,
  60    gid_t __user *grouplist)
  61{
  62        struct user_namespace *user_ns = current_user_ns();
  63        int i;
  64        unsigned int count = group_info->ngroups;
  65
  66        for (i = 0; i < count; i++) {
  67                gid_t gid;
  68                kgid_t kgid;
  69                if (get_user(gid, grouplist+i))
  70                        return -EFAULT;
  71
  72                kgid = make_kgid(user_ns, gid);
  73                if (!gid_valid(kgid))
  74                        return -EINVAL;
  75
  76                group_info->gid[i] = kgid;
  77        }
  78        return 0;
  79}
  80
  81static int gid_cmp(const void *_a, const void *_b)
  82{
  83        kgid_t a = *(kgid_t *)_a;
  84        kgid_t b = *(kgid_t *)_b;
  85
  86        return gid_gt(a, b) - gid_lt(a, b);
  87}
  88
  89void groups_sort(struct group_info *group_info)
  90{
  91        sort(group_info->gid, group_info->ngroups, sizeof(*group_info->gid),
  92             gid_cmp, NULL);
  93}
  94EXPORT_SYMBOL(groups_sort);
  95
  96/* a simple bsearch */
  97int groups_search(const struct group_info *group_info, kgid_t grp)
  98{
  99        unsigned int left, right;
 100
 101        if (!group_info)
 102                return 0;
 103
 104        left = 0;
 105        right = group_info->ngroups;
 106        while (left < right) {
 107                unsigned int mid = (left+right)/2;
 108                if (gid_gt(grp, group_info->gid[mid]))
 109                        left = mid + 1;
 110                else if (gid_lt(grp, group_info->gid[mid]))
 111                        right = mid;
 112                else
 113                        return 1;
 114        }
 115        return 0;
 116}
 117
 118/**
 119 * set_groups - Change a group subscription in a set of credentials
 120 * @new: The newly prepared set of credentials to alter
 121 * @group_info: The group list to install
 122 */
 123void set_groups(struct cred *new, struct group_info *group_info)
 124{
 125        put_group_info(new->group_info);
 126        get_group_info(group_info);
 127        new->group_info = group_info;
 128}
 129
 130EXPORT_SYMBOL(set_groups);
 131
 132/**
 133 * set_current_groups - Change current's group subscription
 134 * @group_info: The group list to impose
 135 *
 136 * Validate a group subscription and, if valid, impose it upon current's task
 137 * security record.
 138 */
 139int set_current_groups(struct group_info *group_info)
 140{
 141        struct cred *new;
 142
 143        new = prepare_creds();
 144        if (!new)
 145                return -ENOMEM;
 146
 147        set_groups(new, group_info);
 148        return commit_creds(new);
 149}
 150
 151EXPORT_SYMBOL(set_current_groups);
 152
 153SYSCALL_DEFINE2(getgroups, int, gidsetsize, gid_t __user *, grouplist)
 154{
 155        const struct cred *cred = current_cred();
 156        int i;
 157
 158        if (gidsetsize < 0)
 159                return -EINVAL;
 160
 161        /* no need to grab task_lock here; it cannot change */
 162        i = cred->group_info->ngroups;
 163        if (gidsetsize) {
 164                if (i > gidsetsize) {
 165                        i = -EINVAL;
 166                        goto out;
 167                }
 168                if (groups_to_user(grouplist, cred->group_info)) {
 169                        i = -EFAULT;
 170                        goto out;
 171                }
 172        }
 173out:
 174        return i;
 175}
 176
 177bool may_setgroups(void)
 178{
 179        struct user_namespace *user_ns = current_user_ns();
 180
 181        return ns_capable(user_ns, CAP_SETGID) &&
 182                userns_may_setgroups(user_ns);
 183}
 184
 185/*
 186 *      SMP: Our groups are copy-on-write. We can set them safely
 187 *      without another task interfering.
 188 */
 189
 190SYSCALL_DEFINE2(setgroups, int, gidsetsize, gid_t __user *, grouplist)
 191{
 192        struct group_info *group_info;
 193        int retval;
 194
 195        if (!may_setgroups())
 196                return -EPERM;
 197        if ((unsigned)gidsetsize > NGROUPS_MAX)
 198                return -EINVAL;
 199
 200        group_info = groups_alloc(gidsetsize);
 201        if (!group_info)
 202                return -ENOMEM;
 203        retval = groups_from_user(group_info, grouplist);
 204        if (retval) {
 205                put_group_info(group_info);
 206                return retval;
 207        }
 208
 209        groups_sort(group_info);
 210        retval = set_current_groups(group_info);
 211        put_group_info(group_info);
 212
 213        return retval;
 214}
 215
 216/*
 217 * Check whether we're fsgid/egid or in the supplemental group..
 218 */
 219int in_group_p(kgid_t grp)
 220{
 221        const struct cred *cred = current_cred();
 222        int retval = 1;
 223
 224        if (!gid_eq(grp, cred->fsgid))
 225                retval = groups_search(cred->group_info, grp);
 226        return retval;
 227}
 228
 229EXPORT_SYMBOL(in_group_p);
 230
 231int in_egroup_p(kgid_t grp)
 232{
 233        const struct cred *cred = current_cred();
 234        int retval = 1;
 235
 236        if (!gid_eq(grp, cred->egid))
 237                retval = groups_search(cred->group_info, grp);
 238        return retval;
 239}
 240
 241EXPORT_SYMBOL(in_egroup_p);
 242