Index: head/lib/libsecureboot/h/libsecureboot.h =================================================================== --- head/lib/libsecureboot/h/libsecureboot.h (revision 346603) +++ head/lib/libsecureboot/h/libsecureboot.h (revision 346604) @@ -1,94 +1,95 @@ /*- * Copyright (c) 2017-2018, Juniper Networks, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * $FreeBSD$ */ #ifndef _LIBSECUREBOOT_H_ #define _LIBSECUREBOOT_H_ #include #ifdef _STANDALONE #include #else #include #include #include #include #include #include #endif #include #ifndef NEED_BRSSL_H unsigned char * read_file(const char *, size_t *); #endif extern int DebugVe; #define DEBUG_PRINTF(n, x) if (DebugVe >= n) printf x int ve_trust_init(void); int ve_trust_add(const char *); void ve_debug_set(int); void ve_utc_set(time_t utc); char *ve_error_get(void); int ve_error_set(const char *, ...) __printflike(1,2); int ve_self_tests(void); void fingerprint_info_add(const char *, const char *, const char *, const char *, struct stat *); int ve_check_hash(br_hash_compat_context *, const br_hash_class *, const char *, const char *, size_t); struct vectx; struct vectx* vectx_open(int, const char *, off_t, struct stat *, int *); ssize_t vectx_read(struct vectx *, void *, size_t); off_t vectx_lseek(struct vectx *, off_t, int); int vectx_close(struct vectx *); char * hexdigest(char *, size_t, unsigned char *, size_t); int verify_fd(int, const char *, off_t, struct stat *); int verify_open(const char *, int); unsigned char *verify_signed(const char *, int); unsigned char *verify_sig(const char *, int); unsigned char *verify_asc(const char *, int); /* OpenPGP */ void ve_pcr_init(void); void ve_pcr_update(unsigned char *, size_t); ssize_t ve_pcr_get(unsigned char *, size_t); /* flags for verify_{asc,sig,signed} */ #define VEF_VERBOSE 1 #define VE_FINGERPRINT_OK 1 +#define VE_FINGERPRINT_IGNORE 2 /* errors from verify_fd */ #define VE_FINGERPRINT_NONE -2 #define VE_FINGERPRINT_WRONG -3 #define VE_FINGERPRINT_UNKNOWN -4 /* may not be an error */ #endif /* _LIBSECUREBOOT_H_ */ Index: head/lib/libsecureboot/vectx.c =================================================================== --- head/lib/libsecureboot/vectx.c (revision 346603) +++ head/lib/libsecureboot/vectx.c (revision 346604) @@ -1,291 +1,296 @@ /*- * Copyright (c) 2018, Juniper Networks, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #ifndef _STANDALONE /* Avoid unwanted userlandish components */ #define _KERNEL #include #undef _KERNEL #endif #include "libsecureboot-priv.h" /** * @file vectx.c * @brief api to verify file while reading * * This API allows the hash of a file to be computed as it is read. * Key to this is seeking by reading. * * On close an indication of the verification result is returned. */ struct vectx { br_hash_compat_context vec_ctx; /* hash ctx */ const br_hash_class *vec_md; /* hash method */ const char *vec_path; /* path we are verifying */ const char *vec_want; /* hash value we want */ off_t vec_off; /* current offset */ size_t vec_size; /* size of path */ size_t vec_hashsz; /* size of hash */ int vec_fd; /* file descriptor */ int vec_status; /* verification status */ }; /** * @brief * verify an open file as we read it * * If the file has no fingerprint to match, we will still return a * verification context containing little more than the file * descriptor, and an error code in @c error. * * @param[in] fd * open descriptor * * @param[in] path * pathname to open * * @param[in] off * current offset * * @param[in] stp * pointer to struct stat * * @param[out] error * @li 0 all is good * @li ENOMEM out of memory * @li VE_FINGERPRINT_NONE no entry found * @li VE_FINGERPRINT_UNKNOWN no fingerprint in entry * * @return ctx or NULL on error. * NULL is only returned for non-files or out-of-memory. */ struct vectx * vectx_open(int fd, const char *path, off_t off, struct stat *stp, int *error) { struct vectx *ctx; struct stat st; size_t hashsz; char *cp; if (!stp) { if (fstat(fd, &st) == 0) stp = &st; } /* we *should* only get called for files */ if (stp && !S_ISREG(stp->st_mode)) { *error = 0; return (NULL); } ctx = malloc(sizeof(struct vectx)); if (!ctx) goto enomem; ctx->vec_fd = fd; ctx->vec_path = path; ctx->vec_size = stp->st_size; ctx->vec_off = 0; ctx->vec_want = NULL; ctx->vec_status = 0; hashsz = 0; cp = fingerprint_info_lookup(fd, path); if (!cp) { ctx->vec_status = VE_FINGERPRINT_NONE; ve_error_set("%s: no entry", path); } else { - if (strncmp(cp, "sha256=", 7) == 0) { + if (strncmp(cp, "no_hash", 7) == 0) { + ctx->vec_status = VE_FINGERPRINT_IGNORE; + hashsz = 0; + } else if (strncmp(cp, "sha256=", 7) == 0) { ctx->vec_md = &br_sha256_vtable; hashsz = br_sha256_SIZE; cp += 7; #ifdef VE_SHA1_SUPPORT } else if (strncmp(cp, "sha1=", 5) == 0) { ctx->vec_md = &br_sha1_vtable; hashsz = br_sha1_SIZE; cp += 5; #endif #ifdef VE_SHA384_SUPPORT } else if (strncmp(cp, "sha384=", 7) == 0) { ctx->vec_md = &br_sha384_vtable; hashsz = br_sha384_SIZE; cp += 7; #endif #ifdef VE_SHA512_SUPPORT } else if (strncmp(cp, "sha512=", 7) == 0) { ctx->vec_md = &br_sha512_vtable; hashsz = br_sha512_SIZE; cp += 7; #endif } else { ctx->vec_status = VE_FINGERPRINT_UNKNOWN; ve_error_set("%s: no supported fingerprint", path); } } *error = ctx->vec_status; ctx->vec_hashsz = hashsz; ctx->vec_want = cp; - ctx->vec_md->init(&ctx->vec_ctx.vtable); + if (hashsz > 0) { + ctx->vec_md->init(&ctx->vec_ctx.vtable); - if (hashsz > 0 && off > 0) { - lseek(fd, 0, SEEK_SET); - vectx_lseek(ctx, off, SEEK_SET); + if (off > 0) { + lseek(fd, 0, SEEK_SET); + vectx_lseek(ctx, off, SEEK_SET); + } } return (ctx); enomem: /* unlikely */ *error = ENOMEM; free(ctx); return (NULL); } /** * @brief * read bytes from file and update hash * * It is critical that all file I/O comes through here. * We keep track of current offset. * * @param[in] pctx * pointer to ctx * * @param[in] buf * * @param[in] nbytes * * @return bytes read or error. */ ssize_t vectx_read(struct vectx *ctx, void *buf, size_t nbytes) { unsigned char *bp = buf; int n; size_t off; if (ctx->vec_hashsz == 0) /* nothing to do */ return (read(ctx->vec_fd, buf, nbytes)); off = 0; do { n = read(ctx->vec_fd, &bp[off], nbytes - off); if (n < 0) return (n); if (n > 0) { ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], n); off += n; ctx->vec_off += n; } } while (n > 0 && off < nbytes); return (off); } /** * @brief * vectx equivalent of lseek * * We do not actually, seek, but call vectx_read * to reach the desired offset. * * We do not support seeking backwards. * * @param[in] pctx * pointer to ctx * * @param[in] off * desired offset * * @param[in] whence * * @return offset or error. */ off_t vectx_lseek(struct vectx *ctx, off_t off, int whence) { unsigned char buf[PAGE_SIZE]; size_t delta; ssize_t n; if (ctx->vec_hashsz == 0) /* nothing to do */ return (lseek(ctx->vec_fd, off, whence)); /* * Try to convert whence to SEEK_SET * but we cannot support seeking backwards! * Nor beyond end of file. */ if (whence == SEEK_END && off <= 0) { whence = SEEK_SET; off += ctx->vec_size; } else if (whence == SEEK_CUR && off >= 0) { whence = SEEK_SET; off += ctx->vec_off; } if (whence != SEEK_SET || off < ctx->vec_off || (size_t)off > ctx->vec_size) { printf("ERROR: %s: unsupported operation\n", __func__); return (-1); } n = 0; do { delta = off - ctx->vec_off; if (delta > 0) { delta = MIN(PAGE_SIZE, delta); n = vectx_read(ctx, buf, delta); if (n < 0) return (n); } } while (ctx->vec_off < off && n > 0); return (ctx->vec_off); } /** * @brief * check that hashes match and cleanup * * We have finished reading file, compare the hash with what * we wanted. * * @param[in] pctx * pointer to ctx * * @return 0 or an error. */ int vectx_close(struct vectx *ctx) { int rc; if (ctx->vec_hashsz == 0) { rc = ctx->vec_status; } else { rc = ve_check_hash(&ctx->vec_ctx, ctx->vec_md, ctx->vec_path, ctx->vec_want, ctx->vec_hashsz); } free(ctx); return ((rc < 0) ? rc : 0); } Index: head/lib/libsecureboot/veopen.c =================================================================== --- head/lib/libsecureboot/veopen.c (revision 346603) +++ head/lib/libsecureboot/veopen.c (revision 346604) @@ -1,458 +1,461 @@ /*- * Copyright (c) 2017-2018, Juniper Networks, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include __FBSDID("$FreeBSD$"); #include #include "libsecureboot-priv.h" struct fingerprint_info { char *fi_prefix; /**< manifest entries relative to */ char *fi_skip; /**< manifest entries prefixed with */ const char *fi_data; /**< manifest data */ size_t fi_prefix_len; /**< length of prefix */ size_t fi_skip_len; /**< length of skip */ dev_t fi_dev; /**< device id */ LIST_ENTRY(fingerprint_info) entries; }; static LIST_HEAD(, fingerprint_info) fi_list; static void fingerprint_info_init(void) { static int once; if (once) return; LIST_INIT(&fi_list); once = 1; } /** * @brief * add manifest data to list * * list is kept sorted by longest prefix. * * @param[in] prefix * path that all manifest entries are resolved via * * @param[in] skip * optional prefix within manifest entries which should be skipped * * @param[in] data * manifest data */ void fingerprint_info_add(const char *filename, const char *prefix, const char *skip, const char *data, struct stat *stp) { struct fingerprint_info *fip, *nfip, *lfip; char *cp; int n; fingerprint_info_init(); nfip = malloc(sizeof(struct fingerprint_info)); if (prefix) { nfip->fi_prefix = strdup(prefix); } else { if (!filename) { free(nfip); return; } nfip->fi_prefix = strdup(filename); cp = strrchr(nfip->fi_prefix, '/'); if (cp) *cp = '\0'; else { free(nfip->fi_prefix); free(nfip); return; } } /* collapse any trailing ..[/] */ n = 0; while ((cp = strrchr(nfip->fi_prefix, '/')) != NULL) { if (cp[1] == '\0') { /* trailing "/" */ *cp = '\0'; continue; } if (strcmp(&cp[1], "..") == 0) { n++; *cp = '\0'; continue; } if (n > 0) { n--; *cp = '\0'; } if (n == 0) break; } #ifdef UNIT_TEST nfip->fi_dev = 0; #else nfip->fi_dev = stp->st_dev; #endif nfip->fi_data = data; nfip->fi_prefix_len = strlen(nfip->fi_prefix); if (skip) { nfip->fi_skip_len = strlen(skip); if (nfip->fi_skip_len) nfip->fi_skip = strdup(skip); else nfip->fi_skip = NULL; } else { nfip->fi_skip = NULL; nfip->fi_skip_len = 0; } if (LIST_EMPTY(&fi_list)) { LIST_INSERT_HEAD(&fi_list, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s at head\n", nfip->fi_prefix_len, nfip->fi_prefix)); return; } LIST_FOREACH(fip, &fi_list, entries) { if (nfip->fi_prefix_len >= fip->fi_prefix_len) { LIST_INSERT_BEFORE(fip, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s before %zu %s\n", nfip->fi_prefix_len, nfip->fi_prefix, fip->fi_prefix_len, fip->fi_prefix)); return; } lfip = fip; } LIST_INSERT_AFTER(lfip, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s after %zu %s\n", nfip->fi_prefix_len, nfip->fi_prefix, lfip->fi_prefix_len, lfip->fi_prefix)); } #ifdef MANIFEST_SKIP_MAYBE /* * Deal with old incompatible boot/manifest * if fp[-1] is '/' and start of entry matches * MANIFEST_SKIP_MAYBE, we want it. */ static char * maybe_skip(char *fp, struct fingerprint_info *fip, size_t *nplenp) { char *tp; tp = fp - sizeof(MANIFEST_SKIP_MAYBE); if (tp >= fip->fi_data) { DEBUG_PRINTF(3, ("maybe: %.48s\n", tp)); if ((tp == fip->fi_data || tp[-1] == '\n') && strncmp(tp, MANIFEST_SKIP_MAYBE, sizeof(MANIFEST_SKIP_MAYBE) - 1) == 0) { fp = tp; *nplenp += sizeof(MANIFEST_SKIP_MAYBE); } } return (fp); } #endif char * fingerprint_info_lookup(int fd, const char *path) { char pbuf[MAXPATHLEN+1]; char nbuf[MAXPATHLEN+1]; struct stat st; struct fingerprint_info *fip; char *cp, *ep, *fp, *np; const char *prefix; size_t n, plen, nlen, nplen; dev_t dev = 0; fingerprint_info_init(); n = strlcpy(pbuf, path, sizeof(pbuf)); if (n >= sizeof(pbuf)) return (NULL); #ifndef UNIT_TEST if (fstat(fd, &st) == 0) dev = st.st_dev; #endif /* * get the first entry - it will have longest prefix * so we can can work out how to initially split path */ fip = LIST_FIRST(&fi_list); if (!fip) return (NULL); prefix = pbuf; ep = NULL; cp = &pbuf[fip->fi_prefix_len]; do { if (ep) { *ep = '/'; cp -= 2; if (cp < pbuf) break; } nlen = plen = 0; /* keep gcc quiet */ if (cp > pbuf) { for ( ; cp >= pbuf && *cp != '/'; cp--) ; /* nothing */ if (cp > pbuf) { ep = cp++; *ep = '\0'; } else { cp = pbuf; } if (ep) { plen = ep - pbuf; nlen = n - plen - 1; } } if (cp == pbuf) { prefix = "/"; plen = 1; if (*cp == '/') { nlen = n - 1; cp++; } else nlen = n; ep = NULL; } DEBUG_PRINTF(2, ("looking for %s %zu %s\n", prefix, plen, cp)); LIST_FOREACH(fip, &fi_list, entries) { DEBUG_PRINTF(4, ("at %zu %s\n", fip->fi_prefix_len, fip->fi_prefix)); if (fip->fi_prefix_len < plen) { DEBUG_PRINTF(3, ("skipping prefix=%s %zu %zu\n", fip->fi_prefix, fip->fi_prefix_len, plen)); break; } if (fip->fi_prefix_len == plen) { if (fip->fi_dev != 0 && fip->fi_dev != dev) { DEBUG_PRINTF(3, ( "skipping dev=%ld != %ld\n", (long)fip->fi_dev, (long)dev)); continue; } if (strcmp(prefix, fip->fi_prefix)) { DEBUG_PRINTF(3, ( "skipping prefix=%s\n", fip->fi_prefix)); continue; } DEBUG_PRINTF(3, ("checking prefix=%s\n", fip->fi_prefix)); if (fip->fi_skip_len) { np = nbuf; nplen = snprintf(nbuf, sizeof(nbuf), "%s/%s", fip->fi_skip, cp); nplen = MIN(nplen, sizeof(nbuf) - 1); } else { np = cp; nplen = nlen; } DEBUG_PRINTF(3, ("lookup: '%s'\n", np)); if (!(fp = strstr(fip->fi_data, np))) continue; #ifdef MANIFEST_SKIP_MAYBE if (fip->fi_skip_len == 0 && fp > fip->fi_data && fp[-1] == '/') { fp = maybe_skip(fp, fip, &nplen); } #endif /* * when we find a match: * fp[nplen] will be space and * fp will be fip->fi_data or * fp[-1] will be \n */ if (!((fp == fip->fi_data || fp[-1] == '\n') && fp[nplen] == ' ')) { do { fp++; fp = strstr(fp, np); if (fp) { #ifdef MANIFEST_SKIP_MAYBE if (fip->fi_skip_len == 0 && fp > fip->fi_data && fp[-1] == '/') { fp = maybe_skip(fp, fip, &nplen); } #endif DEBUG_PRINTF(3, ("fp[-1]=%#x fp[%zu]=%#x fp=%.78s\n", fp[-1], nplen, fp[nplen], fp)); } } while (fp != NULL && !(fp[-1] == '\n' && fp[nplen] == ' ')); if (!fp) continue; } DEBUG_PRINTF(2, ("found %.78s\n", fp)); /* we have a match! */ for (cp = &fp[nplen]; *cp == ' '; cp++) ; /* nothing */ return (cp); } else { DEBUG_PRINTF(3, ("Ignoring prefix=%s\n", fip->fi_prefix)); } } } while (cp > &pbuf[1]); return (NULL); } static int verify_fingerprint(int fd, const char *path, const char *cp, off_t off) { unsigned char buf[PAGE_SIZE]; const br_hash_class *md; br_hash_compat_context mctx; size_t hlen; int n; - if (strncmp(cp, "sha256=", 7) == 0) { + if (strncmp(cp, "no_hash", 7) == 0) { + return (VE_FINGERPRINT_IGNORE); + } else if (strncmp(cp, "sha256=", 7) == 0) { md = &br_sha256_vtable; hlen = br_sha256_SIZE; cp += 7; #ifdef VE_SHA1_SUPPORT } else if (strncmp(cp, "sha1=", 5) == 0) { md = &br_sha1_vtable; hlen = br_sha1_SIZE; cp += 5; #endif #ifdef VE_SHA384_SUPPORT } else if (strncmp(cp, "sha384=", 7) == 0) { md = &br_sha384_vtable; hlen = br_sha384_SIZE; cp += 7; #endif #ifdef VE_SHA512_SUPPORT } else if (strncmp(cp, "sha512=", 7) == 0) { md = &br_sha512_vtable; hlen = br_sha512_SIZE; cp += 7; #endif } else { ve_error_set("%s: no supported fingerprint", path); return (VE_FINGERPRINT_UNKNOWN); } md->init(&mctx.vtable); if (off) lseek(fd, 0, SEEK_SET); do { n = read(fd, buf, sizeof(buf)); if (n < 0) return (n); if (n > 0) md->update(&mctx.vtable, buf, n); } while (n > 0); lseek(fd, off, SEEK_SET); return (ve_check_hash(&mctx, md, path, cp, hlen)); } /** * @brief * verify an open file * * @param[in] fd * open descriptor * * @param[in] path * pathname to open * * @param[in] off * current offset * * @return 0, VE_FINGERPRINT_OK or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG */ int verify_fd(int fd, const char *path, off_t off, struct stat *stp) { struct stat st; char *cp; int rc; if (!stp) { if (fstat(fd, &st) == 0) stp = &st; } if (stp && !S_ISREG(stp->st_mode)) return (0); /* not relevant */ cp = fingerprint_info_lookup(fd, path); if (!cp) { ve_error_set("%s: no entry", path); return (VE_FINGERPRINT_NONE); } rc = verify_fingerprint(fd, path, cp, off); switch (rc) { case VE_FINGERPRINT_OK: + case VE_FINGERPRINT_IGNORE: case VE_FINGERPRINT_UNKNOWN: return (rc); default: return (VE_FINGERPRINT_WRONG); } } /** * @brief * open a file if it can be verified * * @param[in] path * pathname to open * * @param[in] flags * flags for open * * @return fd or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG */ int verify_open(const char *path, int flags) { int fd; int rc; if ((fd = open(path, flags)) >= 0) { if ((rc = verify_fd(fd, path, 0, NULL)) < 0) { close(fd); fd = rc; } } return (fd); } Index: head/lib/libsecureboot/verify_file.c =================================================================== --- head/lib/libsecureboot/verify_file.c (revision 346603) +++ head/lib/libsecureboot/verify_file.c (revision 346604) @@ -1,419 +1,423 @@ /*- * Copyright (c) 2017-2018, Juniper Networks, Inc. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /* * Routines to verify files loaded. */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "libsecureboot.h" #include #include #ifdef UNIT_TEST # include # define panic warn /* * define MANIFEST_SKIP to Skip - in tests/tvo.c so that * tvo can control the value we use in find_manifest() */ extern char *Skip; # undef MANIFEST_SKIP # define MANIFEST_SKIP Skip # undef VE_DEBUG_LEVEL #endif /* * We sometimes need to know if input is verified or not. * The extra slot is for tracking most recently opened. */ static int ve_status[SOPEN_MAX+1]; static int ve_status_state; struct verify_status; struct verify_status *verified_files = NULL; static int loaded_manifests = 0; /* have we loaded anything? */ #define VE_STATUS_NONE 1 #define VE_STATUS_VALID 2 /** * @brief set ve status for fd */ static void ve_status_set(int fd, int ves) { if (fd >= 0 && fd < SOPEN_MAX) { ve_status[fd] = ves; ve_status_state = VE_STATUS_VALID; } ve_status[SOPEN_MAX] = ves; } /** * @brief get ve status of fd * * What we return depends on ve_status_state. * * @return * @li ve_status[fd] if ve_status_state is valid * @li ve_status[SOPEN_MAX] if ve_status_state is none * @li VE_NOT_CHECKED if ve_status_state uninitialized */ int ve_status_get(int fd) { if (!ve_status_state) { return (VE_NOT_CHECKED); } if (ve_status_state == VE_STATUS_VALID && fd >= 0 && fd < SOPEN_MAX) return (ve_status[fd]); return (ve_status[SOPEN_MAX]); /* most recent */ } /** * @brief track verify status * * occasionally loader will make multiple calls * for the same file, we need only check it once. */ struct verify_status { dev_t vs_dev; ino_t vs_ino; int vs_status; struct verify_status *vs_next; }; int is_verified(struct stat *stp) { struct verify_status *vsp; for (vsp = verified_files; vsp != NULL; vsp = vsp->vs_next) { if (stp->st_dev == vsp->vs_dev && stp->st_ino == vsp->vs_ino) return (vsp->vs_status); } return (VE_NOT_CHECKED); } /* most recent first, since most likely to see repeated calls. */ void add_verify_status(struct stat *stp, int status) { struct verify_status *vsp; vsp = malloc(sizeof(struct verify_status)); vsp->vs_next = verified_files; vsp->vs_dev = stp->st_dev; vsp->vs_ino = stp->st_ino; vsp->vs_status = status; verified_files = vsp; } /** * @brief * load specified manifest if verified */ int load_manifest(const char *name, const char *prefix, const char *skip, struct stat *stp) { struct stat st; size_t n; int rc; char *content; rc = VE_FINGERPRINT_NONE; n = strlen(name); if (n > 4) { if (!stp) { stp = &st; if (stat(name, &st) < 0 || !S_ISREG(st.st_mode)) return (rc); } rc = is_verified(stp); if (rc != VE_NOT_CHECKED) { return (rc); } /* loader has no sense of time */ ve_utc_set(stp->st_mtime); content = (char *)verify_signed(name, VEF_VERBOSE); if (content) { fingerprint_info_add(name, prefix, skip, content, stp); add_verify_status(stp, VE_VERIFIED); loaded_manifests = 1; /* we are verifying! */ DEBUG_PRINTF(3, ("loaded: %s %s %s\n", name, prefix, skip)); rc = 0; } else { rc = VE_FINGERPRINT_WRONG; add_verify_status(stp, rc); /* remember */ } } return (rc); } static int find_manifest(const char *name) { struct stat st; char buf[MAXPATHLEN]; char *prefix; char *skip; const char **tp; int rc; strncpy(buf, name, MAXPATHLEN - 1); if (!(prefix = strrchr(buf, '/'))) return (-1); *prefix = '\0'; prefix = strdup(buf); rc = VE_FINGERPRINT_NONE; for (tp = manifest_names; *tp; tp++) { snprintf(buf, sizeof(buf), "%s/%s", prefix, *tp); DEBUG_PRINTF(5, ("looking for %s\n", buf)); if (stat(buf, &st) == 0 && st.st_size > 0) { #ifdef MANIFEST_SKIP_ALWAYS /* very unlikely */ skip = MANIFEST_SKIP_ALWAYS; #else #ifdef MANIFEST_SKIP /* rare */ if (*tp[0] == '.') { skip = MANIFEST_SKIP; } else #endif skip = NULL; #endif rc = load_manifest(buf, skip ? prefix : NULL, skip, &st); break; } } free(prefix); return (rc); } #ifdef LOADER_VERIEXEC_TESTING # define ACCEPT_NO_FP_DEFAULT VE_MUST + 1 #else # define ACCEPT_NO_FP_DEFAULT VE_MUST #endif #ifndef VE_VERBOSE_DEFAULT # define VE_VERBOSE_DEFAULT 0 #endif static int severity_guess(const char *filename) { const char *cp; /* Some files like *.conf and *.hints may be unsigned */ if ((cp = strrchr(filename, '.'))) { if (strcmp(cp, ".conf") == 0 || strcmp(cp, ".cookie") == 0 || strcmp(cp, ".hints") == 0) return (VE_TRY); } return (VE_WANT); } static void verify_tweak(char *tweak, int *accept_no_fp, int *verbose, int *verifying) { if (strcmp(tweak, "off") == 0) { *verifying = 0; } else if (strcmp(tweak, "strict") == 0) { /* anything caller wants verified must be */ *accept_no_fp = VE_WANT; *verbose = 1; /* warn of anything unverified */ /* treat self test failure as fatal */ if (!ve_self_tests()) { panic("verify self tests failed"); } } else if (strcmp(tweak, "modules") == 0) { /* modules/kernel must be verified */ *accept_no_fp = VE_MUST; } else if (strcmp(tweak, "try") == 0) { /* best effort: always accept no fp */ *accept_no_fp = VE_MUST + 1; } else if (strcmp(tweak, "verbose") == 0) { *verbose = 1; } else if (strcmp(tweak, "quiet") == 0) { *verbose = 0; } } /** * @brief verify an open file * * @param[in] fd * open descriptor * * @param[in] filename * path we opened and will use to lookup fingerprint * * @param[in] off * current offset in fd, must be restored on return * * @param[in] severity * indicator of how to handle case of missing fingerprint * * We look for a signed manifest relative to the filename * just opened and verify/load it if needed. * * We then use verify_fd() in libve to actually verify that hash for * open file. If it returns < 0 we look at the severity arg to decide * what to do about it. * * If verify_fd() returns VE_FINGERPRINT_NONE we accept it if severity * is < accept_no_fp. * * @return >= 0 on success < 0 on failure */ int verify_file(int fd, const char *filename, off_t off, int severity) { static int verifying = -1; static int accept_no_fp = ACCEPT_NO_FP_DEFAULT; static int verbose = VE_VERBOSE_DEFAULT; struct stat st; char *cp; int rc; if (verifying < 0) { verifying = ve_trust_init(); #ifdef VE_DEBUG_LEVEL ve_debug_set(VE_DEBUG_LEVEL); #endif /* initialize ve_status with default result */ rc = verifying ? VE_NOT_CHECKED : VE_NOT_VERIFYING; ve_status_set(0, rc); ve_status_state = VE_STATUS_NONE; if (verifying) ve_self_tests(); } if (!verifying) return (0); if (fd < 0 || fstat(fd, &st) < 0 || !S_ISREG(st.st_mode)) return (0); DEBUG_PRINTF(3, ("fd=%d,name='%s',off=%lld,dev=%lld,ino=%lld\n", fd, filename, (long long)off, (long long)st.st_dev, (long long)st.st_ino)); rc = is_verified(&st); if (rc != VE_NOT_CHECKED) { ve_status_set(fd, rc); return (rc); } rc = find_manifest(filename); if (rc != VE_FINGERPRINT_WRONG && loaded_manifests) { if (severity <= VE_GUESS) severity = severity_guess(filename); if ((rc = verify_fd(fd, filename, off, &st)) >= 0) { if (verbose || severity > VE_WANT) { #if defined(VE_DEBUG_LEVEL) && VE_DEBUG_LEVEL > 0 - printf("Verified %s %llu,%llu\n", filename, + printf("%serified %s %llu,%llu\n", + (rc == VE_FINGERPRINT_IGNORE) ? "Unv" : "V", + filename, (long long)st.st_dev, (long long)st.st_ino); #else - printf("Verified %s\n", filename); + printf("%serified %s\n", + (rc == VE_FINGERPRINT_IGNORE) ? "Unv" : "V", + filename); #endif } if (severity < VE_MUST) { /* not a kernel or module */ if ((cp = strrchr(filename, '/'))) { cp++; if (strncmp(cp, "loader.ve.", 10) == 0) { cp += 10; verify_tweak(cp, &accept_no_fp, &verbose, &verifying); } } } add_verify_status(&st, rc); ve_status_set(fd, rc); return (rc); } if (severity || verbose || rc == VE_FINGERPRINT_WRONG) printf("Unverified: %s\n", ve_error_get()); if (rc == VE_FINGERPRINT_UNKNOWN && severity < VE_MUST) rc = VE_UNVERIFIED_OK; else if (rc == VE_FINGERPRINT_NONE && severity < accept_no_fp) rc = VE_UNVERIFIED_OK; add_verify_status(&st, rc); } #ifdef LOADER_VERIEXEC_TESTING else if (rc != VE_FINGERPRINT_WRONG) { /* * We have not loaded any manifest and * not because of verication failure. * Most likely reason is we have none. * Allow boot to proceed if we are just testing. */ return (VE_UNVERIFIED_OK); } #endif if (rc == VE_FINGERPRINT_WRONG && severity > accept_no_fp) panic("cannot continue"); ve_status_set(fd, rc); return (rc); } /** * @brief get hex string for pcr value and export * * In case we are doing measured boot, provide * value of the "pcr" data we have accumulated. */ void verify_pcr_export(void) { #ifdef VE_PCR_SUPPORT char hexbuf[br_sha256_SIZE * 2 + 2]; unsigned char hbuf[br_sha256_SIZE]; char *hex; ssize_t hlen; hlen = ve_pcr_get(hbuf, sizeof(hbuf)); if (hlen > 0) { hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen); if (hex) { hex[hlen*2] = '\0'; /* clobber newline */ setenv("loader.ve.pcr", hex, 1); } } #endif }