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 | } | ||
