162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/* AFS security handling
362306a36Sopenharmony_ci *
462306a36Sopenharmony_ci * Copyright (C) 2007, 2017 Red Hat, Inc. All Rights Reserved.
562306a36Sopenharmony_ci * Written by David Howells (dhowells@redhat.com)
662306a36Sopenharmony_ci */
762306a36Sopenharmony_ci
862306a36Sopenharmony_ci#include <linux/init.h>
962306a36Sopenharmony_ci#include <linux/slab.h>
1062306a36Sopenharmony_ci#include <linux/fs.h>
1162306a36Sopenharmony_ci#include <linux/ctype.h>
1262306a36Sopenharmony_ci#include <linux/sched.h>
1362306a36Sopenharmony_ci#include <linux/hashtable.h>
1462306a36Sopenharmony_ci#include <keys/rxrpc-type.h>
1562306a36Sopenharmony_ci#include "internal.h"
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistatic DEFINE_HASHTABLE(afs_permits_cache, 10);
1862306a36Sopenharmony_cistatic DEFINE_SPINLOCK(afs_permits_lock);
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_ci/*
2162306a36Sopenharmony_ci * get a key
2262306a36Sopenharmony_ci */
2362306a36Sopenharmony_cistruct key *afs_request_key(struct afs_cell *cell)
2462306a36Sopenharmony_ci{
2562306a36Sopenharmony_ci	struct key *key;
2662306a36Sopenharmony_ci
2762306a36Sopenharmony_ci	_enter("{%x}", key_serial(cell->anonymous_key));
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci	_debug("key %s", cell->anonymous_key->description);
3062306a36Sopenharmony_ci	key = request_key_net(&key_type_rxrpc, cell->anonymous_key->description,
3162306a36Sopenharmony_ci			      cell->net->net, NULL);
3262306a36Sopenharmony_ci	if (IS_ERR(key)) {
3362306a36Sopenharmony_ci		if (PTR_ERR(key) != -ENOKEY) {
3462306a36Sopenharmony_ci			_leave(" = %ld", PTR_ERR(key));
3562306a36Sopenharmony_ci			return key;
3662306a36Sopenharmony_ci		}
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci		/* act as anonymous user */
3962306a36Sopenharmony_ci		_leave(" = {%x} [anon]", key_serial(cell->anonymous_key));
4062306a36Sopenharmony_ci		return key_get(cell->anonymous_key);
4162306a36Sopenharmony_ci	} else {
4262306a36Sopenharmony_ci		/* act as authorised user */
4362306a36Sopenharmony_ci		_leave(" = {%x} [auth]", key_serial(key));
4462306a36Sopenharmony_ci		return key;
4562306a36Sopenharmony_ci	}
4662306a36Sopenharmony_ci}
4762306a36Sopenharmony_ci
4862306a36Sopenharmony_ci/*
4962306a36Sopenharmony_ci * Get a key when pathwalk is in rcuwalk mode.
5062306a36Sopenharmony_ci */
5162306a36Sopenharmony_cistruct key *afs_request_key_rcu(struct afs_cell *cell)
5262306a36Sopenharmony_ci{
5362306a36Sopenharmony_ci	struct key *key;
5462306a36Sopenharmony_ci
5562306a36Sopenharmony_ci	_enter("{%x}", key_serial(cell->anonymous_key));
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci	_debug("key %s", cell->anonymous_key->description);
5862306a36Sopenharmony_ci	key = request_key_net_rcu(&key_type_rxrpc,
5962306a36Sopenharmony_ci				  cell->anonymous_key->description,
6062306a36Sopenharmony_ci				  cell->net->net);
6162306a36Sopenharmony_ci	if (IS_ERR(key)) {
6262306a36Sopenharmony_ci		if (PTR_ERR(key) != -ENOKEY) {
6362306a36Sopenharmony_ci			_leave(" = %ld", PTR_ERR(key));
6462306a36Sopenharmony_ci			return key;
6562306a36Sopenharmony_ci		}
6662306a36Sopenharmony_ci
6762306a36Sopenharmony_ci		/* act as anonymous user */
6862306a36Sopenharmony_ci		_leave(" = {%x} [anon]", key_serial(cell->anonymous_key));
6962306a36Sopenharmony_ci		return key_get(cell->anonymous_key);
7062306a36Sopenharmony_ci	} else {
7162306a36Sopenharmony_ci		/* act as authorised user */
7262306a36Sopenharmony_ci		_leave(" = {%x} [auth]", key_serial(key));
7362306a36Sopenharmony_ci		return key;
7462306a36Sopenharmony_ci	}
7562306a36Sopenharmony_ci}
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci/*
7862306a36Sopenharmony_ci * Dispose of a list of permits.
7962306a36Sopenharmony_ci */
8062306a36Sopenharmony_cistatic void afs_permits_rcu(struct rcu_head *rcu)
8162306a36Sopenharmony_ci{
8262306a36Sopenharmony_ci	struct afs_permits *permits =
8362306a36Sopenharmony_ci		container_of(rcu, struct afs_permits, rcu);
8462306a36Sopenharmony_ci	int i;
8562306a36Sopenharmony_ci
8662306a36Sopenharmony_ci	for (i = 0; i < permits->nr_permits; i++)
8762306a36Sopenharmony_ci		key_put(permits->permits[i].key);
8862306a36Sopenharmony_ci	kfree(permits);
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_ci/*
9262306a36Sopenharmony_ci * Discard a permission cache.
9362306a36Sopenharmony_ci */
9462306a36Sopenharmony_civoid afs_put_permits(struct afs_permits *permits)
9562306a36Sopenharmony_ci{
9662306a36Sopenharmony_ci	if (permits && refcount_dec_and_test(&permits->usage)) {
9762306a36Sopenharmony_ci		spin_lock(&afs_permits_lock);
9862306a36Sopenharmony_ci		hash_del_rcu(&permits->hash_node);
9962306a36Sopenharmony_ci		spin_unlock(&afs_permits_lock);
10062306a36Sopenharmony_ci		call_rcu(&permits->rcu, afs_permits_rcu);
10162306a36Sopenharmony_ci	}
10262306a36Sopenharmony_ci}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci/*
10562306a36Sopenharmony_ci * Clear a permit cache on callback break.
10662306a36Sopenharmony_ci */
10762306a36Sopenharmony_civoid afs_clear_permits(struct afs_vnode *vnode)
10862306a36Sopenharmony_ci{
10962306a36Sopenharmony_ci	struct afs_permits *permits;
11062306a36Sopenharmony_ci
11162306a36Sopenharmony_ci	spin_lock(&vnode->lock);
11262306a36Sopenharmony_ci	permits = rcu_dereference_protected(vnode->permit_cache,
11362306a36Sopenharmony_ci					    lockdep_is_held(&vnode->lock));
11462306a36Sopenharmony_ci	RCU_INIT_POINTER(vnode->permit_cache, NULL);
11562306a36Sopenharmony_ci	spin_unlock(&vnode->lock);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	afs_put_permits(permits);
11862306a36Sopenharmony_ci}
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci/*
12162306a36Sopenharmony_ci * Hash a list of permits.  Use simple addition to make it easy to add an extra
12262306a36Sopenharmony_ci * one at an as-yet indeterminate position in the list.
12362306a36Sopenharmony_ci */
12462306a36Sopenharmony_cistatic void afs_hash_permits(struct afs_permits *permits)
12562306a36Sopenharmony_ci{
12662306a36Sopenharmony_ci	unsigned long h = permits->nr_permits;
12762306a36Sopenharmony_ci	int i;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	for (i = 0; i < permits->nr_permits; i++) {
13062306a36Sopenharmony_ci		h += (unsigned long)permits->permits[i].key / sizeof(void *);
13162306a36Sopenharmony_ci		h += permits->permits[i].access;
13262306a36Sopenharmony_ci	}
13362306a36Sopenharmony_ci
13462306a36Sopenharmony_ci	permits->h = h;
13562306a36Sopenharmony_ci}
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci/*
13862306a36Sopenharmony_ci * Cache the CallerAccess result obtained from doing a fileserver operation
13962306a36Sopenharmony_ci * that returned a vnode status for a particular key.  If a callback break
14062306a36Sopenharmony_ci * occurs whilst the operation was in progress then we have to ditch the cache
14162306a36Sopenharmony_ci * as the ACL *may* have changed.
14262306a36Sopenharmony_ci */
14362306a36Sopenharmony_civoid afs_cache_permit(struct afs_vnode *vnode, struct key *key,
14462306a36Sopenharmony_ci		      unsigned int cb_break, struct afs_status_cb *scb)
14562306a36Sopenharmony_ci{
14662306a36Sopenharmony_ci	struct afs_permits *permits, *xpermits, *replacement, *zap, *new = NULL;
14762306a36Sopenharmony_ci	afs_access_t caller_access = scb->status.caller_access;
14862306a36Sopenharmony_ci	size_t size = 0;
14962306a36Sopenharmony_ci	bool changed = false;
15062306a36Sopenharmony_ci	int i, j;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	_enter("{%llx:%llu},%x,%x",
15362306a36Sopenharmony_ci	       vnode->fid.vid, vnode->fid.vnode, key_serial(key), caller_access);
15462306a36Sopenharmony_ci
15562306a36Sopenharmony_ci	rcu_read_lock();
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	/* Check for the common case first: We got back the same access as last
15862306a36Sopenharmony_ci	 * time we tried and already have it recorded.
15962306a36Sopenharmony_ci	 */
16062306a36Sopenharmony_ci	permits = rcu_dereference(vnode->permit_cache);
16162306a36Sopenharmony_ci	if (permits) {
16262306a36Sopenharmony_ci		if (!permits->invalidated) {
16362306a36Sopenharmony_ci			for (i = 0; i < permits->nr_permits; i++) {
16462306a36Sopenharmony_ci				if (permits->permits[i].key < key)
16562306a36Sopenharmony_ci					continue;
16662306a36Sopenharmony_ci				if (permits->permits[i].key > key)
16762306a36Sopenharmony_ci					break;
16862306a36Sopenharmony_ci				if (permits->permits[i].access != caller_access) {
16962306a36Sopenharmony_ci					changed = true;
17062306a36Sopenharmony_ci					break;
17162306a36Sopenharmony_ci				}
17262306a36Sopenharmony_ci
17362306a36Sopenharmony_ci				if (afs_cb_is_broken(cb_break, vnode)) {
17462306a36Sopenharmony_ci					changed = true;
17562306a36Sopenharmony_ci					break;
17662306a36Sopenharmony_ci				}
17762306a36Sopenharmony_ci
17862306a36Sopenharmony_ci				/* The cache is still good. */
17962306a36Sopenharmony_ci				rcu_read_unlock();
18062306a36Sopenharmony_ci				return;
18162306a36Sopenharmony_ci			}
18262306a36Sopenharmony_ci		}
18362306a36Sopenharmony_ci
18462306a36Sopenharmony_ci		changed |= permits->invalidated;
18562306a36Sopenharmony_ci		size = permits->nr_permits;
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci		/* If this set of permits is now wrong, clear the permits
18862306a36Sopenharmony_ci		 * pointer so that no one tries to use the stale information.
18962306a36Sopenharmony_ci		 */
19062306a36Sopenharmony_ci		if (changed) {
19162306a36Sopenharmony_ci			spin_lock(&vnode->lock);
19262306a36Sopenharmony_ci			if (permits != rcu_access_pointer(vnode->permit_cache))
19362306a36Sopenharmony_ci				goto someone_else_changed_it_unlock;
19462306a36Sopenharmony_ci			RCU_INIT_POINTER(vnode->permit_cache, NULL);
19562306a36Sopenharmony_ci			spin_unlock(&vnode->lock);
19662306a36Sopenharmony_ci
19762306a36Sopenharmony_ci			afs_put_permits(permits);
19862306a36Sopenharmony_ci			permits = NULL;
19962306a36Sopenharmony_ci			size = 0;
20062306a36Sopenharmony_ci		}
20162306a36Sopenharmony_ci	}
20262306a36Sopenharmony_ci
20362306a36Sopenharmony_ci	if (afs_cb_is_broken(cb_break, vnode))
20462306a36Sopenharmony_ci		goto someone_else_changed_it;
20562306a36Sopenharmony_ci
20662306a36Sopenharmony_ci	/* We need a ref on any permits list we want to copy as we'll have to
20762306a36Sopenharmony_ci	 * drop the lock to do memory allocation.
20862306a36Sopenharmony_ci	 */
20962306a36Sopenharmony_ci	if (permits && !refcount_inc_not_zero(&permits->usage))
21062306a36Sopenharmony_ci		goto someone_else_changed_it;
21162306a36Sopenharmony_ci
21262306a36Sopenharmony_ci	rcu_read_unlock();
21362306a36Sopenharmony_ci
21462306a36Sopenharmony_ci	/* Speculatively create a new list with the revised permission set.  We
21562306a36Sopenharmony_ci	 * discard this if we find an extant match already in the hash, but
21662306a36Sopenharmony_ci	 * it's easier to compare with memcmp this way.
21762306a36Sopenharmony_ci	 *
21862306a36Sopenharmony_ci	 * We fill in the key pointers at this time, but we don't get the refs
21962306a36Sopenharmony_ci	 * yet.
22062306a36Sopenharmony_ci	 */
22162306a36Sopenharmony_ci	size++;
22262306a36Sopenharmony_ci	new = kzalloc(struct_size(new, permits, size), GFP_NOFS);
22362306a36Sopenharmony_ci	if (!new)
22462306a36Sopenharmony_ci		goto out_put;
22562306a36Sopenharmony_ci
22662306a36Sopenharmony_ci	refcount_set(&new->usage, 1);
22762306a36Sopenharmony_ci	new->nr_permits = size;
22862306a36Sopenharmony_ci	i = j = 0;
22962306a36Sopenharmony_ci	if (permits) {
23062306a36Sopenharmony_ci		for (i = 0; i < permits->nr_permits; i++) {
23162306a36Sopenharmony_ci			if (j == i && permits->permits[i].key > key) {
23262306a36Sopenharmony_ci				new->permits[j].key = key;
23362306a36Sopenharmony_ci				new->permits[j].access = caller_access;
23462306a36Sopenharmony_ci				j++;
23562306a36Sopenharmony_ci			}
23662306a36Sopenharmony_ci			new->permits[j].key = permits->permits[i].key;
23762306a36Sopenharmony_ci			new->permits[j].access = permits->permits[i].access;
23862306a36Sopenharmony_ci			j++;
23962306a36Sopenharmony_ci		}
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	if (j == i) {
24362306a36Sopenharmony_ci		new->permits[j].key = key;
24462306a36Sopenharmony_ci		new->permits[j].access = caller_access;
24562306a36Sopenharmony_ci	}
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_ci	afs_hash_permits(new);
24862306a36Sopenharmony_ci
24962306a36Sopenharmony_ci	/* Now see if the permit list we want is actually already available */
25062306a36Sopenharmony_ci	spin_lock(&afs_permits_lock);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	hash_for_each_possible(afs_permits_cache, xpermits, hash_node, new->h) {
25362306a36Sopenharmony_ci		if (xpermits->h != new->h ||
25462306a36Sopenharmony_ci		    xpermits->invalidated ||
25562306a36Sopenharmony_ci		    xpermits->nr_permits != new->nr_permits ||
25662306a36Sopenharmony_ci		    memcmp(xpermits->permits, new->permits,
25762306a36Sopenharmony_ci			   new->nr_permits * sizeof(struct afs_permit)) != 0)
25862306a36Sopenharmony_ci			continue;
25962306a36Sopenharmony_ci
26062306a36Sopenharmony_ci		if (refcount_inc_not_zero(&xpermits->usage)) {
26162306a36Sopenharmony_ci			replacement = xpermits;
26262306a36Sopenharmony_ci			goto found;
26362306a36Sopenharmony_ci		}
26462306a36Sopenharmony_ci
26562306a36Sopenharmony_ci		break;
26662306a36Sopenharmony_ci	}
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	for (i = 0; i < new->nr_permits; i++)
26962306a36Sopenharmony_ci		key_get(new->permits[i].key);
27062306a36Sopenharmony_ci	hash_add_rcu(afs_permits_cache, &new->hash_node, new->h);
27162306a36Sopenharmony_ci	replacement = new;
27262306a36Sopenharmony_ci	new = NULL;
27362306a36Sopenharmony_ci
27462306a36Sopenharmony_cifound:
27562306a36Sopenharmony_ci	spin_unlock(&afs_permits_lock);
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	kfree(new);
27862306a36Sopenharmony_ci
27962306a36Sopenharmony_ci	rcu_read_lock();
28062306a36Sopenharmony_ci	spin_lock(&vnode->lock);
28162306a36Sopenharmony_ci	zap = rcu_access_pointer(vnode->permit_cache);
28262306a36Sopenharmony_ci	if (!afs_cb_is_broken(cb_break, vnode) && zap == permits)
28362306a36Sopenharmony_ci		rcu_assign_pointer(vnode->permit_cache, replacement);
28462306a36Sopenharmony_ci	else
28562306a36Sopenharmony_ci		zap = replacement;
28662306a36Sopenharmony_ci	spin_unlock(&vnode->lock);
28762306a36Sopenharmony_ci	rcu_read_unlock();
28862306a36Sopenharmony_ci	afs_put_permits(zap);
28962306a36Sopenharmony_ciout_put:
29062306a36Sopenharmony_ci	afs_put_permits(permits);
29162306a36Sopenharmony_ci	return;
29262306a36Sopenharmony_ci
29362306a36Sopenharmony_cisomeone_else_changed_it_unlock:
29462306a36Sopenharmony_ci	spin_unlock(&vnode->lock);
29562306a36Sopenharmony_cisomeone_else_changed_it:
29662306a36Sopenharmony_ci	/* Someone else changed the cache under us - don't recheck at this
29762306a36Sopenharmony_ci	 * time.
29862306a36Sopenharmony_ci	 */
29962306a36Sopenharmony_ci	rcu_read_unlock();
30062306a36Sopenharmony_ci	return;
30162306a36Sopenharmony_ci}
30262306a36Sopenharmony_ci
30362306a36Sopenharmony_cistatic bool afs_check_permit_rcu(struct afs_vnode *vnode, struct key *key,
30462306a36Sopenharmony_ci				 afs_access_t *_access)
30562306a36Sopenharmony_ci{
30662306a36Sopenharmony_ci	const struct afs_permits *permits;
30762306a36Sopenharmony_ci	int i;
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	_enter("{%llx:%llu},%x",
31062306a36Sopenharmony_ci	       vnode->fid.vid, vnode->fid.vnode, key_serial(key));
31162306a36Sopenharmony_ci
31262306a36Sopenharmony_ci	/* check the permits to see if we've got one yet */
31362306a36Sopenharmony_ci	if (key == vnode->volume->cell->anonymous_key) {
31462306a36Sopenharmony_ci		*_access = vnode->status.anon_access;
31562306a36Sopenharmony_ci		_leave(" = t [anon %x]", *_access);
31662306a36Sopenharmony_ci		return true;
31762306a36Sopenharmony_ci	}
31862306a36Sopenharmony_ci
31962306a36Sopenharmony_ci	permits = rcu_dereference(vnode->permit_cache);
32062306a36Sopenharmony_ci	if (permits) {
32162306a36Sopenharmony_ci		for (i = 0; i < permits->nr_permits; i++) {
32262306a36Sopenharmony_ci			if (permits->permits[i].key < key)
32362306a36Sopenharmony_ci				continue;
32462306a36Sopenharmony_ci			if (permits->permits[i].key > key)
32562306a36Sopenharmony_ci				break;
32662306a36Sopenharmony_ci
32762306a36Sopenharmony_ci			*_access = permits->permits[i].access;
32862306a36Sopenharmony_ci			_leave(" = %u [perm %x]", !permits->invalidated, *_access);
32962306a36Sopenharmony_ci			return !permits->invalidated;
33062306a36Sopenharmony_ci		}
33162306a36Sopenharmony_ci	}
33262306a36Sopenharmony_ci
33362306a36Sopenharmony_ci	_leave(" = f");
33462306a36Sopenharmony_ci	return false;
33562306a36Sopenharmony_ci}
33662306a36Sopenharmony_ci
33762306a36Sopenharmony_ci/*
33862306a36Sopenharmony_ci * check with the fileserver to see if the directory or parent directory is
33962306a36Sopenharmony_ci * permitted to be accessed with this authorisation, and if so, what access it
34062306a36Sopenharmony_ci * is granted
34162306a36Sopenharmony_ci */
34262306a36Sopenharmony_ciint afs_check_permit(struct afs_vnode *vnode, struct key *key,
34362306a36Sopenharmony_ci		     afs_access_t *_access)
34462306a36Sopenharmony_ci{
34562306a36Sopenharmony_ci	struct afs_permits *permits;
34662306a36Sopenharmony_ci	bool valid = false;
34762306a36Sopenharmony_ci	int i, ret;
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_ci	_enter("{%llx:%llu},%x",
35062306a36Sopenharmony_ci	       vnode->fid.vid, vnode->fid.vnode, key_serial(key));
35162306a36Sopenharmony_ci
35262306a36Sopenharmony_ci	/* check the permits to see if we've got one yet */
35362306a36Sopenharmony_ci	if (key == vnode->volume->cell->anonymous_key) {
35462306a36Sopenharmony_ci		_debug("anon");
35562306a36Sopenharmony_ci		*_access = vnode->status.anon_access;
35662306a36Sopenharmony_ci		valid = true;
35762306a36Sopenharmony_ci	} else {
35862306a36Sopenharmony_ci		rcu_read_lock();
35962306a36Sopenharmony_ci		permits = rcu_dereference(vnode->permit_cache);
36062306a36Sopenharmony_ci		if (permits) {
36162306a36Sopenharmony_ci			for (i = 0; i < permits->nr_permits; i++) {
36262306a36Sopenharmony_ci				if (permits->permits[i].key < key)
36362306a36Sopenharmony_ci					continue;
36462306a36Sopenharmony_ci				if (permits->permits[i].key > key)
36562306a36Sopenharmony_ci					break;
36662306a36Sopenharmony_ci
36762306a36Sopenharmony_ci				*_access = permits->permits[i].access;
36862306a36Sopenharmony_ci				valid = !permits->invalidated;
36962306a36Sopenharmony_ci				break;
37062306a36Sopenharmony_ci			}
37162306a36Sopenharmony_ci		}
37262306a36Sopenharmony_ci		rcu_read_unlock();
37362306a36Sopenharmony_ci	}
37462306a36Sopenharmony_ci
37562306a36Sopenharmony_ci	if (!valid) {
37662306a36Sopenharmony_ci		/* Check the status on the file we're actually interested in
37762306a36Sopenharmony_ci		 * (the post-processing will cache the result).
37862306a36Sopenharmony_ci		 */
37962306a36Sopenharmony_ci		_debug("no valid permit");
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci		ret = afs_fetch_status(vnode, key, false, _access);
38262306a36Sopenharmony_ci		if (ret < 0) {
38362306a36Sopenharmony_ci			*_access = 0;
38462306a36Sopenharmony_ci			_leave(" = %d", ret);
38562306a36Sopenharmony_ci			return ret;
38662306a36Sopenharmony_ci		}
38762306a36Sopenharmony_ci	}
38862306a36Sopenharmony_ci
38962306a36Sopenharmony_ci	_leave(" = 0 [access %x]", *_access);
39062306a36Sopenharmony_ci	return 0;
39162306a36Sopenharmony_ci}
39262306a36Sopenharmony_ci
39362306a36Sopenharmony_ci/*
39462306a36Sopenharmony_ci * check the permissions on an AFS file
39562306a36Sopenharmony_ci * - AFS ACLs are attached to directories only, and a file is controlled by its
39662306a36Sopenharmony_ci *   parent directory's ACL
39762306a36Sopenharmony_ci */
39862306a36Sopenharmony_ciint afs_permission(struct mnt_idmap *idmap, struct inode *inode,
39962306a36Sopenharmony_ci		   int mask)
40062306a36Sopenharmony_ci{
40162306a36Sopenharmony_ci	struct afs_vnode *vnode = AFS_FS_I(inode);
40262306a36Sopenharmony_ci	afs_access_t access;
40362306a36Sopenharmony_ci	struct key *key;
40462306a36Sopenharmony_ci	int ret = 0;
40562306a36Sopenharmony_ci
40662306a36Sopenharmony_ci	_enter("{{%llx:%llu},%lx},%x,",
40762306a36Sopenharmony_ci	       vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);
40862306a36Sopenharmony_ci
40962306a36Sopenharmony_ci	if (mask & MAY_NOT_BLOCK) {
41062306a36Sopenharmony_ci		key = afs_request_key_rcu(vnode->volume->cell);
41162306a36Sopenharmony_ci		if (IS_ERR(key))
41262306a36Sopenharmony_ci			return -ECHILD;
41362306a36Sopenharmony_ci
41462306a36Sopenharmony_ci		ret = -ECHILD;
41562306a36Sopenharmony_ci		if (!afs_check_validity(vnode) ||
41662306a36Sopenharmony_ci		    !afs_check_permit_rcu(vnode, key, &access))
41762306a36Sopenharmony_ci			goto error;
41862306a36Sopenharmony_ci	} else {
41962306a36Sopenharmony_ci		key = afs_request_key(vnode->volume->cell);
42062306a36Sopenharmony_ci		if (IS_ERR(key)) {
42162306a36Sopenharmony_ci			_leave(" = %ld [key]", PTR_ERR(key));
42262306a36Sopenharmony_ci			return PTR_ERR(key);
42362306a36Sopenharmony_ci		}
42462306a36Sopenharmony_ci
42562306a36Sopenharmony_ci		ret = afs_validate(vnode, key);
42662306a36Sopenharmony_ci		if (ret < 0)
42762306a36Sopenharmony_ci			goto error;
42862306a36Sopenharmony_ci
42962306a36Sopenharmony_ci		/* check the permits to see if we've got one yet */
43062306a36Sopenharmony_ci		ret = afs_check_permit(vnode, key, &access);
43162306a36Sopenharmony_ci		if (ret < 0)
43262306a36Sopenharmony_ci			goto error;
43362306a36Sopenharmony_ci	}
43462306a36Sopenharmony_ci
43562306a36Sopenharmony_ci	/* interpret the access mask */
43662306a36Sopenharmony_ci	_debug("REQ %x ACC %x on %s",
43762306a36Sopenharmony_ci	       mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");
43862306a36Sopenharmony_ci
43962306a36Sopenharmony_ci	ret = 0;
44062306a36Sopenharmony_ci	if (S_ISDIR(inode->i_mode)) {
44162306a36Sopenharmony_ci		if (mask & (MAY_EXEC | MAY_READ | MAY_CHDIR)) {
44262306a36Sopenharmony_ci			if (!(access & AFS_ACE_LOOKUP))
44362306a36Sopenharmony_ci				goto permission_denied;
44462306a36Sopenharmony_ci		}
44562306a36Sopenharmony_ci		if (mask & MAY_WRITE) {
44662306a36Sopenharmony_ci			if (!(access & (AFS_ACE_DELETE | /* rmdir, unlink, rename from */
44762306a36Sopenharmony_ci					AFS_ACE_INSERT))) /* create, mkdir, symlink, rename to */
44862306a36Sopenharmony_ci				goto permission_denied;
44962306a36Sopenharmony_ci		}
45062306a36Sopenharmony_ci	} else {
45162306a36Sopenharmony_ci		if (!(access & AFS_ACE_LOOKUP))
45262306a36Sopenharmony_ci			goto permission_denied;
45362306a36Sopenharmony_ci		if ((mask & MAY_EXEC) && !(inode->i_mode & S_IXUSR))
45462306a36Sopenharmony_ci			goto permission_denied;
45562306a36Sopenharmony_ci		if (mask & (MAY_EXEC | MAY_READ)) {
45662306a36Sopenharmony_ci			if (!(access & AFS_ACE_READ))
45762306a36Sopenharmony_ci				goto permission_denied;
45862306a36Sopenharmony_ci			if (!(inode->i_mode & S_IRUSR))
45962306a36Sopenharmony_ci				goto permission_denied;
46062306a36Sopenharmony_ci		} else if (mask & MAY_WRITE) {
46162306a36Sopenharmony_ci			if (!(access & AFS_ACE_WRITE))
46262306a36Sopenharmony_ci				goto permission_denied;
46362306a36Sopenharmony_ci			if (!(inode->i_mode & S_IWUSR))
46462306a36Sopenharmony_ci				goto permission_denied;
46562306a36Sopenharmony_ci		}
46662306a36Sopenharmony_ci	}
46762306a36Sopenharmony_ci
46862306a36Sopenharmony_ci	key_put(key);
46962306a36Sopenharmony_ci	_leave(" = %d", ret);
47062306a36Sopenharmony_ci	return ret;
47162306a36Sopenharmony_ci
47262306a36Sopenharmony_cipermission_denied:
47362306a36Sopenharmony_ci	ret = -EACCES;
47462306a36Sopenharmony_cierror:
47562306a36Sopenharmony_ci	key_put(key);
47662306a36Sopenharmony_ci	_leave(" = %d", ret);
47762306a36Sopenharmony_ci	return ret;
47862306a36Sopenharmony_ci}
47962306a36Sopenharmony_ci
48062306a36Sopenharmony_civoid __exit afs_clean_up_permit_cache(void)
48162306a36Sopenharmony_ci{
48262306a36Sopenharmony_ci	int i;
48362306a36Sopenharmony_ci
48462306a36Sopenharmony_ci	for (i = 0; i < HASH_SIZE(afs_permits_cache); i++)
48562306a36Sopenharmony_ci		WARN_ON_ONCE(!hlist_empty(&afs_permits_cache[i]));
48662306a36Sopenharmony_ci
48762306a36Sopenharmony_ci}
488