When the synth output cannot make progress, the catch-up loop in _spk_do_catch_up() (and its local copies in the serial synth drivers) can spin forever: nothing tells it that the synth is dead. This happens when the tty write keeps returning 0 because nothing consumes the data (e.g. a serial port with no device attached), or when an I/O error deactivates the synth and the loop never notices that synth->alive became 0. While speakup_thread() holds spk_mutex across the loop, any synth_add(), synth_remove() or synth_init() then blocks forever, reported as a hung task on module unload: mutex_lock+0x54/0x68 synth_remove+0x28/0xb8 [speakup] _spk_do_catch_up+... [speakup_apollo] Check synth->alive in the loops as well, so catch_up() stops as soon as the synth is dead. synth_release() writes it under speakup_info.spinlock and the I/O error paths write it without the lock; the loops only need to eventually observe the change. On its own this fixes the I/O error case, where the synth is already deactivated when the loop retries. In the no-room case nothing clears synth->alive and speakup_thread() still holds spk_mutex across the loop; the follow-up patch completes the fix by clearing synth->alive from synth_release() and by dropping spk_mutex from the catch_up() call. Fixes: 2067fd92d75b6 ("staging/speakup: Move out of staging") Cc: stable@vger.kernel.org Cc: Samuel Thibault Reviewed-by: Samuel Thibault Signed-off-by: Hongyu Xie --- drivers/accessibility/speakup/speakup_acntpc.c | 4 ++++ drivers/accessibility/speakup/speakup_apollo.c | 4 ++++ drivers/accessibility/speakup/speakup_decext.c | 4 ++++ drivers/accessibility/speakup/speakup_decpc.c | 4 ++++ drivers/accessibility/speakup/speakup_dectlk.c | 4 ++++ drivers/accessibility/speakup/speakup_dtlk.c | 4 ++++ drivers/accessibility/speakup/speakup_keypc.c | 4 ++++ drivers/accessibility/speakup/synth.c | 4 ++++ 8 files changed, 32 insertions(+) diff --git a/drivers/accessibility/speakup/speakup_acntpc.c b/drivers/accessibility/speakup/speakup_acntpc.c index a27e6bbf05da..fe19ba3f0005 100644 --- a/drivers/accessibility/speakup/speakup_acntpc.c +++ b/drivers/accessibility/speakup/speakup_acntpc.c @@ -192,6 +192,10 @@ static void do_catch_up(struct spk_synth *synth) jiff_max = jiffies + jiffy_delta_val; while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_apollo.c b/drivers/accessibility/speakup/speakup_apollo.c index d2fbb3f57221..b62696e050fc 100644 --- a/drivers/accessibility/speakup/speakup_apollo.c +++ b/drivers/accessibility/speakup/speakup_apollo.c @@ -157,6 +157,10 @@ static void do_catch_up(struct spk_synth *synth) while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } jiffy_delta_val = jiffy_delta->u.n.value; full_time_val = full_time->u.n.value; delay_time_val = delay_time->u.n.value; diff --git a/drivers/accessibility/speakup/speakup_decext.c b/drivers/accessibility/speakup/speakup_decext.c index 271bcf279bf9..e6be5a99670b 100644 --- a/drivers/accessibility/speakup/speakup_decext.c +++ b/drivers/accessibility/speakup/speakup_decext.c @@ -171,6 +171,10 @@ static void do_catch_up(struct spk_synth *synth) while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_decpc.c b/drivers/accessibility/speakup/speakup_decpc.c index 1bf36d1e5477..cfafd9475963 100644 --- a/drivers/accessibility/speakup/speakup_decpc.c +++ b/drivers/accessibility/speakup/speakup_decpc.c @@ -391,6 +391,10 @@ static void do_catch_up(struct spk_synth *synth) while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_dectlk.c b/drivers/accessibility/speakup/speakup_dectlk.c index 56334405d865..d9247c53d327 100644 --- a/drivers/accessibility/speakup/speakup_dectlk.c +++ b/drivers/accessibility/speakup/speakup_dectlk.c @@ -246,6 +246,10 @@ static void do_catch_up(struct spk_synth *synth) spin_unlock_irqrestore(&flush_lock, flags); spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_dtlk.c b/drivers/accessibility/speakup/speakup_dtlk.c index fa826568937b..73cd39e781b8 100644 --- a/drivers/accessibility/speakup/speakup_dtlk.c +++ b/drivers/accessibility/speakup/speakup_dtlk.c @@ -206,6 +206,10 @@ static void do_catch_up(struct spk_synth *synth) jiff_max = jiffies + jiffy_delta_val; while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/speakup_keypc.c b/drivers/accessibility/speakup/speakup_keypc.c index 9356f6379560..e229009107f7 100644 --- a/drivers/accessibility/speakup/speakup_keypc.c +++ b/drivers/accessibility/speakup/speakup_keypc.c @@ -193,6 +193,10 @@ static void do_catch_up(struct spk_synth *synth) jiff_max = jiffies + jiffy_delta_val; while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c index 11cba1fd8715..6a49ff475c16 100644 --- a/drivers/accessibility/speakup/synth.c +++ b/drivers/accessibility/speakup/synth.c @@ -75,6 +75,10 @@ static void _spk_do_catch_up(struct spk_synth *synth, int unicode) jiff_max = jiffies + jiffy_delta_val; while (!kthread_should_stop()) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth->alive) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + break; + } if (speakup_info.flushing) { speakup_info.flushing = 0; spin_unlock_irqrestore(&speakup_info.spinlock, flags); -- 2.32.0 speakup_thread() holds spk_mutex while calling synth->catch_up(). Apart from the indefinite retry loops fixed by the previous patch, a synth that digests its buffer slowly keeps the mutex held for the whole drain, so loading, unloading or switching any synth module blocks until all pending speech has been output, which may take arbitrarily long. Take spk_mutex off the catch_up() call path and keep the synth alive for the duration of the call instead: - speakup_thread() raises a catch-up count and releases spk_mutex before calling catch_up(), re-taking it afterwards. - synth_release() first clears synth->alive (which makes catch_up() return) and wakes the thread out of its sleep, then waits for the catch-up count to drop to zero before releasing the synth resources (sysfs attributes, variables, tty). The synth buffer is cleared there as well, so a switch discards the text still pending for the old synth instead of waiting for it to be output. As synth_remove() only returns after the drain, a synth module cannot be unloaded while catch_up() still runs, so the synth structure and its tty stay valid for the whole call without any module reference counting: CPU0 (speakup_thread) CPU1 (synth_release) ------------------- ------------------- atomic_inc(&synth_catch_up_count) mutex_unlock(&spk_mutex) synth->catch_up(synth) spin_lock(&speakup_info.spinlock) /* reads synth->alive, synth->alive = 0 synth->io_ops, the tty */ spin_unlock(...) wake the thread out of its sleep /* returns once ->alive wait_event(!synth_catch_up_count) is 0 (loop exit of the /* resources torn down only previous patch) */ after the count hits zero */ atomic_dec_and_test(...) wake_up(&synth_catch_up_waitq) synth->release(synth) The SPEECH_KILL key handler can revive the synth at any time: the is_alive callbacks set synth->alive back to 1, so a revival landing after synth_release() cleared it keeps the loop running and the drain blocks forever. Mark the synth with ->releasing under speakup_info.spinlock while it is being released, and have spk_synth_is_alive_restart() and spk_synth_is_alive_nop() skip the revival then, so the drain always terminates. The wake_up_process(speakup_task) sites (spk_do_flush(), do_synth_init(), synth_release()) get the same treatment: the keyboard notifier is registered before the kthread is created, so a key press in that window would pass NULL (or the kthread_create() error) to wake_up_process(). They now take the task pointer under rcu_read_lock() with READ_ONCE() and skip IS_ERR_OR_NULL() values; the exit path unregisters the notifiers first, and atomic_notifier_chain_unregister() synchronizes with the RCU readers, so by the time speakup_task is cleared no wakeup site is still running. After this change spk_mutex no longer serializes synth registration, unregistration and switching against speech output. This depends on the previous patch: the catch-up drain introduced here assumes that catch_up() eventually returns once synth->alive has been cleared, including in the local catch_up() copies of the serial synth drivers. Fixes: 2067fd92d75b6 ("staging/speakup: Move out of staging") Cc: stable@vger.kernel.org Cc: Samuel Thibault Reviewed-by: Samuel Thibault Signed-off-by: Hongyu Xie --- drivers/accessibility/speakup/spk_priv.h | 3 ++ drivers/accessibility/speakup/spk_types.h | 1 + drivers/accessibility/speakup/synth.c | 59 +++++++++++++++++++++-- drivers/accessibility/speakup/thread.c | 11 ++++- 4 files changed, 70 insertions(+), 4 deletions(-) diff --git a/drivers/accessibility/speakup/spk_priv.h b/drivers/accessibility/speakup/spk_priv.h index 9da57ead17cb..967862cd0440 100644 --- a/drivers/accessibility/speakup/spk_priv.h +++ b/drivers/accessibility/speakup/spk_priv.h @@ -75,6 +75,9 @@ struct spk_synth *synth_current(void); extern struct speakup_info_t speakup_info; +extern atomic_t synth_catch_up_count; +extern wait_queue_head_t synth_catch_up_waitq; + extern struct var_t synth_time_vars[]; extern struct spk_io_ops spk_serial_io_ops; diff --git a/drivers/accessibility/speakup/spk_types.h b/drivers/accessibility/speakup/spk_types.h index a5762330e249..efb931db1850 100644 --- a/drivers/accessibility/speakup/spk_types.h +++ b/drivers/accessibility/speakup/spk_types.h @@ -200,6 +200,7 @@ struct spk_synth { unsigned char (*get_index)(struct spk_synth *synth); struct synth_indexing indexing; int alive; + bool releasing; /* set while synth_release() drains this synth */ struct attribute_group attributes; void *dev; diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c index 6a49ff475c16..40870949b3c6 100644 --- a/drivers/accessibility/speakup/synth.c +++ b/drivers/accessibility/speakup/synth.c @@ -24,6 +24,14 @@ char spk_pitch_buff[32] = ""; static int module_status; bool spk_quiet_boot; +/* + * Number of catch_up() calls currently in flight, and waitqueue used by + * synth_release() to wait for them to finish before tearing down the + * synth resources they may still be using. + */ +atomic_t synth_catch_up_count = ATOMIC_INIT(0); +DECLARE_WAIT_QUEUE_HEAD(synth_catch_up_waitq); + struct speakup_info_t speakup_info = { /* * This spinlock is used to protect the entire speakup machinery, and @@ -154,6 +162,8 @@ EXPORT_SYMBOL_GPL(spk_synth_get_index); int spk_synth_is_alive_nop(struct spk_synth *synth) { + if (synth->releasing) + return 0; synth->alive = 1; return 1; } @@ -163,6 +173,18 @@ int spk_synth_is_alive_restart(struct spk_synth *synth) { if (synth->alive) return 1; + /* + * The callers run under speakup_info.spinlock (speakup_key() + * holds it across do_spkup()), which is also the lock + * synth_release() takes to clear ->alive, so checking + * ->releasing here cannot race it: a synth being released is + * not revived, or an in-flight catch_up() draining to a stuck + * synth would never see ->alive cleared and synth_release() + * would block forever on the catch-up count while holding + * spk_mutex. + */ + if (synth->releasing) + return 0; if (synth->io_ops->wait_for_xmitr(synth) > 0) { /* restart */ synth->alive = 1; @@ -197,6 +219,8 @@ void synth_start(void) void spk_do_flush(void) { + struct task_struct *task; + if (!synth) return; @@ -209,7 +233,11 @@ void spk_do_flush(void) } } wake_up_interruptible_all(&speakup_event); - wake_up_process(speakup_task); + rcu_read_lock(); + task = READ_ONCE(speakup_task); + if (!IS_ERR_OR_NULL(task)) + wake_up_process(task); + rcu_read_unlock(); } void synth_write(const char *_buf, size_t count) @@ -476,13 +504,18 @@ int synth_init(char *synth_name) /* called by: synth_add() */ static int do_synth_init(struct spk_synth *in_synth) { + struct task_struct *task; struct var_t *var; + unsigned long flags; synth_release(); if (in_synth->checkval != SYNTH_CHECK) return -EINVAL; synth = in_synth; synth->alive = 0; + spin_lock_irqsave(&speakup_info.spinlock, flags); + synth->releasing = false; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); pr_warn("synth probe\n"); if (synth->probe(synth) < 0) { pr_warn("%s: device probe failed\n", in_synth->name); @@ -510,13 +543,17 @@ static int do_synth_init(struct spk_synth *in_synth) return -ENOMEM; synth_flags = synth->flags; wake_up_interruptible_all(&speakup_event); - if (speakup_task) - wake_up_process(speakup_task); + rcu_read_lock(); + task = READ_ONCE(speakup_task); + if (!IS_ERR_OR_NULL(task)) + wake_up_process(task); + rcu_read_unlock(); return 0; } void synth_release(void) { + struct task_struct *task; struct var_t *var; unsigned long flags; @@ -525,8 +562,24 @@ void synth_release(void) spin_lock_irqsave(&speakup_info.spinlock, flags); pr_info("releasing synth %s\n", synth->name); synth->alive = 0; + synth->releasing = true; timer_delete(&thread_timer); + synth_buffer_clear(); spin_unlock_irqrestore(&speakup_info.spinlock, flags); + /* + * catch_up() may still be running without spk_mutex held (it gets + * taken off the mutex by speakup_thread()). Wake it out of its + * sleep and wait until it is done before releasing the resources + * (sysfs, variables, tty) it may be accessing. speakup_task can + * be an ERR_PTR here, when speakup_init() failed after the + * command-line synth_init(). + */ + rcu_read_lock(); + task = READ_ONCE(speakup_task); + if (!IS_ERR_OR_NULL(task)) + wake_up_process(task); + rcu_read_unlock(); + wait_event(synth_catch_up_waitq, !atomic_read(&synth_catch_up_count)); if (synth->attributes.name) sysfs_remove_group(speakup_kobj, &synth->attributes); for (var = synth->vars; var->var_id != MAXVARS; var++) diff --git a/drivers/accessibility/speakup/thread.c b/drivers/accessibility/speakup/thread.c index 2fc75e60fbac..5279e112248b 100644 --- a/drivers/accessibility/speakup/thread.c +++ b/drivers/accessibility/speakup/thread.c @@ -50,9 +50,18 @@ int speakup_thread(void *data) if (synth && synth->catch_up && synth->alive) { /* * It is up to the callee to take the lock, so that it - * can sleep whenever it likes + * can sleep whenever it likes. It may indeed sleep for + * a long while, e.g. while draining to a slow synth, so + * run it without spk_mutex: synth_release() waits for + * the catch-up count to drop before freeing the synth, + * hence the synth stays alive for the whole call. */ + atomic_inc(&synth_catch_up_count); + mutex_unlock(&spk_mutex); synth->catch_up(synth); + if (atomic_dec_and_test(&synth_catch_up_count)) + wake_up(&synth_catch_up_waitq); + mutex_lock(&spk_mutex); } speakup_start_ttys(); -- 2.32.0 speakup_kobj_init()'s error path and speakup_kobj_exit() drop the kobject but do not clear the global pointer. When built-in speakup fails late in speakup_init() (e.g. a kthread_create() failure, whose error path runs speakup_kobj_exit()), the dangling speakup_kobj stays behind, and a later insmod of a synth driver feeds it to sysfs_create_group() through synth_add() -> do_synth_init() - a use-after-free. Clear the pointer right after kobject_put(), so the late sysfs_create_group() fails cleanly on its own NULL check. The removal side needs the same guard: when the kobject setup failed, a first insmod of a synth driver fails in do_synth_init() with synth left set (that error path does not roll it back), and a second insmod goes through synth_release(), whose sysfs_remove_group() would now meet speakup_kobj == NULL and crash on the NULL kobject - a crash the NULL clear above would itself introduce. Skip the group removal when the kobject is gone. Fixes: 2067fd92d75b6 ("staging/speakup: Move out of staging") Cc: stable@vger.kernel.org Cc: Samuel Thibault Reviewed-by: Samuel Thibault Signed-off-by: Hongyu Xie --- drivers/accessibility/speakup/kobjects.c | 2 ++ drivers/accessibility/speakup/synth.c | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/drivers/accessibility/speakup/kobjects.c b/drivers/accessibility/speakup/kobjects.c index a73b478e06de..e1860f8d9d42 100644 --- a/drivers/accessibility/speakup/kobjects.c +++ b/drivers/accessibility/speakup/kobjects.c @@ -1041,6 +1041,7 @@ int speakup_kobj_init(void) sysfs_remove_group(speakup_kobj, &main_attr_group); err_speakup: kobject_put(speakup_kobj); + speakup_kobj = NULL; err_acc: kobject_put(accessibility_kobj); out: @@ -1052,5 +1053,6 @@ void speakup_kobj_exit(void) sysfs_remove_group(speakup_kobj, &i18n_attr_group); sysfs_remove_group(speakup_kobj, &main_attr_group); kobject_put(speakup_kobj); + speakup_kobj = NULL; kobject_put(accessibility_kobj); } diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c index 40870949b3c6..1af80eb116af 100644 --- a/drivers/accessibility/speakup/synth.c +++ b/drivers/accessibility/speakup/synth.c @@ -580,7 +580,7 @@ void synth_release(void) wake_up_process(task); rcu_read_unlock(); wait_event(synth_catch_up_waitq, !atomic_read(&synth_catch_up_count)); - if (synth->attributes.name) + if (synth->attributes.name && speakup_kobj) sysfs_remove_group(speakup_kobj, &synth->attributes); for (var = synth->vars; var->var_id != MAXVARS; var++) speakup_unregister_var(var->var_id); -- 2.32.0 speakup_key() checks the global synth before taking speakup_info.spinlock, but do_spkup() and its handlers dereference synth inside that lock: speech_kill() calls synth->is_alive(), and the flush path reaches synth_start() reading synth->alive. synth_release() clears synth under spk_mutex, which does not serialize against the key path, so a speakup key pressed while the synth is being removed can pass the unlocked check and oops on the stale pointer: CPU0 (speakup key) CPU1 (rmmod -> synth_release) ------------------ ---------------------------- speakup_key(): if (!synth) mutex_lock(&spk_mutex) return /* passes */ synth_release(): spin_lock(&speakup_info. ... drain in-flight catch_up() ... spinlock) synth->release(synth) do_spkup(): speech_kill(): synth->is_alive() /* synth whose release() already tore down its I/O */ Close the window on both sides: clear synth under speakup_info.spinlock in synth_release() and in the do_synth_init() probe-failure path, and recheck it after acquiring the lock in every reader that dereferences it there - do_spkup() at entry, the console notifier paths after their trylock, do_handle_shift(), do_handle_spec(), the speakup_key() special-handler dispatch, synth_direct_store(), and the /dev/synth and /dev/synthu write paths. The unlocked checks that used to gate these paths only duplicated the in-lock recheck, with a window a clear could still slip through, and are dropped; the only behavior difference is that an empty write to /dev/synth or /dev/synthu with no synth loaded now returns 0 instead of -ENODEV, which is harmless. The speakup thread keeps its existing fence: synth_release() drains the catch-up count before clearing. Two sites of this shape remain, preexisting. The cursor_done() timer callback dereferences synth inside the lock with no synth check at all; closing it needs the cursor_timer lifetime handled against synth teardown, which is beyond this series. The serial read buffer handler (synth_readbuf_handler()) has the same shape but cannot run in this tree: the only registration chain goes through spk_serial_synth_probe(), which has no callers, and any registration from outside the tree is still serialized by the free_irq() in the serial synth release path, which runs before the in-lock clear. Fixes: 2067fd92d75b6 ("staging/speakup: Move out of staging") Suggested-by: Samuel Thibault Cc: stable@vger.kernel.org Cc: Samuel Thibault Reviewed-by: Samuel Thibault Signed-off-by: Hongyu Xie --- drivers/accessibility/speakup/devsynth.c | 13 ++++++---- drivers/accessibility/speakup/kobjects.c | 8 +++--- drivers/accessibility/speakup/main.c | 31 ++++++++++++++++++------ drivers/accessibility/speakup/synth.c | 4 +++ 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/drivers/accessibility/speakup/devsynth.c b/drivers/accessibility/speakup/devsynth.c index e3d909bd0480..5a32d3bbf7b8 100644 --- a/drivers/accessibility/speakup/devsynth.c +++ b/drivers/accessibility/speakup/devsynth.c @@ -20,8 +20,6 @@ static ssize_t speakup_file_write(struct file *fp, const char __user *buffer, unsigned long flags; u_char buf[256]; - if (!synth) - return -ENODEV; while (count > 0) { bytes = min(count, sizeof(buf)); if (copy_from_user(buf, ptr, bytes)) @@ -29,6 +27,10 @@ static ssize_t speakup_file_write(struct file *fp, const char __user *buffer, count -= bytes; ptr += bytes; spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return -ENODEV; + } synth_write(buf, bytes); spin_unlock_irqrestore(&speakup_info.spinlock, flags); } @@ -47,9 +49,6 @@ static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer, u16 ubuf[256]; size_t in, out; - if (!synth) - return -ENODEV; - want = 1; while (count >= want) { /* Copy some UTF-8 piece from userland */ @@ -84,6 +83,10 @@ static ssize_t speakup_file_writeu(struct file *fp, const char __user *buffer, /* And speak this up */ if (out) { spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return -ENODEV; + } for (in = 0; in < out; in++) synth_buffer_add(ubuf[in]); synth_start(); diff --git a/drivers/accessibility/speakup/kobjects.c b/drivers/accessibility/speakup/kobjects.c index e1860f8d9d42..45a8e9fbd8de 100644 --- a/drivers/accessibility/speakup/kobjects.c +++ b/drivers/accessibility/speakup/kobjects.c @@ -416,9 +416,6 @@ static ssize_t synth_direct_store(struct kobject *kobj, char *unescaped; unsigned long flags; - if (!synth) - return -EPERM; - unescaped = kstrdup(buf, GFP_KERNEL); if (!unescaped) return -ENOMEM; @@ -426,6 +423,11 @@ static ssize_t synth_direct_store(struct kobject *kobj, string_unescape_any_inplace(unescaped); spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + kfree(unescaped); + return -EPERM; + } synth_write(unescaped, strlen(unescaped)); spin_unlock_irqrestore(&speakup_info.spinlock, flags); diff --git a/drivers/accessibility/speakup/main.c b/drivers/accessibility/speakup/main.c index e9b7c2761f6f..60be4d4df363 100644 --- a/drivers/accessibility/speakup/main.c +++ b/drivers/accessibility/speakup/main.c @@ -1166,9 +1166,13 @@ static void do_handle_shift(struct vc_data *vc, u_char value, char up_flag) { unsigned long flags; - if (!synth || up_flag || spk_killed) + if (up_flag || spk_killed) return; spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } if (cursor_track == read_all_mode) { switch (value) { case KVAL(K_SHIFT): @@ -1763,11 +1767,15 @@ static void speakup_con_write(struct vc_data *vc, u16 *str, int len) { unsigned long flags; - if ((vc->vc_num != fg_console) || spk_shut_up || !synth) + if ((vc->vc_num != fg_console) || spk_shut_up) return; if (!spin_trylock_irqsave(&speakup_info.spinlock, flags)) /* Speakup output, discard */ return; + if (!synth) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } if (spk_bell_pos && spk_keydown && (vc->state.x == spk_bell_pos - 1)) bleep(3); if ((is_cursor) || (cursor_track == read_all_mode)) { @@ -1792,11 +1800,15 @@ static void speakup_con_update(struct vc_data *vc) { unsigned long flags; - if (!speakup_console[vc->vc_num] || spk_parked || !synth) + if (!speakup_console[vc->vc_num] || spk_parked) return; if (!spin_trylock_irqsave(&speakup_info.spinlock, flags)) /* Speakup output, discard */ return; + if (!synth) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } speakup_date(vc); if (vc->vc_mode == KD_GRAPHICS && !spk_paused && spk_str_pause[0]) { synth_printf("%s", spk_str_pause); @@ -1811,9 +1823,13 @@ static void do_handle_spec(struct vc_data *vc, u_char value, char up_flag) int on_off = 2; char *label; - if (!synth || up_flag || spk_killed) + if (up_flag || spk_killed) return; spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth) { + spin_unlock_irqrestore(&speakup_info.spinlock, flags); + return; + } spk_shut_up &= 0xfe; if (spk_no_intr) spk_do_flush(); @@ -2087,6 +2103,8 @@ static spkup_hand spkup_handler[] = { static void do_spkup(struct vc_data *vc, u_char value) { + if (!synth) + return; if (spk_killed && value != SPEECH_KILL) return; spk_keydown = 0; @@ -2115,10 +2133,9 @@ speakup_key(struct vc_data *vc, int shift_state, int keycode, u16 keysym, u_char shift_info, offset; int ret = 0; - if (!synth) - return 0; - spin_lock_irqsave(&speakup_info.spinlock, flags); + if (!synth) + goto out; tty = vc->port.tty; if (type >= 0xf0) type -= 0xf0; diff --git a/drivers/accessibility/speakup/synth.c b/drivers/accessibility/speakup/synth.c index 1af80eb116af..aef482f8b14e 100644 --- a/drivers/accessibility/speakup/synth.c +++ b/drivers/accessibility/speakup/synth.c @@ -519,7 +519,9 @@ static int do_synth_init(struct spk_synth *in_synth) pr_warn("synth probe\n"); if (synth->probe(synth) < 0) { pr_warn("%s: device probe failed\n", in_synth->name); + spin_lock_irqsave(&speakup_info.spinlock, flags); synth = NULL; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); return -ENODEV; } synth_time_vars[0].u.n.value = @@ -585,7 +587,9 @@ void synth_release(void) for (var = synth->vars; var->var_id != MAXVARS; var++) speakup_unregister_var(var->var_id); synth->release(synth); + spin_lock_irqsave(&speakup_info.spinlock, flags); synth = NULL; + spin_unlock_irqrestore(&speakup_info.spinlock, flags); } /* called by: all_driver_init() */ -- 2.32.0