Memory safety - Out-of-bounds read (in kernel debugfs output path for SPI-NOR flags)

HIGH
torvalds/linux
Commit: 26fd6bff2c05
Affected: <= v7.0-rc6 (pre-fix); fixed in 7.1-rc2 by the mtd/fixes-for-7.1-rc2 merge
2026-05-02 13:31 UTC

Description

The commit fixes a memory-safety issue in SPI-NOR printing of device flags via debugfs. Specifically, spi_nor_params_show previously passed an incorrect length to spi_nor_print_flags by using the size in bytes of the flags name array (sizeof(snor_f_names)) instead of the number of elements (ARRAY_SIZE(snor_f_names)). This can cause an out-of-bounds read when the nor->flags bitfield contains bits beyond the number of defined flag entries, leading to potential kernel memory read or instability. The patch also adds safe handling for packed ODTR (on-die transfer) reads and aligns related SPI-NAND/ODTR logic, but the explicit vulnerability fix is the corrected flag-printing length. This is a memory-safety vulnerability in the kernel's debugfs output path.

Proof of Concept

Pre-fix (v7.0-rc6 era) PoC outline: - Preconditions: A vulnerable kernel build that includes this buggy spi_nor_params_show path and a SPI-NOR device with flags set to expose more flag bits than the snor_f_names array defines. - Location to exercise: Read the SPI-NOR params via debugfs, for example path similar to /sys/kernel/debug/mtd/spi-nor/nor0/params (the exact path may vary by arch and kernel config). - PoC steps: 1) Ensure a SPI-NAND/NOR device is present and a corresponding debugfs entry for its params exists. 2) Trigger the read of the params file, e.g.: cat /sys/kernel/debug/mtd/spi-nor/nor0/params 3) On vulnerable kernels, the read may perform an out-of-bounds read from memory while iterating snor_f_names due to an incorrect element count, potentially crashing the kernel or leaking kernel memory in the printed output. - Post-fix (7.1-rc2) behavior: The code uses ARRAY_SIZE(snor_f_names) to supply the correct number of elements to spi_nor_print_flags, preventing the OOB access. - If you cannot access the exact debugfs path on your system, a minimal pseudo PoC is to attempt to read the SPI-NOR params via debugfs and observe for a kernel bug (oops/panic) that occurs when a device has flags beyond the defined set of flag names.

Commit Details

Author: Linus Torvalds

Date: 2026-05-01 00:36 UTC

Message:

Merge tag 'mtd/fixes-for-7.1-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux Pull mtd fixes from Miquel Raynal: "Besides an out-of-bound bug, this is about properly supporting Winbond octal SPI NAND chips which use a specific pattern for stuffing more address bits in some operations. This uses the spi-mem flag in SPI NAND that was added to the spi-mem layer just before the merge window through the spi tree" * tag 'mtd/fixes-for-7.1-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/mtd/linux: mtd: spinand: winbond: Fix ODTR write VCR on W35NxxJW mtd: spinand: winbond: Set the packed page read flag to W35N02/04JW mtd: spinand: Add support for packed read data ODTR commands mtd: spi-nor: debugfs: fix out-of-bounds read in spi_nor_params_show()

Triage Assessment

Vulnerability Type: Memory safety (out-of-bounds read)

Confidence: HIGH

Reasoning:

The commit notes fix for an out-of-bounds read (and related SPI-NAND/ODTR handling improvements). The code changes address an out-of-bounds access in spi_nor_params_show() and related spinand/odtr handling, which are memory-safety vulnerabilities. The changes also improve correctness for packed/ODTR reads, reducing risk of incorrect memory access.

Verification Assessment

Vulnerability Type: Memory safety - Out-of-bounds read (in kernel debugfs output path for SPI-NOR flags)

Confidence: HIGH

Affected Versions: <= v7.0-rc6 (pre-fix); fixed in 7.1-rc2 by the mtd/fixes-for-7.1-rc2 merge

Code Diff

diff --git a/drivers/mtd/nand/spi/core.c b/drivers/mtd/nand/spi/core.c index 8aa3753aaaa1d3..0b076790bd9df6 100644 --- a/drivers/mtd/nand/spi/core.c +++ b/drivers/mtd/nand/spi/core.c @@ -100,6 +100,17 @@ spinand_fill_page_read_op(struct spinand_device *spinand, u64 addr) return op; } +static struct spi_mem_op +spinand_fill_page_read_packed_op(struct spinand_device *spinand, u64 addr) +{ + struct spi_mem_op op = spinand->op_templates->page_read; + + op.cmd.opcode |= addr >> 16; + op.addr.val = addr & 0xFFFF; + + return op; +} + struct spi_mem_op spinand_fill_prog_exec_op(struct spinand_device *spinand, u64 addr) { @@ -453,7 +464,10 @@ static int spinand_load_page_op(struct spinand_device *spinand, { struct nand_device *nand = spinand_to_nand(spinand); unsigned int row = nanddev_pos_to_row(nand, &req->pos); - struct spi_mem_op op = SPINAND_OP(spinand, page_read, row); + bool packed = spinand->flags & SPINAND_ODTR_PACKED_PAGE_READ; + struct spi_mem_op op = packed ? + SPINAND_OP(spinand, page_read_packed, row) : + SPINAND_OP(spinand, page_read, row); return spi_mem_exec_op(spinand->spimem, &op); } @@ -1489,9 +1503,13 @@ static int spinand_init_odtr_instruction_set(struct spinand_device *spinand) if (!spi_mem_supports_op(spinand->spimem, &tmpl->blk_erase)) return -EOPNOTSUPP; - tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_8D_8D_0_OP(0); - if (!spi_mem_supports_op(spinand->spimem, &tmpl->page_read)) + if (spinand->flags & SPINAND_ODTR_PACKED_PAGE_READ) + tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_PACKED_8D_8D_0_OP(0); + else + tmpl->page_read = (struct spi_mem_op)SPINAND_PAGE_READ_8D_8D_0_OP(0); + if (!spi_mem_supports_op(spinand->spimem, &tmpl->page_read)) { return -EOPNOTSUPP; + } tmpl->prog_exec = (struct spi_mem_op)SPINAND_PROG_EXEC_8D_8D_0_OP(0); if (!spi_mem_supports_op(spinand->spimem, &tmpl->prog_exec)) diff --git a/drivers/mtd/nand/spi/winbond.c b/drivers/mtd/nand/spi/winbond.c index ad22774096e612..7cc0f0091430c1 100644 --- a/drivers/mtd/nand/spi/winbond.c +++ b/drivers/mtd/nand/spi/winbond.c @@ -99,7 +99,7 @@ static SPINAND_OP_VARIANTS(update_cache_variants, #define SPINAND_WINBOND_WRITE_VCR_8D_8D_8D(reg, buf) \ SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x81, 8), \ - SPI_MEM_DTR_OP_ADDR(4, reg, 8), \ + SPI_MEM_DTR_OP_ADDR(4, reg << 8, 8), \ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_DTR_OP_DATA_OUT(2, buf, 8)) @@ -518,7 +518,7 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, &write_cache_octal_variants, &update_cache_octal_variants), - 0, + SPINAND_ODTR_PACKED_PAGE_READ, SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), @@ -529,7 +529,7 @@ static const struct spinand_info winbond_spinand_table[] = { SPINAND_INFO_OP_VARIANTS(&read_cache_octal_variants, &write_cache_octal_variants, &update_cache_octal_variants), - 0, + SPINAND_ODTR_PACKED_PAGE_READ, SPINAND_INFO_VENDOR_OPS(&winbond_w35_ops), SPINAND_ECCINFO(&w35n01jw_ooblayout, NULL), SPINAND_CONFIGURE_CHIP(w35n0xjw_vcr_cfg)), diff --git a/drivers/mtd/spi-nor/debugfs.c b/drivers/mtd/spi-nor/debugfs.c index fa6956144d2e44..14ba1680c31547 100644 --- a/drivers/mtd/spi-nor/debugfs.c +++ b/drivers/mtd/spi-nor/debugfs.c @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 +#include <linux/array_size.h> #include <linux/debugfs.h> #include <linux/mtd/spi-nor.h> #include <linux/spi/spi.h> @@ -92,7 +93,8 @@ static int spi_nor_params_show(struct seq_file *s, void *data) seq_printf(s, "address nbytes\t%u\n", nor->addr_nbytes); seq_puts(s, "flags\t\t"); - spi_nor_print_flags(s, nor->flags, snor_f_names, sizeof(snor_f_names)); + spi_nor_print_flags(s, nor->flags, snor_f_names, + ARRAY_SIZE(snor_f_names)); seq_puts(s, "\n"); seq_puts(s, "\nopcodes\n"); diff --git a/include/linux/mtd/spinand.h b/include/linux/mtd/spinand.h index 58abd306ebe394..782984ba3a209a 100644 --- a/include/linux/mtd/spinand.h +++ b/include/linux/mtd/spinand.h @@ -290,6 +290,12 @@ SPI_MEM_OP_NO_DUMMY, \ SPI_MEM_OP_NO_DATA) +#define SPINAND_PAGE_READ_PACKED_8D_8D_0_OP(addr) \ + SPI_MEM_OP(SPI_MEM_DTR_OP_PACKED_CMD(0x13, addr >> 16, 8), \ + SPI_MEM_DTR_OP_ADDR(2, addr & 0xffff, 8), \ + SPI_MEM_OP_NO_DUMMY, \ + SPI_MEM_OP_NO_DATA) + #define SPINAND_PAGE_READ_FROM_CACHE_8D_8D_8D_OP(addr, ndummy, buf, len, freq) \ SPI_MEM_OP(SPI_MEM_DTR_OP_RPT_CMD(0x9d, 8), \ SPI_MEM_DTR_OP_ADDR(2, addr, 8), \ @@ -483,6 +489,7 @@ struct spinand_ecc_info { #define SPINAND_HAS_PROG_PLANE_SELECT_BIT BIT(2) #define SPINAND_HAS_READ_PLANE_SELECT_BIT BIT(3) #define SPINAND_NO_RAW_ACCESS BIT(4) +#define SPINAND_ODTR_PACKED_PAGE_READ BIT(5) /** * struct spinand_ondie_ecc_conf - private SPI-NAND on-die ECC engine structure
← Back to Alerts View on GitHub →