This is a note to let you know that I've just added the patch titled
dm-verity-fec: fix reading parity bytes split across blocks (take 3)
to the 6.6-stable tree which can be found at:
http://www.kernel.org/git/?p=linux/kernel/git/stable/stable-queue.git;a=summary
The filename of the patch is:
dm-verity-fec-fix-reading-parity-bytes-split-across-.patch
and it can be found in the queue-6.6 subdirectory.
If you, or anyone else, feels it should not be added to the stable tree,
please let <[email protected]> know about it.
commit a8d396362ca89fb6c25e7d68c0d3041d0d703751
Author: Eric Biggers <[email protected]>
Date: Thu Feb 5 20:59:24 2026 -0800
dm-verity-fec: fix reading parity bytes split across blocks (take 3)
[ Upstream commit 430a05cb926f6bdf53e81460a2c3a553257f3f61 ]
fec_decode_bufs() assumes that the parity bytes of the first RS codeword
it decodes are never split across parity blocks.
This assumption is false. Consider v->fec->block_size == 4096 &&
v->fec->roots == 17 && fio->nbufs == 1, for example. In that case, each
call to fec_decode_bufs() consumes v->fec->roots * (fio->nbufs <<
DM_VERITY_FEC_BUF_RS_BITS) = 272 parity bytes.
Considering that the parity data for each message block starts on a
block boundary, the byte alignment in the parity data will iterate
through 272*i mod 4096 until the 3 parity blocks have been consumed. On
the 16th call (i=15), the alignment will be 4080 bytes into the first
block. Only 16 bytes remain in that block, but 17 parity bytes will be
needed. The code reads out-of-bounds from the parity block buffer.
Fortunately this doesn't normally happen, since it can occur only for
certain non-default values of fec_roots *and* when the maximum number of
buffers couldn't be allocated due to low memory. For example with
block_size=4096 only the following cases are affected:
fec_roots=17: nbufs in [1, 3, 5, 15]
fec_roots=19: nbufs in [1, 229]
fec_roots=21: nbufs in [1, 3, 5, 13, 15, 39, 65, 195]
fec_roots=23: nbufs in [1, 89]
Regardless, fix it by refactoring how the parity blocks are read.
Fixes: 6df90c02bae4 ("dm-verity FEC: Fix RS FEC repair for roots unaligned
to block size (take 2)")
Cc: [email protected]
Signed-off-by: Eric Biggers <[email protected]>
Signed-off-by: Mikulas Patocka <[email protected]>
Signed-off-by: Sasha Levin <[email protected]>
diff --git a/drivers/md/dm-verity-fec.c b/drivers/md/dm-verity-fec.c
index 5f646c4bb98a6..5073c93cf962c 100644
--- a/drivers/md/dm-verity-fec.c
+++ b/drivers/md/dm-verity-fec.c
@@ -55,36 +55,6 @@ static int fec_decode_rs8(struct dm_verity *v, struct
dm_verity_fec_io *fio,
fio->erasures, 0, NULL);
}
-/*
- * Read error-correcting codes for the requested RS block. Returns a pointer
- * to the data block. Caller is responsible for releasing buf.
- */
-static u8 *fec_read_parity(struct dm_verity *v, u64 rsb, int index,
- unsigned int *offset, unsigned int par_buf_offset,
- struct dm_buffer **buf)
-{
- u64 position, block, rem;
- u8 *res;
-
- /* We have already part of parity bytes read, skip to the next block */
- if (par_buf_offset)
- index++;
-
- position = (index + rsb) * v->fec->roots;
- block = div64_u64_rem(position, v->fec->io_size, &rem);
- *offset = par_buf_offset ? 0 : (unsigned int)rem;
-
- res = dm_bufio_read(v->fec->bufio, block, buf);
- if (IS_ERR(res)) {
- DMERR("%s: FEC %llu: parity read failed (block %llu): %ld",
- v->data_dev->name, (unsigned long long)rsb,
- (unsigned long long)block, PTR_ERR(res));
- *buf = NULL;
- }
-
- return res;
-}
-
/* Loop over each preallocated buffer slot. */
#define fec_for_each_prealloc_buffer(__i) \
for (__i = 0; __i < DM_VERITY_FEC_BUF_PREALLOC; __i++)
@@ -132,13 +102,27 @@ static int fec_decode_bufs(struct dm_verity *v, struct
dm_verity_fec_io *fio,
{
int r, corrected = 0, res;
struct dm_buffer *buf;
- unsigned int n, i, offset, par_buf_offset = 0;
+ unsigned int n, i, parity_pos, to_copy;
u8 *par, *block, par_buf[DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN];
+ u64 parity_block;
- par = fec_read_parity(v, rsb, block_offset, &offset,
- par_buf_offset, &buf);
- if (IS_ERR(par))
+ /*
+ * Compute the index of the first parity block that will be needed and
+ * the starting position in that block. Then read that block.
+ *
+ * io_size is always a power of 2, but roots might not be. Note that
+ * when it's not, a codeword's parity bytes can span a block boundary.
+ */
+ parity_block = (rsb + block_offset) * v->fec->roots;
+ parity_pos = parity_block & (v->fec->io_size - 1);
+ parity_block >>= v->data_dev_block_bits;
+ par = dm_bufio_read(v->fec->bufio, parity_block, &buf);
+ if (IS_ERR(par)) {
+ DMERR("%s: FEC %llu: parity read failed (block %llu): %ld",
+ v->data_dev->name, (unsigned long long)rsb,
+ (unsigned long long)parity_block, PTR_ERR(par));
return PTR_ERR(par);
+ }
/*
* Decode the RS blocks we have in bufs. Each RS block results in
@@ -146,7 +130,31 @@ static int fec_decode_bufs(struct dm_verity *v, struct
dm_verity_fec_io *fio,
*/
fec_for_each_buffer_rs_block(fio, n, i) {
block = fec_buffer_rs_block(v, fio, n, i);
- memcpy(&par_buf[par_buf_offset], &par[offset], v->fec->roots -
par_buf_offset);
+
+ /*
+ * Copy the next 'roots' parity bytes to 'par_buf', reading
+ * another parity block if needed.
+ */
+ to_copy = min(v->fec->io_size - parity_pos, v->fec->roots);
+ memcpy(par_buf, &par[parity_pos], to_copy);
+ parity_pos += to_copy;
+ if (to_copy < v->fec->roots) {
+ parity_block++;
+ parity_pos = 0;
+
+ dm_bufio_release(buf);
+ par = dm_bufio_read(v->fec->bufio, parity_block, &buf);
+ if (IS_ERR(par)) {
+ DMERR("%s: FEC %llu: parity read failed (block
%llu): %ld",
+ v->data_dev->name, (unsigned long
long)rsb,
+ (unsigned long long)parity_block,
+ PTR_ERR(par));
+ return PTR_ERR(par);
+ }
+ memcpy(&par_buf[to_copy], par, v->fec->roots - to_copy);
+ parity_pos = v->fec->roots - to_copy;
+ }
+
res = fec_decode_rs8(v, fio, block, par_buf, neras);
if (res < 0) {
r = res;
@@ -159,25 +167,6 @@ static int fec_decode_bufs(struct dm_verity *v, struct
dm_verity_fec_io *fio,
block_offset++;
if (block_offset >= 1 << v->data_dev_block_bits)
goto done;
-
- /* Read the next block when we run out of parity bytes */
- offset += (v->fec->roots - par_buf_offset);
- /* Check if parity bytes are split between blocks */
- if (offset < v->fec->io_size && (offset + v->fec->roots) >
v->fec->io_size) {
- par_buf_offset = v->fec->io_size - offset;
- memcpy(par_buf, &par[offset], par_buf_offset);
- offset += par_buf_offset;
- } else
- par_buf_offset = 0;
-
- if (offset >= v->fec->io_size) {
- dm_bufio_release(buf);
-
- par = fec_read_parity(v, rsb, block_offset, &offset,
- par_buf_offset, &buf);
- if (IS_ERR(par))
- return PTR_ERR(par);
- }
}
done:
r = corrected;