| Author | Tokens | Token Proportion | Commits | Commit Proportion |
|---|---|---|---|---|
| Sami Tolvanen | 2311 | 70.82% | 9 | 16.36% |
| Eric Biggers | 511 | 15.66% | 23 | 41.82% |
| Mikulas Patocka | 210 | 6.44% | 4 | 7.27% |
| Milan Broz | 65 | 1.99% | 3 | 5.45% |
| Kent Overstreet | 46 | 1.41% | 1 | 1.82% |
| Hongyu Jin | 41 | 1.26% | 1 | 1.82% |
| Kees Cook | 20 | 0.61% | 2 | 3.64% |
| Heinz Mauelshagen | 14 | 0.43% | 1 | 1.82% |
| Shubhankar Mishra | 10 | 0.31% | 1 | 1.82% |
| Sunwook Eom | 7 | 0.21% | 1 | 1.82% |
| John Garry | 6 | 0.18% | 1 | 1.82% |
| Gilad Ben-Yossef | 5 | 0.15% | 1 | 1.82% |
| Thomas Gleixner | 5 | 0.15% | 2 | 3.64% |
| Nathan Huckleberry | 4 | 0.12% | 1 | 1.82% |
| Shetty, Harshini X (EXT-Sony Mobile) | 3 | 0.09% | 1 | 1.82% |
| Jaegeuk Kim | 2 | 0.06% | 1 | 1.82% |
| Neil Brown | 2 | 0.06% | 1 | 1.82% |
| Christoph Hellwig | 1 | 0.03% | 1 | 1.82% |
| Total | 3263 | 55 |
// SPDX-License-Identifier: GPL-2.0-or-later /* * Copyright (C) 2015 Google, Inc. * * Author: Sami Tolvanen <samitolvanen@google.com> */ #include "dm-verity-fec.h" #include <linux/math64.h> #define DM_MSG_PREFIX "verity-fec" /* * When correcting a block, the FEC implementation performs optimally when it * can collect all the associated RS codewords at the same time. As each byte * is part of a different codeword, there are '1 << data_dev_block_bits' * codewords. Each buffer has space for the message bytes for * '1 << DM_VERITY_FEC_BUF_RS_BITS' codewords, so that gives * '1 << (data_dev_block_bits - DM_VERITY_FEC_BUF_RS_BITS)' buffers. */ static inline unsigned int fec_max_nbufs(struct dm_verity *v) { return 1 << (v->data_dev_block_bits - DM_VERITY_FEC_BUF_RS_BITS); } /* Loop over each allocated buffer. */ #define fec_for_each_buffer(io, __i) \ for (__i = 0; __i < (io)->nbufs; __i++) /* Loop over each RS message in each allocated buffer. */ /* To stop early, use 'goto', not 'break' (since this uses nested loops). */ #define fec_for_each_buffer_rs_message(io, __i, __j) \ fec_for_each_buffer(io, __i) \ for (__j = 0; __j < 1 << DM_VERITY_FEC_BUF_RS_BITS; __j++) /* * Return a pointer to the current RS message when called inside * fec_for_each_buffer_rs_message. */ static inline u8 *fec_buffer_rs_message(struct dm_verity *v, struct dm_verity_fec_io *fio, unsigned int i, unsigned int j) { return &fio->bufs[i][j * v->fec->rs_k]; } /* * Decode all RS codewords whose message bytes were loaded into fio->bufs. Copy * the corrected bytes into fio->output starting from out_pos. */ static int fec_decode_bufs(struct dm_verity *v, struct dm_verity_io *io, struct dm_verity_fec_io *fio, u64 target_block, unsigned int target_region, u64 index_in_region, unsigned int out_pos, int neras) { int r = 0, corrected = 0, res; struct dm_buffer *buf; unsigned int n, i, j, parity_pos, to_copy; uint16_t par_buf[DM_VERITY_FEC_MAX_ROOTS]; u8 *par, *msg_buf; u64 parity_block; struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size); /* * Compute the index of the first parity block that will be needed and * the starting position in that block. Then read that block. * * block_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 = ((index_in_region << v->data_dev_block_bits) + out_pos) * v->fec->roots; parity_pos = parity_block & (v->fec->block_size - 1); parity_block >>= v->data_dev_block_bits; par = dm_bufio_read_with_ioprio(v->fec->bufio, parity_block, &buf, bio->bi_ioprio); if (IS_ERR(par)) { DMERR("%s: FEC %llu: parity read failed (block %llu): %ld", v->data_dev->name, target_block, parity_block, PTR_ERR(par)); return PTR_ERR(par); } /* * Decode the RS codewords whose message bytes are in bufs. Each RS * codeword results in one corrected target byte and consumes fec->roots * parity bytes. */ fec_for_each_buffer_rs_message(fio, n, i) { msg_buf = fec_buffer_rs_message(v, fio, n, i); /* * Copy the next 'roots' parity bytes to 'par_buf', reading * another parity block if needed. */ to_copy = min(v->fec->block_size - parity_pos, v->fec->roots); for (j = 0; j < to_copy; j++) par_buf[j] = par[parity_pos++]; if (to_copy < v->fec->roots) { parity_block++; parity_pos = 0; dm_bufio_release(buf); par = dm_bufio_read_with_ioprio(v->fec->bufio, parity_block, &buf, bio->bi_ioprio); if (IS_ERR(par)) { DMERR("%s: FEC %llu: parity read failed (block %llu): %ld", v->data_dev->name, target_block, parity_block, PTR_ERR(par)); return PTR_ERR(par); } for (; j < v->fec->roots; j++) par_buf[j] = par[parity_pos++]; } /* Decode an RS codeword using the Reed-Solomon library. */ res = decode_rs8(fio->rs, msg_buf, par_buf, v->fec->rs_k, NULL, neras, fio->erasures, 0, NULL); if (res < 0) { r = res; goto done; } corrected += res; fio->output[out_pos++] = msg_buf[target_region]; if (out_pos >= v->fec->block_size) goto done; } done: dm_bufio_release(buf); if (r < 0 && neras) DMERR_LIMIT("%s: FEC %llu: failed to correct: %d", v->data_dev->name, target_block, r); else if (r == 0) DMWARN_LIMIT("%s: FEC %llu: corrected %d errors", v->data_dev->name, target_block, corrected); return r; } /* * Locate data block erasures using verity hashes. */ static int fec_is_erasure(struct dm_verity *v, struct dm_verity_io *io, const u8 *want_digest, const u8 *data) { if (unlikely(verity_hash(v, io, data, v->fec->block_size, io->tmp_digest))) return 0; return memcmp(io->tmp_digest, want_digest, v->digest_size) != 0; } /* * Read the message block at index @index_in_region within each of the * @v->fec->rs_k regions and deinterleave their contents into @io->fec_io->bufs. * * @target_block gives the index of specific block within this sequence that is * being corrected, relative to the start of all the FEC message blocks. * * @out_pos gives the current output position, i.e. the position in (each) block * from which to start the deinterleaving. Deinterleaving continues until * either end-of-block is reached or there's no more buffer space. * * If @neras is non-NULL, then also use verity hashes and the presence/absence * of I/O errors to determine which of the message blocks in the sequence are * likely to be incorrect. Write the number of such blocks to *@neras and the * indices of the corresponding RS message bytes in [0, k - 1] to * @io->fec_io->erasures, up to a limit of @v->fec->roots + 1 such blocks. */ static int fec_read_bufs(struct dm_verity *v, struct dm_verity_io *io, u64 target_block, u64 index_in_region, unsigned int out_pos, int *neras) { bool is_zero; int i, j; struct dm_buffer *buf; struct dm_bufio_client *bufio; struct dm_verity_fec_io *fio = io->fec_io; u64 block; u8 *bbuf; u8 want_digest[HASH_MAX_DIGESTSIZE]; unsigned int n, src_pos; struct bio *bio = dm_bio_from_per_bio_data(io, v->ti->per_io_data_size); if (neras) *neras = 0; if (WARN_ON(v->digest_size > sizeof(want_digest))) return -EINVAL; for (i = 0; i < v->fec->rs_k; i++) { /* * Read the block from region i. It contains the i'th message * byte of the target block's RS codewords. */ block = i * v->fec->region_blocks + index_in_region; bufio = v->fec->data_bufio; if (block >= v->data_blocks) { block -= v->data_blocks; /* * blocks outside the area were assumed to contain * zeros when encoding data was generated */ if (unlikely(block >= v->fec->hash_blocks)) continue; block += v->hash_start; bufio = v->bufio; } bbuf = dm_bufio_read_with_ioprio(bufio, block, &buf, bio->bi_ioprio); if (IS_ERR(bbuf)) { DMWARN_LIMIT("%s: FEC %llu: read failed (%llu): %ld", v->data_dev->name, target_block, block, PTR_ERR(bbuf)); /* assume the block is corrupted */ if (neras && *neras <= v->fec->roots) fio->erasures[(*neras)++] = i; continue; } /* locate erasures if the block is on the data device */ if (bufio == v->fec->data_bufio && verity_hash_for_block(v, io, block, want_digest, &is_zero) == 0) { /* skip known zero blocks entirely */ if (is_zero) goto done; /* * skip if we have already found the theoretical * maximum number (i.e. fec->roots) of erasures */ if (neras && *neras <= v->fec->roots && fec_is_erasure(v, io, want_digest, bbuf)) fio->erasures[(*neras)++] = i; } /* * Deinterleave the bytes of the block, starting from 'out_pos', * into the i'th byte of the RS message buffers. Stop when * end-of-block is reached or there are no more buffers. */ src_pos = out_pos; fec_for_each_buffer_rs_message(fio, n, j) { if (src_pos >= v->fec->block_size) goto done; fec_buffer_rs_message(v, fio, n, j)[i] = bbuf[src_pos++]; } done: dm_bufio_release(buf); } return 0; } /* * Allocate and initialize a struct dm_verity_fec_io to use for FEC for a bio. * This runs the first time a block needs to be corrected for a bio. In the * common case where no block needs to be corrected, this code never runs. * * This always succeeds, as all required allocations are done from mempools. * Additional buffers are also allocated opportunistically to improve error * correction performance, but these aren't required to succeed. */ static struct dm_verity_fec_io *fec_alloc_and_init_io(struct dm_verity *v) { const unsigned int max_nbufs = fec_max_nbufs(v); struct dm_verity_fec *f = v->fec; struct dm_verity_fec_io *fio; unsigned int n; fio = mempool_alloc(&f->fio_pool, GFP_NOIO); fio->rs = mempool_alloc(&f->rs_pool, GFP_NOIO); fio->bufs[0] = mempool_alloc(&f->prealloc_pool, GFP_NOIO); /* try to allocate the maximum number of buffers */ for (n = 1; n < max_nbufs; n++) { fio->bufs[n] = kmem_cache_alloc(f->cache, GFP_NOWAIT); /* we can manage with even one buffer if necessary */ if (unlikely(!fio->bufs[n])) break; } fio->nbufs = n; fio->output = mempool_alloc(&f->output_pool, GFP_NOIO); fio->level = 0; return fio; } /* * Initialize buffers and clear erasures. fec_read_bufs() assumes buffers are * zeroed before deinterleaving. */ static void fec_init_bufs(struct dm_verity *v, struct dm_verity_fec_io *fio) { unsigned int n; fec_for_each_buffer(fio, n) memset(fio->bufs[n], 0, v->fec->rs_k << DM_VERITY_FEC_BUF_RS_BITS); memset(fio->erasures, 0, sizeof(fio->erasures)); } /* * Try to correct the message (data or hash) block at index @target_block. * * If @use_erasures is true, use verity hashes to locate erasures. This makes * the error correction slower but up to twice as capable. * * On success, return 0 and write the corrected block to @fio->output. 0 is * returned only if the digest of the corrected block matches @want_digest; this * is critical to ensure that FEC can't cause dm-verity to return bad data. */ static int fec_decode(struct dm_verity *v, struct dm_verity_io *io, struct dm_verity_fec_io *fio, u64 target_block, const u8 *want_digest, bool use_erasures) { int r, neras = 0; unsigned int target_region, out_pos; u64 index_in_region; /* * Compute 'target_region', the index of the region the target block is * in; and 'index_in_region', the index of the target block within its * region. The latter value is also the index within its region of each * message block that shares its RS codewords with the target block. */ target_region = div64_u64_rem(target_block, v->fec->region_blocks, &index_in_region); if (WARN_ON_ONCE(target_region >= v->fec->rs_k)) /* target_block is out-of-bounds. Should never happen. */ return -EIO; for (out_pos = 0; out_pos < v->fec->block_size;) { fec_init_bufs(v, fio); r = fec_read_bufs(v, io, target_block, index_in_region, out_pos, use_erasures ? &neras : NULL); if (unlikely(r < 0)) return r; r = fec_decode_bufs(v, io, fio, target_block, target_region, index_in_region, out_pos, neras); if (r < 0) return r; out_pos += fio->nbufs << DM_VERITY_FEC_BUF_RS_BITS; } /* Always re-validate the corrected block against the expected hash */ r = verity_hash(v, io, fio->output, v->fec->block_size, io->tmp_digest); if (unlikely(r < 0)) return r; if (memcmp(io->tmp_digest, want_digest, v->digest_size)) { DMERR_LIMIT("%s: FEC %llu: failed to correct (%d erasures)", v->data_dev->name, target_block, neras); return -EILSEQ; } return 0; } /* Correct errors in a block. Copies corrected block to dest. */ int verity_fec_decode(struct dm_verity *v, struct dm_verity_io *io, enum verity_block_type type, const u8 *want_digest, sector_t block, u8 *dest) { int r; struct dm_verity_fec_io *fio; if (!verity_fec_is_enabled(v)) return -EOPNOTSUPP; fio = io->fec_io; if (!fio) fio = io->fec_io = fec_alloc_and_init_io(v); if (fio->level) return -EIO; fio->level++; if (type == DM_VERITY_BLOCK_TYPE_METADATA) block = block - v->hash_start + v->data_blocks; /* * Locating erasures is slow, so attempt to recover the block without * them first. Do a second attempt with erasures if the corruption is * bad enough. */ r = fec_decode(v, io, fio, block, want_digest, false); if (r < 0) { r = fec_decode(v, io, fio, block, want_digest, true); if (r < 0) goto done; } memcpy(dest, fio->output, v->fec->block_size); atomic64_inc(&v->fec->corrected); done: fio->level--; return r; } /* * Clean up per-bio data. */ void __verity_fec_finish_io(struct dm_verity_io *io) { unsigned int n; struct dm_verity_fec *f = io->v->fec; struct dm_verity_fec_io *fio = io->fec_io; mempool_free(fio->rs, &f->rs_pool); mempool_free(fio->bufs[0], &f->prealloc_pool); for (n = 1; n < fio->nbufs; n++) kmem_cache_free(f->cache, fio->bufs[n]); mempool_free(fio->output, &f->output_pool); mempool_free(fio, &f->fio_pool); io->fec_io = NULL; } /* * Append feature arguments and values to the status table. */ unsigned int verity_fec_status_table(struct dm_verity *v, unsigned int sz, char *result, unsigned int maxlen) { if (!verity_fec_is_enabled(v)) return sz; DMEMIT(" " DM_VERITY_OPT_FEC_DEV " %s " DM_VERITY_OPT_FEC_BLOCKS " %llu " DM_VERITY_OPT_FEC_START " %llu " DM_VERITY_OPT_FEC_ROOTS " %d", v->fec->dev->name, (unsigned long long)v->fec->blocks, (unsigned long long)v->fec->start, v->fec->roots); return sz; } void verity_fec_dtr(struct dm_verity *v) { struct dm_verity_fec *f = v->fec; if (!verity_fec_is_enabled(v)) goto out; mempool_exit(&f->fio_pool); mempool_exit(&f->rs_pool); mempool_exit(&f->prealloc_pool); mempool_exit(&f->output_pool); kmem_cache_destroy(f->cache); if (!IS_ERR_OR_NULL(f->data_bufio)) dm_bufio_client_destroy(f->data_bufio); if (!IS_ERR_OR_NULL(f->bufio)) dm_bufio_client_destroy(f->bufio); if (f->dev) dm_put_device(v->ti, f->dev); out: kfree(f); v->fec = NULL; } static void *fec_rs_alloc(gfp_t gfp_mask, void *pool_data) { struct dm_verity *v = pool_data; return init_rs_gfp(8, 0x11d, 0, 1, v->fec->roots, gfp_mask); } static void fec_rs_free(void *element, void *pool_data) { struct rs_control *rs = element; if (rs) free_rs(rs); } bool verity_is_fec_opt_arg(const char *arg_name) { return (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_DEV) || !strcasecmp(arg_name, DM_VERITY_OPT_FEC_BLOCKS) || !strcasecmp(arg_name, DM_VERITY_OPT_FEC_START) || !strcasecmp(arg_name, DM_VERITY_OPT_FEC_ROOTS)); } int verity_fec_parse_opt_args(struct dm_arg_set *as, struct dm_verity *v, unsigned int *argc, const char *arg_name) { int r; struct dm_target *ti = v->ti; const char *arg_value; unsigned long long num_ll; unsigned char num_c; char dummy; if (!*argc) { ti->error = "FEC feature arguments require a value"; return -EINVAL; } arg_value = dm_shift_arg(as); (*argc)--; if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_DEV)) { if (v->fec->dev) { ti->error = "FEC device already specified"; return -EINVAL; } r = dm_get_device(ti, arg_value, BLK_OPEN_READ, &v->fec->dev); if (r) { ti->error = "FEC device lookup failed"; return r; } } else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_BLOCKS)) { if (sscanf(arg_value, "%llu%c", &num_ll, &dummy) != 1 || ((sector_t)(num_ll << (v->data_dev_block_bits - SECTOR_SHIFT)) >> (v->data_dev_block_bits - SECTOR_SHIFT) != num_ll)) { ti->error = "Invalid " DM_VERITY_OPT_FEC_BLOCKS; return -EINVAL; } v->fec->blocks = num_ll; } else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_START)) { if (sscanf(arg_value, "%llu%c", &num_ll, &dummy) != 1 || ((sector_t)(num_ll << (v->data_dev_block_bits - SECTOR_SHIFT)) >> (v->data_dev_block_bits - SECTOR_SHIFT) != num_ll)) { ti->error = "Invalid " DM_VERITY_OPT_FEC_START; return -EINVAL; } v->fec->start = num_ll; } else if (!strcasecmp(arg_name, DM_VERITY_OPT_FEC_ROOTS)) { if (sscanf(arg_value, "%hhu%c", &num_c, &dummy) != 1 || !num_c || num_c < DM_VERITY_FEC_MIN_ROOTS || num_c > DM_VERITY_FEC_MAX_ROOTS) { ti->error = "Invalid " DM_VERITY_OPT_FEC_ROOTS; return -EINVAL; } v->fec->roots = num_c; } else { ti->error = "Unrecognized verity FEC feature request"; return -EINVAL; } return 0; } /* * Allocate dm_verity_fec for v->fec. Must be called before verity_fec_ctr. */ int verity_fec_ctr_alloc(struct dm_verity *v) { struct dm_verity_fec *f; f = kzalloc_obj(struct dm_verity_fec); if (!f) { v->ti->error = "Cannot allocate FEC structure"; return -ENOMEM; } v->fec = f; return 0; } /* * Validate arguments and preallocate memory. Must be called after arguments * have been parsed using verity_fec_parse_opt_args. */ int verity_fec_ctr(struct dm_verity *v) { struct dm_verity_fec *f = v->fec; struct dm_target *ti = v->ti; u64 hash_blocks; int ret; if (!verity_fec_is_enabled(v)) { verity_fec_dtr(v); return 0; } /* * FEC is computed over data blocks, possible metadata, and * hash blocks. In other words, FEC covers total of fec_blocks * blocks consisting of the following: * * data blocks | hash blocks | metadata (optional) * * We allow metadata after hash blocks to support a use case * where all data is stored on the same device and FEC covers * the entire area. * * If metadata is included, we require it to be available on the * hash device after the hash blocks. */ hash_blocks = v->hash_end - v->hash_start; /* * Require matching block sizes for data and hash devices for * simplicity. */ if (v->data_dev_block_bits != v->hash_dev_block_bits) { ti->error = "Block sizes must match to use FEC"; return -EINVAL; } f->block_size = 1 << v->data_dev_block_bits; if (!f->roots) { ti->error = "Missing " DM_VERITY_OPT_FEC_ROOTS; return -EINVAL; } f->rs_k = DM_VERITY_FEC_RS_N - f->roots; if (!f->blocks) { ti->error = "Missing " DM_VERITY_OPT_FEC_BLOCKS; return -EINVAL; } f->region_blocks = f->blocks; if (sector_div(f->region_blocks, f->rs_k)) f->region_blocks++; /* * Due to optional metadata, f->blocks can be larger than * data_blocks and hash_blocks combined. */ if (f->blocks < v->data_blocks + hash_blocks || !f->region_blocks) { ti->error = "Invalid " DM_VERITY_OPT_FEC_BLOCKS; return -EINVAL; } /* * Metadata is accessed through the hash device, so we require * it to be large enough. */ f->hash_blocks = f->blocks - v->data_blocks; if (dm_bufio_get_device_size(v->bufio) < v->hash_start + f->hash_blocks) { ti->error = "Hash device is too small for " DM_VERITY_OPT_FEC_BLOCKS; return -E2BIG; } f->bufio = dm_bufio_client_create(f->dev->bdev, f->block_size, 1, 0, NULL, NULL, 0); if (IS_ERR(f->bufio)) { ti->error = "Cannot initialize FEC bufio client"; return PTR_ERR(f->bufio); } dm_bufio_set_sector_offset(f->bufio, f->start << (v->data_dev_block_bits - SECTOR_SHIFT)); if (dm_bufio_get_device_size(f->bufio) < f->region_blocks * f->roots) { ti->error = "FEC device is too small"; return -E2BIG; } f->data_bufio = dm_bufio_client_create(v->data_dev->bdev, f->block_size, 1, 0, NULL, NULL, 0); if (IS_ERR(f->data_bufio)) { ti->error = "Cannot initialize FEC data bufio client"; return PTR_ERR(f->data_bufio); } if (dm_bufio_get_device_size(f->data_bufio) < v->data_blocks) { ti->error = "Data device is too small"; return -E2BIG; } /* Preallocate some dm_verity_fec_io structures */ ret = mempool_init_kmalloc_pool(&f->fio_pool, num_online_cpus(), struct_size((struct dm_verity_fec_io *)0, bufs, fec_max_nbufs(v))); if (ret) { ti->error = "Cannot allocate FEC IO pool"; return ret; } /* Preallocate an rs_control structure for each worker thread */ ret = mempool_init(&f->rs_pool, num_online_cpus(), fec_rs_alloc, fec_rs_free, (void *) v); if (ret) { ti->error = "Cannot allocate RS pool"; return ret; } f->cache = kmem_cache_create("dm_verity_fec_buffers", f->rs_k << DM_VERITY_FEC_BUF_RS_BITS, 0, 0, NULL); if (!f->cache) { ti->error = "Cannot create FEC buffer cache"; return -ENOMEM; } /* Preallocate one buffer for each thread */ ret = mempool_init_slab_pool(&f->prealloc_pool, num_online_cpus(), f->cache); if (ret) { ti->error = "Cannot allocate FEC buffer prealloc pool"; return ret; } /* Preallocate an output buffer for each thread */ ret = mempool_init_kmalloc_pool(&f->output_pool, num_online_cpus(), f->block_size); if (ret) { ti->error = "Cannot allocate FEC output pool"; return ret; } return 0; }
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1