Skip to content

Commit df7b59b

Browse files
mbrozsnitm
authored andcommitted
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: a14e5ec ("dm bufio: subtract the number of initial sectors in dm_bufio_get_device_size") Signed-off-by: Milan Broz <[email protected]> Tested-by: Jérôme Carretero <[email protected]> Reviewed-by: Sami Tolvanen <[email protected]> Cc: [email protected] # 4.5+ Signed-off-by: Mike Snitzer <[email protected]>
1 parent a14e5ec commit df7b59b

File tree

1 file changed

+12
-11
lines changed

1 file changed

+12
-11
lines changed

drivers/md/dm-verity-fec.c

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -61,19 +61,18 @@ static int fec_decode_rs8(struct dm_verity *v, struct dm_verity_fec_io *fio,
6161
static u8 *fec_read_parity(struct dm_verity *v, u64 rsb, int index,
6262
unsigned *offset, struct dm_buffer **buf)
6363
{
64-
u64 position, block;
64+
u64 position, block, rem;
6565
u8 *res;
6666

6767
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;
7070

71-
res = dm_bufio_read(v->fec->bufio, v->fec->start + block, buf);
71+
res = dm_bufio_read(v->fec->bufio, block, buf);
7272
if (IS_ERR(res)) {
7373
DMERR("%s: FEC %llu: parity read failed (block %llu): %ld",
7474
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));
7776
*buf = NULL;
7877
}
7978

@@ -155,7 +154,7 @@ static int fec_decode_bufs(struct dm_verity *v, struct dm_verity_fec_io *fio,
155154

156155
/* read the next block when we run out of parity bytes */
157156
offset += v->fec->roots;
158-
if (offset >= 1 << v->data_dev_block_bits) {
157+
if (offset >= v->fec->roots << SECTOR_SHIFT) {
159158
dm_bufio_release(buf);
160159

161160
par = fec_read_parity(v, rsb, block_offset, &offset, &buf);
@@ -674,7 +673,7 @@ int verity_fec_ctr(struct dm_verity *v)
674673
{
675674
struct dm_verity_fec *f = v->fec;
676675
struct dm_target *ti = v->ti;
677-
u64 hash_blocks;
676+
u64 hash_blocks, fec_blocks;
678677
int ret;
679678

680679
if (!verity_fec_is_enabled(v)) {
@@ -744,15 +743,17 @@ int verity_fec_ctr(struct dm_verity *v)
744743
}
745744

746745
f->bufio = dm_bufio_client_create(f->dev->bdev,
747-
1 << v->data_dev_block_bits,
746+
f->roots << SECTOR_SHIFT,
748747
1, 0, NULL, NULL);
749748
if (IS_ERR(f->bufio)) {
750749
ti->error = "Cannot initialize FEC bufio client";
751750
return PTR_ERR(f->bufio);
752751
}
753752

754-
if (dm_bufio_get_device_size(f->bufio) <
755-
((f->start + f->rounds * f->roots) >> v->data_dev_block_bits)) {
753+
dm_bufio_set_sector_offset(f->bufio, f->start << (v->data_dev_block_bits - SECTOR_SHIFT));
754+
755+
fec_blocks = div64_u64(f->rounds * f->roots, v->fec->roots << SECTOR_SHIFT);
756+
if (dm_bufio_get_device_size(f->bufio) < fec_blocks) {
756757
ti->error = "FEC device is too small";
757758
return -E2BIG;
758759
}

0 commit comments

Comments
 (0)