diff options
author | Jeff Layton <jlayton@redhat.com> | 2014-02-03 12:13:06 -0500 |
---|---|---|
committer | Jeff Layton <jlayton@redhat.com> | 2014-03-31 08:24:42 -0400 |
commit | 24cbe7845ea50b636ab2218b9d648270ff55f148 (patch) | |
tree | 89769bf1271ff804c830d0ba7898294a5762b7a6 /fs/locks.c | |
parent | 18156e7e66e5147d2483382a1e3817d2401ca1a8 (diff) |
locks: close potential race between setlease and open
As Al Viro points out, there is an unlikely, but possible race between
opening a file and setting a lease on it. generic_add_lease is done with
the i_lock held, but the inode->i_flock check in break_lease is
lockless. It's possible for another task doing an open to do the entire
pathwalk and call break_lease between the point where generic_add_lease
checks for a conflicting open and adds the lease to the list. If this
occurs, we can end up with a lease set on the file with a conflicting
open.
To guard against that, check again for a conflicting open after adding
the lease to the i_flock list. If the above race occurs, then we can
simply unwind the lease setting and return -EAGAIN.
Because we take dentry references and acquire write access on the file
before calling break_lease, we know that if the i_flock list is empty
when the open caller goes to check it then the necessary refcounts have
already been incremented. Thus the additional check for a conflicting
open will see that there is one and the setlease call will fail.
Cc: Bruce Fields <bfields@fieldses.org>
Cc: David Howells <dhowells@redhat.com>
Cc: "Paul E. McKenney" <paulmck@linux.vnet.ibm.com>
Reported-by: Al Viro <viro@ZenIV.linux.org.uk>
Signed-off-by: Jeff Layton <jlayton@redhat.com>
Signed-off-by: J. Bruce Fields <bfields@fieldses.org>
Diffstat (limited to 'fs/locks.c')
-rw-r--r-- | fs/locks.c | 75 |
1 files changed, 62 insertions, 13 deletions
diff --git a/fs/locks.c b/fs/locks.c index 92a0f0a52b06..2cfeea622f28 100644 --- a/fs/locks.c +++ b/fs/locks.c | |||
@@ -652,15 +652,18 @@ static void locks_insert_lock(struct file_lock **pos, struct file_lock *fl) | |||
652 | locks_insert_global_locks(fl); | 652 | locks_insert_global_locks(fl); |
653 | } | 653 | } |
654 | 654 | ||
655 | /* | 655 | /** |
656 | * Delete a lock and then free it. | 656 | * locks_delete_lock - Delete a lock and then free it. |
657 | * Wake up processes that are blocked waiting for this lock, | 657 | * @thisfl_p: pointer that points to the fl_next field of the previous |
658 | * notify the FS that the lock has been cleared and | 658 | * inode->i_flock list entry |
659 | * finally free the lock. | 659 | * |
660 | * Unlink a lock from all lists and free the namespace reference, but don't | ||
661 | * free it yet. Wake up processes that are blocked waiting for this lock and | ||
662 | * notify the FS that the lock has been cleared. | ||
660 | * | 663 | * |
661 | * Must be called with the i_lock held! | 664 | * Must be called with the i_lock held! |
662 | */ | 665 | */ |
663 | static void locks_delete_lock(struct file_lock **thisfl_p) | 666 | static void locks_unlink_lock(struct file_lock **thisfl_p) |
664 | { | 667 | { |
665 | struct file_lock *fl = *thisfl_p; | 668 | struct file_lock *fl = *thisfl_p; |
666 | 669 | ||
@@ -675,6 +678,18 @@ static void locks_delete_lock(struct file_lock **thisfl_p) | |||
675 | } | 678 | } |
676 | 679 | ||
677 | locks_wake_up_blocks(fl); | 680 | locks_wake_up_blocks(fl); |
681 | } | ||
682 | |||
683 | /* | ||
684 | * Unlink a lock from all lists and free it. | ||
685 | * | ||
686 | * Must be called with i_lock held! | ||
687 | */ | ||
688 | static void locks_delete_lock(struct file_lock **thisfl_p) | ||
689 | { | ||
690 | struct file_lock *fl = *thisfl_p; | ||
691 | |||
692 | locks_unlink_lock(thisfl_p); | ||
678 | locks_free_lock(fl); | 693 | locks_free_lock(fl); |
679 | } | 694 | } |
680 | 695 | ||
@@ -1472,6 +1487,32 @@ int fcntl_getlease(struct file *filp) | |||
1472 | return type; | 1487 | return type; |
1473 | } | 1488 | } |
1474 | 1489 | ||
1490 | /** | ||
1491 | * check_conflicting_open - see if the given dentry points to a file that has | ||
1492 | * an existing open that would conflict with the | ||
1493 | * desired lease. | ||
1494 | * @dentry: dentry to check | ||
1495 | * @arg: type of lease that we're trying to acquire | ||
1496 | * | ||
1497 | * Check to see if there's an existing open fd on this file that would | ||
1498 | * conflict with the lease we're trying to set. | ||
1499 | */ | ||
1500 | static int | ||
1501 | check_conflicting_open(const struct dentry *dentry, const long arg) | ||
1502 | { | ||
1503 | int ret = 0; | ||
1504 | struct inode *inode = dentry->d_inode; | ||
1505 | |||
1506 | if ((arg == F_RDLCK) && (atomic_read(&inode->i_writecount) > 0)) | ||
1507 | return -EAGAIN; | ||
1508 | |||
1509 | if ((arg == F_WRLCK) && ((d_count(dentry) > 1) || | ||
1510 | (atomic_read(&inode->i_count) > 1))) | ||
1511 | ret = -EAGAIN; | ||
1512 | |||
1513 | return ret; | ||
1514 | } | ||
1515 | |||
1475 | static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp) | 1516 | static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp) |
1476 | { | 1517 | { |
1477 | struct file_lock *fl, **before, **my_before = NULL, *lease; | 1518 | struct file_lock *fl, **before, **my_before = NULL, *lease; |
@@ -1499,12 +1540,8 @@ static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp | |||
1499 | return -EINVAL; | 1540 | return -EINVAL; |
1500 | } | 1541 | } |
1501 | 1542 | ||
1502 | error = -EAGAIN; | 1543 | error = check_conflicting_open(dentry, arg); |
1503 | if ((arg == F_RDLCK) && (atomic_read(&inode->i_writecount) > 0)) | 1544 | if (error) |
1504 | goto out; | ||
1505 | if ((arg == F_WRLCK) | ||
1506 | && ((d_count(dentry) > 1) | ||
1507 | || (atomic_read(&inode->i_count) > 1))) | ||
1508 | goto out; | 1545 | goto out; |
1509 | 1546 | ||
1510 | /* | 1547 | /* |
@@ -1549,7 +1586,19 @@ static int generic_add_lease(struct file *filp, long arg, struct file_lock **flp | |||
1549 | goto out; | 1586 | goto out; |
1550 | 1587 | ||
1551 | locks_insert_lock(before, lease); | 1588 | locks_insert_lock(before, lease); |
1552 | error = 0; | 1589 | /* |
1590 | * The check in break_lease() is lockless. It's possible for another | ||
1591 | * open to race in after we did the earlier check for a conflicting | ||
1592 | * open but before the lease was inserted. Check again for a | ||
1593 | * conflicting open and cancel the lease if there is one. | ||
1594 | * | ||
1595 | * We also add a barrier here to ensure that the insertion of the lock | ||
1596 | * precedes these checks. | ||
1597 | */ | ||
1598 | smp_mb(); | ||
1599 | error = check_conflicting_open(dentry, arg); | ||
1600 | if (error) | ||
1601 | locks_unlink_lock(flp); | ||
1553 | out: | 1602 | out: |
1554 | if (is_deleg) | 1603 | if (is_deleg) |
1555 | mutex_unlock(&inode->i_mutex); | 1604 | mutex_unlock(&inode->i_mutex); |