diff options
Diffstat (limited to 'lib/libsecureboot/veopen.c')
-rw-r--r-- | lib/libsecureboot/veopen.c | 469 |
1 files changed, 469 insertions, 0 deletions
diff --git a/lib/libsecureboot/veopen.c b/lib/libsecureboot/veopen.c new file mode 100644 index 000000000000..0a1fc59eb9b4 --- /dev/null +++ b/lib/libsecureboot/veopen.c @@ -0,0 +1,469 @@ +/*- + * 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 <sys/cdefs.h> +#include <sys/queue.h> + +#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 (nfip == NULL) { +#ifdef _STANDALONE + printf("%s: out of memory! %lu\n", __func__, + (unsigned long)sizeof(struct fingerprint_info)); +#endif + return; + } + 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 == nfip->fi_prefix) { + cp[1] = '\0'; + } else if (cp) { + *cp = '\0'; + } else { + free(nfip->fi_prefix); + free(nfip); + return; + } + } + /* collapse any trailing ..[/] */ + n = 0; + while ((cp = strrchr(nfip->fi_prefix, '/')) > nfip->fi_prefix) { + 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; + } + nfip->fi_dev = stp->st_dev; +#ifdef UNIT_TEST + nfip->fi_dev = 0; +#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); + if (fstat(fd, &st) == 0) + dev = st.st_dev; +#ifdef UNIT_TEST + dev = 0; +#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, "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); +} |