1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3 * Elide and patch handling for 'fsverity setup'
4 *
5 * Copyright (C) 2018 Google LLC
6 *
7 * Written by Eric Biggers.
8 */
9
10 #include <errno.h>
11 #include <fcntl.h>
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15
16 #include "fsverity_uapi.h"
17 #include "fsveritysetup.h"
18
19 /* An elision or a patch */
20 struct fsverity_elide_patch {
21 u64 offset; /* byte offset within the original data */
22 u64 length; /* length in bytes */
23 bool patch; /* false if elision, true if patch */
24 u8 data[]; /* replacement data (if patch=true) */
25 };
26
27 /* Maximum supported patch size, in bytes */
28 #define FS_VERITY_MAX_PATCH_SIZE 255
29
30 /* Parse an --elide=OFFSET,LENGTH option */
parse_elide_option(const char * optarg)31 static struct fsverity_elide_patch *parse_elide_option(const char *optarg)
32 {
33 struct fsverity_elide_patch *ext = NULL;
34 char *sep, *end;
35 unsigned long long offset;
36 unsigned long long length;
37
38 sep = strchr(optarg, ',');
39 if (!sep || sep == optarg)
40 goto invalid;
41 errno = 0;
42 *sep = '\0';
43 offset = strtoull(optarg, &end, 10);
44 *sep = ',';
45 if (errno || end != sep)
46 goto invalid;
47 length = strtoull(sep + 1, &end, 10);
48 if (errno || *end)
49 goto invalid;
50 if (length <= 0 || length > UINT64_MAX - offset) {
51 error_msg("Invalid length in '--elide=%s'", optarg);
52 return NULL;
53 }
54 ext = xzalloc(sizeof(*ext));
55 ext->offset = offset;
56 ext->length = length;
57 ext->patch = false;
58 return ext;
59
60 invalid:
61 error_msg("Invalid --elide option: '%s'. Must be formatted as OFFSET,LENGTH",
62 optarg);
63 return NULL;
64 }
65
66 /* Parse a --patch=OFFSET,PATCHFILE option */
parse_patch_option(const char * optarg)67 static struct fsverity_elide_patch *parse_patch_option(const char *optarg)
68 {
69 struct fsverity_elide_patch *ext = NULL;
70 struct filedes patchfile = { .fd = -1 };
71 char *sep, *end;
72 unsigned long long offset;
73 u64 length;
74
75 sep = strchr(optarg, ',');
76 if (!sep || sep == optarg)
77 goto invalid;
78 errno = 0;
79 *sep = '\0';
80 offset = strtoull(optarg, &end, 10);
81 *sep = ',';
82 if (errno || end != sep)
83 goto invalid;
84 if (!open_file(&patchfile, sep + 1, O_RDONLY, 0))
85 goto out;
86 if (!get_file_size(&patchfile, &length))
87 goto out;
88 if (length <= 0) {
89 error_msg("patch file '%s' is empty", patchfile.name);
90 goto out;
91 }
92 if (length > FS_VERITY_MAX_PATCH_SIZE) {
93 error_msg("Patch file '%s' is too long. Max patch size is %d bytes.",
94 patchfile.name, FS_VERITY_MAX_PATCH_SIZE);
95 goto out;
96 }
97 ext = xzalloc(sizeof(*ext) + length);
98 ext->offset = offset;
99 ext->length = length;
100 ext->patch = true;
101 if (!full_read(&patchfile, ext->data, length)) {
102 free(ext);
103 ext = NULL;
104 }
105 out:
106 filedes_close(&patchfile);
107 return ext;
108
109 invalid:
110 error_msg("Invalid --patch option: '%s'. Must be formatted as OFFSET,PATCHFILE",
111 optarg);
112 goto out;
113 }
114
115 /* Sort by increasing offset */
cmp_elide_patch_exts(const void * _p1,const void * _p2)116 static int cmp_elide_patch_exts(const void *_p1, const void *_p2)
117 {
118 const struct fsverity_elide_patch *ext1, *ext2;
119
120 ext1 = *(const struct fsverity_elide_patch **)_p1;
121 ext2 = *(const struct fsverity_elide_patch **)_p2;
122
123 if (ext1->offset > ext2->offset)
124 return 1;
125 if (ext1->offset < ext2->offset)
126 return -1;
127 return 0;
128 }
129
130 /*
131 * Given the lists of --elide and --patch options, validate and load the
132 * elisions and patches into @params.
133 */
load_elisions_and_patches(const struct string_list * elide_opts,const struct string_list * patch_opts,struct fsveritysetup_params * params)134 bool load_elisions_and_patches(const struct string_list *elide_opts,
135 const struct string_list *patch_opts,
136 struct fsveritysetup_params *params)
137 {
138 const size_t num_exts = elide_opts->length + patch_opts->length;
139 struct fsverity_elide_patch **exts;
140 size_t i, j;
141
142 if (num_exts == 0) /* Normal case: no elisions or patches */
143 return true;
144 params->num_elisions_and_patches = num_exts;
145 exts = xzalloc(num_exts * sizeof(exts[0]));
146 params->elisions_and_patches = exts;
147 j = 0;
148
149 /* Parse the --elide options */
150 for (i = 0; i < elide_opts->length; i++) {
151 exts[j] = parse_elide_option(elide_opts->strings[i]);
152 if (!exts[j++])
153 return false;
154 }
155
156 /* Parse the --patch options */
157 for (i = 0; i < patch_opts->length; i++) {
158 exts[j] = parse_patch_option(patch_opts->strings[i]);
159 if (!exts[j++])
160 return false;
161 }
162
163 /* Sort the elisions and patches by increasing offset */
164 qsort(exts, num_exts, sizeof(exts[0]), cmp_elide_patch_exts);
165
166 /* Verify that no elisions or patches overlap */
167 for (j = 1; j < num_exts; j++) {
168 if (exts[j]->offset <
169 exts[j - 1]->offset + exts[j - 1]->length) {
170 error_msg("%s at [%"PRIu64", %"PRIu64") overlaps "
171 "%s at [%"PRIu64", %"PRIu64")",
172 exts[j - 1]->patch ? "Patch" : "Elision",
173 exts[j - 1]->offset,
174 exts[j - 1]->offset + exts[j - 1]->length,
175 exts[j]->patch ? "patch" : "elision",
176 exts[j]->offset,
177 exts[j]->offset + exts[j]->length);
178 return false;
179 }
180 }
181 return true;
182 }
183
free_elisions_and_patches(struct fsveritysetup_params * params)184 void free_elisions_and_patches(struct fsveritysetup_params *params)
185 {
186 size_t i;
187
188 for (i = 0; i < params->num_elisions_and_patches; i++)
189 free(params->elisions_and_patches[i]);
190 free(params->elisions_and_patches);
191 }
192
193 /*
194 * Given the original file @in of length @in_length bytes, create a temporary
195 * file @out_ret and write to it the data with the elisions and patches applied,
196 * with the end zero-padded to the next block boundary. Returns in
197 * @out_length_ret the length of the elided/patched file in bytes.
198 */
apply_elisions_and_patches(const struct fsveritysetup_params * params,struct filedes * in,u64 in_length,struct filedes * out_ret,u64 * out_length_ret)199 bool apply_elisions_and_patches(const struct fsveritysetup_params *params,
200 struct filedes *in, u64 in_length,
201 struct filedes *out_ret, u64 *out_length_ret)
202 {
203 struct fsverity_elide_patch **exts = params->elisions_and_patches;
204 struct filedes *out = out_ret;
205 size_t i;
206
207 for (i = 0; i < params->num_elisions_and_patches; i++) {
208 if (exts[i]->offset + exts[i]->length > in_length) {
209 error_msg("%s at [%"PRIu64", %"PRIu64") extends beyond end of input file",
210 exts[i]->patch ? "Patch" : "Elision",
211 exts[i]->offset,
212 exts[i]->offset + exts[i]->length);
213 return false;
214 }
215 }
216
217 if (!filedes_seek(in, 0, SEEK_SET))
218 return false;
219
220 if (!open_tempfile(out))
221 return false;
222
223 for (i = 0; i < params->num_elisions_and_patches; i++) {
224 printf("Applying %s: offset=%"PRIu64", length=%"PRIu64"\n",
225 exts[i]->patch ? "patch" : "elision",
226 exts[i]->offset, exts[i]->length);
227
228 if (!copy_file_data(in, out, exts[i]->offset - in->pos))
229 return false;
230
231 if (exts[i]->patch &&
232 !full_write(out, exts[i]->data, exts[i]->length))
233 return false;
234
235 if (!filedes_seek(in, exts[i]->length, SEEK_CUR))
236 return false;
237 }
238 if (!copy_file_data(in, out, in_length - in->pos))
239 return false;
240 if (!write_zeroes(out, ALIGN(out->pos, params->blocksize) - out->pos))
241 return false;
242 *out_length_ret = out->pos;
243 return true;
244 }
245
246 /* Calculate the size the elisions and patches will take up when serialized */
total_elide_patch_ext_length(const struct fsveritysetup_params * params)247 size_t total_elide_patch_ext_length(const struct fsveritysetup_params *params)
248 {
249 size_t total = 0;
250 size_t i;
251
252 for (i = 0; i < params->num_elisions_and_patches; i++) {
253 const struct fsverity_elide_patch *ext =
254 params->elisions_and_patches[i];
255 size_t inner_len;
256
257 if (ext->patch) {
258 inner_len = sizeof(struct fsverity_extension_patch) +
259 ext->length;
260 } else {
261 inner_len = sizeof(struct fsverity_extension_elide);
262 }
263 total += FSVERITY_EXTLEN(inner_len);
264 }
265 return total;
266 }
267
268 /*
269 * Append the elide and patch extensions (if any) to the given buffer.
270 * The buffer must have enough space; call total_elide_patch_ext_length() first.
271 */
append_elide_patch_exts(void ** buf_p,const struct fsveritysetup_params * params)272 void append_elide_patch_exts(void **buf_p,
273 const struct fsveritysetup_params *params)
274 {
275 void *buf = *buf_p;
276 size_t i;
277 union {
278 struct {
279 struct fsverity_extension_patch hdr;
280 u8 data[FS_VERITY_MAX_PATCH_SIZE];
281 } patch;
282 struct fsverity_extension_elide elide;
283 } u;
284
285 for (i = 0; i < params->num_elisions_and_patches; i++) {
286 const struct fsverity_elide_patch *ext =
287 params->elisions_and_patches[i];
288 int type;
289 size_t extlen;
290
291 if (ext->patch) {
292 type = FS_VERITY_EXT_PATCH;
293 u.patch.hdr.offset = cpu_to_le64(ext->offset);
294 ASSERT(ext->length <= sizeof(u.patch.data));
295 memcpy(u.patch.data, ext->data, ext->length);
296 extlen = sizeof(u.patch.hdr) + ext->length;
297 } else {
298 type = FS_VERITY_EXT_ELIDE;
299 u.elide.offset = cpu_to_le64(ext->offset),
300 u.elide.length = cpu_to_le64(ext->length);
301 extlen = sizeof(u.elide);
302 }
303 fsverity_append_extension(&buf, type, &u, extlen);
304 }
305
306 *buf_p = buf;
307 }
308