Skip to content

Commit c293621

Browse files
Peter StaubachLinus Torvalds
authored andcommitted
[PATCH] stale POSIX lock handling
I believe that there is a problem with the handling of POSIX locks, which the attached patch should address. The problem appears to be a race between fcntl(2) and close(2). A multithreaded application could close a file descriptor at the same time as it is trying to acquire a lock using the same file descriptor. I would suggest that that multithreaded application is not providing the proper synchronization for itself, but the OS should still behave correctly. SUS3 (Single UNIX Specification Version 3, read: POSIX) indicates that when a file descriptor is closed, that all POSIX locks on the file, owned by the process which closed the file descriptor, should be released. The trick here is when those locks are released. The current code releases all locks which exist when close is processing, but any locks in progress are handled when the last reference to the open file is released. There are three cases to consider. One is the simple case, a multithreaded (mt) process has a file open and races to close it and acquire a lock on it. In this case, the close will release one reference to the open file and when the fcntl is done, it will release the other reference. For this situation, no locks should exist on the file when both the close and fcntl operations are done. The current system will handle this case because the last reference to the open file is being released. The second case is when the mt process has dup(2)'d the file descriptor. The close will release one reference to the file and the fcntl, when done, will release another, but there will still be at least one more reference to the open file. One could argue that the existence of a lock on the file after the close has completed is okay, because it was acquired after the close operation and there is still a way for the application to release the lock on the file, using an existing file descriptor. The third case is when the mt process has forked, after opening the file and either before or after becoming an mt process. In this case, each process would hold a reference to the open file. For each process, this degenerates to first case above. However, the lock continues to exist until both processes have released their references to the open file. This lock could block other lock requests. The changes to release the lock when the last reference to the open file aren't quite right because they would allow the lock to exist as long as there was a reference to the open file. This is too long. The new proposed solution is to add support in the fcntl code path to detect a race with close and then to release the lock which was just acquired when such as race is detected. This causes locks to be released in a timely fashion and for the system to conform to the POSIX semantic specification. This was tested by instrumenting a kernel to detect the handling locks and then running a program which generates case #3 above. A dangling lock could be reliably generated. When the changes to detect the close/fcntl race were added, a dangling lock could no longer be generated. Cc: Matthew Wilcox <willy@debian.org> Cc: Trond Myklebust <trond.myklebust@fys.uio.no> Signed-off-by: Andrew Morton <akpm@osdl.org> Signed-off-by: Linus Torvalds <torvalds@osdl.org>
1 parent 3e5ea09 commit c293621

File tree

3 files changed

+55
-37
lines changed

3 files changed

+55
-37
lines changed

fs/fcntl.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
288288
break;
289289
case F_SETLK:
290290
case F_SETLKW:
291-
err = fcntl_setlk(filp, cmd, (struct flock __user *) arg);
291+
err = fcntl_setlk(fd, filp, cmd, (struct flock __user *) arg);
292292
break;
293293
case F_GETOWN:
294294
/*
@@ -376,7 +376,8 @@ asmlinkage long sys_fcntl64(unsigned int fd, unsigned int cmd, unsigned long arg
376376
break;
377377
case F_SETLK64:
378378
case F_SETLKW64:
379-
err = fcntl_setlk64(filp, cmd, (struct flock64 __user *) arg);
379+
err = fcntl_setlk64(fd, filp, cmd,
380+
(struct flock64 __user *) arg);
380381
break;
381382
default:
382383
err = do_fcntl(fd, cmd, arg, filp);

fs/locks.c

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1591,7 +1591,8 @@ int fcntl_getlk(struct file *filp, struct flock __user *l)
15911591
/* Apply the lock described by l to an open file descriptor.
15921592
* This implements both the F_SETLK and F_SETLKW commands of fcntl().
15931593
*/
1594-
int fcntl_setlk(struct file *filp, unsigned int cmd, struct flock __user *l)
1594+
int fcntl_setlk(unsigned int fd, struct file *filp, unsigned int cmd,
1595+
struct flock __user *l)
15951596
{
15961597
struct file_lock *file_lock = locks_alloc_lock();
15971598
struct flock flock;
@@ -1620,6 +1621,7 @@ int fcntl_setlk(struct file *filp, unsigned int cmd, struct flock __user *l)
16201621
goto out;
16211622
}
16221623

1624+
again:
16231625
error = flock_to_posix_lock(filp, file_lock, &flock);
16241626
if (error)
16251627
goto out;
@@ -1648,25 +1650,33 @@ int fcntl_setlk(struct file *filp, unsigned int cmd, struct flock __user *l)
16481650
if (error)
16491651
goto out;
16501652

1651-
if (filp->f_op && filp->f_op->lock != NULL) {
1653+
if (filp->f_op && filp->f_op->lock != NULL)
16521654
error = filp->f_op->lock(filp, cmd, file_lock);
1653-
goto out;
1654-
}
1655+
else {
1656+
for (;;) {
1657+
error = __posix_lock_file(inode, file_lock);
1658+
if ((error != -EAGAIN) || (cmd == F_SETLK))
1659+
break;
1660+
error = wait_event_interruptible(file_lock->fl_wait,
1661+
!file_lock->fl_next);
1662+
if (!error)
1663+
continue;
16551664

1656-
for (;;) {
1657-
error = __posix_lock_file(inode, file_lock);
1658-
if ((error != -EAGAIN) || (cmd == F_SETLK))
1665+
locks_delete_block(file_lock);
16591666
break;
1660-
error = wait_event_interruptible(file_lock->fl_wait,
1661-
!file_lock->fl_next);
1662-
if (!error)
1663-
continue;
1667+
}
1668+
}
16641669

1665-
locks_delete_block(file_lock);
1666-
break;
1670+
/*
1671+
* Attempt to detect a close/fcntl race and recover by
1672+
* releasing the lock that was just acquired.
1673+
*/
1674+
if (!error && fcheck(fd) != filp && flock.l_type != F_UNLCK) {
1675+
flock.l_type = F_UNLCK;
1676+
goto again;
16671677
}
16681678

1669-
out:
1679+
out:
16701680
locks_free_lock(file_lock);
16711681
return error;
16721682
}
@@ -1724,7 +1734,8 @@ int fcntl_getlk64(struct file *filp, struct flock64 __user *l)
17241734
/* Apply the lock described by l to an open file descriptor.
17251735
* This implements both the F_SETLK and F_SETLKW commands of fcntl().
17261736
*/
1727-
int fcntl_setlk64(struct file *filp, unsigned int cmd, struct flock64 __user *l)
1737+
int fcntl_setlk64(unsigned int fd, struct file *filp, unsigned int cmd,
1738+
struct flock64 __user *l)
17281739
{
17291740
struct file_lock *file_lock = locks_alloc_lock();
17301741
struct flock64 flock;
@@ -1753,6 +1764,7 @@ int fcntl_setlk64(struct file *filp, unsigned int cmd, struct flock64 __user *l)
17531764
goto out;
17541765
}
17551766

1767+
again:
17561768
error = flock64_to_posix_lock(filp, file_lock, &flock);
17571769
if (error)
17581770
goto out;
@@ -1781,22 +1793,30 @@ int fcntl_setlk64(struct file *filp, unsigned int cmd, struct flock64 __user *l)
17811793
if (error)
17821794
goto out;
17831795

1784-
if (filp->f_op && filp->f_op->lock != NULL) {
1796+
if (filp->f_op && filp->f_op->lock != NULL)
17851797
error = filp->f_op->lock(filp, cmd, file_lock);
1786-
goto out;
1787-
}
1798+
else {
1799+
for (;;) {
1800+
error = __posix_lock_file(inode, file_lock);
1801+
if ((error != -EAGAIN) || (cmd == F_SETLK64))
1802+
break;
1803+
error = wait_event_interruptible(file_lock->fl_wait,
1804+
!file_lock->fl_next);
1805+
if (!error)
1806+
continue;
17881807

1789-
for (;;) {
1790-
error = __posix_lock_file(inode, file_lock);
1791-
if ((error != -EAGAIN) || (cmd == F_SETLK64))
1808+
locks_delete_block(file_lock);
17921809
break;
1793-
error = wait_event_interruptible(file_lock->fl_wait,
1794-
!file_lock->fl_next);
1795-
if (!error)
1796-
continue;
1810+
}
1811+
}
17971812

1798-
locks_delete_block(file_lock);
1799-
break;
1813+
/*
1814+
* Attempt to detect a close/fcntl race and recover by
1815+
* releasing the lock that was just acquired.
1816+
*/
1817+
if (!error && fcheck(fd) != filp && flock.l_type != F_UNLCK) {
1818+
flock.l_type = F_UNLCK;
1819+
goto again;
18001820
}
18011821

18021822
out:
@@ -1888,12 +1908,7 @@ void locks_remove_flock(struct file *filp)
18881908

18891909
while ((fl = *before) != NULL) {
18901910
if (fl->fl_file == filp) {
1891-
/*
1892-
* We might have a POSIX lock that was created at the same time
1893-
* the filp was closed for the last time. Just remove that too,
1894-
* regardless of ownership, since nobody can own it.
1895-
*/
1896-
if (IS_FLOCK(fl) || IS_POSIX(fl)) {
1911+
if (IS_FLOCK(fl)) {
18971912
locks_delete_lock(before);
18981913
continue;
18991914
}

include/linux/fs.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -697,11 +697,13 @@ extern struct list_head file_lock_list;
697697
#include <linux/fcntl.h>
698698

699699
extern int fcntl_getlk(struct file *, struct flock __user *);
700-
extern int fcntl_setlk(struct file *, unsigned int, struct flock __user *);
700+
extern int fcntl_setlk(unsigned int, struct file *, unsigned int,
701+
struct flock __user *);
701702

702703
#if BITS_PER_LONG == 32
703704
extern int fcntl_getlk64(struct file *, struct flock64 __user *);
704-
extern int fcntl_setlk64(struct file *, unsigned int, struct flock64 __user *);
705+
extern int fcntl_setlk64(unsigned int, struct file *, unsigned int,
706+
struct flock64 __user *);
705707
#endif
706708

707709
extern void send_sigio(struct fown_struct *fown, int fd, int band);

0 commit comments

Comments
 (0)