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
541 lines
13 KiB
C
541 lines
13 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.
|
|
*/
|
|
/*
|
|
* Routines to verify files loaded.
|
|
*/
|
|
#include <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include <sys/param.h>
|
|
#include <string.h>
|
|
#include <sys/queue.h>
|
|
|
|
#include "libsecureboot.h"
|
|
#include <verify_file.h>
|
|
#include <manifests.h>
|
|
|
|
#ifdef UNIT_TEST
|
|
# include <err.h>
|
|
# 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 *Destdir;
|
|
extern size_t DestdirLen;
|
|
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;
|
|
|
|
if (stp->st_ino > 0) {
|
|
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) {
|
|
#ifdef UNIT_TEST
|
|
if (DestdirLen > 0 &&
|
|
strncmp(name, Destdir, DestdirLen) == 0) {
|
|
name += DestdirLen;
|
|
if (prefix &&
|
|
strncmp(prefix, Destdir, DestdirLen) == 0)
|
|
prefix += DestdirLen;
|
|
}
|
|
#endif
|
|
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 = VE_VERIFIED;
|
|
} 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 int Verifying = -1; /* 0 if not verifying */
|
|
|
|
static void
|
|
verify_tweak(int fd, off_t off, struct stat *stp,
|
|
char *tweak, int *accept_no_fp,
|
|
int *verbose)
|
|
{
|
|
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;
|
|
} else if (strncmp(tweak, "trust", 5) == 0) {
|
|
/* content is trust anchor to add or revoke */
|
|
unsigned char *ucp;
|
|
size_t num;
|
|
|
|
if (off > 0)
|
|
lseek(fd, 0, SEEK_SET);
|
|
ucp = read_fd(fd, stp->st_size);
|
|
if (ucp == NULL)
|
|
return;
|
|
if (strstr(tweak, "revoke")) {
|
|
num = ve_trust_anchors_revoke(ucp, stp->st_size);
|
|
DEBUG_PRINTF(3, ("revoked %d trust anchors\n",
|
|
(int) num));
|
|
} else {
|
|
num = ve_trust_anchors_add_buf(ucp, stp->st_size);
|
|
DEBUG_PRINTF(3, ("added %d trust anchors\n",
|
|
(int) num));
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef VE_DEBUG_LEVEL
|
|
# define VE_DEBUG_LEVEL 0
|
|
#endif
|
|
|
|
static int
|
|
getenv_int(const char *var, int def)
|
|
{
|
|
const char *cp;
|
|
char *ep;
|
|
long val;
|
|
|
|
val = def;
|
|
cp = getenv(var);
|
|
if (cp && *cp) {
|
|
val = strtol(cp, &ep, 0);
|
|
if ((ep && *ep) || val != (int)val) {
|
|
val = def;
|
|
}
|
|
}
|
|
return (int)val;
|
|
}
|
|
|
|
|
|
/**
|
|
* @brief prepare to verify an open file
|
|
*
|
|
* @param[in] fd
|
|
* open descriptor
|
|
*
|
|
* @param[in] filename
|
|
* path we opened and will use to lookup fingerprint
|
|
*
|
|
* @param[in] stp
|
|
* stat pointer so we can check file type
|
|
*/
|
|
int
|
|
verify_prep(int fd, const char *filename, off_t off, struct stat *stp,
|
|
const char *caller)
|
|
{
|
|
int rc;
|
|
|
|
if (Verifying < 0) {
|
|
Verifying = ve_trust_init();
|
|
#ifndef UNIT_TEST
|
|
ve_debug_set(getenv_int("VE_DEBUG_LEVEL", 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();
|
|
ve_anchor_verbose_set(1);
|
|
}
|
|
}
|
|
if (!Verifying || fd < 0)
|
|
return (0);
|
|
if (stp) {
|
|
if (fstat(fd, stp) < 0 || !S_ISREG(stp->st_mode))
|
|
return (0);
|
|
}
|
|
DEBUG_PRINTF(2,
|
|
("verify_prep: caller=%s,fd=%d,name='%s',off=%lld,dev=%lld,ino=%lld\n",
|
|
caller, fd, filename, (long long)off, (long long)stp->st_dev,
|
|
(long long)stp->st_ino));
|
|
rc = is_verified(stp);
|
|
DEBUG_PRINTF(4,("verify_prep: is_verified()->%d\n", rc));
|
|
if (rc == VE_NOT_CHECKED) {
|
|
rc = find_manifest(filename);
|
|
} else {
|
|
ve_status_set(fd, rc);
|
|
}
|
|
return (rc);
|
|
}
|
|
|
|
/**
|
|
* @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,
|
|
const char *caller)
|
|
{
|
|
static int once;
|
|
static int accept_no_fp = ACCEPT_NO_FP_DEFAULT;
|
|
static int verbose = VE_VERBOSE_DEFAULT;
|
|
struct stat st;
|
|
char *cp;
|
|
int rc;
|
|
|
|
rc = verify_prep(fd, filename, off, &st, caller);
|
|
|
|
if (!rc)
|
|
return (0);
|
|
|
|
if (!once) {
|
|
once++;
|
|
verbose = getenv_int("VE_VERBOSE", VE_VERBOSE_DEFAULT);
|
|
}
|
|
|
|
if (rc != VE_FINGERPRINT_WRONG && loaded_manifests) {
|
|
if (severity <= VE_GUESS)
|
|
severity = severity_guess(filename);
|
|
#ifdef VE_PCR_SUPPORT
|
|
/*
|
|
* Only update pcr with things that must verify
|
|
* these tend to be processed in a more deterministic
|
|
* order, which makes our pseudo pcr more useful.
|
|
*/
|
|
ve_pcr_updating_set((severity == VE_MUST));
|
|
#endif
|
|
#ifdef UNIT_TEST
|
|
if (DestdirLen > 0 &&
|
|
strncmp(filename, Destdir, DestdirLen) == 0) {
|
|
filename += DestdirLen;
|
|
}
|
|
#endif
|
|
if ((rc = verify_fd(fd, filename, off, &st)) >= 0) {
|
|
if (verbose || severity > VE_WANT) {
|
|
#if defined(VE_DEBUG_LEVEL) && VE_DEBUG_LEVEL > 0
|
|
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("%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(fd, off, &st, cp,
|
|
&accept_no_fp, &verbose);
|
|
}
|
|
}
|
|
}
|
|
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 *hinfo;
|
|
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);
|
|
DEBUG_PRINTF(1,
|
|
("%s: setenv(loader.ve.pcr, %s\n", __func__,
|
|
hex));
|
|
hinfo = ve_pcr_hashed_get(1);
|
|
if (hinfo) {
|
|
setenv("loader.ve.hashed", hinfo, 1);
|
|
DEBUG_PRINTF(1,
|
|
("%s: setenv(loader.ve.hashed, %s\n",
|
|
__func__, hinfo));
|
|
free(hinfo);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|