diff options
author | Trond Myklebust <Trond.Myklebust@netapp.com> | 2006-03-31 05:30:55 -0500 |
---|---|---|
committer | Linus Torvalds <torvalds@g5.osdl.org> | 2006-03-31 15:18:56 -0500 |
commit | 993dfa8776308dcfd311cf77a3bbed4aa11e9868 (patch) | |
tree | 2e90eabd31d3e8db1f2f3c95494109ff29a78237 /fs/locks.c | |
parent | 7a2bd3f7efa86e8b56482a8f8948c8b222064a67 (diff) |
[PATCH] fs/locks.c: Fix sys_flock() race
sys_flock() currently has a race which can result in a double free in the
multi-thread case.
Thread 1 Thread 2
sys_flock(file, LOCK_EX)
sys_flock(file, LOCK_UN)
If Thread 2 removes the lock from inode->i_lock before Thread 1 tests for
list_empty(&lock->fl_link) at the end of sys_flock, then both threads will
end up calling locks_free_lock for the same lock.
Fix is to make flock_lock_file() do the same as posix_lock_file(), namely
to make a copy of the request, so that the caller can always free the lock.
This also has the side-effect of fixing up a reference problem in the
lockd handling of flock.
Signed-off-by: Trond Myklebust <Trond.Myklebust@netapp.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
Diffstat (limited to 'fs/locks.c')
-rw-r--r-- | fs/locks.c | 30 |
1 files changed, 16 insertions, 14 deletions
diff --git a/fs/locks.c b/fs/locks.c index 8fcfeb177a2a..dda83d6cd48b 100644 --- a/fs/locks.c +++ b/fs/locks.c | |||
@@ -726,8 +726,9 @@ EXPORT_SYMBOL(posix_locks_deadlock); | |||
726 | * at the head of the list, but that's secret knowledge known only to | 726 | * at the head of the list, but that's secret knowledge known only to |
727 | * flock_lock_file and posix_lock_file. | 727 | * flock_lock_file and posix_lock_file. |
728 | */ | 728 | */ |
729 | static int flock_lock_file(struct file *filp, struct file_lock *new_fl) | 729 | static int flock_lock_file(struct file *filp, struct file_lock *request) |
730 | { | 730 | { |
731 | struct file_lock *new_fl = NULL; | ||
731 | struct file_lock **before; | 732 | struct file_lock **before; |
732 | struct inode * inode = filp->f_dentry->d_inode; | 733 | struct inode * inode = filp->f_dentry->d_inode; |
733 | int error = 0; | 734 | int error = 0; |
@@ -742,17 +743,19 @@ static int flock_lock_file(struct file *filp, struct file_lock *new_fl) | |||
742 | continue; | 743 | continue; |
743 | if (filp != fl->fl_file) | 744 | if (filp != fl->fl_file) |
744 | continue; | 745 | continue; |
745 | if (new_fl->fl_type == fl->fl_type) | 746 | if (request->fl_type == fl->fl_type) |
746 | goto out; | 747 | goto out; |
747 | found = 1; | 748 | found = 1; |
748 | locks_delete_lock(before); | 749 | locks_delete_lock(before); |
749 | break; | 750 | break; |
750 | } | 751 | } |
751 | unlock_kernel(); | ||
752 | 752 | ||
753 | if (new_fl->fl_type == F_UNLCK) | 753 | if (request->fl_type == F_UNLCK) |
754 | return 0; | 754 | goto out; |
755 | 755 | ||
756 | new_fl = locks_alloc_lock(); | ||
757 | if (new_fl == NULL) | ||
758 | goto out; | ||
756 | /* | 759 | /* |
757 | * If a higher-priority process was blocked on the old file lock, | 760 | * If a higher-priority process was blocked on the old file lock, |
758 | * give it the opportunity to lock the file. | 761 | * give it the opportunity to lock the file. |
@@ -760,26 +763,27 @@ static int flock_lock_file(struct file *filp, struct file_lock *new_fl) | |||
760 | if (found) | 763 | if (found) |
761 | cond_resched(); | 764 | cond_resched(); |
762 | 765 | ||
763 | lock_kernel(); | ||
764 | for_each_lock(inode, before) { | 766 | for_each_lock(inode, before) { |
765 | struct file_lock *fl = *before; | 767 | struct file_lock *fl = *before; |
766 | if (IS_POSIX(fl)) | 768 | if (IS_POSIX(fl)) |
767 | break; | 769 | break; |
768 | if (IS_LEASE(fl)) | 770 | if (IS_LEASE(fl)) |
769 | continue; | 771 | continue; |
770 | if (!flock_locks_conflict(new_fl, fl)) | 772 | if (!flock_locks_conflict(request, fl)) |
771 | continue; | 773 | continue; |
772 | error = -EAGAIN; | 774 | error = -EAGAIN; |
773 | if (new_fl->fl_flags & FL_SLEEP) { | 775 | if (request->fl_flags & FL_SLEEP) |
774 | locks_insert_block(fl, new_fl); | 776 | locks_insert_block(fl, request); |
775 | } | ||
776 | goto out; | 777 | goto out; |
777 | } | 778 | } |
779 | locks_copy_lock(new_fl, request); | ||
778 | locks_insert_lock(&inode->i_flock, new_fl); | 780 | locks_insert_lock(&inode->i_flock, new_fl); |
779 | error = 0; | 781 | new_fl = NULL; |
780 | 782 | ||
781 | out: | 783 | out: |
782 | unlock_kernel(); | 784 | unlock_kernel(); |
785 | if (new_fl) | ||
786 | locks_free_lock(new_fl); | ||
783 | return error; | 787 | return error; |
784 | } | 788 | } |
785 | 789 | ||
@@ -1560,9 +1564,7 @@ asmlinkage long sys_flock(unsigned int fd, unsigned int cmd) | |||
1560 | error = flock_lock_file_wait(filp, lock); | 1564 | error = flock_lock_file_wait(filp, lock); |
1561 | 1565 | ||
1562 | out_free: | 1566 | out_free: |
1563 | if (list_empty(&lock->fl_link)) { | 1567 | locks_free_lock(lock); |
1564 | locks_free_lock(lock); | ||
1565 | } | ||
1566 | 1568 | ||
1567 | out_putf: | 1569 | out_putf: |
1568 | fput(filp); | 1570 | fput(filp); |