linux/fs/afs/security.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0-or-later
   2/* AFS security handling
   3 *
   4 * Copyright (C) 2007, 2017 Red Hat, Inc. All Rights Reserved.
   5 * Written by David Howells (dhowells@redhat.com)
   6 */
   7
   8#include <linux/init.h>
   9#include <linux/slab.h>
  10#include <linux/fs.h>
  11#include <linux/ctype.h>
  12#include <linux/sched.h>
  13#include <linux/hashtable.h>
  14#include <keys/rxrpc-type.h>
  15#include "internal.h"
  16
  17static DEFINE_HASHTABLE(afs_permits_cache, 10);
  18static DEFINE_SPINLOCK(afs_permits_lock);
  19
  20/*
  21 * get a key
  22 */
  23struct key *afs_request_key(struct afs_cell *cell)
  24{
  25        struct key *key;
  26
  27        _enter("{%x}", key_serial(cell->anonymous_key));
  28
  29        _debug("key %s", cell->anonymous_key->description);
  30        key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
  31                          NULL);
  32        if (IS_ERR(key)) {
  33                if (PTR_ERR(key) != -ENOKEY) {
  34                        _leave(" = %ld", PTR_ERR(key));
  35                        return key;
  36                }
  37
  38                /* act as anonymous user */
  39                _leave(" = {%x} [anon]", key_serial(cell->anonymous_key));
  40                return key_get(cell->anonymous_key);
  41        } else {
  42                /* act as authorised user */
  43                _leave(" = {%x} [auth]", key_serial(key));
  44                return key;
  45        }
  46}
  47
  48/*
  49 * Dispose of a list of permits.
  50 */
  51static void afs_permits_rcu(struct rcu_head *rcu)
  52{
  53        struct afs_permits *permits =
  54                container_of(rcu, struct afs_permits, rcu);
  55        int i;
  56
  57        for (i = 0; i < permits->nr_permits; i++)
  58                key_put(permits->permits[i].key);
  59        kfree(permits);
  60}
  61
  62/*
  63 * Discard a permission cache.
  64 */
  65void afs_put_permits(struct afs_permits *permits)
  66{
  67        if (permits && refcount_dec_and_test(&permits->usage)) {
  68                spin_lock(&afs_permits_lock);
  69                hash_del_rcu(&permits->hash_node);
  70                spin_unlock(&afs_permits_lock);
  71                call_rcu(&permits->rcu, afs_permits_rcu);
  72        }
  73}
  74
  75/*
  76 * Clear a permit cache on callback break.
  77 */
  78void afs_clear_permits(struct afs_vnode *vnode)
  79{
  80        struct afs_permits *permits;
  81
  82        spin_lock(&vnode->lock);
  83        permits = rcu_dereference_protected(vnode->permit_cache,
  84                                            lockdep_is_held(&vnode->lock));
  85        RCU_INIT_POINTER(vnode->permit_cache, NULL);
  86        spin_unlock(&vnode->lock);
  87
  88        afs_put_permits(permits);
  89}
  90
  91/*
  92 * Hash a list of permits.  Use simple addition to make it easy to add an extra
  93 * one at an as-yet indeterminate position in the list.
  94 */
  95static void afs_hash_permits(struct afs_permits *permits)
  96{
  97        unsigned long h = permits->nr_permits;
  98        int i;
  99
 100        for (i = 0; i < permits->nr_permits; i++) {
 101                h += (unsigned long)permits->permits[i].key / sizeof(void *);
 102                h += permits->permits[i].access;
 103        }
 104
 105        permits->h = h;
 106}
 107
 108/*
 109 * Cache the CallerAccess result obtained from doing a fileserver operation
 110 * that returned a vnode status for a particular key.  If a callback break
 111 * occurs whilst the operation was in progress then we have to ditch the cache
 112 * as the ACL *may* have changed.
 113 */
 114void afs_cache_permit(struct afs_vnode *vnode, struct key *key,
 115                      unsigned int cb_break, struct afs_status_cb *scb)
 116{
 117        struct afs_permits *permits, *xpermits, *replacement, *zap, *new = NULL;
 118        afs_access_t caller_access = scb->status.caller_access;
 119        size_t size = 0;
 120        bool changed = false;
 121        int i, j;
 122
 123        _enter("{%llx:%llu},%x,%x",
 124               vnode->fid.vid, vnode->fid.vnode, key_serial(key), caller_access);
 125
 126        rcu_read_lock();
 127
 128        /* Check for the common case first: We got back the same access as last
 129         * time we tried and already have it recorded.
 130         */
 131        permits = rcu_dereference(vnode->permit_cache);
 132        if (permits) {
 133                if (!permits->invalidated) {
 134                        for (i = 0; i < permits->nr_permits; i++) {
 135                                if (permits->permits[i].key < key)
 136                                        continue;
 137                                if (permits->permits[i].key > key)
 138                                        break;
 139                                if (permits->permits[i].access != caller_access) {
 140                                        changed = true;
 141                                        break;
 142                                }
 143
 144                                if (afs_cb_is_broken(cb_break, vnode,
 145                                                     rcu_dereference(vnode->cb_interest))) {
 146                                        changed = true;
 147                                        break;
 148                                }
 149
 150                                /* The cache is still good. */
 151                                rcu_read_unlock();
 152                                return;
 153                        }
 154                }
 155
 156                changed |= permits->invalidated;
 157                size = permits->nr_permits;
 158
 159                /* If this set of permits is now wrong, clear the permits
 160                 * pointer so that no one tries to use the stale information.
 161                 */
 162                if (changed) {
 163                        spin_lock(&vnode->lock);
 164                        if (permits != rcu_access_pointer(vnode->permit_cache))
 165                                goto someone_else_changed_it_unlock;
 166                        RCU_INIT_POINTER(vnode->permit_cache, NULL);
 167                        spin_unlock(&vnode->lock);
 168
 169                        afs_put_permits(permits);
 170                        permits = NULL;
 171                        size = 0;
 172                }
 173        }
 174
 175        if (afs_cb_is_broken(cb_break, vnode, rcu_dereference(vnode->cb_interest)))
 176                goto someone_else_changed_it;
 177
 178        /* We need a ref on any permits list we want to copy as we'll have to
 179         * drop the lock to do memory allocation.
 180         */
 181        if (permits && !refcount_inc_not_zero(&permits->usage))
 182                goto someone_else_changed_it;
 183
 184        rcu_read_unlock();
 185
 186        /* Speculatively create a new list with the revised permission set.  We
 187         * discard this if we find an extant match already in the hash, but
 188         * it's easier to compare with memcmp this way.
 189         *
 190         * We fill in the key pointers at this time, but we don't get the refs
 191         * yet.
 192         */
 193        size++;
 194        new = kzalloc(sizeof(struct afs_permits) +
 195                      sizeof(struct afs_permit) * size, GFP_NOFS);
 196        if (!new)
 197                goto out_put;
 198
 199        refcount_set(&new->usage, 1);
 200        new->nr_permits = size;
 201        i = j = 0;
 202        if (permits) {
 203                for (i = 0; i < permits->nr_permits; i++) {
 204                        if (j == i && permits->permits[i].key > key) {
 205                                new->permits[j].key = key;
 206                                new->permits[j].access = caller_access;
 207                                j++;
 208                        }
 209                        new->permits[j].key = permits->permits[i].key;
 210                        new->permits[j].access = permits->permits[i].access;
 211                        j++;
 212                }
 213        }
 214
 215        if (j == i) {
 216                new->permits[j].key = key;
 217                new->permits[j].access = caller_access;
 218        }
 219
 220        afs_hash_permits(new);
 221
 222        /* Now see if the permit list we want is actually already available */
 223        spin_lock(&afs_permits_lock);
 224
 225        hash_for_each_possible(afs_permits_cache, xpermits, hash_node, new->h) {
 226                if (xpermits->h != new->h ||
 227                    xpermits->invalidated ||
 228                    xpermits->nr_permits != new->nr_permits ||
 229                    memcmp(xpermits->permits, new->permits,
 230                           new->nr_permits * sizeof(struct afs_permit)) != 0)
 231                        continue;
 232
 233                if (refcount_inc_not_zero(&xpermits->usage)) {
 234                        replacement = xpermits;
 235                        goto found;
 236                }
 237
 238                break;
 239        }
 240
 241        for (i = 0; i < new->nr_permits; i++)
 242                key_get(new->permits[i].key);
 243        hash_add_rcu(afs_permits_cache, &new->hash_node, new->h);
 244        replacement = new;
 245        new = NULL;
 246
 247found:
 248        spin_unlock(&afs_permits_lock);
 249
 250        kfree(new);
 251
 252        rcu_read_lock();
 253        spin_lock(&vnode->lock);
 254        zap = rcu_access_pointer(vnode->permit_cache);
 255        if (!afs_cb_is_broken(cb_break, vnode, rcu_dereference(vnode->cb_interest)) &&
 256            zap == permits)
 257                rcu_assign_pointer(vnode->permit_cache, replacement);
 258        else
 259                zap = replacement;
 260        spin_unlock(&vnode->lock);
 261        rcu_read_unlock();
 262        afs_put_permits(zap);
 263out_put:
 264        afs_put_permits(permits);
 265        return;
 266
 267someone_else_changed_it_unlock:
 268        spin_unlock(&vnode->lock);
 269someone_else_changed_it:
 270        /* Someone else changed the cache under us - don't recheck at this
 271         * time.
 272         */
 273        rcu_read_unlock();
 274        return;
 275}
 276
 277/*
 278 * check with the fileserver to see if the directory or parent directory is
 279 * permitted to be accessed with this authorisation, and if so, what access it
 280 * is granted
 281 */
 282int afs_check_permit(struct afs_vnode *vnode, struct key *key,
 283                     afs_access_t *_access)
 284{
 285        struct afs_permits *permits;
 286        bool valid = false;
 287        int i, ret;
 288
 289        _enter("{%llx:%llu},%x",
 290               vnode->fid.vid, vnode->fid.vnode, key_serial(key));
 291
 292        /* check the permits to see if we've got one yet */
 293        if (key == vnode->volume->cell->anonymous_key) {
 294                _debug("anon");
 295                *_access = vnode->status.anon_access;
 296                valid = true;
 297        } else {
 298                rcu_read_lock();
 299                permits = rcu_dereference(vnode->permit_cache);
 300                if (permits) {
 301                        for (i = 0; i < permits->nr_permits; i++) {
 302                                if (permits->permits[i].key < key)
 303                                        continue;
 304                                if (permits->permits[i].key > key)
 305                                        break;
 306
 307                                *_access = permits->permits[i].access;
 308                                valid = !permits->invalidated;
 309                                break;
 310                        }
 311                }
 312                rcu_read_unlock();
 313        }
 314
 315        if (!valid) {
 316                /* Check the status on the file we're actually interested in
 317                 * (the post-processing will cache the result).
 318                 */
 319                _debug("no valid permit");
 320
 321                ret = afs_fetch_status(vnode, key, false, _access);
 322                if (ret < 0) {
 323                        *_access = 0;
 324                        _leave(" = %d", ret);
 325                        return ret;
 326                }
 327        }
 328
 329        _leave(" = 0 [access %x]", *_access);
 330        return 0;
 331}
 332
 333/*
 334 * check the permissions on an AFS file
 335 * - AFS ACLs are attached to directories only, and a file is controlled by its
 336 *   parent directory's ACL
 337 */
 338int afs_permission(struct inode *inode, int mask)
 339{
 340        struct afs_vnode *vnode = AFS_FS_I(inode);
 341        afs_access_t uninitialized_var(access);
 342        struct key *key;
 343        int ret;
 344
 345        if (mask & MAY_NOT_BLOCK)
 346                return -ECHILD;
 347
 348        _enter("{{%llx:%llu},%lx},%x,",
 349               vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);
 350
 351        key = afs_request_key(vnode->volume->cell);
 352        if (IS_ERR(key)) {
 353                _leave(" = %ld [key]", PTR_ERR(key));
 354                return PTR_ERR(key);
 355        }
 356
 357        ret = afs_validate(vnode, key);
 358        if (ret < 0)
 359                goto error;
 360
 361        /* check the permits to see if we've got one yet */
 362        ret = afs_check_permit(vnode, key, &access);
 363        if (ret < 0)
 364                goto error;
 365
 366        /* interpret the access mask */
 367        _debug("REQ %x ACC %x on %s",
 368               mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");
 369
 370        if (S_ISDIR(inode->i_mode)) {
 371                if (mask & (MAY_EXEC | MAY_READ | MAY_CHDIR)) {
 372                        if (!(access & AFS_ACE_LOOKUP))
 373                                goto permission_denied;
 374                }
 375                if (mask & MAY_WRITE) {
 376                        if (!(access & (AFS_ACE_DELETE | /* rmdir, unlink, rename from */
 377                                        AFS_ACE_INSERT))) /* create, mkdir, symlink, rename to */
 378                                goto permission_denied;
 379                }
 380        } else {
 381                if (!(access & AFS_ACE_LOOKUP))
 382                        goto permission_denied;
 383                if ((mask & MAY_EXEC) && !(inode->i_mode & S_IXUSR))
 384                        goto permission_denied;
 385                if (mask & (MAY_EXEC | MAY_READ)) {
 386                        if (!(access & AFS_ACE_READ))
 387                                goto permission_denied;
 388                        if (!(inode->i_mode & S_IRUSR))
 389                                goto permission_denied;
 390                } else if (mask & MAY_WRITE) {
 391                        if (!(access & AFS_ACE_WRITE))
 392                                goto permission_denied;
 393                        if (!(inode->i_mode & S_IWUSR))
 394                                goto permission_denied;
 395                }
 396        }
 397
 398        key_put(key);
 399        _leave(" = %d", ret);
 400        return ret;
 401
 402permission_denied:
 403        ret = -EACCES;
 404error:
 405        key_put(key);
 406        _leave(" = %d", ret);
 407        return ret;
 408}
 409
 410void __exit afs_clean_up_permit_cache(void)
 411{
 412        int i;
 413
 414        for (i = 0; i < HASH_SIZE(afs_permits_cache); i++)
 415                WARN_ON_ONCE(!hlist_empty(&afs_permits_cache[i]));
 416
 417}
 418