Commit 7b667ced authored by Jeff Layton's avatar Jeff Layton Committed by Greg Kroah-Hartman

locks: fix unlock when fcntl_setlk races with a close

commit 7f3697e2 upstream.

Dmitry reported that he was able to reproduce the WARN_ON_ONCE that
fires in locks_free_lock_context when the flc_posix list isn't empty.

The problem turns out to be that we're basically rebuilding the
file_lock from scratch in fcntl_setlk when we discover that the setlk
has raced with a close. If the l_whence field is SEEK_CUR or SEEK_END,
then we may end up with fl_start and fl_end values that differ from
when the lock was initially set, if the file position or length of the
file has changed in the interim.

Fix this by just reusing the same lock request structure, and simply
override fl_type value with F_UNLCK as appropriate. That ensures that
we really are unlocking the lock that was initially set.

While we're there, make sure that we do pop a WARN_ON_ONCE if the
removal ever fails. Also return -EBADF in this event, since that's
what we would have returned if the close had happened earlier.

Cc: Alexander Viro <viro@zeniv.linux.org.uk>
Fixes: c293621b (stale POSIX lock handling)
Reported-by: default avatarDmitry Vyukov <dvyukov@google.com>
Signed-off-by: default avatarJeff Layton <jeff.layton@primarydata.com>
Acked-by: default avatar"J. Bruce Fields" <bfields@fieldses.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 8a3b2a49
...@@ -2007,7 +2007,6 @@ int fcntl_setlk(unsigned int fd, struct file *filp, unsigned int cmd, ...@@ -2007,7 +2007,6 @@ int fcntl_setlk(unsigned int fd, struct file *filp, unsigned int cmd,
goto out; goto out;
} }
again:
error = flock_to_posix_lock(filp, file_lock, &flock); error = flock_to_posix_lock(filp, file_lock, &flock);
if (error) if (error)
goto out; goto out;
...@@ -2038,19 +2037,22 @@ int fcntl_setlk(unsigned int fd, struct file *filp, unsigned int cmd, ...@@ -2038,19 +2037,22 @@ int fcntl_setlk(unsigned int fd, struct file *filp, unsigned int cmd,
* Attempt to detect a close/fcntl race and recover by * Attempt to detect a close/fcntl race and recover by
* releasing the lock that was just acquired. * releasing the lock that was just acquired.
*/ */
/* if (!error && file_lock->fl_type != F_UNLCK) {
* we need that spin_lock here - it prevents reordering between /*
* update of inode->i_flock and check for it done in close(). * We need that spin_lock here - it prevents reordering between
* rcu_read_lock() wouldn't do. * update of inode->i_flock and check for it done in
*/ * close(). rcu_read_lock() wouldn't do.
spin_lock(&current->files->file_lock); */
f = fcheck(fd); spin_lock(&current->files->file_lock);
spin_unlock(&current->files->file_lock); f = fcheck(fd);
if (!error && f != filp && flock.l_type != F_UNLCK) { spin_unlock(&current->files->file_lock);
flock.l_type = F_UNLCK; if (f != filp) {
goto again; file_lock->fl_type = F_UNLCK;
error = do_lock_file_wait(filp, cmd, file_lock);
WARN_ON_ONCE(error);
error = -EBADF;
}
} }
out: out:
locks_free_lock(file_lock); locks_free_lock(file_lock);
return error; return error;
...@@ -2125,7 +2127,6 @@ int fcntl_setlk64(unsigned int fd, struct file *filp, unsigned int cmd, ...@@ -2125,7 +2127,6 @@ int fcntl_setlk64(unsigned int fd, struct file *filp, unsigned int cmd,
goto out; goto out;
} }
again:
error = flock64_to_posix_lock(filp, file_lock, &flock); error = flock64_to_posix_lock(filp, file_lock, &flock);
if (error) if (error)
goto out; goto out;
...@@ -2156,14 +2157,22 @@ int fcntl_setlk64(unsigned int fd, struct file *filp, unsigned int cmd, ...@@ -2156,14 +2157,22 @@ int fcntl_setlk64(unsigned int fd, struct file *filp, unsigned int cmd,
* Attempt to detect a close/fcntl race and recover by * Attempt to detect a close/fcntl race and recover by
* releasing the lock that was just acquired. * releasing the lock that was just acquired.
*/ */
spin_lock(&current->files->file_lock); if (!error && file_lock->fl_type != F_UNLCK) {
f = fcheck(fd); /*
spin_unlock(&current->files->file_lock); * We need that spin_lock here - it prevents reordering between
if (!error && f != filp && flock.l_type != F_UNLCK) { * update of inode->i_flock and check for it done in
flock.l_type = F_UNLCK; * close(). rcu_read_lock() wouldn't do.
goto again; */
spin_lock(&current->files->file_lock);
f = fcheck(fd);
spin_unlock(&current->files->file_lock);
if (f != filp) {
file_lock->fl_type = F_UNLCK;
error = do_lock_file_wait(filp, cmd, file_lock);
WARN_ON_ONCE(error);
error = -EBADF;
}
} }
out: out:
locks_free_lock(file_lock); locks_free_lock(file_lock);
return error; return error;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment