Re: [PATCH v4] Input: synaptics - add transparent pass-through mode for TrackPoint

[email protected] Mon, 10 Aug 2026 00:10:30 +0000
Newsgroups org.kernel.vger.linux-input
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:

New issues:
- [High] TOCTOU races in synaptics_pt_stop and synaptics_set_transparent_mode cause hardware and software state desynchronization.
- [High] synaptics_pt_write bypasses ps2_cmd_mutex in transparent mode, corrupting the PS/2 bus byte stream.
- [High] synaptics_reconnect lacks pt_mutex synchronization, causing races with concurrent guest writes.
- [High] Delayed NULL pointer dereference (or Use-After-Free) in synaptics_reconnect due to a TOCTOU read of priv->pt_port.

Pre-existing issues:
- [High] Ineffective driver type check allows type confusion and out-of-bounds access in pass-through port handling.
- [High] Incomplete fix for the child port leak: child port leaks if input_register_device fails, leading to UAF.
--

--- Patch [1]: [PATCH v4] Input: synaptics - add transparent pass-through mode for TrackPoint ---
commit 7d4fbfed011585417421459058126e46e98ff9df
Author: Evan Lawrence <[email protected]>

Input: synaptics - add transparent pass-through mode for TrackPoint
    
This commit introduces a transparent pass-through mode for Synaptics touchpads,
allowing a pass-through guest to operate at a higher poll rate. It adds a sysfs
attribute to toggle the mode, handles hardware state switches, and adjusts the
protocol handler dynamically.

> diff --git a/drivers/input/mouse/synaptics.c b/drivers/input/mouse/synaptics.c
> index 2170bbe4c5897..2a004ae34158d 100644
> --- a/drivers/input/mouse/synaptics.c
> +++ b/drivers/input/mouse/synaptics.c
[ ... ]
>  static int synaptics_pt_write(struct serio *serio, u8 c)
>  {
>  	struct psmouse *parent = psmouse_from_serio(serio->parent);
> +	struct synaptics_data *priv = parent->private;
>  	u8 rate_param = SYN_PS_CLIENT_CMD; /* indicates that we want pass-through port */
>  	int error;
>  
> +	guard(mutex)(&priv->pt_mutex);
> +
> +	if (READ_ONCE(priv->transparent_mode))
> +		return parent->ps2dev.serio->write(parent->ps2dev.serio, c);

[Severity: High]
Does bypassing ps2_cmd_mutex here allow raw guest bytes to interleave with host
commands on the PS/2 bus?

In drivers/input/mouse/synaptics.c:synaptics_pt_write():
When transparent mode is active, this fast-path directly writes to the
hardware under pt_mutex, but does not acquire ps2_cmd_mutex. If a sysfs
attribute on the parent touchpad is updated concurrently, it issues a
ps2_command(), potentially interleaving guest bytes and host commands and
corrupting communication with the touchpad.

>  	error = ps2_sliced_command(&parent->ps2dev, c);
[ ... ]
> @@ -669,9 +749,30 @@ static void synaptics_pt_stop(struct serio *serio)
>  {
>  	struct psmouse *parent = psmouse_from_serio(serio->parent);
>  	struct synaptics_data *priv = parent->private;
> +	bool exit_mode = READ_ONCE(priv->transparent_mode);
>  
> -	guard(serio_pause_rx)(parent->ps2dev.serio);
> -	priv->pt_port = NULL;
> +	/*
> +	 * The guest is going away. Hold the pass-through mutex across
> +	 * the hardware exit and the software state update so a
> +	 * concurrent synaptics_pt_write() cannot observe the old
> +	 * transparent_mode after the hardware has already been
> +	 * switched back to normal operation.
> +	 */
> +	guard(mutex)(&priv->pt_mutex);
> +
> +	if (exit_mode) {
> +		if (synaptics_exit_transparent_mode(parent))

[Severity: High]
Does reading exit_mode outside of pt_mutex allow the hardware and software
state to become desynchronized?

In drivers/input/mouse/synaptics.c:synaptics_pt_stop():
If synaptics_pt_stop() is called concurrently with 
synaptics_set_transparent_mode(), exit_mode is cached before acquiring the lock.
The mode switch could enable hardware transparent mode, and then
synaptics_pt_stop() acquires the lock, skips the hardware exit because the
cached exit_mode was false, but forces the software state to normal mode.

> +			psmouse_warn(parent,
> +				     "failed to exit transparent mode after pass-through port removal\n");
> +	}
[ ... ]
> @@ -709,7 +822,7 @@ static void synaptics_pass_pt_packet(struct synaptics_data *priv, u8 *packet)
>  
>  	serio_interrupt(ptport, packet[1], 0);
>  
> -	if (priv->pt_port_open) {
> +	if (priv->pt_port_open && ptport->id.type == SERIO_PS_PSTHRU) {
>  		struct psmouse *child = psmouse_from_serio(ptport);
>  
>  		if (child->state == PSMOUSE_ACTIVATED) {

[Severity: High]
This is a pre-existing issue, but does checking the hardware ID reliably
verify the bound software driver type?

In drivers/input/mouse/synaptics.c:synaptics_pass_pt_packet():
If a root user unbinds the psmouse driver from the pass-through child port
and binds a different driver like serio_raw, this check passes because it
evaluates the hardware ID. The foreign driver's private data is then
downcasted to struct psmouse, potentially allowing out-of-bounds writes to
child->state or child->oob_data_type.

> @@ -724,7 +837,14 @@ static void synaptics_pass_pt_packet(struct synaptics_data *priv, u8 *packet)
[ ... ]
> +static ssize_t synaptics_set_transparent_mode(struct psmouse *psmouse,
> +					      void *data, const char *buf,
> +					      size_t len)
> +{
> +	struct synaptics_data *priv = psmouse->private;
> +	unsigned int value;
> +	int err;
> +
> +	err = kstrtouint(buf, 10, &value);
> +	if (err)
> +		return err;
> +
> +	if (value > 1)
> +		return -EINVAL;
> +
> +	if (value == READ_ONCE(priv->transparent_mode))
> +		return len;
> +
> +	/* Transparent mode only makes sense while the guest is attached */
> +	if (value && !READ_ONCE(priv->pt_port))
> +		return -ENODEV;
> +
> +	/*
> +	 * Switch the hardware first and only commit the new mode and protocol
> +	 * handler once it succeeded, so a failing command cannot leave the
> +	 * driver with bookkeeping that does not match the device. The touchpad
> +	 * is deactivated for the duration of this write (see
> +	 * psmouse_attr_set_helper), so no live data can be misparsed in
> +	 * between.  Hold the pass-through mutex so that a concurrent
> +	 * synaptics_pt_write() cannot inject a byte onto the bus in the
> +	 * middle of the mode-switch command sequence.
> +	 */
> +	guard(mutex)(&priv->pt_mutex);

[Severity: High]
Does checking pt_port outside of pt_mutex create a race condition with
synaptics_pt_stop()?

In drivers/input/mouse/synaptics.c:synaptics_set_transparent_mode():
Since the pt_port check happens before acquiring pt_mutex,
synaptics_pt_stop() could concurrently destroy pt_port before the mode switch
executes, leading to misconfigured protocol handlers.

> +
> +	if (value) {
> +		err = synaptics_enter_transparent_mode(psmouse);
[ ... ]
> @@ -1478,8 +1681,35 @@ static int synaptics_reconnect(struct psmouse *psmouse)
>  			 */
>  			ssleep(1);
>  		}
> -		ps2_command(&psmouse->ps2dev, param, PSMOUSE_CMD_GETID);
> -		error = synaptics_detect(psmouse, 0);
> +		if (READ_ONCE(priv->transparent_mode) && READ_ONCE(priv->pt_port)) {
> +			/*
> +			 * psmouse_reset() above put the touchpad back into its
> +			 * power-on state, so run the full initialization before
> +			 * re-entering transparent mode. Otherwise the device
> +			 * would come back missing the configuration set up by
> +			 * synaptics_set_mode() (e.g. Advanced Gesture Mode) and
> +			 * would stay uninitialized once transparent mode is
> +			 * disabled again.
> +			 */
> +			error = synaptics_query_hardware(psmouse, &info);
> +			if (!error &&
> +			    (info.identity != priv->info.identity ||
> +			     info.model_id != priv->info.model_id ||
> +			     info.capabilities != priv->info.capabilities ||
> +			     info.ext_cap != priv->info.ext_cap))
> +				error = -ENXIO;
> +			if (!error)
> +				error = synaptics_set_mode(psmouse);
> +			if (!error)
> +				error = synaptics_enter_transparent_mode(psmouse);

[Severity: High]
Can concurrent guest writes interleave with the hardware initialization
sequence here?

In drivers/input/mouse/synaptics.c:synaptics_reconnect():
The entire re-initialization sequence runs without holding pt_mutex. If the
child port driver writes data concurrently, synaptics_pt_write() will see
transparent_mode as true, lock pt_mutex, and inject bytes directly onto the
bus between the PS/2 commands issued here.

> +			if (!error) {
> +				serio_reconnect(priv->pt_port);

[Severity: High]
Can priv->pt_port be NULL or point to freed memory by the time it is accessed
here?

In drivers/input/mouse/synaptics.c:synaptics_reconnect():
priv->pt_port is checked before the lengthy hardware query sequence. If the
child port is concurrently unbound during this time, synaptics_pt_stop() could
nullify and free the port structure, causing a NULL pointer dereference or
use-after-free when passed to serio_reconnect().

> +				return 0;
> +			}
[ ... ]
> @@ -1691,9 +1913,35 @@ static int synaptics_init_ps2(struct psmouse *psmouse,
[ ... ]
> +	/*
> +	 * Register the pass-through port only once all initialization steps
> +	 * that can fail have succeeded, so that a failure in one of the steps
> +	 * above cannot leak a registered child port whose callbacks would
> +	 * dereference the freed synaptics_data.
> +	 */
> +	if (SYN_CAP_PASS_THROUGH(info->capabilities))
> +		synaptics_pt_create(psmouse);
> +
>  	return 0;

[Severity: High]
This is a pre-existing issue, but does this still leak the child port if
the parent's input_register_device() fails later?

In drivers/input/mouse/synaptics.c:synaptics_init_ps2():
Even though synaptics_pt_create() is moved to the end of this function, if
the caller (psmouse_connect()) subsequently fails in input_register_device(),
it aborts and frees the psmouse instance via synaptics_disconnect(). The child
port is never unregistered, and if it is bound later, its handlers will
dereference the freed parent private data.

>  
>   init_fail:

-- 
Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=1