Something went wrong. Try again.
Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521// SPDX-License-Identifier: GPL-2.0-or-later/* AFS security handling * * Copyright (C) 2007, 2017 Red Hat, Inc. All Rights Reserved. * Written by David Howells (dhowells@redhat.com) */
#include <linux/init.h>#include <linux/slab.h>#include <linux/fs.h>#include <linux/ctype.h>#include <linux/sched.h>#include <linux/hashtable.h>#include <keys/rxrpc-type.h>#include "internal.h"
static DEFINE_HASHTABLE(afs_permits_cache, 10);static DEFINE_SPINLOCK(afs_permits_lock);static DEFINE_MUTEX(afs_key_lock);
/* * Allocate a key to use as a placeholder for anonymous user security. */static int afs_alloc_anon_key(struct afs_cell *cell){ struct key *key;
mutex_lock(&afs_key_lock); key = cell->anonymous_key; if (!key) { key = rxrpc_get_null_key(cell->key_desc); if (!IS_ERR(key)) cell->anonymous_key = key; } mutex_unlock(&afs_key_lock);
if (IS_ERR(key)) return PTR_ERR(key);
_debug("anon key %p{%x}", cell->anonymous_key, key_serial(cell->anonymous_key)); return 0;}
/* * get a key */struct key *afs_request_key(struct afs_cell *cell){ struct key *key; int ret;
_enter("{%s}", cell->key_desc);
_debug("key %s", cell->key_desc); key = request_key_net(&key_type_rxrpc, cell->key_desc, cell->net->net, NULL); if (IS_ERR(key)) { if (PTR_ERR(key) != -ENOKEY) { _leave(" = %ld", PTR_ERR(key)); return key; }
if (!cell->anonymous_key) { ret = afs_alloc_anon_key(cell); if (ret < 0) return ERR_PTR(ret); }
/* act as anonymous user */ _leave(" = {%x} [anon]", key_serial(cell->anonymous_key)); return key_get(cell->anonymous_key); } else { /* act as authorised user */ _leave(" = {%x} [auth]", key_serial(key)); return key; }}
/* * Get a key when pathwalk is in rcuwalk mode. */struct key *afs_request_key_rcu(struct afs_cell *cell){ struct key *key;
_enter("{%s}", cell->key_desc);
_debug("key %s", cell->key_desc); key = request_key_net_rcu(&key_type_rxrpc, cell->key_desc, cell->net->net); if (IS_ERR(key)) { if (PTR_ERR(key) != -ENOKEY) { _leave(" = %ld", PTR_ERR(key)); return key; }
/* act as anonymous user */ if (!cell->anonymous_key) return NULL; /* Need to allocate */ _leave(" = {%x} [anon]", key_serial(cell->anonymous_key)); return key_get(cell->anonymous_key); } else { /* act as authorised user */ _leave(" = {%x} [auth]", key_serial(key)); return key; }}
/* * Dispose of a list of permits. */static void afs_permits_rcu(struct rcu_head *rcu){ struct afs_permits *permits = container_of(rcu, struct afs_permits, rcu); int i;
for (i = 0; i < permits->nr_permits; i++) key_put(permits->permits[i].key); kfree(permits);}
/* * Discard a permission cache. */void afs_put_permits(struct afs_permits *permits){ if (permits && refcount_dec_and_test(&permits->usage)) { spin_lock(&afs_permits_lock); hash_del_rcu(&permits->hash_node); spin_unlock(&afs_permits_lock); call_rcu(&permits->rcu, afs_permits_rcu); }}
/* * Clear a permit cache on callback break. */void afs_clear_permits(struct afs_vnode *vnode){ struct afs_permits *permits;
spin_lock(&vnode->lock); permits = rcu_dereference_protected(vnode->permit_cache, lockdep_is_held(&vnode->lock)); RCU_INIT_POINTER(vnode->permit_cache, NULL); spin_unlock(&vnode->lock);
afs_put_permits(permits);}
/* * Hash a list of permits. Use simple addition to make it easy to add an extra * one at an as-yet indeterminate position in the list. */static void afs_hash_permits(struct afs_permits *permits){ unsigned long h = permits->nr_permits; int i;
for (i = 0; i < permits->nr_permits; i++) { h += (unsigned long)permits->permits[i].key / sizeof(void *); h += permits->permits[i].access; }
permits->h = h;}
/* * Cache the CallerAccess result obtained from doing a fileserver operation * that returned a vnode status for a particular key. If a callback break * occurs whilst the operation was in progress then we have to ditch the cache * as the ACL *may* have changed. */void afs_cache_permit(struct afs_vnode *vnode, struct key *key, unsigned int cb_break, struct afs_status_cb *scb){ struct afs_permits *permits, *xpermits, *replacement, *zap, *new = NULL; afs_access_t caller_access = scb->status.caller_access; size_t size = 0; bool changed = false; int i, j;
_enter("{%llx:%llu},%x,%x", vnode->fid.vid, vnode->fid.vnode, key_serial(key), caller_access);
rcu_read_lock();
/* Check for the common case first: We got back the same access as last * time we tried and already have it recorded. */ permits = rcu_dereference(vnode->permit_cache); if (permits) { if (!permits->invalidated) { for (i = 0; i < permits->nr_permits; i++) { if (permits->permits[i].key < key) continue; if (permits->permits[i].key > key) break; if (permits->permits[i].access != caller_access) { changed = true; break; }
if (afs_cb_is_broken(cb_break, vnode)) { changed = true; break; }
/* The cache is still good. */ rcu_read_unlock(); return; } }
changed |= permits->invalidated; size = permits->nr_permits;
/* If this set of permits is now wrong, clear the permits * pointer so that no one tries to use the stale information. */ if (changed) { spin_lock(&vnode->lock); if (permits != rcu_access_pointer(vnode->permit_cache)) goto someone_else_changed_it_unlock; RCU_INIT_POINTER(vnode->permit_cache, NULL); spin_unlock(&vnode->lock);
afs_put_permits(permits); permits = NULL; size = 0; } }
if (afs_cb_is_broken(cb_break, vnode)) goto someone_else_changed_it;
/* We need a ref on any permits list we want to copy as we'll have to * drop the lock to do memory allocation. */ if (permits && !refcount_inc_not_zero(&permits->usage)) goto someone_else_changed_it;
rcu_read_unlock();
/* Speculatively create a new list with the revised permission set. We * discard this if we find an extant match already in the hash, but * it's easier to compare with memcmp this way. * * We fill in the key pointers at this time, but we don't get the refs * yet. */ size++; new = kzalloc_flex(*new, permits, size, GFP_NOFS); if (!new) goto out_put;
refcount_set(&new->usage, 1); new->nr_permits = size; i = j = 0; if (permits) { for (i = 0; i < permits->nr_permits; i++) { if (j == i && permits->permits[i].key > key) { new->permits[j].key = key; new->permits[j].access = caller_access; j++; } new->permits[j].key = permits->permits[i].key; new->permits[j].access = permits->permits[i].access; j++; } }
if (j == i) { new->permits[j].key = key; new->permits[j].access = caller_access; }
afs_hash_permits(new);
/* Now see if the permit list we want is actually already available */ spin_lock(&afs_permits_lock);
hash_for_each_possible(afs_permits_cache, xpermits, hash_node, new->h) { if (xpermits->h != new->h || xpermits->invalidated || xpermits->nr_permits != new->nr_permits || memcmp(xpermits->permits, new->permits, new->nr_permits * sizeof(struct afs_permit)) != 0) continue;
if (refcount_inc_not_zero(&xpermits->usage)) { replacement = xpermits; goto found; }
break; }
for (i = 0; i < new->nr_permits; i++) key_get(new->permits[i].key); hash_add_rcu(afs_permits_cache, &new->hash_node, new->h); replacement = new; new = NULL;
found: spin_unlock(&afs_permits_lock);
kfree(new);
rcu_read_lock(); spin_lock(&vnode->lock); zap = rcu_access_pointer(vnode->permit_cache); if (!afs_cb_is_broken(cb_break, vnode) && zap == permits) rcu_assign_pointer(vnode->permit_cache, replacement); else zap = replacement; spin_unlock(&vnode->lock); rcu_read_unlock(); afs_put_permits(zap);out_put: afs_put_permits(permits); return;
someone_else_changed_it_unlock: spin_unlock(&vnode->lock);someone_else_changed_it: /* Someone else changed the cache under us - don't recheck at this * time. */ rcu_read_unlock(); return;}
static bool afs_check_permit_rcu(struct afs_vnode *vnode, struct key *key, afs_access_t *_access){ const struct afs_permits *permits; int i;
_enter("{%llx:%llu},%x", vnode->fid.vid, vnode->fid.vnode, key_serial(key));
/* check the permits to see if we've got one yet */ if (key == vnode->volume->cell->anonymous_key) { *_access = vnode->status.anon_access; _leave(" = t [anon %x]", *_access); return true; }
permits = rcu_dereference(vnode->permit_cache); if (permits) { for (i = 0; i < permits->nr_permits; i++) { if (permits->permits[i].key < key) continue; if (permits->permits[i].key > key) break;
*_access = permits->permits[i].access; _leave(" = %u [perm %x]", !permits->invalidated, *_access); return !permits->invalidated; } }
_leave(" = f"); return false;}
/* * check with the fileserver to see if the directory or parent directory is * permitted to be accessed with this authorisation, and if so, what access it * is granted */int afs_check_permit(struct afs_vnode *vnode, struct key *key, afs_access_t *_access){ struct afs_permits *permits; bool valid = false; int i, ret;
_enter("{%llx:%llu},%x", vnode->fid.vid, vnode->fid.vnode, key_serial(key));
/* check the permits to see if we've got one yet */ if (key == vnode->volume->cell->anonymous_key) { _debug("anon"); *_access = vnode->status.anon_access; valid = true; } else { rcu_read_lock(); permits = rcu_dereference(vnode->permit_cache); if (permits) { for (i = 0; i < permits->nr_permits; i++) { if (permits->permits[i].key < key) continue; if (permits->permits[i].key > key) break;
*_access = permits->permits[i].access; valid = !permits->invalidated; break; } } rcu_read_unlock(); }
if (!valid) { /* Check the status on the file we're actually interested in * (the post-processing will cache the result). */ _debug("no valid permit");
ret = afs_fetch_status(vnode, key, false, _access); if (ret < 0) { *_access = 0; _leave(" = %d", ret); return ret; } }
_leave(" = 0 [access %x]", *_access); return 0;}
/* * check the permissions on an AFS file * - AFS ACLs are attached to directories only, and a file is controlled by its * parent directory's ACL */int afs_permission(struct mnt_idmap *idmap, struct inode *inode, int mask){ struct afs_vnode *vnode = AFS_FS_I(inode); afs_access_t access; struct key *key; int ret = 0;
_enter("{{%llx:%llu},%lx},%x,", vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);
if (mask & MAY_NOT_BLOCK) { key = afs_request_key_rcu(vnode->volume->cell); if (IS_ERR_OR_NULL(key)) return -ECHILD;
ret = -ECHILD; if (!afs_check_validity(vnode) || !afs_check_permit_rcu(vnode, key, &access)) goto error; } else { key = afs_request_key(vnode->volume->cell); if (IS_ERR(key)) { _leave(" = %ld [key]", PTR_ERR(key)); return PTR_ERR(key); }
ret = afs_validate(vnode, key); if (ret < 0) goto error;
/* check the permits to see if we've got one yet */ ret = afs_check_permit(vnode, key, &access); if (ret < 0) goto error; }
/* interpret the access mask */ _debug("REQ %x ACC %x on %s", mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");
ret = 0; if (S_ISDIR(inode->i_mode)) { if (mask & (MAY_EXEC | MAY_READ | MAY_CHDIR)) { if (!(access & AFS_ACE_LOOKUP)) goto permission_denied; } if (mask & MAY_WRITE) { if (!(access & (AFS_ACE_DELETE | /* rmdir, unlink, rename from */ AFS_ACE_INSERT))) /* create, mkdir, symlink, rename to */ goto permission_denied; } } else { if (!(access & AFS_ACE_LOOKUP)) goto permission_denied; if ((mask & MAY_EXEC) && !(inode->i_mode & S_IXUSR)) goto permission_denied; if (mask & (MAY_EXEC | MAY_READ)) { if (!(access & AFS_ACE_READ)) goto permission_denied; if (!(inode->i_mode & S_IRUSR)) goto permission_denied; } else if (mask & MAY_WRITE) { if (!(access & AFS_ACE_WRITE)) goto permission_denied; if (!(inode->i_mode & S_IWUSR)) goto permission_denied; } }
key_put(key); _leave(" = %d", ret); return ret;
permission_denied: ret = -EACCES;error: key_put(key); _leave(" = %d", ret); return ret;}
void __exit afs_clean_up_permit_cache(void){ int i;
for (i = 0; i < HASH_SIZE(afs_permits_cache); i++) WARN_ON_ONCE(!hlist_empty(&afs_permits_cache[i]));
}