freebsd-dev/lib/libsecureboot/verify_file.c
Simon J. Gerraty 53f151f906 Fix pkgfs stat so it satisfies libsecureboot
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
2020-03-25 19:12:19 +00:00

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
}