Skip to content

Commit

Permalink
[PATCH] stale POSIX lock handling
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Peter Staubach authored and Linus Torvalds committed Jul 27, 2005
1 parent 3e5ea09 commit c293621
Show file tree
Hide file tree
Showing 3 changed files with 55 additions and 37 deletions.
5 changes: 3 additions & 2 deletions fs/fcntl.c
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ static long do_fcntl(int fd, unsigned int cmd, unsigned long arg,
break;
case F_SETLK:
case F_SETLKW:
err = fcntl_setlk(filp, cmd, (struct flock __user *) arg);
err = fcntl_setlk(fd, filp, cmd, (struct flock __user *) arg);
break;
case F_GETOWN:
/*
Expand Down Expand Up @@ -376,7 +376,8 @@ asmlinkage long sys_fcntl64(unsigned int fd, unsigned int cmd, unsigned long arg
break;
case F_SETLK64:
case F_SETLKW64:
err = fcntl_setlk64(filp, cmd, (struct flock64 __user *) arg);
err = fcntl_setlk64(fd, filp, cmd,
(struct flock64 __user *) arg);
break;
default:
err = do_fcntl(fd, cmd, arg, filp);
Expand Down
81 changes: 48 additions & 33 deletions fs/locks.c
Original file line number Diff line number Diff line change
Expand Up @@ -1591,7 +1591,8 @@ int fcntl_getlk(struct file *filp, struct flock __user *l)
/* Apply the lock described by l to an open file descriptor.
* This implements both the F_SETLK and F_SETLKW commands of fcntl().
*/
int fcntl_setlk(struct file *filp, unsigned int cmd, struct flock __user *l)
int fcntl_setlk(unsigned int fd, struct file *filp, unsigned int cmd,
struct flock __user *l)
{
struct file_lock *file_lock = locks_alloc_lock();
struct flock flock;
Expand Down Expand Up @@ -1620,6 +1621,7 @@ int fcntl_setlk(struct file *filp, unsigned int cmd, struct flock __user *l)
goto out;
}

again:
error = flock_to_posix_lock(filp, file_lock, &flock);
if (error)
goto out;
Expand Down Expand Up @@ -1648,25 +1650,33 @@ int fcntl_setlk(struct file *filp, unsigned int cmd, struct flock __user *l)
if (error)
goto out;

if (filp->f_op && filp->f_op->lock != NULL) {
if (filp->f_op && filp->f_op->lock != NULL)
error = filp->f_op->lock(filp, cmd, file_lock);
goto out;
}
else {
for (;;) {
error = __posix_lock_file(inode, file_lock);
if ((error != -EAGAIN) || (cmd == F_SETLK))
break;
error = wait_event_interruptible(file_lock->fl_wait,
!file_lock->fl_next);
if (!error)
continue;

for (;;) {
error = __posix_lock_file(inode, file_lock);
if ((error != -EAGAIN) || (cmd == F_SETLK))
locks_delete_block(file_lock);
break;
error = wait_event_interruptible(file_lock->fl_wait,
!file_lock->fl_next);
if (!error)
continue;
}
}

locks_delete_block(file_lock);
break;
/*
* Attempt to detect a close/fcntl race and recover by
* releasing the lock that was just acquired.
*/
if (!error && fcheck(fd) != filp && flock.l_type != F_UNLCK) {
flock.l_type = F_UNLCK;
goto again;
}

out:
out:
locks_free_lock(file_lock);
return error;
}
Expand Down Expand Up @@ -1724,7 +1734,8 @@ int fcntl_getlk64(struct file *filp, struct flock64 __user *l)
/* Apply the lock described by l to an open file descriptor.
* This implements both the F_SETLK and F_SETLKW commands of fcntl().
*/
int fcntl_setlk64(struct file *filp, unsigned int cmd, struct flock64 __user *l)
int fcntl_setlk64(unsigned int fd, struct file *filp, unsigned int cmd,
struct flock64 __user *l)
{
struct file_lock *file_lock = locks_alloc_lock();
struct flock64 flock;
Expand Down Expand Up @@ -1753,6 +1764,7 @@ int fcntl_setlk64(struct file *filp, unsigned int cmd, struct flock64 __user *l)
goto out;
}

again:
error = flock64_to_posix_lock(filp, file_lock, &flock);
if (error)
goto out;
Expand Down Expand Up @@ -1781,22 +1793,30 @@ int fcntl_setlk64(struct file *filp, unsigned int cmd, struct flock64 __user *l)
if (error)
goto out;

if (filp->f_op && filp->f_op->lock != NULL) {
if (filp->f_op && filp->f_op->lock != NULL)
error = filp->f_op->lock(filp, cmd, file_lock);
goto out;
}
else {
for (;;) {
error = __posix_lock_file(inode, file_lock);
if ((error != -EAGAIN) || (cmd == F_SETLK64))
break;
error = wait_event_interruptible(file_lock->fl_wait,
!file_lock->fl_next);
if (!error)
continue;

for (;;) {
error = __posix_lock_file(inode, file_lock);
if ((error != -EAGAIN) || (cmd == F_SETLK64))
locks_delete_block(file_lock);
break;
error = wait_event_interruptible(file_lock->fl_wait,
!file_lock->fl_next);
if (!error)
continue;
}
}

locks_delete_block(file_lock);
break;
/*
* Attempt to detect a close/fcntl race and recover by
* releasing the lock that was just acquired.
*/
if (!error && fcheck(fd) != filp && flock.l_type != F_UNLCK) {
flock.l_type = F_UNLCK;
goto again;
}

out:
Expand Down Expand Up @@ -1888,12 +1908,7 @@ void locks_remove_flock(struct file *filp)

while ((fl = *before) != NULL) {
if (fl->fl_file == filp) {
/*
* We might have a POSIX lock that was created at the same time
* the filp was closed for the last time. Just remove that too,
* regardless of ownership, since nobody can own it.
*/
if (IS_FLOCK(fl) || IS_POSIX(fl)) {
if (IS_FLOCK(fl)) {
locks_delete_lock(before);
continue;
}
Expand Down
6 changes: 4 additions & 2 deletions include/linux/fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -697,11 +697,13 @@ extern struct list_head file_lock_list;
#include <linux/fcntl.h>

extern int fcntl_getlk(struct file *, struct flock __user *);
extern int fcntl_setlk(struct file *, unsigned int, struct flock __user *);
extern int fcntl_setlk(unsigned int, struct file *, unsigned int,
struct flock __user *);

#if BITS_PER_LONG == 32
extern int fcntl_getlk64(struct file *, struct flock64 __user *);
extern int fcntl_setlk64(struct file *, unsigned int, struct flock64 __user *);
extern int fcntl_setlk64(unsigned int, struct file *, unsigned int,
struct flock64 __user *);
#endif

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

0 comments on commit c293621

Please sign in to comment.