diff options
author | Kirill Tkhai <ktkhai@virtuozzo.com> | 2018-04-05 07:58:06 -0400 |
---|---|---|
committer | Jeff Layton <jlayton@redhat.com> | 2018-05-01 07:39:50 -0400 |
commit | 7a107c0f55a3b4c6f84a4323df5610360bde1684 (patch) | |
tree | 776ce70555dbb4714b1ea10f6f10d06a767f0294 /fs/fcntl.c | |
parent | fff75eb2a08c2ac96404a2d79685668f3cf5a7a3 (diff) |
fasync: Fix deadlock between task-context and interrupt-context kill_fasync()
I observed the following deadlock between them:
[task 1] [task 2] [task 3]
kill_fasync() mm_update_next_owner() copy_process()
spin_lock_irqsave(&fa->fa_lock) read_lock(&tasklist_lock) write_lock_irq(&tasklist_lock)
send_sigio() <IRQ> ...
read_lock(&fown->lock) kill_fasync() ...
read_lock(&tasklist_lock) spin_lock_irqsave(&fa->fa_lock) ...
Task 1 can't acquire read locked tasklist_lock, since there is
already task 3 expressed its wish to take the lock exclusive.
Task 2 holds the read locked lock, but it can't take the spin lock.
Also, there is possible another deadlock (which I haven't observed):
[task 1] [task 2]
f_getown() kill_fasync()
read_lock(&f_own->lock) spin_lock_irqsave(&fa->fa_lock,)
<IRQ> send_sigio() write_lock_irq(&f_own->lock)
kill_fasync() read_lock(&fown->lock)
spin_lock_irqsave(&fa->fa_lock,)
Actually, we do not need exclusive fa->fa_lock in kill_fasync_rcu(),
as it guarantees fa->fa_file->f_owner integrity only. It may seem,
that it used to give a task a small possibility to receive two sequential
signals, if there are two parallel kill_fasync() callers, and task
handles the first signal fastly, but the behaviour won't become
different, since there is exclusive sighand lock in do_send_sig_info().
The patch converts fa_lock into rwlock_t, and this fixes two above
deadlocks, as rwlock is allowed to be taken from interrupt handler
by qrwlock design.
Signed-off-by: Kirill Tkhai <ktkhai@virtuozzo.com>
Signed-off-by: Jeff Layton <jlayton@redhat.com>
Diffstat (limited to 'fs/fcntl.c')
-rw-r--r-- | fs/fcntl.c | 15 |
1 files changed, 7 insertions, 8 deletions
diff --git a/fs/fcntl.c b/fs/fcntl.c index d737ff082472..c42169459298 100644 --- a/fs/fcntl.c +++ b/fs/fcntl.c | |||
@@ -871,9 +871,9 @@ int fasync_remove_entry(struct file *filp, struct fasync_struct **fapp) | |||
871 | if (fa->fa_file != filp) | 871 | if (fa->fa_file != filp) |
872 | continue; | 872 | continue; |
873 | 873 | ||
874 | spin_lock_irq(&fa->fa_lock); | 874 | write_lock_irq(&fa->fa_lock); |
875 | fa->fa_file = NULL; | 875 | fa->fa_file = NULL; |
876 | spin_unlock_irq(&fa->fa_lock); | 876 | write_unlock_irq(&fa->fa_lock); |
877 | 877 | ||
878 | *fp = fa->fa_next; | 878 | *fp = fa->fa_next; |
879 | call_rcu(&fa->fa_rcu, fasync_free_rcu); | 879 | call_rcu(&fa->fa_rcu, fasync_free_rcu); |
@@ -918,13 +918,13 @@ struct fasync_struct *fasync_insert_entry(int fd, struct file *filp, struct fasy | |||
918 | if (fa->fa_file != filp) | 918 | if (fa->fa_file != filp) |
919 | continue; | 919 | continue; |
920 | 920 | ||
921 | spin_lock_irq(&fa->fa_lock); | 921 | write_lock_irq(&fa->fa_lock); |
922 | fa->fa_fd = fd; | 922 | fa->fa_fd = fd; |
923 | spin_unlock_irq(&fa->fa_lock); | 923 | write_unlock_irq(&fa->fa_lock); |
924 | goto out; | 924 | goto out; |
925 | } | 925 | } |
926 | 926 | ||
927 | spin_lock_init(&new->fa_lock); | 927 | rwlock_init(&new->fa_lock); |
928 | new->magic = FASYNC_MAGIC; | 928 | new->magic = FASYNC_MAGIC; |
929 | new->fa_file = filp; | 929 | new->fa_file = filp; |
930 | new->fa_fd = fd; | 930 | new->fa_fd = fd; |
@@ -987,14 +987,13 @@ static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band) | |||
987 | { | 987 | { |
988 | while (fa) { | 988 | while (fa) { |
989 | struct fown_struct *fown; | 989 | struct fown_struct *fown; |
990 | unsigned long flags; | ||
991 | 990 | ||
992 | if (fa->magic != FASYNC_MAGIC) { | 991 | if (fa->magic != FASYNC_MAGIC) { |
993 | printk(KERN_ERR "kill_fasync: bad magic number in " | 992 | printk(KERN_ERR "kill_fasync: bad magic number in " |
994 | "fasync_struct!\n"); | 993 | "fasync_struct!\n"); |
995 | return; | 994 | return; |
996 | } | 995 | } |
997 | spin_lock_irqsave(&fa->fa_lock, flags); | 996 | read_lock(&fa->fa_lock); |
998 | if (fa->fa_file) { | 997 | if (fa->fa_file) { |
999 | fown = &fa->fa_file->f_owner; | 998 | fown = &fa->fa_file->f_owner; |
1000 | /* Don't send SIGURG to processes which have not set a | 999 | /* Don't send SIGURG to processes which have not set a |
@@ -1003,7 +1002,7 @@ static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band) | |||
1003 | if (!(sig == SIGURG && fown->signum == 0)) | 1002 | if (!(sig == SIGURG && fown->signum == 0)) |
1004 | send_sigio(fown, fa->fa_fd, band); | 1003 | send_sigio(fown, fa->fa_fd, band); |
1005 | } | 1004 | } |
1006 | spin_unlock_irqrestore(&fa->fa_lock, flags); | 1005 | read_unlock(&fa->fa_lock); |
1007 | fa = rcu_dereference(fa->fa_next); | 1006 | fa = rcu_dereference(fa->fa_next); |
1008 | } | 1007 | } |
1009 | } | 1008 | } |