[PATCH 3/3] mtdchar: keep OOB chunks aligned with their data pages
James Hilliard
james.hilliard1 at gmail.com
Fri Nov 6 21:20:41 PST 2026
MEMREAD and MEMWRITE advance the flash address by the data bytes
transferred. OOB-only operations transfer no data, so another buffer-sized
chunk starts at the same address and repeats pages.
Advance OOB-only requests by the number of pages transferred. Round
non-final OOB chunks down to whole pages so the next call starts at the
next page, rather than repeating a partially consumed page. Allow the
buffer to hold at least one page of OOB and reject requests when the
selected mode has no OOB capacity.
When OOB is the limiting buffer, also cap the data chunk to those same
pages. Otherwise a combined request can exhaust its data before all OOB
has been transferred.
Fixes: 6420ac0af95d ("mtdchar: prevent unbounded allocation in MEMWRITE ioctl")
Fixes: 095bb6e44eb1 ("mtdchar: add MEMREAD ioctl")
Cc: stable at vger.kernel.org
Signed-off-by: James Hilliard <james.hilliard1 at gmail.com>
---
drivers/mtd/mtdchar.c | 53 +++++++++++++++++++++++++++++++++++++++++++++------
1 file changed, 47 insertions(+), 6 deletions(-)
diff --git a/drivers/mtd/mtdchar.c b/drivers/mtd/mtdchar.c
index 189e462a9df0..85e98905f732 100644
--- a/drivers/mtd/mtdchar.c
+++ b/drivers/mtd/mtdchar.c
@@ -589,6 +589,30 @@ static void adjust_oob_length(struct mtd_info *mtd, uint64_t start,
ops->ooblen = min_t(u64, ops->ooblen, pages * oob_per_page);
}
+static int mtdchar_adjust_io_lengths(struct mtd_info *mtd, uint64_t start,
+ struct mtd_oob_ops *ops, uint64_t remaining_oob)
+{
+ u32 oob_per_page = mtd_oobavail(mtd, ops);
+ u64 len;
+
+ if (!ops->ooblen)
+ return 0;
+ if (!oob_per_page)
+ return -EINVAL;
+
+ if (ops->ooblen < remaining_oob) {
+ /* Keep a page's OOB together when another chunk follows. */
+ ops->ooblen = rounddown(ops->ooblen, oob_per_page);
+ if (ops->len) {
+ len = (u64)(ops->ooblen / oob_per_page) * mtd->writesize;
+ len -= mtd_mod_by_ws(start, mtd);
+ ops->len = min_t(u64, ops->len, len);
+ }
+ }
+
+ return 0;
+}
+
static noinline_for_stack int
mtdchar_write_ioctl(struct mtd_info *mtd, struct mtd_write_req __user *argp)
{
@@ -628,7 +652,8 @@ mtdchar_write_ioctl(struct mtd_info *mtd, struct mtd_write_req __user *argp)
return -ENOMEM;
}
- oobbuf_len = min_t(size_t, req.ooblen, mtd->erasesize);
+ oobbuf_len = min_t(size_t, req.ooblen,
+ max(mtd->erasesize, mtd->oobsize));
if (oobbuf_len > 0) {
oobbuf = kvmalloc(oobbuf_len, GFP_KERNEL);
if (!oobbuf) {
@@ -648,13 +673,17 @@ mtdchar_write_ioctl(struct mtd_info *mtd, struct mtd_write_req __user *argp)
/*
* Shorten non-page-aligned, eraseblock-sized writes so that
- * the write ends on an eraseblock boundary. This is necessary
+ * the write ends on a page boundary. This is necessary
* for adjust_oob_length() to properly handle non-page-aligned
* writes.
*/
if (ops.len == mtd->erasesize)
ops.len -= mtd_mod_by_ws(req.start + ops.len, mtd);
+ ret = mtdchar_adjust_io_lengths(mtd, req.start, &ops, req.ooblen);
+ if (ret)
+ break;
+
/*
* For writes which are not OOB-only, adjust the amount of OOB
* data written according to the number of data pages written.
@@ -674,7 +703,11 @@ mtdchar_write_ioctl(struct mtd_info *mtd, struct mtd_write_req __user *argp)
if (ret)
break;
- req.start += ops.retlen;
+ if (usr_data)
+ req.start += ops.retlen;
+ else
+ req.start += (u64)(ops.oobretlen / mtd_oobavail(mtd, &ops)) *
+ mtd->writesize;
req.len -= ops.retlen;
usr_data += ops.retlen;
@@ -739,7 +772,8 @@ mtdchar_read_ioctl(struct mtd_info *mtd, struct mtd_read_req __user *argp)
}
}
- oobbuf_len = min_t(size_t, req.ooblen, mtd->erasesize);
+ oobbuf_len = min_t(size_t, req.ooblen,
+ max(mtd->erasesize, mtd->oobsize));
if (oobbuf_len > 0) {
oobbuf = kvmalloc(oobbuf_len, GFP_KERNEL);
if (!oobbuf) {
@@ -761,7 +795,7 @@ mtdchar_read_ioctl(struct mtd_info *mtd, struct mtd_read_req __user *argp)
/*
* Shorten non-page-aligned, eraseblock-sized reads so that the
- * read ends on an eraseblock boundary. This is necessary in
+ * read ends on a page boundary. This is necessary in
* order to prevent OOB data for some pages from being
* duplicated in the output of non-page-aligned reads requiring
* multiple mtd_read_oob() calls to be completed.
@@ -769,6 +803,9 @@ mtdchar_read_ioctl(struct mtd_info *mtd, struct mtd_read_req __user *argp)
if (ops.len == mtd->erasesize)
ops.len -= mtd_mod_by_ws(req.start + ops.len, mtd);
+ ret = mtdchar_adjust_io_lengths(mtd, req.start, &ops, req.ooblen);
+ if (ret)
+ break;
if (ops.len < req.len)
adjust_oob_length(mtd, req.start, &ops);
@@ -789,7 +826,11 @@ mtdchar_read_ioctl(struct mtd_info *mtd, struct mtd_read_req __user *argp)
break;
}
- req.start += ops.retlen;
+ if (usr_data)
+ req.start += ops.retlen;
+ else
+ req.start += (u64)(ops.oobretlen / mtd_oobavail(mtd, &ops)) *
+ mtd->writesize;
req.len -= ops.retlen;
usr_data += ops.retlen;
--
2.53.0
More information about the linux-mtd
mailing list