]>
Commit | Line | Data |
---|---|---|
00d3b7a4 DH |
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 | } |