59b95f1bfb
We need a valid st_dev, st_ino and st_mtime to correctly track which files have been verified and to update our notion of time. ve_utc_set(): ignore utc if it would jump our current time by more than VE_UTC_MAX_JUMP (20 years). Allow testing of install command via userboot. Need to fix its stat implementation too. bhyveload also needs stat fixed - due to change to userboot.h Call ve_error_get() from vectx_close() when hash is wrong. Track the names of files we have hashed into pcr For the purposes of measured boot, it is important to be able to reproduce the hash reflected in loader.ve.pcr so loader.ve.hashed provides a list of names in the order they were added. Reviewed by: imp MFC after: 1 week Sponsored by: Juniper Networks Differential Revision: https://reviews.freebsd.org//D24027
464 lines
10 KiB
C
464 lines
10 KiB
C
/*-
|
|
* 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>
|
|
__FBSDID("$FreeBSD$");
|
|
#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 (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;
|
|
}
|
|
#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, "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);
|
|
}
|