diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2011-02-16 15:08:54 -0500 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2011-03-14 09:15:23 -0400 |
commit | 15a9155fe3e8215c02b80df51ec2cac7c0d726ad (patch) | |
tree | 8336c506ffd9968678b906a10d625330aaaa83b3 /kernel/audit_watch.c | |
parent | 586ce098a23b6ab7383df853a84ae3d48dc889aa (diff) |
fix race in audit_get_nd()
don't rely on pathname resolution ending up twice at the same point...
Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'kernel/audit_watch.c')
-rw-r--r-- | kernel/audit_watch.c | 85 |
1 files changed, 32 insertions, 53 deletions
diff --git a/kernel/audit_watch.c b/kernel/audit_watch.c index d2e3c7866460..20b9fe6907d0 100644 --- a/kernel/audit_watch.c +++ b/kernel/audit_watch.c | |||
@@ -144,9 +144,9 @@ int audit_watch_compare(struct audit_watch *watch, unsigned long ino, dev_t dev) | |||
144 | } | 144 | } |
145 | 145 | ||
146 | /* Initialize a parent watch entry. */ | 146 | /* Initialize a parent watch entry. */ |
147 | static struct audit_parent *audit_init_parent(struct nameidata *ndp) | 147 | static struct audit_parent *audit_init_parent(struct path *path) |
148 | { | 148 | { |
149 | struct inode *inode = ndp->path.dentry->d_inode; | 149 | struct inode *inode = path->dentry->d_inode; |
150 | struct audit_parent *parent; | 150 | struct audit_parent *parent; |
151 | int ret; | 151 | int ret; |
152 | 152 | ||
@@ -353,53 +353,40 @@ static void audit_remove_parent_watches(struct audit_parent *parent) | |||
353 | } | 353 | } |
354 | 354 | ||
355 | /* Get path information necessary for adding watches. */ | 355 | /* Get path information necessary for adding watches. */ |
356 | static int audit_get_nd(char *path, struct nameidata **ndp, struct nameidata **ndw) | 356 | static int audit_get_nd(struct audit_watch *watch, struct path *parent) |
357 | { | 357 | { |
358 | struct nameidata *ndparent, *ndwatch; | 358 | struct nameidata nd; |
359 | struct dentry *d; | ||
359 | int err; | 360 | int err; |
360 | 361 | ||
361 | ndparent = kmalloc(sizeof(*ndparent), GFP_KERNEL); | 362 | err = path_lookup(watch->path, LOOKUP_PARENT, &nd); |
362 | if (unlikely(!ndparent)) | 363 | if (err) |
363 | return -ENOMEM; | 364 | return err; |
364 | 365 | ||
365 | ndwatch = kmalloc(sizeof(*ndwatch), GFP_KERNEL); | 366 | if (nd.last_type != LAST_NORM) { |
366 | if (unlikely(!ndwatch)) { | 367 | path_put(&nd.path); |
367 | kfree(ndparent); | 368 | return -EINVAL; |
368 | return -ENOMEM; | ||
369 | } | 369 | } |
370 | 370 | ||
371 | err = path_lookup(path, LOOKUP_PARENT, ndparent); | 371 | mutex_lock_nested(&nd.path.dentry->d_inode->i_mutex, I_MUTEX_PARENT); |
372 | if (err) { | 372 | d = lookup_one_len(nd.last.name, nd.path.dentry, nd.last.len); |
373 | kfree(ndparent); | 373 | if (IS_ERR(d)) { |
374 | kfree(ndwatch); | 374 | mutex_unlock(&nd.path.dentry->d_inode->i_mutex); |
375 | return err; | 375 | path_put(&nd.path); |
376 | return PTR_ERR(d); | ||
376 | } | 377 | } |
377 | 378 | if (d->d_inode) { | |
378 | err = path_lookup(path, 0, ndwatch); | 379 | /* update watch filter fields */ |
379 | if (err) { | 380 | watch->dev = d->d_inode->i_sb->s_dev; |
380 | kfree(ndwatch); | 381 | watch->ino = d->d_inode->i_ino; |
381 | ndwatch = NULL; | ||
382 | } | 382 | } |
383 | mutex_unlock(&nd.path.dentry->d_inode->i_mutex); | ||
383 | 384 | ||
384 | *ndp = ndparent; | 385 | *parent = nd.path; |
385 | *ndw = ndwatch; | 386 | dput(d); |
386 | |||
387 | return 0; | 387 | return 0; |
388 | } | 388 | } |
389 | 389 | ||
390 | /* Release resources used for watch path information. */ | ||
391 | static void audit_put_nd(struct nameidata *ndp, struct nameidata *ndw) | ||
392 | { | ||
393 | if (ndp) { | ||
394 | path_put(&ndp->path); | ||
395 | kfree(ndp); | ||
396 | } | ||
397 | if (ndw) { | ||
398 | path_put(&ndw->path); | ||
399 | kfree(ndw); | ||
400 | } | ||
401 | } | ||
402 | |||
403 | /* Associate the given rule with an existing parent. | 390 | /* Associate the given rule with an existing parent. |
404 | * Caller must hold audit_filter_mutex. */ | 391 | * Caller must hold audit_filter_mutex. */ |
405 | static void audit_add_to_parent(struct audit_krule *krule, | 392 | static void audit_add_to_parent(struct audit_krule *krule, |
@@ -440,31 +427,24 @@ int audit_add_watch(struct audit_krule *krule, struct list_head **list) | |||
440 | { | 427 | { |
441 | struct audit_watch *watch = krule->watch; | 428 | struct audit_watch *watch = krule->watch; |
442 | struct audit_parent *parent; | 429 | struct audit_parent *parent; |
443 | struct nameidata *ndp = NULL, *ndw = NULL; | 430 | struct path parent_path; |
444 | int h, ret = 0; | 431 | int h, ret = 0; |
445 | 432 | ||
446 | mutex_unlock(&audit_filter_mutex); | 433 | mutex_unlock(&audit_filter_mutex); |
447 | 434 | ||
448 | /* Avoid calling path_lookup under audit_filter_mutex. */ | 435 | /* Avoid calling path_lookup under audit_filter_mutex. */ |
449 | ret = audit_get_nd(watch->path, &ndp, &ndw); | 436 | ret = audit_get_nd(watch, &parent_path); |
450 | if (ret) { | ||
451 | /* caller expects mutex locked */ | ||
452 | mutex_lock(&audit_filter_mutex); | ||
453 | goto error; | ||
454 | } | ||
455 | 437 | ||
438 | /* caller expects mutex locked */ | ||
456 | mutex_lock(&audit_filter_mutex); | 439 | mutex_lock(&audit_filter_mutex); |
457 | 440 | ||
458 | /* update watch filter fields */ | 441 | if (ret) |
459 | if (ndw) { | 442 | return ret; |
460 | watch->dev = ndw->path.dentry->d_inode->i_sb->s_dev; | ||
461 | watch->ino = ndw->path.dentry->d_inode->i_ino; | ||
462 | } | ||
463 | 443 | ||
464 | /* either find an old parent or attach a new one */ | 444 | /* either find an old parent or attach a new one */ |
465 | parent = audit_find_parent(ndp->path.dentry->d_inode); | 445 | parent = audit_find_parent(parent_path.dentry->d_inode); |
466 | if (!parent) { | 446 | if (!parent) { |
467 | parent = audit_init_parent(ndp); | 447 | parent = audit_init_parent(&parent_path); |
468 | if (IS_ERR(parent)) { | 448 | if (IS_ERR(parent)) { |
469 | ret = PTR_ERR(parent); | 449 | ret = PTR_ERR(parent); |
470 | goto error; | 450 | goto error; |
@@ -479,9 +459,8 @@ int audit_add_watch(struct audit_krule *krule, struct list_head **list) | |||
479 | h = audit_hash_ino((u32)watch->ino); | 459 | h = audit_hash_ino((u32)watch->ino); |
480 | *list = &audit_inode_hash[h]; | 460 | *list = &audit_inode_hash[h]; |
481 | error: | 461 | error: |
482 | audit_put_nd(ndp, ndw); /* NULL args OK */ | 462 | path_put(&parent_path); |
483 | return ret; | 463 | return ret; |
484 | |||
485 | } | 464 | } |
486 | 465 | ||
487 | void audit_remove_watch_rule(struct audit_krule *krule) | 466 | void audit_remove_watch_rule(struct audit_krule *krule) |