From: sashiko-bot@kernel.org
To: "Link Mauve" <linkmauve@linkmauve.fr>
Cc: neil.armstrong@linaro.org, Frank.Li@kernel.org, ojeda@kernel.org,
linux-amlogic@lists.infradead.org, imx@lists.linux.dev
Subject: Re: [PATCH v2 2/2] nvmem: make all reg_write callbacks take const void *
Date: Wed, 15 Jul 2026 20:21:01 +0000 [thread overview]
Message-ID: <20260715202101.9E1CD1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260715195520.25410-3-linkmauve@linkmauve.fr>
Thank you for your contribution! Sashiko AI review found 10 potential issue(s) to consider:
New issues:
- [Critical] The nvmem core incorrectly evaluates write permissions, causing all drivers providing only `.reg_write_const` to be unconditionally treated as read-only.
Pre-existing issues:
- [Critical] The Spreadtrum eFuse driver incorrectly calculates memory offsets and size constraints, leading to out-of-bounds MMIO writes, memory corruption, and kernel stack information leaks.
- [High] The bcm-ocotp driver fails to disable programming mode on the error path.
- [High] The stm32-bsec-optee-ta driver leaks TEE sessions when unaligned writes are requested.
- [High] The imx-ocotp-scu driver has a Time-of-Check to Time-of-Use (TOCTOU) race condition when writing to ECC regions.
- [High] The meson-sm firmware driver exposes a lockless shared memory race when interacting with meson-efuse.
- [High] The lpc18xx-eeprom driver lacks serialization, causing a race condition on the EEPROM power state that can crash the system.
- [High] The qfprom driver modifies global clock rates and regulator voltages without serialization.
- [High] The lan9662-otpc driver modifies hardware state and registers without locking, leading to race conditions and fuse corruption.
- [High] The bcm-ocotp driver modifies command and address registers without locking, leading to OTP fuse corruption.
--
--- Patch [2]: [PATCH v2 2/2] nvmem: make all reg_write callbacks take const void * ---
commit 757b2f38ab10e7e5415fbd52779fa12f901c3ac4
Author: Link Mauve <linkmauve@linkmauve.fr>
Subject: nvmem: make all reg_write callbacks take const void *
The previous commit switched from a pointer to mutable data to a pointer
to immutable data, so let’s fix all users of this API.
> diff --git a/drivers/nvmem/apple-spmi-nvmem.c b/drivers/nvmem/apple-spmi-nvmem.c
> --- a/drivers/nvmem/apple-spmi-nvmem.c
> +++ b/drivers/nvmem/apple-spmi-nvmem.c
> @@ -29,7 +29,7 @@ static int apple_spmi_nvmem_probe(struct spmi_device *sdev)
> .stride = 1,
> .size = 0xffff,
> .reg_read = (void *)regmap_bulk_read,
> - .reg_write = (void *)regmap_bulk_write,
> + .reg_write_const = (void *)regmap_bulk_write,
[Severity: Critical]
By migrating to .reg_write_const and leaving .reg_write as NULL, does this
inadvertently make all these devices read-only?
Looking at the nvmem core in drivers/nvmem/core.c:
nvmem_register() {
...
nvmem->read_only = device_property_present(config->dev, "read-only") ||
config->read_only || !nvmem->reg_write ||
!nvmem->reg_write_const;
...
}
bin_attr_nvmem_write() {
...
if (!nvmem->reg_write || !nvmem->reg_write_const || nvmem->read_only)
return -EPERM;
...
}
Because the core checks !nvmem->reg_write || !nvmem->reg_write_const,
setting reg_write to NULL causes the core to strip write permissions and
return -EPERM for all sysfs writes.
> diff --git a/drivers/nvmem/bcm-ocotp.c b/drivers/nvmem/bcm-ocotp.c
> --- a/drivers/nvmem/bcm-ocotp.c
> +++ b/drivers/nvmem/bcm-ocotp.c
> [ ... ]
> @@ -179,11 +179,11 @@ static int bcm_otpc_read(void *context, unsigned int offset, void *val,
> return 0;
> }
>
> -static int bcm_otpc_write(void *context, unsigned int offset, void *val,
> +static int bcm_otpc_write(void *context, unsigned int offset, const void *val,
> size_t bytes)
> {
> struct otpc_priv *priv = context;
> - u32 *buf = val;
> + const u32 *buf = val;
[Severity: High]
This isn't a bug introduced by this patch, but is it possible for concurrent
reads and writes to corrupt hardware state?
In bcm_otpc_write():
for (bytes_written = 0; bytes_written < bytes;) {
set_command(priv->base, OTPC_CMD_PROGRAM);
set_cpu_address(priv->base, address++);
And similarly in bcm_otpc_read():
for (bytes_read = 0; bytes_read < bytes;) {
set_command(priv->base, OTPC_CMD_READ);
set_cpu_address(priv->base, address++);
Without serialization, concurrent sysfs operations could cause the command and
address registers to be overwritten mid-operation by another thread,
potentially redirecting writes to unintended fuse locations.
[Severity: High]
This isn't a bug introduced by this patch, but does the error path in
bcm_otpc_write() permanently leave the hardware in programming mode?
ret = enable_ocotp_program(priv->base);
if (ret)
return -EIO;
for (bytes_written = 0; bytes_written < bytes;) {
...
ret = poll_cpu_status(priv->base, OTPC_STAT_CMD_DONE);
reset_start_bit(priv->base);
if (ret) {
dev_err(priv->dev, "otp write error: 0x%x", ret);
return -EIO;
}
}
If poll_cpu_status() returns an error, the function returns -EIO directly and
skips the disable_ocotp_program(priv->base) call at the end of the function,
potentially leaving programming voltages applied.
> diff --git a/drivers/nvmem/imx-ocotp-scu.c b/drivers/nvmem/imx-ocotp-scu.c
> --- a/drivers/nvmem/imx-ocotp-scu.c
> +++ b/drivers/nvmem/imx-ocotp-scu.c
> @@ -178,11 +178,11 @@ static int imx_scu_ocotp_read(void *context, unsigned int offset,
> }
>
> static int imx_scu_ocotp_write(void *context, unsigned int offset,
> - void *val, size_t bytes)
> + const void *val, size_t bytes)
> {
> struct ocotp_priv *priv = context;
> struct arm_smccc_res res;
> - u32 *buf = val;
> + const u32 *buf = val;
[Severity: High]
This isn't a bug introduced by this patch, but is there a race condition when
writing to ECC regions in imx_scu_ocotp_write()?
if (in_ecc(context, index)) {
pr_warn("ECC region, only program once\n");
mutex_lock(&scu_ocotp_mutex);
ret = imx_sc_misc_otp_fuse_read(priv->nvmem_ipc, index, &tmp);
mutex_unlock(&scu_ocotp_mutex);
if (ret)
return ret;
if (tmp) {
pr_warn("ECC region, already has value: %x\n", tmp);
return -EIO;
}
}
mutex_lock(&scu_ocotp_mutex);
arm_smccc_smc(IMX_SIP_OTP_WRITE, index, *buf, 0, 0, 0, 0, 0, &res);
mutex_unlock(&scu_ocotp_mutex);
Because the lock is released after checking if the ECC region is programmed and
then re-acquired for the SMC write, two concurrent threads could both pass the
initial check and perform a double-write to the write-once ECC region.
> diff --git a/drivers/nvmem/lan9662-otpc.c b/drivers/nvmem/lan9662-otpc.c
> --- a/drivers/nvmem/lan9662-otpc.c
> +++ b/drivers/nvmem/lan9662-otpc.c
> @@ -140,10 +140,10 @@ static int lan9662_otp_read(void *context, unsigned int offset,
> }
>
> static int lan9662_otp_write(void *context, unsigned int offset,
> - void *_val, size_t bytes)
> + const void *_val, size_t bytes)
> {
> struct lan9662_otp *otp = context;
> - u8 *val = _val;
> + const u8 *val = _val;
[Severity: High]
This isn't a bug introduced by this patch, but can concurrent accesses to
lan9662_otp_write() and lan9662_otp_read() corrupt OTP operations?
for (i = 0; i < bytes; i++) {
if (val[i]) {
rc = lan9662_otp_read_byte(otp, offset + i, &data);
if (rc < 0)
break;
...
rc = lan9662_otp_write_byte(otp, offset + i, newdata);
Since these hardware register accesses and state changes (such as powering up
and polling) are performed without holding a local lock, concurrent sysfs
operations might overwrite each other's commands in flight, potentially
corrupting OTP data.
> diff --git a/drivers/nvmem/lpc18xx_eeprom.c b/drivers/nvmem/lpc18xx_eeprom.c
> --- a/drivers/nvmem/lpc18xx_eeprom.c
> +++ b/drivers/nvmem/lpc18xx_eeprom.c
> @@ -88,7 +88,7 @@ static int lpc18xx_eeprom_busywait_until_prog(struct lpc18xx_eeprom_dev *eeprom)
> }
>
> static int lpc18xx_eeprom_gather_write(void *context, unsigned int reg,
> - void *val, size_t bytes)
> + const void *val, size_t bytes)
> {
> struct lpc18xx_eeprom_dev *eeprom = context;
> unsigned int offset = reg;
> @@ -110,7 +110,7 @@ static int lpc18xx_eeprom_gather_write(void *context, unsigned int reg,
> usleep_range(100, 200);
>
> while (bytes) {
> - writel(*(u32 *)val, eeprom->mem_base + offset);
> + writel(*(const u32 *)val, eeprom->mem_base + offset);
[Severity: High]
This isn't a bug introduced by this patch, but does the lack of a lock around
EEPROM power management in lpc18xx_eeprom_gather_write() and
lpc18xx_eeprom_read() risk a system crash?
lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
LPC18XX_EEPROM_PWRDWN_NO);
usleep_range(100, 200);
...
writel(*(const u32 *)val, eeprom->mem_base + offset);
lpc18xx_eeprom_writel(eeprom, LPC18XX_EEPROM_PWRDWN,
LPC18XX_EEPROM_PWRDWN_YES);
If a read and write occur concurrently, one thread might power down the EEPROM
while the other is still inside its memory access loop. Accessing powered-down
MMIO regions typically causes a synchronous data abort.
> diff --git a/drivers/nvmem/meson-efuse.c b/drivers/nvmem/meson-efuse.c
> --- a/drivers/nvmem/meson-efuse.c
> +++ b/drivers/nvmem/meson-efuse.c
> @@ -27,12 +27,12 @@ static int meson_efuse_read(void *context, unsigned int offset,
> }
>
> static int meson_efuse_write(void *context, unsigned int offset,
> - void *val, size_t bytes)
> + const void *val, size_t bytes)
> {
> struct meson_sm_firmware *fw = context;
> int ret;
>
> - ret = meson_sm_call_write(fw, (u8 *)val, bytes, SM_EFUSE_WRITE, offset,
> + ret = meson_sm_call_write(fw, (void *)val, bytes, SM_EFUSE_WRITE, offset,
[Severity: High]
This isn't a bug introduced by this patch, but does meson_sm_call_write()
expose a lockless shared memory race?
In drivers/firmware/meson/meson_sm.c:
memcpy(fw->sm_shmem_in_base, buffer, size);
if (meson_sm_call(fw, cmd_index, &written, arg0, arg1, arg2, arg3, arg4) < 0)
return -EINVAL;
Since fw->sm_shmem_in_base is a global shared memory buffer and there is no
locking, concurrent writes could overwrite each other's payloads before the SMC
call executes, potentially writing corrupted or arbitrary data to the
write-once eFuses.
> diff --git a/drivers/nvmem/qfprom.c b/drivers/nvmem/qfprom.c
> --- a/drivers/nvmem/qfprom.c
> +++ b/drivers/nvmem/qfprom.c
> @@ -253,13 +253,13 @@ static int qfprom_enable_fuse_blowing(const struct qfprom_priv *priv,
> *
> * Return: 0 or -err.
> */
> -static int qfprom_reg_write(void *context, unsigned int reg, void *_val,
> +static int qfprom_reg_write(void *context, unsigned int reg, const void *_val,
> size_t bytes)
> {
> struct qfprom_priv *priv = context;
> struct qfprom_touched_values old;
> int words = bytes / 4;
> - u32 *value = _val;
> + const u32 *value = _val;
[Severity: High]
This isn't a bug introduced by this patch, but can concurrent calls to
qfprom_reg_write() leave the system clock and regulators permanently unbalanced?
ret = qfprom_enable_fuse_blowing(priv, &old);
...
qfprom_disable_fuse_blowing(priv, &old);
Inside qfprom_enable_fuse_blowing():
old->clk_rate = clk_get_rate(priv->secclk);
ret = clk_set_rate(priv->secclk, priv->soc_data->qfprom_blow_set_freq);
Because the old state is saved to a local stack variable without any mutex
serialization, a concurrent write could save the already-elevated clock rate
and regulator voltage as the "old" state. Upon completion, the hardware would
be restored to this elevated state rather than the original one.
> diff --git a/drivers/nvmem/sprd-efuse.c b/drivers/nvmem/sprd-efuse.c
> --- a/drivers/nvmem/sprd-efuse.c
> +++ b/drivers/nvmem/sprd-efuse.c
> @@ -192,7 +192,7 @@ static void sprd_efuse_set_prog_en(struct sprd_efuse *efuse, bool en)
> }
>
> static int sprd_efuse_raw_prog(struct sprd_efuse *efuse, u32 blk, bool doub,
> - bool lock, u32 *data)
> + bool lock, const u32 *data)
> {
> u32 status;
> int ret = 0;
> [ ... ]
> @@ -321,7 +321,7 @@ static int sprd_efuse_read(void *context, u32 offset, void *val, size_t bytes)
> return ret;
> }
>
> -static int sprd_efuse_write(void *context, u32 offset, void *val, size_t bytes)
> +static int sprd_efuse_write(void *context, u32 offset, const void *val, size_t bytes)
> {
> struct sprd_efuse *efuse = context;
> bool blk_double = efuse->data->blk_double;
[Severity: Critical]
This isn't a bug introduced by this patch, but does the offset handling in
sprd_efuse_write() and sprd_efuse_read() lead to out-of-bounds access?
In sprd_efuse_write(), the raw byte offset is passed directly as the block
index to sprd_efuse_raw_prog():
ret = sprd_efuse_raw_prog(efuse, offset, blk_double, lock, val);
Then, in sprd_efuse_raw_prog():
writel(*data, efuse->base + SPRD_EFUSE_MEM(blk));
The macro SPRD_EFUSE_MEM(blk) shifts the offset by 2 (multiplying by 4).
Because word_size = 1 and stride = 1, userspace can request arbitrary offsets,
causing this to write far outside the intended eFuse MMIO region.
Additionally, in sprd_efuse_read():
u32 data;
...
ret = sprd_efuse_raw_read(efuse, index, &data, blk_double);
if (!ret) {
data >>= blk_offset;
memcpy(val, &data, bytes);
}
If userspace requests a read larger than 4 bytes, memcpy() will read past the
local stack variable 'data' and copy uninitialized adjacent kernel stack memory
to userspace.
> diff --git a/drivers/nvmem/stm32-bsec-optee-ta.c b/drivers/nvmem/stm32-bsec-optee-ta.c
> --- a/drivers/nvmem/stm32-bsec-optee-ta.c
> +++ b/drivers/nvmem/stm32-bsec-optee-ta.c
> @@ -203,7 +203,7 @@ int stm32_bsec_optee_ta_read(struct tee_context *ctx, unsigned int offset,
>
> /* stm32_bsec_optee_ta_write() - nvmem write access using PTA client driver */
> int stm32_bsec_optee_ta_write(struct tee_context *ctx, unsigned int lower,
> - unsigned int offset, void *buf, size_t bytes)
> + unsigned int offset, const void *buf, size_t bytes)
> { struct tee_shm *shm;
[Severity: High]
This isn't a bug introduced by this patch, but does an unaligned write request
leak TEE sessions?
ret = stm32_bsec_ta_open_session(ctx, &session_id);
if (ret)
return ret;
/* Allow only writing complete 32-bits aligned words */
if ((bytes % 4) || (offset % 4))
return -EINVAL;
If the offset or bytes are not 32-bit aligned, the function returns -EINVAL
directly, bypassing the call to stm32_bsec_ta_close_session(ctx, session_id).
Because the nvmem core allows unaligned writes by default, this could be
triggered repeatedly to exhaust all available TEE sessions.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260715195520.25410-1-linkmauve@linkmauve.fr?part=2
next prev parent reply other threads:[~2026-07-15 20:21 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-15 19:55 [PATCH v2 0/2] nvmem: fix a const-unsoundness in reg_write Link Mauve
2026-07-15 19:55 ` [PATCH v2 1/2] nvmem: core: deprecate reg_write callback and add reg_write_const Link Mauve
2026-07-15 20:04 ` sashiko-bot
2026-07-15 19:55 ` [PATCH v2 2/2] nvmem: make all reg_write callbacks take const void * Link Mauve
2026-07-15 20:21 ` sashiko-bot [this message]
2026-07-16 9:03 ` [PATCH v2 0/2] nvmem: fix a const-unsoundness in reg_write Andy Shevchenko
2026-07-16 11:07 ` Link Mauve
2026-07-16 11:29 ` Link Mauve
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=20260715202101.9E1CD1F000E9@smtp.kernel.org \
--to=sashiko-bot@kernel.org \
--cc=Frank.Li@kernel.org \
--cc=imx@lists.linux.dev \
--cc=linkmauve@linkmauve.fr \
--cc=linux-amlogic@lists.infradead.org \
--cc=neil.armstrong@linaro.org \
--cc=ojeda@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