cachepc-linux

Fork of AMDESE/linux with modifications for CachePC side-channel attack
git clone https://git.sinitax.com/sinitax/cachepc-linux
Log | Files | Refs | README | LICENSE | sfeed.txt

security.c (11921B)


      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_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 extra
    122 * 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 operation
    139 * that returned a vnode status for a particular key.  If a callback break
    140 * occurs whilst the operation was in progress then we have to ditch the cache
    141 * 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 last
    158	 * 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 permits
    188		 * 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 to
    207	 * 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.  We
    215	 * discard this if we find an extant match already in the hash, but
    216	 * 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 refs
    219	 * 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	else
    285		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 this
    297	 * 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 is
    339 * permitted to be accessed with this authorisation, and if so, what access it
    340 * is granted
    341 */
    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 in
    377		 * (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 file
    395 * - AFS ACLs are attached to directories only, and a file is controlled by its
    396 *   parent directory's ACL
    397 */
    398int afs_permission(struct user_namespace *mnt_userns, 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}