dm verity: fix FEC for RS roots unaligned to block size

Optional Forward Error Correction (FEC) code in dm-verity uses
Reed-Solomon code and should support roots from 2 to 24.

The error correction parity bytes (of roots lengths per RS block) are
stored on a separate device in sequence without any padding.

Currently, to access FEC device, the dm-verity-fec code uses dm-bufio
client with block size set to verity data block (usually 4096 or 512
bytes).

Because this block size is not divisible by some (most!) of the roots
supported lengths, data repair cannot work for partially stored parity
bytes.

This fix changes FEC device dm-bufio block size to "roots << SECTOR_SHIFT"
where we can be sure that the full parity data is always available.
(There cannot be partial FEC blocks because parity must cover whole
sectors.)

Because the optional FEC starting offset could be unaligned to this
new block size, we have to use dm_bufio_set_sector_offset() to
configure it.

The problem is easily reproduced using veritysetup, e.g. for roots=13:

# 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=13 | awk '/^Root hash/{ print $3 }' >roothash

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

# try to read it through dm-verity
veritysetup open data.img test hash.img --fec-device=fec.img --fec-roots=13 $(cat roothash)
dd if=/dev/mapper/test of=/dev/null bs=4096 status=noxfer
# wait for possible recursive recovery in kernel
udevadm settle
veritysetup close test

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

Without it, FEC code usually ends on unrecoverable failure in RS decoder:
device-mapper: verity-fec: 7:1: FEC 0: failed to correct: -74
...

This problem is present in all kernels since the FEC code's
introduction (kernel 4.5).

It is thought that this problem is not visible in Android ecosystem
because it always uses a default RS roots=2.

Depends-on: a14e5ec66a7a ("dm bufio: subtract the number of initial sectors in dm_bufio_get_device_size")
Signed-off-by: Milan Broz <gmazyland@gmail.com>
Tested-by: Jérôme Carretero <cJ-ko@zougloub.eu>
Reviewed-by: Sami Tolvanen <samitolvanen@google.com>
Cc: stable@vger.kernel.org # 4.5+
Signed-off-by: Mike Snitzer <snitzer@redhat.com>

authored by Milan Broz and committed by Mike Snitzer df7b59ba a14e5ec6

Changed files
+12 -11
drivers
+12 -11
drivers/md/dm-verity-fec.c
··· 61 61 static u8 *fec_read_parity(struct dm_verity *v, u64 rsb, int index, 62 62 unsigned *offset, struct dm_buffer **buf) 63 63 { 64 - u64 position, block; 64 + u64 position, block, rem; 65 65 u8 *res; 66 66 67 67 position = (index + rsb) * v->fec->roots; 68 - block = position >> v->data_dev_block_bits; 69 - *offset = (unsigned)(position - (block << v->data_dev_block_bits)); 68 + block = div64_u64_rem(position, v->fec->roots << SECTOR_SHIFT, &rem); 69 + *offset = (unsigned)rem; 70 70 71 - res = dm_bufio_read(v->fec->bufio, v->fec->start + block, buf); 71 + res = dm_bufio_read(v->fec->bufio, block, buf); 72 72 if (IS_ERR(res)) { 73 73 DMERR("%s: FEC %llu: parity read failed (block %llu): %ld", 74 74 v->data_dev->name, (unsigned long long)rsb, 75 - (unsigned long long)(v->fec->start + block), 76 - PTR_ERR(res)); 75 + (unsigned long long)block, PTR_ERR(res)); 77 76 *buf = NULL; 78 77 } 79 78 ··· 154 155 155 156 /* read the next block when we run out of parity bytes */ 156 157 offset += v->fec->roots; 157 - if (offset >= 1 << v->data_dev_block_bits) { 158 + if (offset >= v->fec->roots << SECTOR_SHIFT) { 158 159 dm_bufio_release(buf); 159 160 160 161 par = fec_read_parity(v, rsb, block_offset, &offset, &buf); ··· 673 674 { 674 675 struct dm_verity_fec *f = v->fec; 675 676 struct dm_target *ti = v->ti; 676 - u64 hash_blocks; 677 + u64 hash_blocks, fec_blocks; 677 678 int ret; 678 679 679 680 if (!verity_fec_is_enabled(v)) { ··· 743 744 } 744 745 745 746 f->bufio = dm_bufio_client_create(f->dev->bdev, 746 - 1 << v->data_dev_block_bits, 747 + f->roots << SECTOR_SHIFT, 747 748 1, 0, NULL, NULL); 748 749 if (IS_ERR(f->bufio)) { 749 750 ti->error = "Cannot initialize FEC bufio client"; 750 751 return PTR_ERR(f->bufio); 751 752 } 752 753 753 - if (dm_bufio_get_device_size(f->bufio) < 754 - ((f->start + f->rounds * f->roots) >> v->data_dev_block_bits)) { 754 + dm_bufio_set_sector_offset(f->bufio, f->start << (v->data_dev_block_bits - SECTOR_SHIFT)); 755 + 756 + fec_blocks = div64_u64(f->rounds * f->roots, v->fec->roots << SECTOR_SHIFT); 757 + if (dm_bufio_get_device_size(f->bufio) < fec_blocks) { 755 758 ti->error = "FEC device is too small"; 756 759 return -E2BIG; 757 760 }