All of lore.kernel.org
 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: 20+ 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 ` Pankaj Gupta (OSS)
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 14:05   ` Pankaj Gupta (OSS)
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   ` Pankaj Gupta (OSS)
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 14:06   ` Pankaj Gupta (OSS)
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   ` Pankaj Gupta (OSS)
2026-08-17 14:06 ` [PATCH v36 5/7] firmware: imx: adds miscdev Pankaj Gupta via B4 Relay
2026-08-17 14:06   ` Pankaj Gupta (OSS)
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 14:06   ` Pankaj Gupta (OSS)
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
2026-08-17 14:06   ` Pankaj Gupta (OSS)

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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.