488 lines · c
1// SPDX-License-Identifier: GPL-2.0-or-later2/* AFS security handling3 *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 key22 */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_net(&key_type_rxrpc, cell->anonymous_key->description,31 cell->net->net, 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 * Get a key when pathwalk is in rcuwalk mode.50 */51struct key *afs_request_key_rcu(struct afs_cell *cell)52{53 struct key *key;54 55 _enter("{%x}", key_serial(cell->anonymous_key));56 57 _debug("key %s", cell->anonymous_key->description);58 key = request_key_net_rcu(&key_type_rxrpc,59 cell->anonymous_key->description,60 cell->net->net);61 if (IS_ERR(key)) {62 if (PTR_ERR(key) != -ENOKEY) {63 _leave(" = %ld", PTR_ERR(key));64 return key;65 }66 67 /* act as anonymous user */68 _leave(" = {%x} [anon]", key_serial(cell->anonymous_key));69 return key_get(cell->anonymous_key);70 } else {71 /* act as authorised user */72 _leave(" = {%x} [auth]", key_serial(key));73 return key;74 }75}76 77/*78 * Dispose of a list of permits.79 */80static void afs_permits_rcu(struct rcu_head *rcu)81{82 struct afs_permits *permits =83 container_of(rcu, struct afs_permits, rcu);84 int i;85 86 for (i = 0; i < permits->nr_permits; i++)87 key_put(permits->permits[i].key);88 kfree(permits);89}90 91/*92 * Discard a permission cache.93 */94void afs_put_permits(struct afs_permits *permits)95{96 if (permits && refcount_dec_and_test(&permits->usage)) {97 spin_lock(&afs_permits_lock);98 hash_del_rcu(&permits->hash_node);99 spin_unlock(&afs_permits_lock);100 call_rcu(&permits->rcu, afs_permits_rcu);101 }102}103 104/*105 * Clear a permit cache on callback break.106 */107void afs_clear_permits(struct afs_vnode *vnode)108{109 struct afs_permits *permits;110 111 spin_lock(&vnode->lock);112 permits = rcu_dereference_protected(vnode->permit_cache,113 lockdep_is_held(&vnode->lock));114 RCU_INIT_POINTER(vnode->permit_cache, NULL);115 spin_unlock(&vnode->lock);116 117 afs_put_permits(permits);118}119 120/*121 * Hash a list of permits. Use simple addition to make it easy to add an extra122 * one at an as-yet indeterminate position in the list.123 */124static void afs_hash_permits(struct afs_permits *permits)125{126 unsigned long h = permits->nr_permits;127 int i;128 129 for (i = 0; i < permits->nr_permits; i++) {130 h += (unsigned long)permits->permits[i].key / sizeof(void *);131 h += permits->permits[i].access;132 }133 134 permits->h = h;135}136 137/*138 * Cache the CallerAccess result obtained from doing a fileserver operation139 * that returned a vnode status for a particular key. If a callback break140 * occurs whilst the operation was in progress then we have to ditch the cache141 * as the ACL *may* have changed.142 */143void afs_cache_permit(struct afs_vnode *vnode, struct key *key,144 unsigned int cb_break, struct afs_status_cb *scb)145{146 struct afs_permits *permits, *xpermits, *replacement, *zap, *new = NULL;147 afs_access_t caller_access = scb->status.caller_access;148 size_t size = 0;149 bool changed = false;150 int i, j;151 152 _enter("{%llx:%llu},%x,%x",153 vnode->fid.vid, vnode->fid.vnode, key_serial(key), caller_access);154 155 rcu_read_lock();156 157 /* Check for the common case first: We got back the same access as last158 * time we tried and already have it recorded.159 */160 permits = rcu_dereference(vnode->permit_cache);161 if (permits) {162 if (!permits->invalidated) {163 for (i = 0; i < permits->nr_permits; i++) {164 if (permits->permits[i].key < key)165 continue;166 if (permits->permits[i].key > key)167 break;168 if (permits->permits[i].access != caller_access) {169 changed = true;170 break;171 }172 173 if (afs_cb_is_broken(cb_break, vnode)) {174 changed = true;175 break;176 }177 178 /* The cache is still good. */179 rcu_read_unlock();180 return;181 }182 }183 184 changed |= permits->invalidated;185 size = permits->nr_permits;186 187 /* If this set of permits is now wrong, clear the permits188 * pointer so that no one tries to use the stale information.189 */190 if (changed) {191 spin_lock(&vnode->lock);192 if (permits != rcu_access_pointer(vnode->permit_cache))193 goto someone_else_changed_it_unlock;194 RCU_INIT_POINTER(vnode->permit_cache, NULL);195 spin_unlock(&vnode->lock);196 197 afs_put_permits(permits);198 permits = NULL;199 size = 0;200 }201 }202 203 if (afs_cb_is_broken(cb_break, vnode))204 goto someone_else_changed_it;205 206 /* We need a ref on any permits list we want to copy as we'll have to207 * drop the lock to do memory allocation.208 */209 if (permits && !refcount_inc_not_zero(&permits->usage))210 goto someone_else_changed_it;211 212 rcu_read_unlock();213 214 /* Speculatively create a new list with the revised permission set. We215 * discard this if we find an extant match already in the hash, but216 * it's easier to compare with memcmp this way.217 *218 * We fill in the key pointers at this time, but we don't get the refs219 * yet.220 */221 size++;222 new = kzalloc(struct_size(new, permits, size), GFP_NOFS);223 if (!new)224 goto out_put;225 226 refcount_set(&new->usage, 1);227 new->nr_permits = size;228 i = j = 0;229 if (permits) {230 for (i = 0; i < permits->nr_permits; i++) {231 if (j == i && permits->permits[i].key > key) {232 new->permits[j].key = key;233 new->permits[j].access = caller_access;234 j++;235 }236 new->permits[j].key = permits->permits[i].key;237 new->permits[j].access = permits->permits[i].access;238 j++;239 }240 }241 242 if (j == i) {243 new->permits[j].key = key;244 new->permits[j].access = caller_access;245 }246 247 afs_hash_permits(new);248 249 /* Now see if the permit list we want is actually already available */250 spin_lock(&afs_permits_lock);251 252 hash_for_each_possible(afs_permits_cache, xpermits, hash_node, new->h) {253 if (xpermits->h != new->h ||254 xpermits->invalidated ||255 xpermits->nr_permits != new->nr_permits ||256 memcmp(xpermits->permits, new->permits,257 new->nr_permits * sizeof(struct afs_permit)) != 0)258 continue;259 260 if (refcount_inc_not_zero(&xpermits->usage)) {261 replacement = xpermits;262 goto found;263 }264 265 break;266 }267 268 for (i = 0; i < new->nr_permits; i++)269 key_get(new->permits[i].key);270 hash_add_rcu(afs_permits_cache, &new->hash_node, new->h);271 replacement = new;272 new = NULL;273 274found:275 spin_unlock(&afs_permits_lock);276 277 kfree(new);278 279 rcu_read_lock();280 spin_lock(&vnode->lock);281 zap = rcu_access_pointer(vnode->permit_cache);282 if (!afs_cb_is_broken(cb_break, vnode) && zap == permits)283 rcu_assign_pointer(vnode->permit_cache, replacement);284 else285 zap = replacement;286 spin_unlock(&vnode->lock);287 rcu_read_unlock();288 afs_put_permits(zap);289out_put:290 afs_put_permits(permits);291 return;292 293someone_else_changed_it_unlock:294 spin_unlock(&vnode->lock);295someone_else_changed_it:296 /* Someone else changed the cache under us - don't recheck at this297 * time.298 */299 rcu_read_unlock();300 return;301}302 303static bool afs_check_permit_rcu(struct afs_vnode *vnode, struct key *key,304 afs_access_t *_access)305{306 const struct afs_permits *permits;307 int i;308 309 _enter("{%llx:%llu},%x",310 vnode->fid.vid, vnode->fid.vnode, key_serial(key));311 312 /* check the permits to see if we've got one yet */313 if (key == vnode->volume->cell->anonymous_key) {314 *_access = vnode->status.anon_access;315 _leave(" = t [anon %x]", *_access);316 return true;317 }318 319 permits = rcu_dereference(vnode->permit_cache);320 if (permits) {321 for (i = 0; i < permits->nr_permits; i++) {322 if (permits->permits[i].key < key)323 continue;324 if (permits->permits[i].key > key)325 break;326 327 *_access = permits->permits[i].access;328 _leave(" = %u [perm %x]", !permits->invalidated, *_access);329 return !permits->invalidated;330 }331 }332 333 _leave(" = f");334 return false;335}336 337/*338 * check with the fileserver to see if the directory or parent directory is339 * permitted to be accessed with this authorisation, and if so, what access it340 * is granted341 */342int afs_check_permit(struct afs_vnode *vnode, struct key *key,343 afs_access_t *_access)344{345 struct afs_permits *permits;346 bool valid = false;347 int i, ret;348 349 _enter("{%llx:%llu},%x",350 vnode->fid.vid, vnode->fid.vnode, key_serial(key));351 352 /* check the permits to see if we've got one yet */353 if (key == vnode->volume->cell->anonymous_key) {354 _debug("anon");355 *_access = vnode->status.anon_access;356 valid = true;357 } else {358 rcu_read_lock();359 permits = rcu_dereference(vnode->permit_cache);360 if (permits) {361 for (i = 0; i < permits->nr_permits; i++) {362 if (permits->permits[i].key < key)363 continue;364 if (permits->permits[i].key > key)365 break;366 367 *_access = permits->permits[i].access;368 valid = !permits->invalidated;369 break;370 }371 }372 rcu_read_unlock();373 }374 375 if (!valid) {376 /* Check the status on the file we're actually interested in377 * (the post-processing will cache the result).378 */379 _debug("no valid permit");380 381 ret = afs_fetch_status(vnode, key, false, _access);382 if (ret < 0) {383 *_access = 0;384 _leave(" = %d", ret);385 return ret;386 }387 }388 389 _leave(" = 0 [access %x]", *_access);390 return 0;391}392 393/*394 * check the permissions on an AFS file395 * - AFS ACLs are attached to directories only, and a file is controlled by its396 * parent directory's ACL397 */398int afs_permission(struct mnt_idmap *idmap, struct inode *inode,399 int mask)400{401 struct afs_vnode *vnode = AFS_FS_I(inode);402 afs_access_t access;403 struct key *key;404 int ret = 0;405 406 _enter("{{%llx:%llu},%lx},%x,",407 vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);408 409 if (mask & MAY_NOT_BLOCK) {410 key = afs_request_key_rcu(vnode->volume->cell);411 if (IS_ERR(key))412 return -ECHILD;413 414 ret = -ECHILD;415 if (!afs_check_validity(vnode) ||416 !afs_check_permit_rcu(vnode, key, &access))417 goto error;418 } else {419 key = afs_request_key(vnode->volume->cell);420 if (IS_ERR(key)) {421 _leave(" = %ld [key]", PTR_ERR(key));422 return PTR_ERR(key);423 }424 425 ret = afs_validate(vnode, key);426 if (ret < 0)427 goto error;428 429 /* check the permits to see if we've got one yet */430 ret = afs_check_permit(vnode, key, &access);431 if (ret < 0)432 goto error;433 }434 435 /* interpret the access mask */436 _debug("REQ %x ACC %x on %s",437 mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");438 439 ret = 0;440 if (S_ISDIR(inode->i_mode)) {441 if (mask & (MAY_EXEC | MAY_READ | MAY_CHDIR)) {442 if (!(access & AFS_ACE_LOOKUP))443 goto permission_denied;444 }445 if (mask & MAY_WRITE) {446 if (!(access & (AFS_ACE_DELETE | /* rmdir, unlink, rename from */447 AFS_ACE_INSERT))) /* create, mkdir, symlink, rename to */448 goto permission_denied;449 }450 } else {451 if (!(access & AFS_ACE_LOOKUP))452 goto permission_denied;453 if ((mask & MAY_EXEC) && !(inode->i_mode & S_IXUSR))454 goto permission_denied;455 if (mask & (MAY_EXEC | MAY_READ)) {456 if (!(access & AFS_ACE_READ))457 goto permission_denied;458 if (!(inode->i_mode & S_IRUSR))459 goto permission_denied;460 } else if (mask & MAY_WRITE) {461 if (!(access & AFS_ACE_WRITE))462 goto permission_denied;463 if (!(inode->i_mode & S_IWUSR))464 goto permission_denied;465 }466 }467 468 key_put(key);469 _leave(" = %d", ret);470 return ret;471 472permission_denied:473 ret = -EACCES;474error:475 key_put(key);476 _leave(" = %d", ret);477 return ret;478}479 480void __exit afs_clean_up_permit_cache(void)481{482 int i;483 484 for (i = 0; i < HASH_SIZE(afs_permits_cache); i++)485 WARN_ON_ONCE(!hlist_empty(&afs_permits_cache[i]));486 487}488