aboutsummaryrefslogtreecommitdiff
path: root/lib/libsecureboot/veopen.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/libsecureboot/veopen.c')
-rw-r--r--lib/libsecureboot/veopen.c469
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);
+}