security.c 10.4 KB
Newer Older
David Howells's avatar
David Howells committed
1 2
/* AFS security handling
 *
David Howells's avatar
David Howells committed
3
 * Copyright (C) 2007, 2017 Red Hat, Inc. All Rights Reserved.
David Howells's avatar
David Howells committed
4 5 6 7 8 9 10 11 12 13 14 15
 * Written by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */

#include <linux/init.h>
#include <linux/slab.h>
#include <linux/fs.h>
#include <linux/ctype.h>
Alexey Dobriyan's avatar
Alexey Dobriyan committed
16
#include <linux/sched.h>
David Howells's avatar
David Howells committed
17
#include <linux/hashtable.h>
David Howells's avatar
David Howells committed
18 19 20
#include <keys/rxrpc-type.h>
#include "internal.h"

David Howells's avatar
David Howells committed
21 22 23
static DEFINE_HASHTABLE(afs_permits_cache, 10);
static DEFINE_SPINLOCK(afs_permits_lock);

David Howells's avatar
David Howells committed
24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
/*
 * get a key
 */
struct key *afs_request_key(struct afs_cell *cell)
{
	struct key *key;

	_enter("{%x}", key_serial(cell->anonymous_key));

	_debug("key %s", cell->anonymous_key->description);
	key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
			  NULL);
	if (IS_ERR(key)) {
		if (PTR_ERR(key) != -ENOKEY) {
			_leave(" = %ld", PTR_ERR(key));
			return key;
		}

		/* 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;
	}
}

/*
David Howells's avatar
David Howells committed
53
 * Dispose of a list of permits.
David Howells's avatar
David Howells committed
54
 */
David Howells's avatar
David Howells committed
55
static void afs_permits_rcu(struct rcu_head *rcu)
David Howells's avatar
David Howells committed
56 57 58
{
	struct afs_permits *permits =
		container_of(rcu, struct afs_permits, rcu);
David Howells's avatar
David Howells committed
59
	int i;
David Howells's avatar
David Howells committed
60

David Howells's avatar
David Howells committed
61 62
	for (i = 0; i < permits->nr_permits; i++)
		key_put(permits->permits[i].key);
David Howells's avatar
David Howells committed
63 64 65 66
	kfree(permits);
}

/*
David Howells's avatar
David Howells committed
67
 * Discard a permission cache.
David Howells's avatar
David Howells committed
68
 */
David Howells's avatar
David Howells committed
69
void afs_put_permits(struct afs_permits *permits)
David Howells's avatar
David Howells committed
70
{
David Howells's avatar
David Howells committed
71 72 73 74 75 76
	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);
	}
David Howells's avatar
David Howells committed
77 78 79
}

/*
David Howells's avatar
David Howells committed
80
 * Clear a permit cache on callback break.
David Howells's avatar
David Howells committed
81
 */
David Howells's avatar
David Howells committed
82
void afs_clear_permits(struct afs_vnode *vnode)
David Howells's avatar
David Howells committed
83
{
David Howells's avatar
David Howells committed
84
	struct afs_permits *permits;
David Howells's avatar
David Howells committed
85

David Howells's avatar
David Howells committed
86 87 88 89 90 91
	spin_lock(&vnode->lock);
	permits = rcu_dereference_protected(vnode->permit_cache,
					    lockdep_is_held(&vnode->lock));
	RCU_INIT_POINTER(vnode->permit_cache, NULL);
	vnode->cb_break++;
	spin_unlock(&vnode->lock);
David Howells's avatar
David Howells committed
92

David Howells's avatar
David Howells committed
93 94
	if (permits)
		afs_put_permits(permits);
David Howells's avatar
David Howells committed
95 96 97
}

/*
David Howells's avatar
David Howells committed
98 99
 * 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.
David Howells's avatar
David Howells committed
100
 */
David Howells's avatar
David Howells committed
101
static void afs_hash_permits(struct afs_permits *permits)
David Howells's avatar
David Howells committed
102
{
David Howells's avatar
David Howells committed
103 104
	unsigned long h = permits->nr_permits;
	int i;
David Howells's avatar
David Howells committed
105

David Howells's avatar
David Howells committed
106 107 108 109
	for (i = 0; i < permits->nr_permits; i++) {
		h += (unsigned long)permits->permits[i].key / sizeof(void *);
		h += permits->permits[i].access;
	}
David Howells's avatar
David Howells committed
110

David Howells's avatar
David Howells committed
111
	permits->h = h;
David Howells's avatar
David Howells committed
112 113 114
}

/*
David Howells's avatar
David Howells committed
115 116 117 118
 * 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.
David Howells's avatar
David Howells committed
119
 */
David Howells's avatar
David Howells committed
120 121
void afs_cache_permit(struct afs_vnode *vnode, struct key *key,
		      unsigned int cb_break)
David Howells's avatar
David Howells committed
122
{
David Howells's avatar
David Howells committed
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
	struct afs_permits *permits, *xpermits, *replacement, *new = NULL;
	afs_access_t caller_access = READ_ONCE(vnode->status.caller_access);
	size_t size = 0;
	bool changed = false;
	int i, j;

	_enter("{%x:%u},%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;
				}
David Howells's avatar
David Howells committed
149

David Howells's avatar
David Howells committed
150 151 152 153 154
				if (cb_break != (vnode->cb_break +
						 vnode->cb_interest->server->cb_s_break)) {
					changed = true;
					break;
				}
David Howells's avatar
David Howells committed
155

David Howells's avatar
David Howells committed
156 157 158 159 160
				/* The cache is still good. */
				rcu_read_unlock();
				return;
			}
		}
David Howells's avatar
David Howells committed
161

David Howells's avatar
David Howells committed
162 163
		changed |= permits->invalidated;
		size = permits->nr_permits;
David Howells's avatar
David Howells committed
164

David Howells's avatar
David Howells committed
165 166 167 168 169 170 171 172 173 174 175 176 177 178
		/* 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;
		}
David Howells's avatar
David Howells committed
179 180
	}

David Howells's avatar
David Howells committed
181 182 183
	if (cb_break != (vnode->cb_break + vnode->cb_interest->server->cb_s_break)) {
		rcu_read_unlock();
		goto someone_else_changed_it;
David Howells's avatar
David Howells committed
184 185
	}

David Howells's avatar
David Howells committed
186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
	/* 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)) {
		rcu_read_unlock();
		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(sizeof(struct afs_permits) +
		      sizeof(struct afs_permit) * size, GFP_NOFS);
	if (!new)
		return;

	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++;
David Howells's avatar
David Howells committed
218
			}
David Howells's avatar
David Howells committed
219 220 221
			new->permits[j].key = permits->permits[i].key;
			new->permits[j].access = permits->permits[i].access;
			j++;
David Howells's avatar
David Howells committed
222 223 224
		}
	}

David Howells's avatar
David Howells committed
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
	if (j == i) {
		new->permits[j].key = key;
		new->permits[j].access = caller_access;
	}

	afs_hash_permits(new);

	afs_put_permits(permits);

	/* 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);

	spin_lock(&vnode->lock);
	if (cb_break != (vnode->cb_break + vnode->cb_interest->server->cb_s_break) ||
	    permits != rcu_access_pointer(vnode->permit_cache))
		goto someone_else_changed_it_unlock;
	rcu_assign_pointer(vnode->permit_cache, replacement);
	spin_unlock(&vnode->lock);
	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.
	 */
	return;
David Howells's avatar
David Howells committed
280 281 282 283 284 285 286
}

/*
 * 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
 */
David Howells's avatar
David Howells committed
287 288
int afs_check_permit(struct afs_vnode *vnode, struct key *key,
		     afs_access_t *_access)
David Howells's avatar
David Howells committed
289 290
{
	struct afs_permits *permits;
David Howells's avatar
David Howells committed
291 292
	bool valid = false;
	int i, ret;
David Howells's avatar
David Howells committed
293

David Howells's avatar
David Howells committed
294 295
	_enter("{%x:%u},%x",
	       vnode->fid.vid, vnode->fid.vnode, key_serial(key));
David Howells's avatar
David Howells committed
296

David Howells's avatar
David Howells committed
297
	permits = vnode->permit_cache;
David Howells's avatar
David Howells committed
298 299

	/* check the permits to see if we've got one yet */
David Howells's avatar
David Howells committed
300
	if (key == vnode->volume->cell->anonymous_key) {
David Howells's avatar
David Howells committed
301
		_debug("anon");
David Howells's avatar
David Howells committed
302
		*_access = vnode->status.anon_access;
David Howells's avatar
David Howells committed
303 304 305
		valid = true;
	} else {
		rcu_read_lock();
David Howells's avatar
David Howells committed
306
		permits = rcu_dereference(vnode->permit_cache);
David Howells's avatar
David Howells committed
307
		if (permits) {
David Howells's avatar
David Howells committed
308 309 310 311
			for (i = 0; i < permits->nr_permits; i++) {
				if (permits->permits[i].key < key)
					continue;
				if (permits->permits[i].key > key)
David Howells's avatar
David Howells committed
312
					break;
David Howells's avatar
David Howells committed
313 314 315 316

				*_access = permits->permits[i].access;
				valid = !permits->invalidated;
				break;
David Howells's avatar
David Howells committed
317 318 319 320 321 322
			}
		}
		rcu_read_unlock();
	}

	if (!valid) {
David Howells's avatar
David Howells committed
323 324 325
		/* Check the status on the file we're actually interested in
		 * (the post-processing will cache the result).
		 */
David Howells's avatar
David Howells committed
326 327
		_debug("no valid permit");

328
		ret = afs_fetch_status(vnode, key);
David Howells's avatar
David Howells committed
329 330 331 332 333
		if (ret < 0) {
			*_access = 0;
			_leave(" = %d", ret);
			return ret;
		}
David Howells's avatar
David Howells committed
334
		*_access = vnode->status.caller_access;
David Howells's avatar
David Howells committed
335 336 337 338 339 340 341 342 343 344 345
	}

	_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
 */
346
int afs_permission(struct inode *inode, int mask)
David Howells's avatar
David Howells committed
347 348
{
	struct afs_vnode *vnode = AFS_FS_I(inode);
349
	afs_access_t uninitialized_var(access);
David Howells's avatar
David Howells committed
350 351 352
	struct key *key;
	int ret;

353
	if (mask & MAY_NOT_BLOCK)
354 355
		return -ECHILD;

David Howells's avatar
David Howells committed
356
	_enter("{{%x:%u},%lx},%x,",
357
	       vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);
David Howells's avatar
David Howells committed
358 359 360 361 362 363 364

	key = afs_request_key(vnode->volume->cell);
	if (IS_ERR(key)) {
		_leave(" = %ld [key]", PTR_ERR(key));
		return PTR_ERR(key);
	}

365 366 367
	ret = afs_validate(vnode, key);
	if (ret < 0)
		goto error;
368

David Howells's avatar
David Howells committed
369 370
	/* check the permits to see if we've got one yet */
	ret = afs_check_permit(vnode, key, &access);
371 372
	if (ret < 0)
		goto error;
David Howells's avatar
David Howells committed
373 374 375 376 377 378 379 380 381 382

	/* interpret the access mask */
	_debug("REQ %x ACC %x on %s",
	       mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");

	if (S_ISDIR(inode->i_mode)) {
		if (mask & MAY_EXEC) {
			if (!(access & AFS_ACE_LOOKUP))
				goto permission_denied;
		} else if (mask & MAY_READ) {
383
			if (!(access & AFS_ACE_LOOKUP))
David Howells's avatar
David Howells committed
384 385 386
				goto permission_denied;
		} else if (mask & MAY_WRITE) {
			if (!(access & (AFS_ACE_DELETE | /* rmdir, unlink, rename from */
387
					AFS_ACE_INSERT))) /* create, mkdir, symlink, rename to */
David Howells's avatar
David Howells committed
388 389 390 391 392 393 394
				goto permission_denied;
		} else {
			BUG();
		}
	} else {
		if (!(access & AFS_ACE_LOOKUP))
			goto permission_denied;
Marc Dionne's avatar
Marc Dionne committed
395 396
		if ((mask & MAY_EXEC) && !(inode->i_mode & S_IXUSR))
			goto permission_denied;
David Howells's avatar
David Howells committed
397 398 399
		if (mask & (MAY_EXEC | MAY_READ)) {
			if (!(access & AFS_ACE_READ))
				goto permission_denied;
Marc Dionne's avatar
Marc Dionne committed
400 401
			if (!(inode->i_mode & S_IRUSR))
				goto permission_denied;
David Howells's avatar
David Howells committed
402 403 404
		} else if (mask & MAY_WRITE) {
			if (!(access & AFS_ACE_WRITE))
				goto permission_denied;
Marc Dionne's avatar
Marc Dionne committed
405 406
			if (!(inode->i_mode & S_IWUSR))
				goto permission_denied;
David Howells's avatar
David Howells committed
407 408 409 410
		}
	}

	key_put(key);
411 412
	_leave(" = %d", ret);
	return ret;
David Howells's avatar
David Howells committed
413 414

permission_denied:
415 416
	ret = -EACCES;
error:
David Howells's avatar
David Howells committed
417
	key_put(key);
418 419
	_leave(" = %d", ret);
	return ret;
David Howells's avatar
David Howells committed
420
}
David Howells's avatar
David Howells committed
421 422 423 424 425 426 427 428 429

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]));

}