]> nv-tegra.nvidia Code Review - linux-2.6.git/blob - fs/afs/security.c
Merge git://git.kernel.org/pub/scm/linux/kernel/git/aegl/linux-2.6
[linux-2.6.git] / fs / afs / security.c
1 /* AFS security handling
2  *
3  * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
4  * Written by David Howells (dhowells@redhat.com)
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version
9  * 2 of the License, or (at your option) any later version.
10  */
11
12 #include <linux/init.h>
13 #include <linux/slab.h>
14 #include <linux/fs.h>
15 #include <linux/ctype.h>
16 #include <keys/rxrpc-type.h>
17 #include "internal.h"
18
19 /*
20  * get a key
21  */
22 struct key *afs_request_key(struct afs_cell *cell)
23 {
24         struct key *key;
25
26         _enter("{%x}", key_serial(cell->anonymous_key));
27
28         _debug("key %s", cell->anonymous_key->description);
29         key = request_key(&key_type_rxrpc, cell->anonymous_key->description,
30                           NULL);
31         if (IS_ERR(key)) {
32                 if (PTR_ERR(key) != -ENOKEY) {
33                         _leave(" = %ld", PTR_ERR(key));
34                         return key;
35                 }
36
37                 /* act as anonymous user */
38                 _leave(" = {%x} [anon]", key_serial(cell->anonymous_key));
39                 return key_get(cell->anonymous_key);
40         } else {
41                 /* act as authorised user */
42                 _leave(" = {%x} [auth]", key_serial(key));
43                 return key;
44         }
45 }
46
47 /*
48  * dispose of a permits list
49  */
50 void afs_zap_permits(struct rcu_head *rcu)
51 {
52         struct afs_permits *permits =
53                 container_of(rcu, struct afs_permits, rcu);
54         int loop;
55
56         _enter("{%d}", permits->count);
57
58         for (loop = permits->count - 1; loop >= 0; loop--)
59                 key_put(permits->permits[loop].key);
60         kfree(permits);
61 }
62
63 /*
64  * dispose of a permits list in which all the key pointers have been copied
65  */
66 static void afs_dispose_of_permits(struct rcu_head *rcu)
67 {
68         struct afs_permits *permits =
69                 container_of(rcu, struct afs_permits, rcu);
70
71         _enter("{%d}", permits->count);
72
73         kfree(permits);
74 }
75
76 /*
77  * get the authorising vnode - this is the specified inode itself if it's a
78  * directory or it's the parent directory if the specified inode is a file or
79  * symlink
80  * - the caller must release the ref on the inode
81  */
82 static struct afs_vnode *afs_get_auth_inode(struct afs_vnode *vnode,
83                                             struct key *key)
84 {
85         struct afs_vnode *auth_vnode;
86         struct inode *auth_inode;
87
88         _enter("");
89
90         if (S_ISDIR(vnode->vfs_inode.i_mode)) {
91                 auth_inode = igrab(&vnode->vfs_inode);
92                 ASSERT(auth_inode != NULL);
93         } else {
94                 auth_inode = afs_iget(vnode->vfs_inode.i_sb, key,
95                                       &vnode->status.parent, NULL, NULL);
96                 if (IS_ERR(auth_inode))
97                         return ERR_PTR(PTR_ERR(auth_inode));
98         }
99
100         auth_vnode = AFS_FS_I(auth_inode);
101         _leave(" = {%x}", auth_vnode->fid.vnode);
102         return auth_vnode;
103 }
104
105 /*
106  * clear the permit cache on a directory vnode
107  */
108 void afs_clear_permits(struct afs_vnode *vnode)
109 {
110         struct afs_permits *permits;
111
112         _enter("{%x:%u}", vnode->fid.vid, vnode->fid.vnode);
113
114         mutex_lock(&vnode->permits_lock);
115         permits = vnode->permits;
116         rcu_assign_pointer(vnode->permits, NULL);
117         mutex_unlock(&vnode->permits_lock);
118
119         if (permits)
120                 call_rcu(&permits->rcu, afs_zap_permits);
121         _leave("");
122 }
123
124 /*
125  * add the result obtained for a vnode to its or its parent directory's cache
126  * for the key used to access it
127  */
128 void afs_cache_permit(struct afs_vnode *vnode, struct key *key, long acl_order)
129 {
130         struct afs_permits *permits, *xpermits;
131         struct afs_permit *permit;
132         struct afs_vnode *auth_vnode;
133         int count, loop;
134
135         _enter("{%x:%u},%x,%lx",
136                vnode->fid.vid, vnode->fid.vnode, key_serial(key), acl_order);
137
138         auth_vnode = afs_get_auth_inode(vnode, key);
139         if (IS_ERR(auth_vnode)) {
140                 _leave(" [get error %ld]", PTR_ERR(auth_vnode));
141                 return;
142         }
143
144         mutex_lock(&auth_vnode->permits_lock);
145
146         /* guard against a rename being detected whilst we waited for the
147          * lock */
148         if (memcmp(&auth_vnode->fid, &vnode->status.parent,
149                    sizeof(struct afs_fid)) != 0) {
150                 _debug("renamed");
151                 goto out_unlock;
152         }
153
154         /* have to be careful as the directory's callback may be broken between
155          * us receiving the status we're trying to cache and us getting the
156          * lock to update the cache for the status */
157         if (auth_vnode->acl_order - acl_order > 0) {
158                 _debug("ACL changed?");
159                 goto out_unlock;
160         }
161
162         /* always update the anonymous mask */
163         _debug("anon access %x", vnode->status.anon_access);
164         auth_vnode->status.anon_access = vnode->status.anon_access;
165         if (key == vnode->volume->cell->anonymous_key)
166                 goto out_unlock;
167
168         xpermits = auth_vnode->permits;
169         count = 0;
170         if (xpermits) {
171                 /* see if the permit is already in the list
172                  * - if it is then we just amend the list
173                  */
174                 count = xpermits->count;
175                 permit = xpermits->permits;
176                 for (loop = count; loop > 0; loop--) {
177                         if (permit->key == key) {
178                                 permit->access_mask =
179                                         vnode->status.caller_access;
180                                 goto out_unlock;
181                         }
182                         permit++;
183                 }
184         }
185
186         permits = kmalloc(sizeof(*permits) + sizeof(*permit) * (count + 1),
187                           GFP_NOFS);
188         if (!permits)
189                 goto out_unlock;
190
191         memcpy(permits->permits, xpermits->permits,
192                count * sizeof(struct afs_permit));
193
194         _debug("key %x access %x",
195                key_serial(key), vnode->status.caller_access);
196         permits->permits[count].access_mask = vnode->status.caller_access;
197         permits->permits[count].key = key_get(key);
198         permits->count = count + 1;
199
200         rcu_assign_pointer(auth_vnode->permits, permits);
201         if (xpermits)
202                 call_rcu(&xpermits->rcu, afs_dispose_of_permits);
203
204 out_unlock:
205         mutex_unlock(&auth_vnode->permits_lock);
206         iput(&auth_vnode->vfs_inode);
207         _leave("");
208 }
209
210 /*
211  * check with the fileserver to see if the directory or parent directory is
212  * permitted to be accessed with this authorisation, and if so, what access it
213  * is granted
214  */
215 static int afs_check_permit(struct afs_vnode *vnode, struct key *key,
216                             afs_access_t *_access)
217 {
218         struct afs_permits *permits;
219         struct afs_permit *permit;
220         struct afs_vnode *auth_vnode;
221         bool valid;
222         int loop, ret;
223
224         _enter("{%x:%u},%x",
225                vnode->fid.vid, vnode->fid.vnode, key_serial(key));
226
227         auth_vnode = afs_get_auth_inode(vnode, key);
228         if (IS_ERR(auth_vnode)) {
229                 *_access = 0;
230                 _leave(" = %ld", PTR_ERR(auth_vnode));
231                 return PTR_ERR(auth_vnode);
232         }
233
234         ASSERT(S_ISDIR(auth_vnode->vfs_inode.i_mode));
235
236         /* check the permits to see if we've got one yet */
237         if (key == auth_vnode->volume->cell->anonymous_key) {
238                 _debug("anon");
239                 *_access = auth_vnode->status.anon_access;
240                 valid = true;
241         } else {
242                 valid = false;
243                 rcu_read_lock();
244                 permits = rcu_dereference(auth_vnode->permits);
245                 if (permits) {
246                         permit = permits->permits;
247                         for (loop = permits->count; loop > 0; loop--) {
248                                 if (permit->key == key) {
249                                         _debug("found in cache");
250                                         *_access = permit->access_mask;
251                                         valid = true;
252                                         break;
253                                 }
254                                 permit++;
255                         }
256                 }
257                 rcu_read_unlock();
258         }
259
260         if (!valid) {
261                 /* check the status on the file we're actually interested in
262                  * (the post-processing will cache the result on auth_vnode) */
263                 _debug("no valid permit");
264
265                 set_bit(AFS_VNODE_CB_BROKEN, &vnode->flags);
266                 ret = afs_vnode_fetch_status(vnode, auth_vnode, key);
267                 if (ret < 0) {
268                         iput(&auth_vnode->vfs_inode);
269                         *_access = 0;
270                         _leave(" = %d", ret);
271                         return ret;
272                 }
273                 *_access = vnode->status.caller_access;
274         }
275
276         iput(&auth_vnode->vfs_inode);
277         _leave(" = 0 [access %x]", *_access);
278         return 0;
279 }
280
281 /*
282  * check the permissions on an AFS file
283  * - AFS ACLs are attached to directories only, and a file is controlled by its
284  *   parent directory's ACL
285  */
286 int afs_permission(struct inode *inode, int mask, struct nameidata *nd)
287 {
288         struct afs_vnode *vnode = AFS_FS_I(inode);
289         afs_access_t access;
290         struct key *key;
291         int ret;
292
293         _enter("{{%x:%u},%lx},%x,",
294                vnode->fid.vid, vnode->fid.vnode, vnode->flags, mask);
295
296         key = afs_request_key(vnode->volume->cell);
297         if (IS_ERR(key)) {
298                 _leave(" = %ld [key]", PTR_ERR(key));
299                 return PTR_ERR(key);
300         }
301
302         /* if the promise has expired, we need to check the server again */
303         if (!vnode->cb_promised) {
304                 _debug("not promised");
305                 ret = afs_vnode_fetch_status(vnode, NULL, key);
306                 if (ret < 0)
307                         goto error;
308                 _debug("new promise [fl=%lx]", vnode->flags);
309         }
310
311         /* check the permits to see if we've got one yet */
312         ret = afs_check_permit(vnode, key, &access);
313         if (ret < 0)
314                 goto error;
315
316         /* interpret the access mask */
317         _debug("REQ %x ACC %x on %s",
318                mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file");
319
320         if (S_ISDIR(inode->i_mode)) {
321                 if (mask & MAY_EXEC) {
322                         if (!(access & AFS_ACE_LOOKUP))
323                                 goto permission_denied;
324                 } else if (mask & MAY_READ) {
325                         if (!(access & AFS_ACE_READ))
326                                 goto permission_denied;
327                 } else if (mask & MAY_WRITE) {
328                         if (!(access & (AFS_ACE_DELETE | /* rmdir, unlink, rename from */
329                                         AFS_ACE_INSERT | /* create, mkdir, symlink, rename to */
330                                         AFS_ACE_WRITE))) /* chmod */
331                                 goto permission_denied;
332                 } else {
333                         BUG();
334                 }
335         } else {
336                 if (!(access & AFS_ACE_LOOKUP))
337                         goto permission_denied;
338                 if (mask & (MAY_EXEC | MAY_READ)) {
339                         if (!(access & AFS_ACE_READ))
340                                 goto permission_denied;
341                 } else if (mask & MAY_WRITE) {
342                         if (!(access & AFS_ACE_WRITE))
343                                 goto permission_denied;
344                 }
345         }
346
347         key_put(key);
348         ret = generic_permission(inode, mask, NULL);
349         _leave(" = %d", ret);
350         return ret;
351
352 permission_denied:
353         ret = -EACCES;
354 error:
355         key_put(key);
356         _leave(" = %d", ret);
357         return ret;
358 }