xref: /freebsd/lib/libsecureboot/vectx.c (revision a3cefe7f2b4df0f70ff92d4570ce18e517af43ec)
1 /*-
2  * Copyright (c) 2018, Juniper Networks, Inc.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
14  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
15  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
16  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
17  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
18  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
19  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
20  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
21  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25 #include <sys/cdefs.h>
26 #ifndef _STANDALONE
27 /* Avoid unwanted userlandish components */
28 #define _KERNEL
29 #include <sys/errno.h>
30 #undef _KERNEL
31 #endif
32 
33 #ifdef VECTX_DEBUG
34 static int vectx_debug = VECTX_DEBUG;
35 # define DEBUG_PRINTF(n, x) if (vectx_debug >= n) printf x
36 #endif
37 
38 #include "libsecureboot-priv.h"
39 #include <verify_file.h>
40 
41 /**
42  * @file vectx.c
43  * @brief api to verify file while reading
44  *
45  * This API allows the hash of a file to be computed as it is read.
46  * Key to this is seeking by reading.
47  *
48  * On close an indication of the verification result is returned.
49  */
50 
51 struct vectx {
52 	br_hash_compat_context vec_ctx;	/* hash ctx */
53 	const br_hash_class *vec_md;	/* hash method */
54 	const char	*vec_path;	/* path we are verifying */
55 	const char	*vec_want;	/* hash value we want */
56 	off_t		vec_off;	/* current offset */
57 	off_t		vec_hashed;	/* where we have hashed to */
58 	off_t		vec_size;	/* size of path */
59 	size_t		vec_hashsz;	/* size of hash */
60 	int		vec_fd;		/* file descriptor */
61 	int		vec_status;	/* verification status */
62 	int		vec_closing;	/* we are closing */
63 	int		vec_severity;	/* usually VE_MUST */
64 };
65 
66 
67 /**
68  * @brief
69  * verify an open file as we read it
70  *
71  * If the file has no fingerprint to match, we will still return a
72  * verification context containing little more than the file
73  * descriptor, and an error code in @c error.
74  *
75  * @param[in] fd
76  *	open descriptor
77  *
78  * @param[in] path
79  *	pathname to open
80  *
81  * @param[in] off
82  *	current offset
83  *
84  * @param[in] stp
85  *	pointer to struct stat
86  *
87  * @param[out] error
88  *	@li 0 all is good
89  *	@li ENOMEM out of memory
90  *	@li VE_FINGERPRINT_NONE	no entry found
91  *	@li VE_FINGERPRINT_UNKNOWN no fingerprint in entry
92  *
93  * @return ctx or NULL on error.
94  *	NULL is only returned for non-files or out-of-memory.
95  */
96 struct vectx *
97 vectx_open(int fd, const char *path, int severity,
98     off_t off, struct stat *stp,
99     int *error, const char *caller)
100 {
101 	struct vectx *ctx;
102 	struct stat st;
103 	size_t hashsz;
104 	char *cp;
105 	int rc;
106 
107 	if (!stp)
108 	    stp = &st;
109 
110 	rc = verify_prep(fd, path, off, stp, __func__);
111 	if (severity == VE_GUESS)
112 		severity = severity_guess(path);
113 
114 	DEBUG_PRINTF(2,
115 	    ("vectx_open: caller=%s,fd=%d,name='%s',prep_rc=%d,severity=%d\n",
116 		caller, fd, path, rc, severity));
117 
118 	switch (rc) {
119 	case VE_FINGERPRINT_NONE:
120 	case VE_FINGERPRINT_UNKNOWN:
121 		if (severity < VE_MUST)
122 			break;
123 		/* FALLTHROUGH */
124 	case VE_FINGERPRINT_WRONG:
125 		*error = rc;
126 		return (NULL);
127 	}
128 	ctx = malloc(sizeof(struct vectx));
129 	if (!ctx)
130 		goto enomem;
131 	ctx->vec_fd = fd;
132 	ctx->vec_path = path;
133 	ctx->vec_size = stp->st_size;
134 	ctx->vec_off = 0;
135 	ctx->vec_hashed = 0;
136 	ctx->vec_want = NULL;
137 	ctx->vec_severity = severity;
138 	ctx->vec_status = 0;
139 	ctx->vec_hashsz = hashsz = 0;
140 	ctx->vec_closing = 0;
141 
142 	if (rc == VE_UNVERIFIED_OK) {
143 		/* we are not verifying this */
144 		*error = 0;
145 		return (ctx);
146 	}
147 	cp = fingerprint_info_lookup(fd, path);
148 	if (!cp) {
149 		if (severity < VE_MUST)
150 			ctx->vec_status = VE_UNVERIFIED_OK;
151 		else {
152 			ctx->vec_status = VE_FINGERPRINT_NONE;
153 			ve_error_set("%s: no entry", path);
154 		}
155 	} else {
156 		if (strncmp(cp, "no_hash", 7) == 0) {
157 			ctx->vec_status = VE_FINGERPRINT_IGNORE;
158 			hashsz = 0;
159 		} else if (strncmp(cp, "sha256=", 7) == 0) {
160 			ctx->vec_md = &br_sha256_vtable;
161 			hashsz = br_sha256_SIZE;
162 			cp += 7;
163 #ifdef VE_SHA1_SUPPORT
164 		} else if (strncmp(cp, "sha1=", 5) == 0) {
165 			ctx->vec_md = &br_sha1_vtable;
166 			hashsz = br_sha1_SIZE;
167 			cp += 5;
168 #endif
169 #ifdef VE_SHA384_SUPPORT
170 		} else if (strncmp(cp, "sha384=", 7) == 0) {
171 		    ctx->vec_md = &br_sha384_vtable;
172 		    hashsz = br_sha384_SIZE;
173 		    cp += 7;
174 #endif
175 #ifdef VE_SHA512_SUPPORT
176 		} else if (strncmp(cp, "sha512=", 7) == 0) {
177 		    ctx->vec_md = &br_sha512_vtable;
178 		    hashsz = br_sha512_SIZE;
179 		    cp += 7;
180 #endif
181 		} else {
182 			if (severity < VE_MUST)
183 				ctx->vec_status = VE_UNVERIFIED_OK;
184 			else {
185 				ctx->vec_status = VE_FINGERPRINT_UNKNOWN;
186 				ve_error_set("%s: no supported fingerprint", path);
187 			}
188 		}
189 	}
190 	*error = ctx->vec_status;
191 	ctx->vec_hashsz = hashsz;
192 	ctx->vec_want = cp;
193 	if (hashsz > 0) {
194 		ctx->vec_md->init(&ctx->vec_ctx.vtable);
195 
196 		if (off > 0) {
197 			lseek(fd, 0, SEEK_SET);
198 			vectx_lseek(ctx, off, SEEK_SET);
199 		}
200 	}
201 	DEBUG_PRINTF(2,
202 	    ("vectx_open: caller=%s,name='%s',hashsz=%lu,severity=%d,status=%d\n",
203 		caller, path, (unsigned long)ctx->vec_hashsz,
204 		severity, ctx->vec_status));
205 	return (ctx);
206 
207 enomem:					/* unlikely */
208 	*error = ENOMEM;
209 	free(ctx);
210 	return (NULL);
211 }
212 
213 /**
214  * @brief
215  * read bytes from file and update hash
216  *
217  * It is critical that all file I/O comes through here.
218  * We keep track of current offset.
219  * We also track what offset we have hashed to,
220  * so we won't replay data if we seek backwards.
221  *
222  * @param[in] pctx
223  *	pointer to ctx
224  *
225  * @param[in] buf
226  *
227  * @param[in] nbytes
228  *
229  * @return bytes read or error.
230  */
231 ssize_t
232 vectx_read(struct vectx *ctx, void *buf, size_t nbytes)
233 {
234 	unsigned char *bp = buf;
235 	int d;
236 	int n;
237 	int delta;
238 	int x;
239 	size_t off;
240 
241 	if (ctx->vec_hashsz == 0)	/* nothing to do */
242 		return (read(ctx->vec_fd, buf, nbytes));
243 
244 	off = 0;
245 	do {
246 		/*
247 		 * Do this in reasonable chunks so
248 		 * we don't timeout if doing tftp
249 		 */
250 		x = nbytes - off;
251 		x = MIN(PAGE_SIZE, x);
252 		d = n = read(ctx->vec_fd, &bp[off], x);
253 		if (ctx->vec_closing && n < x) {
254 			DEBUG_PRINTF(3,
255 			    ("%s: read %d off=%ld hashed=%ld size=%ld\n",
256 			     __func__, n, (long)ctx->vec_off,
257 			     (long)ctx->vec_hashed, (long)ctx->vec_size));
258 		}
259 		if (n < 0) {
260 			return (n);
261 		}
262 		if (d > 0) {
263 			/* we may have seeked backwards! */
264 			delta = ctx->vec_hashed - ctx->vec_off;
265 			if (delta > 0) {
266 				x = MIN(delta, d);
267 				off += x;
268 				d -= x;
269 				ctx->vec_off += x;
270 			}
271 			if (d > 0) {
272 				if (ctx->vec_closing && d < PAGE_SIZE) {
273 					DEBUG_PRINTF(3,
274 					    ("%s: update %ld + %d\n",
275 						__func__,
276 						(long)ctx->vec_hashed, d));
277 				}
278 				ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], d);
279 				off += d;
280 				ctx->vec_off += d;
281 				ctx->vec_hashed += d;
282 			}
283 		}
284 	} while (n > 0 && off < nbytes);
285 	return (off);
286 }
287 
288 /**
289  * @brief
290  * vectx equivalent of lseek
291  *
292  * When seeking forwards we actually call vectx_read
293  * to reach the desired offset.
294  *
295  * We support seeking backwards.
296  *
297  * @param[in] pctx
298  *	pointer to ctx
299  *
300  * @param[in] off
301  *	desired offset
302  *
303  * @param[in] whence
304  * 	We try to convert whence to ``SEEK_SET``.
305  *	We do not support ``SEEK_DATA`` or ``SEEK_HOLE``.
306  *
307  * @return offset or error.
308  */
309 off_t
310 vectx_lseek(struct vectx *ctx, off_t off, int whence)
311 {
312 	unsigned char buf[PAGE_SIZE];
313 	size_t delta;
314 	ssize_t n;
315 
316 	if (ctx->vec_hashsz == 0)	/* nothing to do */
317 		return (lseek(ctx->vec_fd, off, whence));
318 
319 	/*
320 	 * Convert whence to SEEK_SET
321 	 */
322 	DEBUG_PRINTF(3,
323 	    ("%s(%s, %ld, %d)\n", __func__, ctx->vec_path, (long)off, whence));
324 	if (whence == SEEK_END && off <= 0) {
325 		if (ctx->vec_size < 0) {
326 			if (ctx->vec_closing) {
327 				/* size unknown - read until EOF */
328 				do {
329 					n = vectx_read(ctx, buf, PAGE_SIZE);
330 					if (n < 0)
331 						return (n);
332 				} while (n > 0);
333 				return (ctx->vec_off);
334 			}
335 		} else {
336 			if (ctx->vec_closing && ctx->vec_hashed < ctx->vec_size) {
337 				DEBUG_PRINTF(3, ("%s: SEEK_END %ld\n",
338 					__func__,
339 					(long)(ctx->vec_size - ctx->vec_hashed)));
340 			}
341 			whence = SEEK_SET;
342 			off += ctx->vec_size;
343 		}
344 	} else if (whence == SEEK_CUR) {
345 		whence = SEEK_SET;
346 		off += ctx->vec_off;
347 	}
348 	if (whence != SEEK_SET ||
349 	    (off > ctx->vec_size && ctx->vec_size > 0)) {
350 		printf("ERROR: %s: unsupported operation: whence=%d off=%ld -> %ld\n",
351 		    __func__, whence, (long)ctx->vec_off, (long)off);
352 		return (-1);
353 	}
354 	if (off < ctx->vec_hashed) {
355 #ifdef _STANDALONE
356 		struct open_file *f = fd2open_file(ctx->vec_fd);
357 
358 		if (f != NULL &&
359 		    strncmp(f->f_ops->fs_name, "tftp", 4) == 0) {
360 			/* we cannot rewind if we've hashed much of the file */
361 			if (ctx->vec_hashed > ctx->vec_size / 5)
362 				return (-1);	/* refuse! */
363 		}
364 #endif
365 		/* seeking backwards! just do it */
366 		ctx->vec_off = lseek(ctx->vec_fd, off, whence);
367 		return (ctx->vec_off);
368 	}
369 	n = 0;
370 	do {
371 		delta = off - ctx->vec_off;
372 		if (delta > 0) {
373 			delta = MIN(PAGE_SIZE, delta);
374 			n = vectx_read(ctx, buf, delta);
375 			if (n < 0)
376 				return (n);
377 		}
378 	} while (ctx->vec_off < off && n > 0);
379 	return (ctx->vec_off);
380 }
381 
382 /**
383  * @brief
384  * check that hashes match and cleanup
385  *
386  * We have finished reading file, compare the hash with what
387  * we wanted.
388  *
389  * Be sure to call this before closing the file, since we may
390  * need to seek to the end to ensure hashing is complete.
391  *
392  * @param[in] pctx
393  *	pointer to ctx
394  *
395  * @return 0 or an error.
396  */
397 int
398 vectx_close(struct vectx *ctx, const char *caller)
399 {
400 	int rc;
401 
402 	ctx->vec_closing = 1;
403 	if (ctx->vec_hashsz == 0) {
404 		rc = ctx->vec_status;
405 	} else {
406 #ifdef VE_PCR_SUPPORT
407 		/*
408 		 * Only update pcr with things that must verify
409 		 * these tend to be processed in a more deterministic
410 		 * order, which makes our pseudo pcr more useful.
411 		 */
412 		ve_pcr_updating_set((ctx->vec_severity == VE_MUST));
413 #endif
414 		/* make sure we have hashed it all */
415 		vectx_lseek(ctx, 0, SEEK_END);
416 		rc = ve_check_hash(&ctx->vec_ctx, ctx->vec_md,
417 		    ctx->vec_path, ctx->vec_want, ctx->vec_hashsz);
418 	}
419 	DEBUG_PRINTF(2,
420 	    ("vectx_close: caller=%s,name='%s',severity=%d,rc=%d\n",
421 		caller,ctx->vec_path, ctx->vec_severity, rc));
422 	verify_report(ctx->vec_path, ctx->vec_severity, rc, NULL);
423 	if (rc == VE_FINGERPRINT_WRONG) {
424 #if !defined(UNIT_TEST) && !defined(DEBUG_VECTX)
425 		/* we are generally called with VE_MUST */
426 		if (ctx->vec_severity > VE_WANT)
427 			panic("cannot continue");
428 #endif
429 	}
430 	free(ctx);
431 	return ((rc < 0) ? rc : 0);
432 }
433