Linux Input/HID development
 help / color / mirror / Atom feed
* [PATCH v3 1/2] Input: atmel_mxt_ts: Fix async config use-after-free
@ 2026-05-28  7:43 Hendrik Noack
  2026-05-28  7:43 ` [PATCH v3 2/2] Input: atmel_mxt_ts: Allow per-machine config via DT compatible Hendrik Noack
  2026-05-28  8:48 ` [PATCH v3 1/2] Input: atmel_mxt_ts: Fix async config use-after-free sashiko-bot
  0 siblings, 2 replies; 4+ messages in thread
From: Hendrik Noack @ 2026-05-28  7:43 UTC (permalink / raw)
  To: Nick Dyer, Dmitry Torokhov; +Cc: Hendrik Noack, linux-input, linux-kernel

The Atmel maXTouch driver starts asynchronous config loading from both
probe and a sysfs trigger via request_firmware_nowait() and passes the
devm-managed struct mxt_data as the callback context. If the driver is
removed while an asynchronous request is still pending, devres will free
mxt_data before the firmware callback runs. When the callback eventually
executes, it dereferences the freed mxt_data pointer, leading to a
use-after-free.

This is resolved by introducing a completion that serialises the
request_firmware_nowait() and remove():

Before calling request_firmware_nowait(), a completion serialises the
calls. If remove() has started, the asynchronous loading will no longer be
started and all waiters will be signaled. The completion is completed at
the end of the callback or if request_firmware_nowait() fails.

In mxt_remove(), the completion of pending request_firmware_nowait() is
awaited and all waiters will be signaled.

Signed-off-by: Hendrik Noack <hendrik-noack@gmx.de>
---
Changes in v2:
- this fix is added to the patch series (thanks sashiko.dev)

---
Changes in v3:
- remove unnecessary lock

---
 drivers/input/touchscreen/atmel_mxt_ts.c | 34 +++++++++++++++++++++++-
 1 file changed, 33 insertions(+), 1 deletion(-)

diff --git a/drivers/input/touchscreen/atmel_mxt_ts.c b/drivers/input/touchscreen/atmel_mxt_ts.c
index a9e86ad7ed5e..a88dc7e6827c 100644
--- a/drivers/input/touchscreen/atmel_mxt_ts.c
+++ b/drivers/input/touchscreen/atmel_mxt_ts.c
@@ -347,6 +347,10 @@ struct mxt_data {
 	/* for config update handling */
 	struct completion crc_completion;
 
+	/* for loading config */
+	struct completion config_completion;
+	bool shutting_down;
+
 	u32 *t19_keymap;
 	unsigned int t19_num_keys;
 
@@ -2221,8 +2225,11 @@ static int mxt_configure_objects(struct mxt_data *data,
 
 static void mxt_config_cb(const struct firmware *cfg, void *ctx)
 {
-	mxt_configure_objects(ctx, cfg);
+	struct mxt_data *data = ctx;
+
+	mxt_configure_objects(data, cfg);
 	release_firmware(cfg);
+	complete(&data->config_completion);
 }
 
 static int mxt_initialize(struct mxt_data *data)
@@ -2271,12 +2278,26 @@ static int mxt_initialize(struct mxt_data *data)
 	if (error)
 		return error;
 
+	wait_for_completion(&data->config_completion);
+
+	if (data->shutting_down) {
+		/* complete() only signals one waiter, signal all waiters once during shutdown */
+		if (!completion_done(&data->config_completion)) {
+			complete_all(&data->config_completion);
+			dev_info(&client->dev, "Shutting down, not loading new config: %d\n",
+				 error);
+		}
+
+		return -EBUSY;
+	}
+
 	error = request_firmware_nowait(THIS_MODULE, true, MXT_CFG_NAME,
 					&client->dev, GFP_KERNEL, data,
 					mxt_config_cb);
 	if (error) {
 		dev_err(&client->dev, "Failed to invoke firmware loader: %d\n",
 			error);
+		complete(&data->config_completion);
 		return error;
 	}
 
@@ -3237,6 +3258,9 @@ static int mxt_probe(struct i2c_client *client)
 	init_completion(&data->bl_completion);
 	init_completion(&data->reset_completion);
 	init_completion(&data->crc_completion);
+	init_completion(&data->config_completion);
+	complete(&data->config_completion);
+	data->shutting_down = false;
 
 	data->suspend_mode = dmi_check_system(chromebook_T9_suspend_dmi) ?
 		MXT_SUSPEND_T9_CTRL : MXT_SUSPEND_DEEP_SLEEP;
@@ -3342,6 +3366,14 @@ static void mxt_remove(struct i2c_client *client)
 {
 	struct mxt_data *data = i2c_get_clientdata(client);
 
+	data->shutting_down = true;
+
+	wait_for_completion(&data->config_completion);
+
+	/* complete() only signals one waiter, signal all waiters once during shutdown */
+	if (!completion_done(&data->config_completion))
+		complete_all(&data->config_completion);
+
 	disable_irq(data->irq);
 	mxt_free_input_device(data);
 	mxt_free_object_table(data);
-- 
2.43.0


^ permalink raw reply related	[flat|nested] 4+ messages in thread

end of thread, other threads:[~2026-05-28  9:28 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-28  7:43 [PATCH v3 1/2] Input: atmel_mxt_ts: Fix async config use-after-free Hendrik Noack
2026-05-28  7:43 ` [PATCH v3 2/2] Input: atmel_mxt_ts: Allow per-machine config via DT compatible Hendrik Noack
2026-05-28  9:28   ` sashiko-bot
2026-05-28  8:48 ` [PATCH v3 1/2] Input: atmel_mxt_ts: Fix async config use-after-free sashiko-bot

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox