sbin/veriexec will ignore entries that have no hash anyway, but loader needs to be explicitly told that such files are ok to ignore (not verify). We will report as Unverified depending on verbose level, but with no reason - because we are not rejecting the file. Reviewed by: imp, mindal_semihalf Sponsored by: Juniper Networks MFC After: 1 week Differential Revision: https://reviews.freebsd.org//D20018
462 lines
10 KiB
C
462 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)
|
|
*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, "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);
|
|
}
|