From: Boqun Feng <boqun.f...@gmail.com>

commit 8d1ddb5e79374fb277985a6b3faa2ed8631c5b4c upstream.

Syzbot reports a potential deadlock found by the newly added recursive
read deadlock detection in lockdep:

[...] ========================================================
[...] WARNING: possible irq lock inversion dependency detected
[...] 5.9.0-rc2-syzkaller #0 Not tainted
[...] --------------------------------------------------------
[...] syz-executor.1/10214 just changed the state of lock:
[...] ffff88811f506338 (&f->f_owner.lock){.+..}-{2:2}, at: 
send_sigurg+0x1d/0x200
[...] but this lock was taken by another, HARDIRQ-safe lock in the past:
[...]  (&dev->event_lock){-...}-{2:2}
[...]
[...]
[...] and interrupts could create inverse lock ordering between them.
[...]
[...]
[...] other info that might help us debug this:
[...] Chain exists of:
[...]   &dev->event_lock --> &new->fa_lock --> &f->f_owner.lock
[...]
[...]  Possible interrupt unsafe locking scenario:
[...]
[...]        CPU0                    CPU1
[...]        ----                    ----
[...]   lock(&f->f_owner.lock);
[...]                                local_irq_disable();
[...]                                lock(&dev->event_lock);
[...]                                lock(&new->fa_lock);
[...]   <Interrupt>
[...]     lock(&dev->event_lock);
[...]
[...]  *** DEADLOCK ***

The corresponding deadlock case is as followed:

        CPU 0           CPU 1           CPU 2
        read_lock(&fown->lock);
                        spin_lock_irqsave(&dev->event_lock, ...)
                                        write_lock_irq(&filp->f_owner.lock); // 
wait for the lock
                        read_lock(&fown-lock); // have to wait until the writer 
release
                                               // due to the fairness
        <interrupted>
        spin_lock_irqsave(&dev->event_lock); // wait for the lock

The lock dependency on CPU 1 happens if there exists a call sequence:

        input_inject_event():
          spin_lock_irqsave(&dev->event_lock,...);
          input_handle_event():
            input_pass_values():
              input_to_handler():
                handler->event(): // evdev_event()
                  evdev_pass_values():
                    spin_lock(&client->buffer_lock);
                    __pass_event():
                      kill_fasync():
                        kill_fasync_rcu():
                          read_lock(&fa->fa_lock);
                          send_sigio():
                            read_lock(&fown->lock);

To fix this, make the reader in send_sigurg() and send_sigio() use
read_lock_irqsave() and read_lock_irqrestore().

Reported-by: syzbot+22e87cdf94021b984...@syzkaller.appspotmail.com
Reported-by: syzbot+c5e32344981ad9f33...@syzkaller.appspotmail.com
Signed-off-by: Boqun Feng <boqun.f...@gmail.com>
Signed-off-by: Jeff Layton <jlay...@kernel.org>
Signed-off-by: Greg Kroah-Hartman <gre...@linuxfoundation.org>

---
 fs/fcntl.c |   10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

--- a/fs/fcntl.c
+++ b/fs/fcntl.c
@@ -779,9 +779,10 @@ void send_sigio(struct fown_struct *fown
 {
        struct task_struct *p;
        enum pid_type type;
+       unsigned long flags;
        struct pid *pid;
        
-       read_lock(&fown->lock);
+       read_lock_irqsave(&fown->lock, flags);
 
        type = fown->pid_type;
        pid = fown->pid;
@@ -802,7 +803,7 @@ void send_sigio(struct fown_struct *fown
                read_unlock(&tasklist_lock);
        }
  out_unlock_fown:
-       read_unlock(&fown->lock);
+       read_unlock_irqrestore(&fown->lock, flags);
 }
 
 static void send_sigurg_to_task(struct task_struct *p,
@@ -817,9 +818,10 @@ int send_sigurg(struct fown_struct *fown
        struct task_struct *p;
        enum pid_type type;
        struct pid *pid;
+       unsigned long flags;
        int ret = 0;
        
-       read_lock(&fown->lock);
+       read_lock_irqsave(&fown->lock, flags);
 
        type = fown->pid_type;
        pid = fown->pid;
@@ -842,7 +844,7 @@ int send_sigurg(struct fown_struct *fown
                read_unlock(&tasklist_lock);
        }
  out_unlock_fown:
-       read_unlock(&fown->lock);
+       read_unlock_irqrestore(&fown->lock, flags);
        return ret;
 }
 


Reply via email to