mirror of
https://github.com/torvalds/linux.git
synced 2024-12-05 18:41:23 +00:00
640969a69c
speakup_cut() calls speakup_clear_selection() which calls console_lock. Problem is: speakup_cut() is called from a keyboard interrupt context. This would hang if speakup_cut is pressed while the console lock is unfortunately already held. We can however as well just defer calling clear_selection() until the already-deferred set_selection_kernel() call. This was spotted by the lock hardener: Possible unsafe locking scenario:\x0a CPU0 ---- lock(console_lock); <Interrupt> lock(console_lock); \x0a *** DEADLOCK ***\x0a [...] Call Trace: <IRQ> dump_stack+0xc2/0x11a print_usage_bug.cold+0x3e0/0x4b1 mark_lock+0xd95/0x1390 ? print_irq_inversion_bug+0xa0/0xa0 __lock_acquire+0x21eb/0x5730 ? __kasan_check_read+0x11/0x20 ? check_chain_key+0x215/0x5e0 ? register_lock_class+0x1580/0x1580 ? lock_downgrade+0x7a0/0x7a0 ? __rwlock_init+0x140/0x140 lock_acquire+0x13f/0x370 ? speakup_clear_selection+0xe/0x20 [speakup] console_lock+0x33/0x50 ? speakup_clear_selection+0xe/0x20 [speakup] speakup_clear_selection+0xe/0x20 [speakup] speakup_cut+0x19e/0x4b0 [speakup] keyboard_notifier_call+0x1f04/0x4a40 [speakup] ? read_all_doc+0x240/0x240 [speakup] notifier_call_chain+0xbf/0x130 __atomic_notifier_call_chain+0x80/0x130 atomic_notifier_call_chain+0x16/0x20 kbd_event+0x7d7/0x3b20 ? k_pad+0x850/0x850 ? sysrq_filter+0x450/0xd40 input_to_handler+0x362/0x4b0 ? rcu_read_lock_sched_held+0xe0/0xe0 input_pass_values+0x408/0x5a0 ? __rwlock_init+0x140/0x140 ? lock_acquire+0x13f/0x370 input_handle_event+0x70e/0x1380 input_event+0x67/0x90 atkbd_interrupt+0xe62/0x1d4e [atkbd] ? __kasan_check_write+0x14/0x20 ? atkbd_event_work+0x130/0x130 [atkbd] ? _raw_spin_lock_irqsave+0x26/0x70 serio_interrupt+0x93/0x120 [serio] i8042_interrupt+0x232/0x510 [i8042] ? rcu_read_lock_bh_held+0xd0/0xd0 ? handle_irq_event+0xa5/0x13a ? i8042_remove+0x1f0/0x1f0 [i8042] __handle_irq_event_percpu+0xe6/0x6c0 handle_irq_event_percpu+0x71/0x150 ? __handle_irq_event_percpu+0x6c0/0x6c0 ? __kasan_check_read+0x11/0x20 ? do_raw_spin_unlock+0x5c/0x240 handle_irq_event+0xad/0x13a handle_edge_irq+0x233/0xa90 do_IRQ+0x10b/0x310 common_interrupt+0xf/0xf </IRQ> Cc: stable@vger.kernel.org Reported-by: Jookia <contact@jookia.org> Signed-off-by: Samuel Thibault <samuel.thibault@ens-lyon.org> Link: https://lore.kernel.org/r/20201107233310.7iisvaozpiqj3yvy@function Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
142 lines
3.5 KiB
C
142 lines
3.5 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/slab.h> /* for kmalloc */
|
|
#include <linux/consolemap.h>
|
|
#include <linux/interrupt.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/device.h> /* for dev_warn */
|
|
#include <linux/selection.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/tty.h>
|
|
#include <linux/tty_flip.h>
|
|
#include <linux/atomic.h>
|
|
#include <linux/console.h>
|
|
|
|
#include "speakup.h"
|
|
|
|
unsigned short spk_xs, spk_ys, spk_xe, spk_ye; /* our region points */
|
|
struct vc_data *spk_sel_cons;
|
|
|
|
struct speakup_selection_work {
|
|
struct work_struct work;
|
|
struct tiocl_selection sel;
|
|
struct tty_struct *tty;
|
|
};
|
|
|
|
static void __speakup_set_selection(struct work_struct *work)
|
|
{
|
|
struct speakup_selection_work *ssw =
|
|
container_of(work, struct speakup_selection_work, work);
|
|
|
|
struct tty_struct *tty;
|
|
struct tiocl_selection sel;
|
|
|
|
sel = ssw->sel;
|
|
|
|
/* this ensures we copy sel before releasing the lock below */
|
|
rmb();
|
|
|
|
/* release the lock by setting tty of the struct to NULL */
|
|
tty = xchg(&ssw->tty, NULL);
|
|
|
|
if (spk_sel_cons != vc_cons[fg_console].d) {
|
|
spk_sel_cons = vc_cons[fg_console].d;
|
|
pr_warn("Selection: mark console not the same as cut\n");
|
|
goto unref;
|
|
}
|
|
|
|
console_lock();
|
|
clear_selection();
|
|
console_unlock();
|
|
|
|
set_selection_kernel(&sel, tty);
|
|
|
|
unref:
|
|
tty_kref_put(tty);
|
|
}
|
|
|
|
static struct speakup_selection_work speakup_sel_work = {
|
|
.work = __WORK_INITIALIZER(speakup_sel_work.work,
|
|
__speakup_set_selection)
|
|
};
|
|
|
|
int speakup_set_selection(struct tty_struct *tty)
|
|
{
|
|
/* we get kref here first in order to avoid a subtle race when
|
|
* cancelling selection work. getting kref first establishes the
|
|
* invariant that if speakup_sel_work.tty is not NULL when
|
|
* speakup_cancel_selection() is called, it must be the case that a put
|
|
* kref is pending.
|
|
*/
|
|
tty_kref_get(tty);
|
|
if (cmpxchg(&speakup_sel_work.tty, NULL, tty)) {
|
|
tty_kref_put(tty);
|
|
return -EBUSY;
|
|
}
|
|
/* now we have the 'lock' by setting tty member of
|
|
* speakup_selection_work. wmb() ensures that writes to
|
|
* speakup_sel_work don't happen before cmpxchg() above.
|
|
*/
|
|
wmb();
|
|
|
|
speakup_sel_work.sel.xs = spk_xs + 1;
|
|
speakup_sel_work.sel.ys = spk_ys + 1;
|
|
speakup_sel_work.sel.xe = spk_xe + 1;
|
|
speakup_sel_work.sel.ye = spk_ye + 1;
|
|
speakup_sel_work.sel.sel_mode = TIOCL_SELCHAR;
|
|
|
|
schedule_work_on(WORK_CPU_UNBOUND, &speakup_sel_work.work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void speakup_cancel_selection(void)
|
|
{
|
|
struct tty_struct *tty;
|
|
|
|
cancel_work_sync(&speakup_sel_work.work);
|
|
/* setting to null so that if work fails to run and we cancel it,
|
|
* we can run it again without getting EBUSY forever from there on.
|
|
* we need to use xchg here to avoid race with speakup_set_selection()
|
|
*/
|
|
tty = xchg(&speakup_sel_work.tty, NULL);
|
|
if (tty)
|
|
tty_kref_put(tty);
|
|
}
|
|
|
|
static void __speakup_paste_selection(struct work_struct *work)
|
|
{
|
|
struct speakup_selection_work *ssw =
|
|
container_of(work, struct speakup_selection_work, work);
|
|
struct tty_struct *tty = xchg(&ssw->tty, NULL);
|
|
|
|
paste_selection(tty);
|
|
tty_kref_put(tty);
|
|
}
|
|
|
|
static struct speakup_selection_work speakup_paste_work = {
|
|
.work = __WORK_INITIALIZER(speakup_paste_work.work,
|
|
__speakup_paste_selection)
|
|
};
|
|
|
|
int speakup_paste_selection(struct tty_struct *tty)
|
|
{
|
|
tty_kref_get(tty);
|
|
if (cmpxchg(&speakup_paste_work.tty, NULL, tty)) {
|
|
tty_kref_put(tty);
|
|
return -EBUSY;
|
|
}
|
|
|
|
schedule_work_on(WORK_CPU_UNBOUND, &speakup_paste_work.work);
|
|
return 0;
|
|
}
|
|
|
|
void speakup_cancel_paste(void)
|
|
{
|
|
struct tty_struct *tty;
|
|
|
|
cancel_work_sync(&speakup_paste_work.work);
|
|
tty = xchg(&speakup_paste_work.tty, NULL);
|
|
if (tty)
|
|
tty_kref_put(tty);
|
|
}
|