diff options
Diffstat (limited to 'fs/afs/security.c')
-rw-r--r-- | fs/afs/security.c | 345 |
1 files changed, 345 insertions, 0 deletions
diff --git a/fs/afs/security.c b/fs/afs/security.c new file mode 100644 index 000000000000..cbdd7f7162fa --- /dev/null +++ b/fs/afs/security.c | |||
@@ -0,0 +1,345 @@ | |||
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); | ||
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}", 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},%x,%lx", vnode->fid.vnode, key_serial(key), acl_order); | ||
136 | |||
137 | auth_vnode = afs_get_auth_inode(vnode, key); | ||
138 | if (IS_ERR(auth_vnode)) { | ||
139 | _leave(" [get error %ld]", PTR_ERR(auth_vnode)); | ||
140 | return; | ||
141 | } | ||
142 | |||
143 | mutex_lock(&auth_vnode->permits_lock); | ||
144 | |||
145 | /* guard against a rename being detected whilst we waited for the | ||
146 | * lock */ | ||
147 | if (memcmp(&auth_vnode->fid, &vnode->status.parent, | ||
148 | sizeof(struct afs_fid)) != 0) { | ||
149 | _debug("renamed"); | ||
150 | goto out_unlock; | ||
151 | } | ||
152 | |||
153 | /* have to be careful as the directory's callback may be broken between | ||
154 | * us receiving the status we're trying to cache and us getting the | ||
155 | * lock to update the cache for the status */ | ||
156 | if (auth_vnode->acl_order - acl_order > 0) { | ||
157 | _debug("ACL changed?"); | ||
158 | goto out_unlock; | ||
159 | } | ||
160 | |||
161 | /* always update the anonymous mask */ | ||
162 | _debug("anon access %x", vnode->status.anon_access); | ||
163 | auth_vnode->status.anon_access = vnode->status.anon_access; | ||
164 | if (key == vnode->volume->cell->anonymous_key) | ||
165 | goto out_unlock; | ||
166 | |||
167 | xpermits = auth_vnode->permits; | ||
168 | count = 0; | ||
169 | if (xpermits) { | ||
170 | /* see if the permit is already in the list | ||
171 | * - if it is then we just amend the list | ||
172 | */ | ||
173 | count = xpermits->count; | ||
174 | permit = xpermits->permits; | ||
175 | for (loop = count; loop > 0; loop--) { | ||
176 | if (permit->key == key) { | ||
177 | permit->access_mask = | ||
178 | vnode->status.caller_access; | ||
179 | goto out_unlock; | ||
180 | } | ||
181 | permit++; | ||
182 | } | ||
183 | } | ||
184 | |||
185 | permits = kmalloc(sizeof(*permits) + sizeof(*permit) * (count + 1), | ||
186 | GFP_NOFS); | ||
187 | if (!permits) | ||
188 | goto out_unlock; | ||
189 | |||
190 | memcpy(permits->permits, xpermits->permits, | ||
191 | count * sizeof(struct afs_permit)); | ||
192 | |||
193 | _debug("key %x access %x", | ||
194 | key_serial(key), vnode->status.caller_access); | ||
195 | permits->permits[count].access_mask = vnode->status.caller_access; | ||
196 | permits->permits[count].key = key_get(key); | ||
197 | permits->count = count + 1; | ||
198 | |||
199 | rcu_assign_pointer(auth_vnode->permits, permits); | ||
200 | if (xpermits) | ||
201 | call_rcu(&xpermits->rcu, afs_dispose_of_permits); | ||
202 | |||
203 | out_unlock: | ||
204 | mutex_unlock(&auth_vnode->permits_lock); | ||
205 | iput(&auth_vnode->vfs_inode); | ||
206 | _leave(""); | ||
207 | } | ||
208 | |||
209 | /* | ||
210 | * check with the fileserver to see if the directory or parent directory is | ||
211 | * permitted to be accessed with this authorisation, and if so, what access it | ||
212 | * is granted | ||
213 | */ | ||
214 | static int afs_check_permit(struct afs_vnode *vnode, struct key *key, | ||
215 | afs_access_t *_access) | ||
216 | { | ||
217 | struct afs_permits *permits; | ||
218 | struct afs_permit *permit; | ||
219 | struct afs_vnode *auth_vnode; | ||
220 | bool valid; | ||
221 | int loop, ret; | ||
222 | |||
223 | _enter(""); | ||
224 | |||
225 | auth_vnode = afs_get_auth_inode(vnode, key); | ||
226 | if (IS_ERR(auth_vnode)) { | ||
227 | *_access = 0; | ||
228 | _leave(" = %ld", PTR_ERR(auth_vnode)); | ||
229 | return PTR_ERR(auth_vnode); | ||
230 | } | ||
231 | |||
232 | ASSERT(S_ISDIR(auth_vnode->vfs_inode.i_mode)); | ||
233 | |||
234 | /* check the permits to see if we've got one yet */ | ||
235 | if (key == auth_vnode->volume->cell->anonymous_key) { | ||
236 | _debug("anon"); | ||
237 | *_access = auth_vnode->status.anon_access; | ||
238 | valid = true; | ||
239 | } else { | ||
240 | valid = false; | ||
241 | rcu_read_lock(); | ||
242 | permits = rcu_dereference(auth_vnode->permits); | ||
243 | if (permits) { | ||
244 | permit = permits->permits; | ||
245 | for (loop = permits->count; loop > 0; loop--) { | ||
246 | if (permit->key == key) { | ||
247 | _debug("found in cache"); | ||
248 | *_access = permit->access_mask; | ||
249 | valid = true; | ||
250 | break; | ||
251 | } | ||
252 | permit++; | ||
253 | } | ||
254 | } | ||
255 | rcu_read_unlock(); | ||
256 | } | ||
257 | |||
258 | if (!valid) { | ||
259 | /* check the status on the file we're actually interested in | ||
260 | * (the post-processing will cache the result on auth_vnode) */ | ||
261 | _debug("no valid permit"); | ||
262 | |||
263 | set_bit(AFS_VNODE_CB_BROKEN, &vnode->flags); | ||
264 | ret = afs_vnode_fetch_status(vnode, auth_vnode, key); | ||
265 | if (ret < 0) { | ||
266 | iput(&auth_vnode->vfs_inode); | ||
267 | *_access = 0; | ||
268 | _leave(" = %d", ret); | ||
269 | return ret; | ||
270 | } | ||
271 | } | ||
272 | |||
273 | *_access = vnode->status.caller_access; | ||
274 | iput(&auth_vnode->vfs_inode); | ||
275 | _leave(" = 0 [access %x]", *_access); | ||
276 | return 0; | ||
277 | } | ||
278 | |||
279 | /* | ||
280 | * check the permissions on an AFS file | ||
281 | * - AFS ACLs are attached to directories only, and a file is controlled by its | ||
282 | * parent directory's ACL | ||
283 | */ | ||
284 | int afs_permission(struct inode *inode, int mask, struct nameidata *nd) | ||
285 | { | ||
286 | struct afs_vnode *vnode = AFS_FS_I(inode); | ||
287 | afs_access_t access; | ||
288 | struct key *key; | ||
289 | int ret; | ||
290 | |||
291 | _enter("{%x:%x},%x,", vnode->fid.vid, vnode->fid.vnode, mask); | ||
292 | |||
293 | key = afs_request_key(vnode->volume->cell); | ||
294 | if (IS_ERR(key)) { | ||
295 | _leave(" = %ld [key]", PTR_ERR(key)); | ||
296 | return PTR_ERR(key); | ||
297 | } | ||
298 | |||
299 | /* check the permits to see if we've got one yet */ | ||
300 | ret = afs_check_permit(vnode, key, &access); | ||
301 | if (ret < 0) { | ||
302 | key_put(key); | ||
303 | _leave(" = %d [check]", ret); | ||
304 | return ret; | ||
305 | } | ||
306 | |||
307 | /* interpret the access mask */ | ||
308 | _debug("REQ %x ACC %x on %s", | ||
309 | mask, access, S_ISDIR(inode->i_mode) ? "dir" : "file"); | ||
310 | |||
311 | if (S_ISDIR(inode->i_mode)) { | ||
312 | if (mask & MAY_EXEC) { | ||
313 | if (!(access & AFS_ACE_LOOKUP)) | ||
314 | goto permission_denied; | ||
315 | } else if (mask & MAY_READ) { | ||
316 | if (!(access & AFS_ACE_READ)) | ||
317 | goto permission_denied; | ||
318 | } else if (mask & MAY_WRITE) { | ||
319 | if (!(access & (AFS_ACE_DELETE | /* rmdir, unlink, rename from */ | ||
320 | AFS_ACE_INSERT | /* create, mkdir, symlink, rename to */ | ||
321 | AFS_ACE_WRITE))) /* chmod */ | ||
322 | goto permission_denied; | ||
323 | } else { | ||
324 | BUG(); | ||
325 | } | ||
326 | } else { | ||
327 | if (!(access & AFS_ACE_LOOKUP)) | ||
328 | goto permission_denied; | ||
329 | if (mask & (MAY_EXEC | MAY_READ)) { | ||
330 | if (!(access & AFS_ACE_READ)) | ||
331 | goto permission_denied; | ||
332 | } else if (mask & MAY_WRITE) { | ||
333 | if (!(access & AFS_ACE_WRITE)) | ||
334 | goto permission_denied; | ||
335 | } | ||
336 | } | ||
337 | |||
338 | key_put(key); | ||
339 | return generic_permission(inode, mask, NULL); | ||
340 | |||
341 | permission_denied: | ||
342 | key_put(key); | ||
343 | _leave(" = -EACCES"); | ||
344 | return -EACCES; | ||
345 | } | ||