[PATCH v3 3/4] speakup: Guard late sysfs operations on speakup_kobj

From: Hongyu Xie

Date: Mon Nov 02 2026 - 20:16:53 EST


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@xxxxxxxxxxxxxxx
Cc: Samuel Thibault <samuel.thibault@xxxxxxxxxxxx>
Reviewed-by: Samuel Thibault <samuel.thibault@xxxxxxxxxxxx>
Signed-off-by: Hongyu Xie <xiehongyu1@xxxxxxxxxx>
---
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