Linux Input/HID development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Vicki Pfau" <vi@endrift.com>
Cc: linux-input@vger.kernel.org, dmitry.torokhov@gmail.com
Subject: Re: [PATCH v7 07/12] Input: xbox_gip - Add security implementation from xone
Date: Sat, 12 Sep 2026 03:21:20 +0000	[thread overview]
Message-ID: <20260912032121.0F5411F000FF@smtp.kernel.org> (raw)
In-Reply-To: <20260912030426.2997003-8-vi@endrift.com>

Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:

New issues:
- [High] Use-After-Free during device disconnect due to asynchronous cancellation of security workqueues.
- [High] Dynamic `INIT_WORK()` calls in `gip_security_start_handshake()` can corrupt workqueue state if re-entered.
- [High] Data race on shared security state (`transcript`, `random_client`, `pubkey_client`) due to lack of synchronization.
- [High] Heap variables embedded directly in a dynamically allocated struct are passed to scatterlists, risking DMA memory corruption.
- [Medium] Transcript hash incorrectly includes the message trailer, causing a protocol mismatch.
- [Medium] Sensitive session key is left on the kernel stack without explicit zeroization.

Pre-existing issues:
- [High] Index typo in `gip_disconnect()` causes out_fragments delayed works to not be cancelled, leading to a Use-After-Free.
--

commit 90b08fdfc4b71ac21c6c33cfb71aa69cf4b03d4c
Author: Vicki Pfau <vi@endrift.com>

Input: xbox_gip - Add security implementation from xone

This patch imports and adapts the security message implementation from the
out-of-tree xone driver. This message is necessary for most controllers
that use wireless dongles.

> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> index 1bfb559f5f7a2..ec21740bba50c 100644
> --- a/drivers/input/joystick/gip/gip-core.c
> +++ b/drivers/input/joystick/gip/gip-core.c

[ ... ]

This is a pre-existing issue, but I noticed a potential use-after-free bug
in the disconnect flow.

Looking at gip_disconnect() in drivers/input/joystick/gip/gip-core.c:

	mutex_lock(&gip->attachment_lock);
	for (i = 0; i < MAX_ATTACHMENTS; i++) {
		struct gip_attachment *attachment = gip->attachments[i];

		if (!attachment)
			continue;

		for (j = 0; j < MAX_OUT_FRAGMENTS; j++)
			cancel_delayed_work_sync(&attachment->out_fragments[i].timeout);
		devm_kfree(to_gip_device(attachment), attachment->uniq);
		devm_kfree(to_gip_device(attachment), attachment);
		gip->attachments[i] = NULL;
	}
	mutex_unlock(&gip->attachment_lock);

[Severity: High]
Does this code use the outer loop index `i` (attachment index) instead of the
inner loop index `j` (fragment index)?

If `i` is used, timeouts for fragments other than the one at index `i` are
never cancelled. The function then proceeds to free the `attachment` structure.
If any of the un-cancelled delayed works execute later, they will access the
freed `attachment`, leading to a use-after-free.

> diff --git a/drivers/input/joystick/gip/gip-security.c b/drivers/input/joystick/gip/gip-security.c
> new file mode 100644
> index 0000000000000..1850850b60e67
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-security.c
> @@ -0,0 +1,810 @@
> +// SPDX-License-Identifier: GPL-2.0-or-later

[ ... ]

> +static int gip_security_ecdh_get_secret(struct crypto_kpp *tfm, u8 *pubkey,
> +	int pubkey_len, u8 *secret, int secret_len)
> +{
> +	struct kpp_request *req;
> +	struct scatterlist src, dest;
> +	DECLARE_CRYPTO_WAIT(wait);
> +	int err;
> +
> +	req = kpp_request_alloc(tfm, GFP_KERNEL);
> +	if (!req)
> +		return -ENOMEM;
> +
> +	sg_init_one(&src, pubkey, pubkey_len);
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: High]
Can passing these embedded, unaligned struct fields directly to
`sg_init_one()` cause DMA memory corruption?

The fields `security->pubkey_client2` and `security->pms` are embedded directly
inside `struct gip_security`, which itself is embedded in
`struct gip_attachment`. If the underlying crypto backend uses hardware DMA,
the DMA transfer will share a cacheline with adjacent fields in the
`gip_attachment` structure. A simultaneous CPU access to these adjacent fields
while the DMA is ongoing can corrupt either the DMA payload or the adjacent
struct fields.

[ ... ]

> +static void gip_security_complete_handshake(struct work_struct *work)
> +{
> +	struct gip_security *security = container_of(work, typeof(*security), work_complete);
> +	struct gip_attachment *attachment = container_of(security, struct gip_attachment, security);
> +	struct gip_security_header_control hdr = {
> +		.context = GIP_SECURITY_CTX_CONTROL,
> +		.control = GIP_SECURITY_CTRL_COMPLETE,
> +	};
> +	u8 random[GIP_SECURITY_RANDOM_LEN * 2];
> +	u8 key[GIP_SECURITY_SESSION_KEY_LEN];
> +	int err;
> +
> +	guard(mutex)(&attachment->lock);
> +	if (security->status != GIP_SECURITY_SENT)
> +		return;
> +
> +	memcpy(random, security->random_host, sizeof(security->random_host));
> +	memcpy(random + sizeof(security->random_host), security->random_client,
> +		sizeof(security->random_client));
> +
> +	gip_security_compute_prf(
> +		"EXPORTER DAWN data channel session key for controller",
> +		security->master_secret, sizeof(security->master_secret),
> +		random, sizeof(random), key, sizeof(key));
> +
> +	gip_dbg(security, "%s: key=%*phD\n", __func__, (int)sizeof(key), key);
> +
> +	err = gip_send_system_message(attachment, GIP_CMD_SECURITY,
> +		0, &hdr, sizeof(hdr));
> +	if (err) {
> +		gip_err(security, "%s: send complete failed: %d\n", __func__, err);
> +		return;
> +	}
> +
> +	security->status = GIP_SECURITY_DONE;
> +}
    ^^^

[Severity: Medium]
Should `memzero_explicit()` be used to wipe the sensitive session key
material from the stack?

The symmetric session key `key` and the pre-master secret/random data `random`
are generated into local stack arrays. After sending the completion message,
the function returns without zeroing out this sensitive cryptographic material,
which could be exposed if a subsequent kernel vulnerability allows reading
uninitialized stack memory.

[ ... ]

> +int gip_security_handle_message(struct gip_security *security, const void *bytes, int num_bytes)
> +{
> +	const struct gip_security_header_handshake *handshake = bytes;
> +	const struct gip_security_header_full *hdr;
> +	int err;
> +
> +	if (num_bytes < sizeof(*handshake))
> +		return -EINVAL;
> +
> +	if (security->status != GIP_SECURITY_SENT) {
> +		gip_err(security, "%s: security message received when none expected: 0x%02x\n",
> +			__func__, handshake->command);
> +		return -EPROTO;
> +	}
> +
> +	if (handshake->error)
> +		return -EPROTO;
> +
> +	if (handshake->options & GIP_SECURITY_OPT_ACKNOWLEDGE) {
> +		if (handshake->command == 0x01)
> +			return gip_security_handle_acknowledge(security);
> +
> +		gip_err(security, "%s: handshake failed: 0x%02x\n",
> +			__func__, handshake->command);
> +		return -EPROTO;
> +	}
> +
> +	if (num_bytes < sizeof(*hdr))
> +		return -EINVAL;
> +
> +	hdr = bytes;
> +	/* client uses v2 */
> +	if (num_bytes < sizeof(*hdr) || handshake->command != hdr->data.command) {
> +		/* reset transcript hash and restart handshake */
> +		gip_dbg(security, "%s: protocol upgrade\n", __func__);
> +		sha256_init(&security->transcript);
> +		return gip_security2_send_hello(security);
> +	}
> +
> +	err = gip_security_dispatch_message(security, hdr->data.command,
> +		bytes + sizeof(*hdr), num_bytes - sizeof(*hdr));
> +	if (err)
> +		return err;
> +
> +	sha256_update(&security->transcript, bytes + sizeof(hdr->handshake),
> +		num_bytes - sizeof(hdr->handshake));
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: High]
Is there a data race on the shared security state (`transcript`,
`random_client`, `pubkey_client`) due to lack of synchronization?

Incoming messages are processed by `gip_receive_work`, which calls
`gip_security_handle_message()` without acquiring `attachment->lock`.
This function modifies shared state such as `security->transcript` and
`security->pubkey_client`.

Concurrently, asynchronous work items (like `gip_security_exchange_rsa`)
acquire `attachment->lock` and call `gip_security_send_message()`, which also
updates the `transcript` hash. Since one side does not hold the lock, these
threads can race and corrupt the internal state of the SHA256 context or
copy torn data.

[Severity: Medium]
Does this transcript hash incorrectly include the message trailer?

The transcript hash is updated using `num_bytes - sizeof(hdr->handshake)`.
If the received packet includes the 8-byte trailer, `num_bytes` encompasses it,
and the trailer gets hashed. However, the sender (`gip_security_send_message()`)
specifically excludes the trailer from the hash. This discrepancy causes a
transcript mismatch during the `Finished` step.

> +
> +	return 0;
> +}
> +
> +void gip_security_release(struct gip_security *security)
> +{
> +	if (security->status < GIP_SECURITY_SENT)
> +		return;
> +
> +	cancel_work(&security->work_exchange_rsa);
> +	cancel_work(&security->work_exchange_ecdh);
> +	cancel_work(&security->work_complete);
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: High]
Is it safe to use the non-blocking `cancel_work()` instead of
`cancel_work_sync()` here?

During device disconnect, `gip_disconnect()` calls `gip_shutdown()` which calls
`gip_security_release()`. This uses the non-blocking `cancel_work()` to cancel
the security work items.

`gip_disconnect()` then explicitly frees the `attachment` structure using
`devm_kfree()`. If a security work item is actively executing on another CPU,
it will continue running and attempt to acquire the freed `attachment->lock` or
access other freed fields (such as inside `gip_security_exchange_rsa()`),
resulting in a use-after-free.

> +
> +	memzero_explicit(&security->transcript, sizeof(security->transcript));
> +	memzero_explicit(&security->pms, sizeof(security->pms));
> +	memzero_explicit(&security->master_secret, sizeof(security->master_secret));
> +
> +	security->status = GIP_SECURITY_PENDING;
> +}
> +
> +int gip_security_start_handshake(struct gip_security *security)
> +{
> +	int err;
> +
> +	sha256_init(&security->transcript);
> +
> +	INIT_WORK(&security->work_exchange_rsa, gip_security_exchange_rsa);
> +	INIT_WORK(&security->work_exchange_ecdh, gip_security2_exchange_ecdh);
> +	INIT_WORK(&security->work_complete, gip_security_complete_handshake);
        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

[Severity: High]
Can dynamically re-initializing these work items corrupt the workqueue state
if they are re-entered?

`INIT_WORK()` is dynamically invoked every time `gip_security_start_handshake()`
runs. If the handshake fails or is reset (e.g., via `HELLO_DEVICE`),
`gip_security_release()` cancels the work asynchronously.

A subsequent `STATUS_DEVICE` message will call `gip_security_start_handshake()`
again. If the previous work item is still executing (because `cancel_work()` is
asynchronous), calling `INIT_WORK()` will overwrite its internal
`work_data_bits` and list pointers, leading to workqueue corruption.

> +
> +	err = gip_security_send_hello(security);
> +	if (err < 0)
> +		return err;
> +
> +	security->status = GIP_SECURITY_SENT;
> +	return 0;
> +}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260912030426.2997003-1-vi@endrift.com?part=7

  reply	other threads:[~2026-09-12  3:21 UTC|newest]

Thread overview: 25+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-12  3:04 [PATCH v7 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
2026-09-12  3:04 ` [PATCH v7 01/12] " Vicki Pfau
2026-09-12  3:25   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 02/12] Input: xpad - Remove Xbox One support Vicki Pfau
2026-09-12  3:22   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 03/12] Input: xbox_gip - Add controllable LED support Vicki Pfau
2026-09-12  3:20   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 04/12] Input: xbox_gip - Add HID relaying Vicki Pfau
2026-09-12  3:21   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 05/12] Input: xbox_gip - Add battery support Vicki Pfau
2026-09-12  3:22   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages Vicki Pfau
2026-09-12  3:23   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 07/12] Input: xbox_gip - Add security implementation from xone Vicki Pfau
2026-09-12  3:21   ` sashiko-bot [this message]
2026-09-12  3:04 ` [PATCH v7 08/12] Input: xbox_gip - Add arcade stick support Vicki Pfau
2026-09-12  3:16   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 09/12] Input: xbox_gip - Add support for PDP guitar controllers Vicki Pfau
2026-09-12  3:23   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER Vicki Pfau
2026-09-12  3:28   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 11/12] HID: Map more automobile simulation inputs Vicki Pfau
2026-09-12  3:28   ` sashiko-bot
2026-09-12  3:04 ` [PATCH v7 12/12] Input: xbox_gip - Add wheel support Vicki Pfau
2026-09-12  3:33   ` sashiko-bot

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260912032121.0F5411F000FF@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-input@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=vi@endrift.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox