]> git.proxmox.com Git - mirror_ubuntu-hirsute-kernel.git/commitdiff
fcntl: fix potential deadlocks for &fown_struct.lock
authorDesmond Cheong Zhi Xi <desmondcheongzx@gmail.com>
Fri, 2 Jul 2021 09:18:30 +0000 (17:18 +0800)
committerStefan Bader <stefan.bader@canonical.com>
Fri, 15 Oct 2021 09:27:42 +0000 (11:27 +0200)
BugLink: https://bugs.launchpad.net/bugs/1946788
[ Upstream commit f671a691e299f58835d4660d642582bf0e8f6fda ]

Syzbot reports a potential deadlock in do_fcntl:

========================================================
WARNING: possible irq lock inversion dependency detected
5.12.0-syzkaller #0 Not tainted
--------------------------------------------------------
syz-executor132/8391 just changed the state of lock:
ffff888015967bf8 (&f->f_owner.lock){.+..}-{2:2}, at: f_getown_ex fs/fcntl.c:211 [inline]
ffff888015967bf8 (&f->f_owner.lock){.+..}-{2:2}, at: do_fcntl+0x8b4/0x1200 fs/fcntl.c:395
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 ***

This happens because there is a lock hierarchy of
&dev->event_lock --> &new->fa_lock --> &f->f_owner.lock
from the following call chain:

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

However, since &dev->event_lock is HARDIRQ-safe, interrupts have to be
disabled while grabbing &f->f_owner.lock, otherwise we invert the lock
hierarchy.

Hence, we replace calls to read_lock/read_unlock on &f->f_owner.lock,
with read_lock_irq/read_unlock_irq.

Reported-and-tested-by: syzbot+e6d5398a02c516ce5e70@syzkaller.appspotmail.com
Signed-off-by: Desmond Cheong Zhi Xi <desmondcheongzx@gmail.com>
Signed-off-by: Jeff Layton <jlayton@kernel.org>
Signed-off-by: Sasha Levin <sashal@kernel.org>
Signed-off-by: Kamal Mostafa <kamal@canonical.com>
Signed-off-by: Kelsey Skunberg <kelsey.skunberg@canonical.com>
fs/fcntl.c

index 96bc0e48b746708b17e9097b75e2d8f9223155aa..89427572ab6187d4fdb5e82186e17675e289d745 100644 (file)
@@ -152,11 +152,12 @@ void f_delown(struct file *filp)
 pid_t f_getown(struct file *filp)
 {
        pid_t pid;
-       read_lock(&filp->f_owner.lock);
+
+       read_lock_irq(&filp->f_owner.lock);
        pid = pid_vnr(filp->f_owner.pid);
        if (filp->f_owner.pid_type == PIDTYPE_PGID)
                pid = -pid;
-       read_unlock(&filp->f_owner.lock);
+       read_unlock_irq(&filp->f_owner.lock);
        return pid;
 }
 
@@ -206,7 +207,7 @@ static int f_getown_ex(struct file *filp, unsigned long arg)
        struct f_owner_ex owner;
        int ret = 0;
 
-       read_lock(&filp->f_owner.lock);
+       read_lock_irq(&filp->f_owner.lock);
        owner.pid = pid_vnr(filp->f_owner.pid);
        switch (filp->f_owner.pid_type) {
        case PIDTYPE_PID:
@@ -226,7 +227,7 @@ static int f_getown_ex(struct file *filp, unsigned long arg)
                ret = -EINVAL;
                break;
        }
-       read_unlock(&filp->f_owner.lock);
+       read_unlock_irq(&filp->f_owner.lock);
 
        if (!ret) {
                ret = copy_to_user(owner_p, &owner, sizeof(owner));
@@ -244,10 +245,10 @@ static int f_getowner_uids(struct file *filp, unsigned long arg)
        uid_t src[2];
        int err;
 
-       read_lock(&filp->f_owner.lock);
+       read_lock_irq(&filp->f_owner.lock);
        src[0] = from_kuid(user_ns, filp->f_owner.uid);
        src[1] = from_kuid(user_ns, filp->f_owner.euid);
-       read_unlock(&filp->f_owner.lock);
+       read_unlock_irq(&filp->f_owner.lock);
 
        err  = put_user(src[0], &dst[0]);
        err |= put_user(src[1], &dst[1]);