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