diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2009-08-03 14:54:56 -0700 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@suse.de> | 2009-08-04 13:46:30 -0700 |
commit | cbe9352fa08f90aa03b4dbf1bbabfc95d196e562 (patch) | |
tree | 960187b610e13e5ef7f461397bd0846a36221436 | |
parent | 65b770468e98941e45e19780dff9283e663e6b8b (diff) | |
download | linux-cbe9352fa08f90aa03b4dbf1bbabfc95d196e562.tar.gz linux-cbe9352fa08f90aa03b4dbf1bbabfc95d196e562.tar.bz2 linux-cbe9352fa08f90aa03b4dbf1bbabfc95d196e562.zip |
tty-ldisc: be more careful in 'put_ldisc' locking
Use 'atomic_dec_and_lock()' to make sure that we always hold the
tty_ldisc_lock when the ldisc count goes to zero. That way we can never
race against 'tty_ldisc_try()' increasing the count again.
Reported-by: OGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Tested-by: Sergey Senozhatsky <sergey.senozhatsky@mail.by>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
-rw-r--r-- | drivers/char/tty_ldisc.c | 17 |
1 files changed, 12 insertions, 5 deletions
diff --git a/drivers/char/tty_ldisc.c b/drivers/char/tty_ldisc.c index be55dfcf59ac..1733d3439ad2 100644 --- a/drivers/char/tty_ldisc.c +++ b/drivers/char/tty_ldisc.c @@ -55,25 +55,32 @@ static inline struct tty_ldisc *get_ldisc(struct tty_ldisc *ld) return ld; } -static inline void put_ldisc(struct tty_ldisc *ld) +static void put_ldisc(struct tty_ldisc *ld) { + unsigned long flags; + if (WARN_ON_ONCE(!ld)) return; /* * If this is the last user, free the ldisc, and * release the ldisc ops. + * + * We really want an "atomic_dec_and_lock_irqsave()", + * but we don't have it, so this does it by hand. */ - if (atomic_dec_and_test(&ld->users)) { - unsigned long flags; + local_irq_save(flags); + if (atomic_dec_and_lock(&ld->users, &tty_ldisc_lock)) { struct tty_ldisc_ops *ldo = ld->ops; - kfree(ld); - spin_lock_irqsave(&tty_ldisc_lock, flags); ldo->refcount--; module_put(ldo->owner); spin_unlock_irqrestore(&tty_ldisc_lock, flags); + + kfree(ld); + return; } + local_irq_restore(flags); } /** |