From: sashiko-bot@kernel.org
To: "Pankaj Gupta" <pankaj.gupta@nxp.com>
Cc: imx@lists.linux.dev, Frank.Li@kernel.org, robh@kernel.org,
conor+dt@kernel.org, devicetree@vger.kernel.org
Subject: Re: [PATCH v37 5/7] firmware: imx: adds miscdev
Date: Wed, 19 Aug 2026 06:27:28 +0000 [thread overview]
Message-ID: <20260819062729.129E21F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260819-imx-se-if-v37-5-5ef5de9ff1dc@nxp.com>
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
- [High] Race condition between ELE_STORAGE_OPEN_REQ pre-check and command receiver registration allows hijacking NVM traffic.
- [High] Global static variables used to store firmware-requested DMA buffer sizes can be overwritten by concurrent operations, leading to out-of-bounds DMA.
- [High] Flawed DMA address validation for input-only buffers with unbounded sizes allows out-of-bounds DMA reads.
- [High] A malicious command receiver can bypass the DMA end-bound check for storage export responses by supplying a small size to read(), causing the expected buffer size to be zero-padded.
- [High] Internal device context (priv_dev_ctx) permanently leaks shared memory on firmware timeout.
- [High] The fix for the unbind race condition (using !rx_delivered instead of !completion_done()) was only applied to the -ERESTARTSYS path, leaving the timeout and deadline paths vulnerable to the exact same DMA-after-free.
- [High] Use-After-Free of the devname string pointer in se_if_rx_callback() due to accessing it after dropping clbk_rx_lock.
- [High] The command receiver bypasses strict session ownership checks, allowing it to maliciously spoof handles and close arbitrary sessions belonging to other processes.
- [High] The driver unconditionally allocates 128KB of coherent DMA memory every time a user opens the misc device, without tracking total allocations or limiting the number of open file descriptors.
- [Medium] Self-deadlock in fw_api_specific_ops() via recursive acquisition of priv->modify_lock.
--
--- Patch [5]: [PATCH v37 5/7] firmware: imx: adds miscdev ---
commit 6bbd6617ac10e062e148f028c3c0f9417699688c
Author: Pankaj Gupta <pankaj.gupta@nxp.com>
Subject: firmware: imx: adds miscdev
Adds the driver for communication interface to secure-enclave, that
enables exchanging messages with NXP secure enclave HW IP(s)
like EdgeLock Enclave, from:
- User-Space Applications via character driver.
ABI documentation for the NXP secure-enclave driver.
Link: https://github.com/nxp-imx/imx-secure-enclave.git
Link: https://github.com/nxp-imx/imx-smw.git
> diff --git a/drivers/firmware/imx/ele_common.c b/drivers/firmware/imx/ele_common.c
> index b662063c3b1c2..e1c53b3c7036c 100644
> --- a/drivers/firmware/imx/ele_common.c
> +++ b/drivers/firmware/imx/ele_common.c
[ ... ]
> @@ -110,6 +110,9 @@ int se_val_cmd_addrs(struct se_if_device_ctx *dev_ctx, struct se_api_msg *msg,
> /* 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
[Severity: High]
Does this validation allow out-of-bounds DMA reads? For input-only buffers
like ELE_OEM_AUTH_CONTAINER_REQ in ele_common.c, the size_idx is set to
SE_CMD_ADDR_NO_SIZE and buf_size to 0. This bypasses the end-boundary
check entirely. Could an attacker place the start address near the end of
the shared memory window and provide a large embedded length in the payload,
forcing the firmware to read past the DMA window into adjacent physical
memory?
> * buffer [addr, addr + len) must fit inside the window, not just
> * its start. addr is already >= base and < end here, so end - addr
> * is a positive value and the comparison cannot overflow.
> */
[ ... ]
> @@ -133,6 +133,6 @@ int se_val_cmd_addrs(struct se_if_device_ctx *dev_ctx, struct se_api_msg *msg,
> } else if (f->buf_size) {
> /* buf_size: literal byte count
> * (FW-defined constant or saved at runtime).
> */
> if ((u64)f->buf_size > end - addr)
> return -EACCES;
> }
[Severity: High]
Could this be bypassed if buf_size is dynamically set to zero? If a malicious
command receiver provides a small size to se_if_fops_read() in se_ctrl.c, the
message could be truncated, leaving rx_msg_snap zero-padded.
fw_api_specific_ops() would then read data[1] as 0 and pass it to
ele_set_sz_in_field_addr(), setting buf_size to 0. This would bypass the
end-bound check entirely in se_val_cmd_addrs(), potentially allowing
out-of-bounds DMA.
> }
>
> return 0;
> }
[ ... ]
> @@ -230,8 +230,8 @@ int ele_msg_rcv(struct se_if_device_ctx *dev_ctx, struct se_clbk_handle *se_clbk
> if (time_after_eq(now, deadline_jiffies)) {
> /* Deadline hit: fence hung FW, like the ret==0 path. */
> spin_lock_irqsave(&se_clbk_hdl->clbk_rx_lock, flags);
> 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]
Is it possible for this path to suffer from the same DMA-after-free
regression that was fixed in the signal interruption path? The time_after_eq
deadline hit path and the ret == 0 timeout path in ele_msg_rcv() still use
completion_done() instead of checking rx_delivered. If a timeout races with
teardown, could the circuit breaker fail to arm, causing the DMA buffer to
be freed while firmware is still writing to it?
> ret = -ETIMEDOUT;
> break;
> }
[ ... ]
> diff --git a/drivers/firmware/imx/ele_fw_api.c b/drivers/firmware/imx/ele_fw_api.c
> new file mode 100644
> index 0000000000000..0c4c6f7de0205
> --- /dev/null
> +++ b/drivers/firmware/imx/ele_fw_api.c
> @@ -0,0 +1,361 @@
[ ... ]
> +static int se_cmd_receiver_allowed_cmd(struct se_if_device_ctx *dev_ctx,
> + struct se_api_msg *msg, u32 tx_msg_sz)
> +{
> + u8 cmd = msg->header.command;
> +
> + switch (cmd) {
> + case ELE_SESSION_CLOSE_REQ:
> + case ELE_STORAGE_CLOSE_REQ:
> + return 0;
[Severity: High]
Does this allow the command receiver to bypass strict session ownership
checks? By returning 0 unconditionally for ELE_SESSION_CLOSE_REQ and
ELE_STORAGE_CLOSE_REQ in se_cmd_receiver_allowed_cmd(), it seems a malicious
command receiver could spoof handles and close arbitrary sessions belonging
to other processes.
> + case ELE_STORAGE_MASTER_IMPORT_REQ:
> + const struct se_cmd_addr_field *fields;
> + size_t count;
[ ... ]
> + case ELE_STORAGE_OPEN_REQ:
> + /* Might be cleared as part of tear down. */
> + if (dev_ctx->strg_hdl) {
> + ret = -EEXIST;
> + break;
> + }
> + /*
> + * Reject the storage-open request when another context is
> + * already registered as the command receiver. If we let the
> + * command through, FW would allocate a new storage handle and
> + * start sending NVM callbacks for it; those callbacks would be
> + * routed to the existing receiver (process A), not to the
> + * caller (process B). This would let process A observe and
> + * tamper with process B's NVM traffic. Reject early, before
> + * the command reaches FW, so no handle is allocated and the
> + * state stays consistent.
> + */
> + scoped_guard(mutex, &priv->modify_lock)
> + if (priv->cmd_receiver_clbk_hdl.dev_ctx &&
> + priv->cmd_receiver_clbk_hdl.dev_ctx != dev_ctx)
> + ret = -EBUSY;
[Severity: High]
Is there a race condition here since modify_lock is dropped before the
command is sent to firmware? Could another process hijack the command
receiver slot via SE_IOCTL_ENABLE_CMD_RCV during this window, causing the
NVM traffic for this storage session to be routed to the attacker instead?
> + if (rx_msg_sz < ELE_STORAGE_OPEN_RSP_SZ)
> + ret = -EINVAL;
> + break;
[ ... ]
> + 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);
[Severity: High]
If set_dev_ctx_as_command_receiver() fails and returns -EBUSY, the error is
logged but execution continues in fw_api_specific_ops(). Could this leave
the storage handle assigned to the caller while the actual NVM traffic gets
routed to an attacker who won the race to become the command receiver?
> + break;
> + }
[ ... ]
> diff --git a/drivers/firmware/imx/ele_msg_addr_field.c b/drivers/firmware/imx/ele_msg_addr_field.c
> new file mode 100644
> index 0000000000000..1da6583b6c312
> --- /dev/null
> +++ b/drivers/firmware/imx/ele_msg_addr_field.c
> @@ -0,0 +1,654 @@
[ ... ]
> +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;
[Severity: High]
Is it safe to mutate the buf_size of a global static array here? If multiple
sessions or devices handle concurrent export commands, could this create a
data race in ele_set_sz_in_field_addr() where one operation overwrites the
expected buffer size of another, potentially leading to out-of-bounds DMA?
> + case ELE_STORAGE_CHUNK_EXPORT_REQ:
> + ele_storage_chunk_export_addr_fields[0].buf_size = size;
> + break;
> + }
> +}
[ ... ]
> diff --git a/drivers/firmware/imx/se_ctrl.c b/drivers/firmware/imx/se_ctrl.c
> index a8974eef190b6..56a4e53be37b8 100644
> --- a/drivers/firmware/imx/se_ctrl.c
> +++ b/drivers/firmware/imx/se_ctrl.c
[ ... ]
> +static int init_se_shared_mem(struct se_if_device_ctx *dev_ctx)
> +{
> + struct se_shared_mem_mgmt_info *se_shared_mem_mgmt = &dev_ctx->se_shared_mem_mgmt;
> + struct se_if_priv *priv = dev_ctx->priv;
> +
> + INIT_LIST_HEAD(&se_shared_mem_mgmt->pending_out);
> + INIT_LIST_HEAD(&se_shared_mem_mgmt->pending_in);
> +
> + if (priv->mem_pool)
> + INIT_LIST_HEAD(&se_shared_mem_mgmt->mem_pool_buf_list);
> +
> + se_shared_mem_mgmt->non_secure_mem.ptr =
> + dma_alloc_coherent(priv->dev, MAX_DATA_SIZE_PER_USER,
> + &se_shared_mem_mgmt->non_secure_mem.dma_addr,
> + GFP_KERNEL);
[Severity: High]
Does this unconditionally allocate 128KB of contiguous DMA memory every time
a user opens the device? Without tracking total allocations or limiting the
number of open file descriptors in se_if_fops_open(), could a malicious user
open the device in a loop and quickly exhaust the CMA/DMA memory pools,
causing a system-wide denial of service?
> + if (!se_shared_mem_mgmt->non_secure_mem.ptr)
> + return -ENOMEM;
[ ... ]
> @@ -540,6 +540,6 @@ void se_if_rx_callback(...)
> se_clbk_hdl->rx_msg_sz = min_t(u32, rx_msg_sz, MAX_NVM_MSG_LEN);
> devname = se_clbk_hdl->dev_ctx->devname;
> memcpy(se_clbk_hdl->rx_msg, msg, se_clbk_hdl->rx_msg_sz);
> complete(&se_clbk_hdl->done);
> spin_unlock_irqrestore(&se_clbk_hdl->clbk_rx_lock, flags);
> if (sz_mismatch)
> dev_err(dev,
> "%s: CMD-RCVER NVM: hdr(0x%x) with different sz(%d != %d).\n",
> devname, *(u32 *)header,
[Severity: High]
Is it safe to dereference devname after releasing the clbk_rx_lock? Since
complete() is called before the lock is dropped in se_if_rx_callback(),
could a user process wake up, close the file descriptor, and free dev_ctx
and its devname before dev_err() is executed, resulting in a use-after-free?
This also appears to happen in the rsp_tag path below.
> (header->size << 2), rx_msg_sz);
> } else if (header->tag == priv->if_defs->rsp_tag) {
[ ... ]
> +static void se_clear_fw_busy(struct se_if_priv *priv)
> +{
> + struct se_if_device_ctx *dev_ctx = NULL;
> + unsigned long flags;
> +
> + spin_lock_irqsave(&priv->fw_busy_lock, flags);
> + dev_ctx = priv->fw_busy_dev_ctx;
> + priv->fw_busy_dev_ctx = NULL;
> + atomic_set(&priv->fw_busy, 0);
> + spin_unlock_irqrestore(&priv->fw_busy_lock, flags);
> +
> + if (!dev_ctx)
> + return;
[ ... ]
> + scoped_guard(mutex, &dev_ctx->fops_lock) {
> + if (atomic_read(&priv->going_away)) {
> + /*
> + * Fatal, but deliberately non-panic: the enclave is
> + * unresponsive at unbind with a transaction still in
> + * flight. Both the coherent staging buffer and any
> + * gen_pool buffers this context owns are abandoned
> + * (host descriptors freed, DMA-visible memory leaked)
> + * to avoid a DMA-after-free while the enclave may still
> + * be writing. Emit one headline error here rather than
> + * per-buffer so the count of faulted contexts is clear.
> + * Do not use WARN/BUG: this path is recoverable and
> + * panic_on_warn kernels must not be brought down by it.
> + */
> + dev_err(priv->dev,
> + "%s: FATAL: enclave stuck at unbind, DMA leaked.\n",
> + dev_ctx->devname);
> + cleanup_se_shared_mem(dev_ctx, false);
> + } else if (dev_ctx->cleanup_done) {
> + cleanup_se_shared_mem(dev_ctx, true);
> + }
> + }
[Severity: High]
Could this permanently leak the 128KB shared memory slot for priv_dev_ctx
if a firmware timeout occurs? Since priv_dev_ctx->cleanup_done is false
until module unload, se_clear_fw_busy() skips the cleanup pass when a late
response arrives. Because se_dev_ctx_shared_mem_cleanup() won't reset pos
while the context is marked busy, the allocation is never reclaimed.
> +
> + kref_put(&dev_ctx->refcount, se_if_dev_ctx_release);
> +}
[ ... ]
> +static ssize_t se_if_fops_read(struct file *fp, char __user *buf, size_t size,
> + loff_t *ppos)
> +{
> + struct se_if_device_ctx *dev_ctx = fp->private_data;
> + u8 rx_msg_snap[MAX_NVM_MSG_LEN] = {};
[ ... ]
> + /* 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);
> + err = copy_len;
[Severity: Medium]
Can this lead to a self-deadlock? se_if_fops_read() holds priv->modify_lock
when it calls fw_api_specific_ops(). If the firmware sends a command like
ELE_STORAGE_OPEN_REQ or ELE_STORAGE_CLOSE_REQ, fw_api_specific_ops() will
attempt to re-acquire priv->modify_lock either directly or via
set_dev_ctx_as_command_receiver(), causing a deadlock.
> + if (copy_to_user(buf, rx_msg_snap, copy_len))
> + err = -EFAULT;
> +
> + se_dev_ctx_shared_mem_cleanup(dev_ctx);
> + mutex_unlock(&dev_ctx->fops_lock);
> + }
> +
> + return err;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260819-imx-se-if-v37-0-5ef5de9ff1dc@nxp.com?part=5
next prev parent reply other threads:[~2026-08-19 6:27 UTC|newest]
Thread overview: 13+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-19 11:42 [PATCH v37 0/7] firmware: imx: driver for NXP secure-enclave Pankaj Gupta via B4 Relay
2026-08-19 11:42 ` [PATCH v37 1/7] Documentation/firmware: add imx/se to other_interfaces Pankaj Gupta via B4 Relay
2026-08-19 6:16 ` sashiko-bot
2026-08-19 11:42 ` [PATCH v37 2/7] dt-bindings: arm: fsl: add imx-se-fw binding doc Pankaj Gupta via B4 Relay
2026-08-19 11:42 ` [PATCH v37 3/7] firmware: imx: add driver for NXP EdgeLock Enclave Pankaj Gupta via B4 Relay
2026-08-19 6:23 ` sashiko-bot
2026-08-19 11:42 ` [PATCH v37 4/7] firmware: imx: device context dedicated to priv Pankaj Gupta via B4 Relay
2026-08-19 11:42 ` [PATCH v37 5/7] firmware: imx: adds miscdev Pankaj Gupta via B4 Relay
2026-08-19 6:27 ` sashiko-bot [this message]
2026-08-19 11:49 ` [EXT] " Pankaj Gupta
2026-08-19 11:42 ` [PATCH v37 6/7] arm64: dts: imx8ulp: add secure enclave node Pankaj Gupta via B4 Relay
2026-08-19 6:23 ` sashiko-bot
2026-08-19 11:42 ` [PATCH v37 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=20260819062729.129E21F000E9@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@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