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