summaryrefslogtreecommitdiffstats
path: root/fs/fcntl.c
diff options
context:
space:
mode:
authorKirill Tkhai <ktkhai@virtuozzo.com>2018-04-05 07:58:06 -0400
committerJeff Layton <jlayton@redhat.com>2018-05-01 07:39:50 -0400
commit7a107c0f55a3b4c6f84a4323df5610360bde1684 (patch)
tree776ce70555dbb4714b1ea10f6f10d06a767f0294 /fs/fcntl.c
parentfff75eb2a08c2ac96404a2d79685668f3cf5a7a3 (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.c15
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}