Linux kernel and device drivers for NXP i.MX platforms
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Pankaj Gupta (OSS)" <pankaj.gupta@oss.nxp.com>
Cc: robh@kernel.org, imx@lists.linux.dev, Frank.Li@kernel.org,
	conor+dt@kernel.org, devicetree@vger.kernel.org
Subject: Re: [PATCH v36 5/7] firmware: imx: adds miscdev
Date: Mon, 17 Aug 2026 08:49:38 +0000	[thread overview]
Message-ID: <20260817084938.C2B541F00A3D@smtp.kernel.org> (raw)
In-Reply-To: <20260817-imx-se-if-v36-5-45c42847bfd8@oss.nxp.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] Dynamic export buffer sizes are stored in global static arrays, causing a race condition between concurrent contexts that allows out-of-bounds firmware DMA writes.
- [High] `se_if_fops_read()` calls `fw_api_specific_ops()` on a partially uninitialized stack buffer if a short `size` is provided by userspace.
- [High] Firmware is allowed to read physical memory outside the userspace-provided shared-memory window for commands lacking explicit buffer sizes.
- [High] Silent failure to register a command receiver during `ELE_STORAGE_OPEN_REQ` allows NVM callback hijacking.
- [High] A concurrent fatal signal and driver unbind bypasses the firmware-busy circuit breaker, causing a DMA-after-free vulnerability.
- [Medium] A maliciously small `rx_buf_sz` in `SE_IOCTL_CMD_SEND_RCV_RSP` causes the driver to truncate the firmware response, skip recording the allocated session/storage handle, and permanently leak it.
--

--- Patch [5]: [PATCH v36 5/7] firmware: imx: adds miscdev ---
commit ce3441d90d0e822b990027611af7d091a4f0299f
Author: Pankaj Gupta <pankaj.gupta@nxp.com>

firmware: imx: adds miscdev

This commit introduces the character driver and ABI for communicating with
NXP secure enclaves (like EdgeLock). It includes shared memory management,
message header validation, and command/response processing.

Link: https://github.com/nxp-imx/imx-secure-enclave.git
Link: https://github.com/nxp-imx/imx-smw.git

> --- a/drivers/firmware/imx/ele_common.c
> +++ b/drivers/firmware/imx/ele_common.c
> @@ -101,15 +101,15 @@ int se_val_cmd_addrs(struct se_if_device_ctx *dev_ctx, struct se_api_msg *msg,
>  
>  		addr = msg->data[f->lsb_idx];
>  		if (f->has_msb)
>  			addr |= (u64)msg->data[f->msb_idx] << 32;
>  
>  		/* Zero marks an absent optional buffer. */
>  		if (!addr)
>  			continue;
>  
>  		if (addr < base || addr >= end)
>  			return -EACCES;
>  
>  		/*
>  		 * When the message also carries this buffer's length, the whole
>  		 * buffer [addr, addr + len) must fit inside the window, not just

[Severity: High]
Does this code prevent firmware from reading outside the shared memory window
when the command lacks an explicit buffer size?

For commands like ELE_OEM_AUTH_CONTAINER_REQ that use SE_CMD_ADDR_NO_SIZE
with no buf_size, only the start address is checked against the bounds. If a
caller provides a start address at the very end of the window (e.g., end - 1),
could the firmware read the payload out-of-bounds into adjacent physical
memory?

[ ... ]

> @@ -290,16 +290,16 @@ int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk
>  			 * before the fatal signal: se_if_rx_callback() has already
>  			 * copied it and set rx_delivered under the same lock, so the
>  			 * enclave is done with the buffer. Report it as a normal
>  			 * receive (rx_msg_sz) so the handle it carries is still
>  			 * recorded and later closed, rather than leaked.
>  			 */
>  			if (is_rsp_wait_with_timeout) {
>  				spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
>  				if (se_clbk_hdl->rx_delivered) {
>  					ret = se_clbk_hdl->rx_msg_sz;
>  					spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
>  					break;
>  				}
>  				if (se_clbk_hdl->rx_msg) {
>  					se_clbk_hdl->rx_msg = NULL;
>  					if (!completion_done(&se_clbk_hdl->done))
>  						se_mark_fw_busy(dev_ctx);
>  				}
>  				spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);

[Severity: High]
Can a concurrent fatal signal and driver unbind bypass the firmware-busy
circuit breaker here?

If a process blocking in ele_msg_rcv() receives a fatal signal at the exact
moment se_if_probe_cleanup() executes and calls complete_all(), the wait
completes. The signal path wakes up, and completion_done() evaluates to true.
This causes the signal path to skip calling se_mark_fw_busy().

Could this lead to teardown freeing the DMA buffer via
cleanup_se_shared_mem(..., true) while the firmware is still busy and writing
into the freed memory?

> --- a/drivers/firmware/imx/ele_fw_api.c
> +++ b/drivers/firmware/imx/ele_fw_api.c
> @@ -180,14 +180,14 @@ void fw_api_specific_ops(struct se_if_device_ctx *dev_ctx, struct se_api_msg *rx
>  	case ELE_STORAGE_OPEN_REQ: {
>  		int rc = 0;
>  
>  		/*
>  		 * Record the storage handle before registering as command
>  		 * receiver. FW has already allocated the handle; if we assigned
>  		 * it only after a successful registration, a failing
>  		 * set_dev_ctx_as_command_receiver() (e.g. -EBUSY) would leave
>  		 * strg_hdl at 0 while the ioctl still returns success to
>  		 * userspace. The kernel would then never close the handle on
>  		 * teardown, leaking it in FW. Storing it first guarantees
>  		 * cleanup_dev_ctx() closes it regardless of registration.
>  		 */
>  		dev_ctx->strg_hdl = rx_msg->data[1];
>  
>  		rc = set_dev_ctx_as_command_receiver(dev_ctx);
>  		if (rc)
>  			dev_err(priv->dev,
>  				"Failed to register %s as CMD-Receiver: %d\n",
>  				dev_ctx->devname, rc);
>  		break;
>  	}

[Severity: High]
Does this silently ignore a registration failure and leave the state out of
sync?

If process A already opened a storage session and is the command receiver, and
process B calls ELE_STORAGE_OPEN_REQ, set_dev_ctx_as_command_receiver() will
return -EBUSY. However, the error is only logged, and dev_ctx->strg_hdl remains
set.

Could this trick the firmware into sending NVM callbacks for process B's
storage session that then get routed to process A?

> --- a/drivers/firmware/imx/ele_msg_addr_field.c
> +++ b/drivers/firmware/imx/ele_msg_addr_field.c
> @@ -568,10 +568,10 @@ static const struct se_cmd_addr_field ele_rng_get_random_addr_fields[] = {
>  	/*
>  	 * PSA_COMPLIANT get-random payload. reserved/flags is data[0]; rnd_addr

[ ... ]

>  static struct se_cmd_addr_field ele_storage_master_export_addr_fields[] = {
>  	{ .lsb_idx = 2, .flag_idx = SE_CMD_ADDR_ALWAYS,
>  	  .size_idx = SE_CMD_ADDR_NO_SIZE, .buf_size = 0 },	/* key_store_export_address */
>  };

[ ... ]

>  void ele_set_sz_in_field_addr(u8 cmd, u32 size)
>  {
>  	switch (cmd) {
>  	case ELE_STORAGE_MASTER_EXPORT_REQ:
>  		ele_storage_master_export_addr_fields[0].buf_size = size;
>  		break;
>  	case ELE_STORAGE_CHUNK_EXPORT_REQ:
>  		ele_storage_chunk_export_addr_fields[0].buf_size = size;
>  		break;
>  	}
>  }

[Severity: High]
Can concurrent operations cause a race condition when storing transaction
sizes in these global static arrays?

If two callers concurrently trigger ELE_STORAGE_MASTER_EXPORT_REQ,
fw_api_specific_ops() calls ele_set_sz_in_field_addr() which mutates the
shared global array ele_storage_master_export_addr_fields without
synchronization.

If a context requiring a large buffer is overwritten by a context supplying a
small size, could se_val_cmd_addrs() use the clobbered small size, pass the
bounds check, and allow the firmware to write the large export out-of-bounds?

> --- a/drivers/firmware/imx/se_ctrl.c
> +++ b/drivers/firmware/imx/se_ctrl.c
> @@ -1727,14 +1727,14 @@ static int se_ioctl_cmd_snd_rcv_rsp_handler(struct se_if_device_ctx *dev_ctx,
>  	 * ioctl reports plain success; the firmware status is conveyed to
>  	 * userspace inside the response buffer itself.
>  	 */
>  	cmd_snd_rcv_rsp_info.rx_buf_sz = err;
>  	err = 0;
>  
>  	dev_dbg(priv->dev, "%s: %s %s.", dev_ctx->devname, __func__,
>  		"message received, start transmit to user");
>  
>  	rsp_status_err =
>  		se_val_rsp_hdr_n_status(priv, rx_msg, tx_msg->header.command,
>  					cmd_snd_rcv_rsp_info.rx_buf_sz,
>  					tx_msg->header.ver == priv->if_defs->base_api_ver);
>  
>  	if (!rsp_status_err) {
>  		/*
>  		 * The response is well formed and fully fits the caller's
>  		 * buffer, so any FW-allocated session/storage handle it carries
>  		 * (data[1]) has been delivered. Record it now, before the
>  		 * copy-out steps below. The FW has already committed the handle;
>  		 * running fw_api_specific_ops() only after a successful
>  		 * se_dev_ctx_cpy_out_data()/copy_to_user() would leave the
>  		 * handle untracked - and so never closed on teardown, leaking it
>  		 * in FW - whenever the caller supplied a bad output pointer.
>  		 */
>  		fw_api_specific_ops(dev_ctx, rx_msg);
>  
>  		err = se_dev_ctx_cpy_out_data(dev_ctx);

[Severity: Medium]
Does a short rx_buf_sz from userspace bypass handle recording and cause a
resource leak?

If a caller issues ELE_SESSION_OPEN_REQ but specifies an artificially small
rx_buf_sz (e.g., 4 bytes), se_val_rsp_hdr_n_status() will return -EINVAL
because the returned buffer size is smaller than the firmware's header length.

Because rsp_status_err is set, the call to fw_api_specific_ops() is skipped.
Will the successfully allocated handle be permanently leaked since it is
never recorded in dev_ctx->sess_hdl for teardown?

[ ... ]

> @@ -1849,21 +1849,21 @@ static ssize_t se_if_fops_read(struct file *fp, char __user *buf, size_t size,
>  	/*
>  	 * Snapshot rx_msg pointer under clbk_rx_lock before releasing it.
>  	 * unset_dev_ctx_as_command_receiver() can acquire the lock, NULL out
>  	 * rx_msg, and free the buffer at any time after the unlock; using a
>  	 * stale pointer from the shared field after the unlock is a UAF.
>  	 */
>  	scoped_guard(mutex, &priv->modify_lock) {
>  		spin_lock_irqsave(&priv->cmd_receiver_clbk_hdl.clbk_rx_lock, flags);
>  		if (priv->cmd_receiver_clbk_hdl.dev_ctx != dev_ctx ||
>  		    !priv->cmd_receiver_clbk_hdl.rx_msg ||
>  		    !priv->cmd_receiver_clbk_hdl.rx_msg_sz) {
>  			spin_unlock_irqrestore(&priv->cmd_receiver_clbk_hdl.clbk_rx_lock, flags);
>  			mutex_unlock(&dev_ctx->fops_lock);
>  			return -ENODEV;
>  		}
>  		/* Taking snapshot is enough for the one common pre-allocated buffer. */
>  		copy_len = min(size, priv->cmd_receiver_clbk_hdl.rx_msg_sz);
>  		memcpy(rx_msg_snap, priv->cmd_receiver_clbk_hdl.rx_msg, copy_len);
>  		priv->cmd_receiver_clbk_hdl.rx_msg_sz = 0;
>  		spin_unlock_irqrestore(&priv->cmd_receiver_clbk_hdl.clbk_rx_lock, flags);
>  
>  		/* We may need to copy the output data to user before
>  		 * delivering the completion message.
>  		 */
>  		err = se_dev_ctx_cpy_out_data(dev_ctx);
>  		if (err < 0) {
>  			se_dev_ctx_shared_mem_cleanup(dev_ctx);
>  			mutex_unlock(&dev_ctx->fops_lock);
>  			return err;
>  		}
>  		/* Copy data from the buffer using the snapshot taken under the lock. */
>  		print_hex_dump_debug("to user ", DUMP_PREFIX_OFFSET, 4, 4,
>  				     rx_msg_snap, copy_len, false);
>  
>  		fw_api_specific_ops(dev_ctx, (struct se_api_msg *)rx_msg_snap);

[Severity: High]
Can this sequence read uninitialized stack memory if the user provides a short
length?

If a user acting as the command receiver calls read() with a short length
(e.g., 4 bytes), se_if_fops_read() copies only 4 bytes into the 256-byte stack
array rx_msg_snap.

It then unconditionally calls fw_api_specific_ops() with this partially
uninitialized buffer. If the command is ELE_STORAGE_MASTER_EXPORT_REQ,
fw_api_specific_ops() reads rx_msg->data[1], which now contains garbage stack
memory. Could this update the expected buffer size with an invalid value and
break DMA bounds checking?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260817-imx-se-if-v36-0-45c42847bfd8@oss.nxp.com?part=5

  reply	other threads:[~2026-08-17  8:49 UTC|newest]

Thread overview: 12+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-17 14:05 [PATCH v36 0/7] firmware: imx: driver for NXP secure-enclave Pankaj Gupta via B4 Relay
2026-08-17 14:05 ` [PATCH v36 1/7] Documentation/firmware: add imx/se to other_interfaces Pankaj Gupta via B4 Relay
2026-08-17  8:39   ` sashiko-bot
2026-08-17 14:06 ` [PATCH v36 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc Pankaj Gupta via B4 Relay
2026-08-17 14:06 ` [PATCH v36 3/7] firmware: imx: add driver for NXP EdgeLock Enclave Pankaj Gupta via B4 Relay
2026-08-17  8:48   ` sashiko-bot
2026-08-17 14:06 ` [PATCH v36 4/7] firmware: imx: device context dedicated to priv Pankaj Gupta via B4 Relay
2026-08-17 14:06 ` [PATCH v36 5/7] firmware: imx: adds miscdev Pankaj Gupta via B4 Relay
2026-08-17  8:49   ` sashiko-bot [this message]
2026-08-17 14:06 ` [PATCH v36 6/7] arm64: dts: imx8ulp: add secure enclave node Pankaj Gupta via B4 Relay
2026-08-17  8:43   ` sashiko-bot
2026-08-17 14:06 ` [PATCH v36 7/7] arm64: dts: imx8ulp: add reserved memory for EdgeLock Enclave Pankaj Gupta via B4 Relay

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=20260817084938.C2B541F00A3D@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=Frank.Li@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=imx@lists.linux.dev \
    --cc=pankaj.gupta@oss.nxp.com \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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