dm-verity FEC: Fix RS FEC repair for roots unaligned to block size (take 2)

This patch fixes an issue that was fixed in the commit
df7b59ba9245 ("dm verity: fix FEC for RS roots unaligned to block size")
but later broken again in the commit
8ca7cab82bda ("dm verity fec: fix misaligned RS roots IO")

If the Reed-Solomon roots setting spans multiple blocks, the code does not
use proper parity bytes and randomly fails to repair even trivial errors.

This bug cannot happen if the sector size is multiple of RS roots
setting (Android case with roots 2).

The previous solution was to find a dm-bufio block size that is multiple
of the device sector size and roots size. Unfortunately, the optimization
in commit 8ca7cab82bda ("dm verity fec: fix misaligned RS roots IO")
is incorrect and uses data block size for some roots (for example, it uses
4096 block size for roots = 20).

This patch uses a different approach:

- It always uses a configured data block size for dm-bufio to avoid
possible misaligned IOs.

- and it caches the processed parity bytes, so it can join it
if it spans two blocks.

As the RS calculation is called only if an error is detected and
the process is computationally intensive, copying a few more bytes
should not introduce performance issues.

The issue was reported to cryptsetup with trivial reproducer
https://gitlab.com/cryptsetup/cryptsetup/-/issues/923

Reproducer (with roots=20):

# create verity device with RS FEC
dd if=/dev/urandom of=data.img bs=4096 count=8 status=none
veritysetup format data.img hash.img --fec-device=fec.img --fec-roots=20 | \
awk '/^Root hash/{ print $3 }' >roothash

# create an erasure that should always be repairable with this roots setting
dd if=/dev/zero of=data.img conv=notrunc bs=1 count=4 seek=4 status=none

# try to read it through dm-verity
veritysetup open data.img test hash.img --fec-device=fec.img --fec-roots=20 $(cat roothash)
dd if=/dev/mapper/test of=/dev/null bs=4096 status=noxfer

Even now the log says it cannot repair it:
: verity-fec: 7:1: FEC 0: failed to correct: -74
: device-mapper: verity: 7:1: data block 0 is corrupted
...

With this fix, errors are properly repaired.
: verity-fec: 7:1: FEC 0: corrected 4 errors

Signed-off-by: Milan Broz <gmazyland@gmail.com>
Fixes: 8ca7cab82bda ("dm verity fec: fix misaligned RS roots IO")
Cc: stable@vger.kernel.org
Signed-off-by: Mikulas Patocka <mpatocka@redhat.com>

authored by Milan Broz and committed by Mikulas Patocka 6df90c02 0bb1968d

Changed files
+26 -14
drivers
+26 -14
drivers/md/dm-verity-fec.c
··· 60 60 * to the data block. Caller is responsible for releasing buf. 61 61 */ 62 62 static u8 *fec_read_parity(struct dm_verity *v, u64 rsb, int index, 63 - unsigned int *offset, struct dm_buffer **buf, 64 - unsigned short ioprio) 63 + unsigned int *offset, unsigned int par_buf_offset, 64 + struct dm_buffer **buf, unsigned short ioprio) 65 65 { 66 66 u64 position, block, rem; 67 67 u8 *res; 68 68 69 + /* We have already part of parity bytes read, skip to the next block */ 70 + if (par_buf_offset) 71 + index++; 72 + 69 73 position = (index + rsb) * v->fec->roots; 70 74 block = div64_u64_rem(position, v->fec->io_size, &rem); 71 - *offset = (unsigned int)rem; 75 + *offset = par_buf_offset ? 0 : (unsigned int)rem; 72 76 73 77 res = dm_bufio_read_with_ioprio(v->fec->bufio, block, buf, ioprio); 74 78 if (IS_ERR(res)) { ··· 132 128 { 133 129 int r, corrected = 0, res; 134 130 struct dm_buffer *buf; 135 - unsigned int n, i, offset; 136 - u8 *par, *block; 131 + unsigned int n, i, offset, par_buf_offset = 0; 132 + u8 *par, *block, par_buf[DM_VERITY_FEC_RSM - DM_VERITY_FEC_MIN_RSN]; 137 133 struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size); 138 134 139 - par = fec_read_parity(v, rsb, block_offset, &offset, &buf, bio_prio(bio)); 135 + par = fec_read_parity(v, rsb, block_offset, &offset, 136 + par_buf_offset, &buf, bio_prio(bio)); 140 137 if (IS_ERR(par)) 141 138 return PTR_ERR(par); 142 139 ··· 147 142 */ 148 143 fec_for_each_buffer_rs_block(fio, n, i) { 149 144 block = fec_buffer_rs_block(v, fio, n, i); 150 - res = fec_decode_rs8(v, fio, block, &par[offset], neras); 145 + memcpy(&par_buf[par_buf_offset], &par[offset], v->fec->roots - par_buf_offset); 146 + res = fec_decode_rs8(v, fio, block, par_buf, neras); 151 147 if (res < 0) { 152 148 r = res; 153 149 goto error; ··· 161 155 if (block_offset >= 1 << v->data_dev_block_bits) 162 156 goto done; 163 157 164 - /* read the next block when we run out of parity bytes */ 165 - offset += v->fec->roots; 158 + /* Read the next block when we run out of parity bytes */ 159 + offset += (v->fec->roots - par_buf_offset); 160 + /* Check if parity bytes are split between blocks */ 161 + if (offset < v->fec->io_size && (offset + v->fec->roots) > v->fec->io_size) { 162 + par_buf_offset = v->fec->io_size - offset; 163 + memcpy(par_buf, &par[offset], par_buf_offset); 164 + offset += par_buf_offset; 165 + } else 166 + par_buf_offset = 0; 167 + 166 168 if (offset >= v->fec->io_size) { 167 169 dm_bufio_release(buf); 168 170 169 - par = fec_read_parity(v, rsb, block_offset, &offset, &buf, bio_prio(bio)); 171 + par = fec_read_parity(v, rsb, block_offset, &offset, 172 + par_buf_offset, &buf, bio_prio(bio)); 170 173 if (IS_ERR(par)) 171 174 return PTR_ERR(par); 172 175 } ··· 739 724 return -E2BIG; 740 725 } 741 726 742 - if ((f->roots << SECTOR_SHIFT) & ((1 << v->data_dev_block_bits) - 1)) 743 - f->io_size = 1 << v->data_dev_block_bits; 744 - else 745 - f->io_size = v->fec->roots << SECTOR_SHIFT; 727 + f->io_size = 1 << v->data_dev_block_bits; 746 728 747 729 f->bufio = dm_bufio_client_create(f->dev->bdev, 748 730 f->io_size,