[PATCH v2 2/2] nvmem: sprd: fix out-of-bounds read in sprd_efuse_read()

From: Pradhan, Sanman

Date: Mon Sep 21 2026 - 12:58:19 EST


From: Sanman Pradhan <psanman@xxxxxxxxxxx>

sprd_efuse_read() reads a single four-byte block into the stack variable
"data" and then copies the caller's full "bytes" length from it. The
provider advertises word_size and stride of 1, so a whole-device sysfs
read reaches the callback as a single 96-byte request, and
__nvmem_cell_read() likewise submits cell->raw_len in one call.

Reading the whole device (96 bytes for ums312_data) therefore copies 92
bytes past the end of the four-byte stack variable into the caller's
buffer. The read is reachable from the nvmem sysfs file when
CONFIG_NVMEM_SYSFS is enabled, and from any DT cell wider than four
bytes. Only the first block is read, so the returned data is also
incorrect: later blocks are never fetched.

Read and copy one block at a time, capping each copy to the bytes left
in the block so it cannot exceed sizeof(data), and account for an
unaligned starting offset.

Fixes: 096030e7f449 ("nvmem: sprd: Add Spreadtrum SoCs eFuse support")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Sanman Pradhan <psanman@xxxxxxxxxxx>
---

Changes in v2:
- Drop the provider-side offset + bytes range check; it is now done in
the nvmem core (patch 1).
- Qualify the sysfs note with CONFIG_NVMEM_SYSFS, and use min() instead
of min_t() (both operands are size_t).

Compile-tested only (NVMEM_SPRD_EFUSE, ARCH_SPRD || COMPILE_TEST); no
Spreadtrum eFuse hardware available for runtime testing.

drivers/nvmem/sprd-efuse.c | 17 ++++++++++++++---
1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/drivers/nvmem/sprd-efuse.c b/drivers/nvmem/sprd-efuse.c
index 1a7e4e5d8b86c..82995f8bce471 100644
--- a/drivers/nvmem/sprd-efuse.c
+++ b/drivers/nvmem/sprd-efuse.c
@@ -5,6 +5,7 @@
#include <linux/delay.h>
#include <linux/hwspinlock.h>
#include <linux/io.h>
+#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/nvmem-provider.h>
#include <linux/of.h>
@@ -297,6 +298,7 @@ static int sprd_efuse_read(void *context, u32 offset, void *val, size_t bytes)
bool blk_double = efuse->data->blk_double;
u32 index = offset / SPRD_EFUSE_BLOCK_WIDTH + efuse->data->blk_offset;
u32 blk_offset = (offset % SPRD_EFUSE_BLOCK_WIDTH) * BITS_PER_BYTE;
+ u8 *buf = val;
u32 data;
int ret;

@@ -308,10 +310,19 @@ static int sprd_efuse_read(void *context, u32 offset, void *val, size_t bytes)
if (ret)
goto unlock;

- ret = sprd_efuse_raw_read(efuse, index, &data, blk_double);
- if (!ret) {
+ while (bytes) {
+ size_t avail = SPRD_EFUSE_BLOCK_WIDTH - blk_offset / BITS_PER_BYTE;
+ size_t count = min(bytes, avail);
+
+ ret = sprd_efuse_raw_read(efuse, index++, &data, blk_double);
+ if (ret)
+ break;
+
data >>= blk_offset;
- memcpy(val, &data, bytes);
+ memcpy(buf, &data, count);
+ buf += count;
+ bytes -= count;
+ blk_offset = 0;
}

clk_disable_unprepare(efuse->clk);
--
2.34.1