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