/src/cryptsetup/lib/verity/verity_fec.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: LGPL-2.1-or-later |
2 | | /* |
3 | | * dm-verity Forward Error Correction (FEC) support |
4 | | * |
5 | | * Copyright (C) 2015 Google, Inc. All rights reserved. |
6 | | * Copyright (C) 2017-2025 Red Hat, Inc. All rights reserved. |
7 | | */ |
8 | | |
9 | | #include <stdlib.h> |
10 | | #include <errno.h> |
11 | | |
12 | | #include "verity.h" |
13 | | #include "internal.h" |
14 | | #include "rs.h" |
15 | | |
16 | | /* ecc parameters */ |
17 | 0 | #define FEC_RSM 255 |
18 | 0 | #define FEC_MIN_RSN 231 |
19 | 0 | #define FEC_MAX_RSN 253 |
20 | | |
21 | 0 | #define FEC_INPUT_DEVICES 2 |
22 | | |
23 | | /* parameters to init_rs_char */ |
24 | | #define FEC_PARAMS(roots) \ |
25 | 0 | 8, /* symbol size in bits */ \ |
26 | 0 | 0x11d, /* field generator polynomial coefficients */ \ |
27 | 0 | 0, /* first root of the generator */ \ |
28 | 0 | 1, /* primitive element to generate polynomial roots */ \ |
29 | 0 | (roots), /* polynomial degree (number of roots) */ \ |
30 | 0 | 0 /* padding bytes at the front of shortened block */ |
31 | | |
32 | | struct fec_input_device { |
33 | | struct device *device; |
34 | | int fd; |
35 | | uint64_t start; |
36 | | uint64_t count; |
37 | | }; |
38 | | |
39 | | struct fec_context { |
40 | | uint32_t rsn; |
41 | | uint32_t roots; |
42 | | uint64_t size; |
43 | | uint64_t blocks; |
44 | | uint64_t rounds; |
45 | | uint32_t block_size; |
46 | | struct fec_input_device *inputs; |
47 | | size_t ninputs; |
48 | | }; |
49 | | |
50 | | /* computes ceil(x / y) */ |
51 | | static inline uint64_t FEC_div_round_up(uint64_t x, uint64_t y) |
52 | 0 | { |
53 | 0 | return (x / y) + (x % y > 0 ? 1 : 0); |
54 | 0 | } |
55 | | |
56 | | /* returns a physical offset for the given RS offset */ |
57 | | static inline uint64_t FEC_interleave(struct fec_context *ctx, uint64_t offset) |
58 | 0 | { |
59 | 0 | return (offset / ctx->rsn) + |
60 | 0 | (offset % ctx->rsn) * ctx->rounds * ctx->block_size; |
61 | 0 | } |
62 | | |
63 | | /* returns data for a byte at the specified RS offset */ |
64 | | static int FEC_read_interleaved(struct fec_context *ctx, uint64_t i, |
65 | | void *output, size_t count) |
66 | 0 | { |
67 | 0 | size_t n; |
68 | 0 | uint64_t offset = FEC_interleave(ctx, i); |
69 | | |
70 | | /* offsets outside input area are assumed to contain zeros */ |
71 | 0 | if (offset >= ctx->size) { |
72 | 0 | memset(output, 0, count); |
73 | 0 | return 0; |
74 | 0 | } |
75 | | |
76 | | /* find the correct input device and read from it */ |
77 | 0 | for (n = 0; n < ctx->ninputs; ++n) { |
78 | 0 | if (offset >= ctx->inputs[n].count) { |
79 | 0 | offset -= ctx->inputs[n].count; |
80 | 0 | continue; |
81 | 0 | } |
82 | | |
83 | | /* FIXME: read_lseek_blockwise candidate */ |
84 | 0 | if (lseek(ctx->inputs[n].fd, ctx->inputs[n].start + offset, SEEK_SET) < 0) |
85 | 0 | return -1; |
86 | 0 | return (read_buffer(ctx->inputs[n].fd, output, count) == (ssize_t)count) ? 0 : -1; |
87 | 0 | } |
88 | | |
89 | | /* should never be reached */ |
90 | 0 | return -1; |
91 | 0 | } |
92 | | |
93 | | /* encodes/decode inputs to/from fd */ |
94 | | static int FEC_process_inputs(struct crypt_device *cd, |
95 | | struct crypt_params_verity *params, |
96 | | struct fec_input_device *inputs, |
97 | | size_t ninputs, int fd, |
98 | | int decode, unsigned int *errors) |
99 | 0 | { |
100 | 0 | int r = 0; |
101 | 0 | unsigned int i; |
102 | 0 | struct fec_context ctx; |
103 | 0 | uint32_t b; |
104 | 0 | uint64_t n; |
105 | 0 | uint8_t rs_block[FEC_RSM]; |
106 | 0 | uint8_t *buf = NULL; |
107 | 0 | void *rs; |
108 | | |
109 | | /* initialize parameters */ |
110 | 0 | ctx.roots = params->fec_roots; |
111 | 0 | ctx.rsn = FEC_RSM - ctx.roots; |
112 | 0 | ctx.block_size = params->data_block_size; |
113 | 0 | ctx.inputs = inputs; |
114 | 0 | ctx.ninputs = ninputs; |
115 | |
|
116 | 0 | rs = init_rs_char(FEC_PARAMS(ctx.roots)); |
117 | 0 | if (!rs) { |
118 | 0 | log_err(cd, _("Failed to allocate RS context.")); |
119 | 0 | return -ENOMEM; |
120 | 0 | } |
121 | | |
122 | | /* calculate the total area covered by error correction codes */ |
123 | 0 | ctx.size = 0; |
124 | 0 | for (n = 0; n < ctx.ninputs; ++n) { |
125 | 0 | log_dbg(cd, "FEC input %s, offset %" PRIu64 " [bytes], length %" PRIu64 " [bytes]", |
126 | 0 | device_path(ctx.inputs[n].device), ctx.inputs[n].start, ctx.inputs[n].count); |
127 | 0 | ctx.size += ctx.inputs[n].count; |
128 | 0 | } |
129 | | |
130 | | /* each byte in a data block is covered by a different code */ |
131 | 0 | ctx.blocks = FEC_div_round_up(ctx.size, ctx.block_size); |
132 | 0 | ctx.rounds = FEC_div_round_up(ctx.blocks, ctx.rsn); |
133 | |
|
134 | 0 | buf = malloc((size_t)ctx.block_size * ctx.rsn); |
135 | 0 | if (!buf) { |
136 | 0 | log_err(cd, _("Failed to allocate buffer.")); |
137 | 0 | r = -ENOMEM; |
138 | 0 | goto out; |
139 | 0 | } |
140 | | |
141 | | /* encode/decode input */ |
142 | 0 | for (n = 0; n < ctx.rounds; ++n) { |
143 | 0 | for (i = 0; i < ctx.rsn; ++i) { |
144 | 0 | if (FEC_read_interleaved(&ctx, n * ctx.rsn * ctx.block_size + i, |
145 | 0 | &buf[i * ctx.block_size], ctx.block_size)) { |
146 | 0 | log_err(cd, _("Failed to read RS block %" PRIu64 " byte %d."), n, i); |
147 | 0 | r = -EIO; |
148 | 0 | goto out; |
149 | 0 | } |
150 | 0 | } |
151 | | |
152 | 0 | for (b = 0; b < ctx.block_size; ++b) { |
153 | 0 | for (i = 0; i < ctx.rsn; ++i) |
154 | 0 | rs_block[i] = buf[i * ctx.block_size + b]; |
155 | | |
156 | | /* decoding from parity device */ |
157 | 0 | if (decode) { |
158 | 0 | if (read_buffer(fd, &rs_block[ctx.rsn], ctx.roots) < 0) { |
159 | 0 | log_err(cd, _("Failed to read parity for RS block %" PRIu64 "."), n); |
160 | 0 | r = -EIO; |
161 | 0 | goto out; |
162 | 0 | } |
163 | | |
164 | | /* coverity[tainted_data] */ |
165 | 0 | r = decode_rs_char(rs, rs_block); |
166 | 0 | if (r < 0) { |
167 | 0 | log_err(cd, _("Failed to repair parity for block %" PRIu64 "."), n); |
168 | 0 | r = -EPERM; |
169 | 0 | goto out; |
170 | 0 | } |
171 | | /* return number of detected errors */ |
172 | 0 | if (errors) |
173 | 0 | *errors += r; |
174 | 0 | r = 0; |
175 | 0 | } else { |
176 | | /* encoding and writing parity data to fec device */ |
177 | 0 | encode_rs_char(rs, rs_block, &rs_block[ctx.rsn]); |
178 | 0 | if (write_buffer(fd, &rs_block[ctx.rsn], ctx.roots) < 0) { |
179 | 0 | log_err(cd, _("Failed to write parity for RS block %" PRIu64 "."), n); |
180 | 0 | r = -EIO; |
181 | 0 | goto out; |
182 | 0 | } |
183 | 0 | } |
184 | 0 | } |
185 | 0 | } |
186 | 0 | out: |
187 | 0 | free_rs_char(rs); |
188 | 0 | free(buf); |
189 | 0 | return r; |
190 | 0 | } |
191 | | |
192 | | static int VERITY_FEC_validate(struct crypt_device *cd, struct crypt_params_verity *params) |
193 | 0 | { |
194 | 0 | if (params->data_block_size != params->hash_block_size) { |
195 | 0 | log_err(cd, _("Block sizes must match for FEC.")); |
196 | 0 | return -EINVAL; |
197 | 0 | } |
198 | | |
199 | 0 | if (params->fec_roots > FEC_RSM - FEC_MIN_RSN || |
200 | 0 | params->fec_roots < FEC_RSM - FEC_MAX_RSN) { |
201 | 0 | log_err(cd, _("Invalid number of parity bytes.")); |
202 | 0 | return -EINVAL; |
203 | 0 | } |
204 | | |
205 | 0 | return 0; |
206 | 0 | } |
207 | | |
208 | | int VERITY_FEC_process(struct crypt_device *cd, |
209 | | struct crypt_params_verity *params, |
210 | | struct device *fec_device, int check_fec, |
211 | | unsigned int *errors) |
212 | 0 | { |
213 | 0 | int r = -EIO, fd = -1; |
214 | 0 | size_t ninputs = FEC_INPUT_DEVICES; |
215 | 0 | struct fec_input_device inputs[FEC_INPUT_DEVICES] = { |
216 | 0 | { |
217 | 0 | .device = crypt_data_device(cd), |
218 | 0 | .fd = -1, |
219 | 0 | .start = 0, |
220 | 0 | .count = params->data_size * params->data_block_size |
221 | 0 | },{ |
222 | 0 | .device = crypt_metadata_device(cd), |
223 | 0 | .fd = -1, |
224 | 0 | .start = VERITY_hash_offset_block(params) * params->data_block_size, |
225 | 0 | .count = (VERITY_FEC_blocks(cd, fec_device, params) - params->data_size) * params->data_block_size |
226 | 0 | } |
227 | 0 | }; |
228 | | |
229 | | /* validate parameters */ |
230 | 0 | r = VERITY_FEC_validate(cd, params); |
231 | 0 | if (r < 0) |
232 | 0 | return r; |
233 | | |
234 | 0 | if (!inputs[0].count) { |
235 | 0 | log_err(cd, _("Invalid FEC segment length.")); |
236 | 0 | return -EINVAL; |
237 | 0 | } |
238 | 0 | if (!inputs[1].count) |
239 | 0 | ninputs--; |
240 | |
|
241 | 0 | if (check_fec) |
242 | 0 | fd = open(device_path(fec_device), O_RDONLY); |
243 | 0 | else |
244 | 0 | fd = open(device_path(fec_device), O_RDWR); |
245 | |
|
246 | 0 | if (fd == -1) { |
247 | 0 | log_err(cd, _("Cannot open device %s."), device_path(fec_device)); |
248 | 0 | goto out; |
249 | 0 | } |
250 | | |
251 | 0 | if (lseek(fd, params->fec_area_offset, SEEK_SET) < 0) { |
252 | 0 | log_dbg(cd, "Cannot seek to requested position in FEC device."); |
253 | 0 | goto out; |
254 | 0 | } |
255 | | |
256 | | /* input devices */ |
257 | 0 | inputs[0].fd = open(device_path(inputs[0].device), O_RDONLY); |
258 | 0 | if (inputs[0].fd == -1) { |
259 | 0 | log_err(cd, _("Cannot open device %s."), device_path(inputs[0].device)); |
260 | 0 | goto out; |
261 | 0 | } |
262 | 0 | inputs[1].fd = open(device_path(inputs[1].device), O_RDONLY); |
263 | 0 | if (inputs[1].fd == -1) { |
264 | 0 | log_err(cd, _("Cannot open device %s."), device_path(inputs[1].device)); |
265 | 0 | goto out; |
266 | 0 | } |
267 | | |
268 | 0 | r = FEC_process_inputs(cd, params, inputs, ninputs, fd, check_fec, errors); |
269 | 0 | out: |
270 | 0 | if (inputs[0].fd != -1) |
271 | 0 | close(inputs[0].fd); |
272 | 0 | if (inputs[1].fd != -1) |
273 | 0 | close(inputs[1].fd); |
274 | 0 | if (fd != -1) |
275 | 0 | close(fd); |
276 | |
|
277 | 0 | return r; |
278 | 0 | } |
279 | | |
280 | | /* All blocks that are covered by FEC */ |
281 | | uint64_t VERITY_FEC_blocks(struct crypt_device *cd, |
282 | | struct device *fec_device, |
283 | | struct crypt_params_verity *params) |
284 | 0 | { |
285 | 0 | uint64_t blocks = 0; |
286 | |
|
287 | 0 | if (!fec_device || VERITY_FEC_validate(cd, params) < 0) |
288 | 0 | return 0; |
289 | | |
290 | | /* |
291 | | * FEC covers this data: |
292 | | * | protected data | hash area | padding (optional foreign metadata) | |
293 | | * |
294 | | * If hash device is in a separate image, metadata covers the whole rest of the image after hash area. |
295 | | * If hash and FEC device is in the image, metadata ends on the FEC area offset. |
296 | | */ |
297 | 0 | if (device_is_identical(crypt_metadata_device(cd), fec_device) > 0) { |
298 | 0 | log_dbg(cd, "FEC and hash device is the same."); |
299 | 0 | blocks = params->fec_area_offset; |
300 | 0 | } else { |
301 | | /* cover the entire hash device starting from hash_offset */ |
302 | 0 | if (device_size(crypt_metadata_device(cd), &blocks)) { |
303 | 0 | log_err(cd, _("Failed to determine size for device %s."), |
304 | 0 | device_path(crypt_metadata_device(cd))); |
305 | 0 | return 0; |
306 | 0 | } |
307 | 0 | } |
308 | | |
309 | 0 | blocks /= params->data_block_size; |
310 | 0 | if (blocks) |
311 | 0 | blocks -= VERITY_hash_offset_block(params); |
312 | | |
313 | | /* Protected data */ |
314 | 0 | blocks += params->data_size; |
315 | |
|
316 | 0 | return blocks; |
317 | 0 | } |
318 | | |
319 | | /* Blocks needed to store FEC data, blocks must be validated/calculated by VERITY_FEC_blocks() */ |
320 | | uint64_t VERITY_FEC_RS_blocks(uint64_t blocks, uint32_t roots) |
321 | 0 | { |
322 | 0 | return FEC_div_round_up(blocks, FEC_RSM - roots) * roots; |
323 | 0 | } |