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
365 lines
8.9 KiB
C
365 lines
8.9 KiB
C
/*-
|
|
* Copyright (c) 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$");
|
|
|
|
#ifndef _STANDALONE
|
|
/* Avoid unwanted userlandish components */
|
|
#define _KERNEL
|
|
#include <sys/errno.h>
|
|
#undef _KERNEL
|
|
#endif
|
|
|
|
#include "libsecureboot-priv.h"
|
|
#include <verify_file.h>
|
|
|
|
/**
|
|
* @file vectx.c
|
|
* @brief api to verify file while reading
|
|
*
|
|
* This API allows the hash of a file to be computed as it is read.
|
|
* Key to this is seeking by reading.
|
|
*
|
|
* On close an indication of the verification result is returned.
|
|
*/
|
|
|
|
struct vectx {
|
|
br_hash_compat_context vec_ctx; /* hash ctx */
|
|
const br_hash_class *vec_md; /* hash method */
|
|
const char *vec_path; /* path we are verifying */
|
|
const char *vec_want; /* hash value we want */
|
|
off_t vec_off; /* current offset */
|
|
off_t vec_hashed; /* where we have hashed to */
|
|
size_t vec_size; /* size of path */
|
|
size_t vec_hashsz; /* size of hash */
|
|
int vec_fd; /* file descriptor */
|
|
int vec_status; /* verification status */
|
|
};
|
|
|
|
|
|
/**
|
|
* @brief
|
|
* verify an open file as we read it
|
|
*
|
|
* If the file has no fingerprint to match, we will still return a
|
|
* verification context containing little more than the file
|
|
* descriptor, and an error code in @c error.
|
|
*
|
|
* @param[in] fd
|
|
* open descriptor
|
|
*
|
|
* @param[in] path
|
|
* pathname to open
|
|
*
|
|
* @param[in] off
|
|
* current offset
|
|
*
|
|
* @param[in] stp
|
|
* pointer to struct stat
|
|
*
|
|
* @param[out] error
|
|
* @li 0 all is good
|
|
* @li ENOMEM out of memory
|
|
* @li VE_FINGERPRINT_NONE no entry found
|
|
* @li VE_FINGERPRINT_UNKNOWN no fingerprint in entry
|
|
*
|
|
* @return ctx or NULL on error.
|
|
* NULL is only returned for non-files or out-of-memory.
|
|
*/
|
|
struct vectx *
|
|
vectx_open(int fd, const char *path, off_t off, struct stat *stp,
|
|
int *error, const char *caller)
|
|
{
|
|
struct vectx *ctx;
|
|
struct stat st;
|
|
size_t hashsz;
|
|
char *cp;
|
|
int rc;
|
|
|
|
if (!stp)
|
|
stp = &st;
|
|
|
|
rc = verify_prep(fd, path, off, stp, __func__);
|
|
|
|
DEBUG_PRINTF(2,
|
|
("vectx_open: caller=%s,fd=%d,name='%s',prep_rc=%d\n",
|
|
caller, fd, path, rc));
|
|
|
|
switch (rc) {
|
|
case VE_FINGERPRINT_NONE:
|
|
case VE_FINGERPRINT_UNKNOWN:
|
|
case VE_FINGERPRINT_WRONG:
|
|
*error = rc;
|
|
return (NULL);
|
|
}
|
|
ctx = malloc(sizeof(struct vectx));
|
|
if (!ctx)
|
|
goto enomem;
|
|
ctx->vec_fd = fd;
|
|
ctx->vec_path = path;
|
|
ctx->vec_size = stp->st_size;
|
|
ctx->vec_off = 0;
|
|
ctx->vec_hashed = 0;
|
|
ctx->vec_want = NULL;
|
|
ctx->vec_status = 0;
|
|
ctx->vec_hashsz = hashsz = 0;
|
|
|
|
if (rc == 0) {
|
|
/* we are not verifying this */
|
|
*error = 0;
|
|
return (ctx);
|
|
}
|
|
cp = fingerprint_info_lookup(fd, path);
|
|
if (!cp) {
|
|
ctx->vec_status = VE_FINGERPRINT_NONE;
|
|
ve_error_set("%s: no entry", path);
|
|
} else {
|
|
if (strncmp(cp, "no_hash", 7) == 0) {
|
|
ctx->vec_status = VE_FINGERPRINT_IGNORE;
|
|
hashsz = 0;
|
|
} else if (strncmp(cp, "sha256=", 7) == 0) {
|
|
ctx->vec_md = &br_sha256_vtable;
|
|
hashsz = br_sha256_SIZE;
|
|
cp += 7;
|
|
#ifdef VE_SHA1_SUPPORT
|
|
} else if (strncmp(cp, "sha1=", 5) == 0) {
|
|
ctx->vec_md = &br_sha1_vtable;
|
|
hashsz = br_sha1_SIZE;
|
|
cp += 5;
|
|
#endif
|
|
#ifdef VE_SHA384_SUPPORT
|
|
} else if (strncmp(cp, "sha384=", 7) == 0) {
|
|
ctx->vec_md = &br_sha384_vtable;
|
|
hashsz = br_sha384_SIZE;
|
|
cp += 7;
|
|
#endif
|
|
#ifdef VE_SHA512_SUPPORT
|
|
} else if (strncmp(cp, "sha512=", 7) == 0) {
|
|
ctx->vec_md = &br_sha512_vtable;
|
|
hashsz = br_sha512_SIZE;
|
|
cp += 7;
|
|
#endif
|
|
} else {
|
|
ctx->vec_status = VE_FINGERPRINT_UNKNOWN;
|
|
ve_error_set("%s: no supported fingerprint", path);
|
|
}
|
|
}
|
|
*error = ctx->vec_status;
|
|
ctx->vec_hashsz = hashsz;
|
|
ctx->vec_want = cp;
|
|
if (hashsz > 0) {
|
|
ctx->vec_md->init(&ctx->vec_ctx.vtable);
|
|
|
|
if (off > 0) {
|
|
lseek(fd, 0, SEEK_SET);
|
|
vectx_lseek(ctx, off, SEEK_SET);
|
|
}
|
|
}
|
|
DEBUG_PRINTF(2,
|
|
("vectx_open: caller=%s,name='%s',hashsz=%lu,status=%d\n",
|
|
caller, path, (unsigned long)ctx->vec_hashsz,
|
|
ctx->vec_status));
|
|
return (ctx);
|
|
|
|
enomem: /* unlikely */
|
|
*error = ENOMEM;
|
|
free(ctx);
|
|
return (NULL);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* read bytes from file and update hash
|
|
*
|
|
* It is critical that all file I/O comes through here.
|
|
* We keep track of current offset.
|
|
* We also track what offset we have hashed to,
|
|
* so we won't replay data if we seek backwards.
|
|
*
|
|
* @param[in] pctx
|
|
* pointer to ctx
|
|
*
|
|
* @param[in] buf
|
|
*
|
|
* @param[in] nbytes
|
|
*
|
|
* @return bytes read or error.
|
|
*/
|
|
ssize_t
|
|
vectx_read(struct vectx *ctx, void *buf, size_t nbytes)
|
|
{
|
|
unsigned char *bp = buf;
|
|
int n;
|
|
int delta;
|
|
int x;
|
|
size_t off;
|
|
|
|
if (ctx->vec_hashsz == 0) /* nothing to do */
|
|
return (read(ctx->vec_fd, buf, nbytes));
|
|
|
|
off = 0;
|
|
do {
|
|
n = read(ctx->vec_fd, &bp[off], nbytes - off);
|
|
if (n < 0)
|
|
return (n);
|
|
if (n > 0) {
|
|
/* we may have seeked backwards! */
|
|
delta = ctx->vec_hashed - ctx->vec_off;
|
|
if (delta > 0) {
|
|
x = MIN(delta, n);
|
|
off += x;
|
|
n -= x;
|
|
ctx->vec_off += x;
|
|
}
|
|
if (n > 0) {
|
|
ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], n);
|
|
off += n;
|
|
ctx->vec_off += n;
|
|
ctx->vec_hashed += n;
|
|
}
|
|
}
|
|
} while (n > 0 && off < nbytes);
|
|
return (off);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* vectx equivalent of lseek
|
|
*
|
|
* When seeking forwards we actually call vectx_read
|
|
* to reach the desired offset.
|
|
*
|
|
* We support seeking backwards.
|
|
*
|
|
* @param[in] pctx
|
|
* pointer to ctx
|
|
*
|
|
* @param[in] off
|
|
* desired offset
|
|
*
|
|
* @param[in] whence
|
|
* We try to convert whence to ``SEEK_SET``.
|
|
* We do not support ``SEEK_DATA`` or ``SEEK_HOLE``.
|
|
*
|
|
* @return offset or error.
|
|
*/
|
|
off_t
|
|
vectx_lseek(struct vectx *ctx, off_t off, int whence)
|
|
{
|
|
unsigned char buf[PAGE_SIZE];
|
|
size_t delta;
|
|
ssize_t n;
|
|
|
|
if (ctx->vec_hashsz == 0) /* nothing to do */
|
|
return (lseek(ctx->vec_fd, off, whence));
|
|
|
|
/*
|
|
* Convert whence to SEEK_SET
|
|
*/
|
|
if (whence == SEEK_END && off <= 0) {
|
|
whence = SEEK_SET;
|
|
off += ctx->vec_size;
|
|
} else if (whence == SEEK_CUR) {
|
|
whence = SEEK_SET;
|
|
off += ctx->vec_off;
|
|
}
|
|
if (whence != SEEK_SET ||
|
|
(size_t)off > ctx->vec_size) {
|
|
printf("ERROR: %s: unsupported operation: whence=%d off=%lld -> %lld\n",
|
|
__func__, whence, (long long)ctx->vec_off, (long long)off);
|
|
return (-1);
|
|
}
|
|
if (off < ctx->vec_hashed) {
|
|
/* seeking backwards! just do it */
|
|
ctx->vec_off = lseek(ctx->vec_fd, off, whence);
|
|
return (ctx->vec_off);
|
|
}
|
|
n = 0;
|
|
do {
|
|
delta = off - ctx->vec_off;
|
|
if (delta > 0) {
|
|
delta = MIN(PAGE_SIZE, delta);
|
|
n = vectx_read(ctx, buf, delta);
|
|
if (n < 0)
|
|
return (n);
|
|
}
|
|
} while (ctx->vec_off < off && n > 0);
|
|
return (ctx->vec_off);
|
|
}
|
|
|
|
/**
|
|
* @brief
|
|
* check that hashes match and cleanup
|
|
*
|
|
* We have finished reading file, compare the hash with what
|
|
* we wanted.
|
|
*
|
|
* Be sure to call this before closing the file, since we may
|
|
* need to seek to the end to ensure hashing is complete.
|
|
*
|
|
* @param[in] pctx
|
|
* pointer to ctx
|
|
*
|
|
* @return 0 or an error.
|
|
*/
|
|
int
|
|
vectx_close(struct vectx *ctx, int severity, const char *caller)
|
|
{
|
|
int rc;
|
|
|
|
if (ctx->vec_hashsz == 0) {
|
|
rc = ctx->vec_status;
|
|
} else {
|
|
#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
|
|
/* make sure we have hashed it all */
|
|
vectx_lseek(ctx, 0, SEEK_END);
|
|
rc = ve_check_hash(&ctx->vec_ctx, ctx->vec_md,
|
|
ctx->vec_path, ctx->vec_want, ctx->vec_hashsz);
|
|
}
|
|
DEBUG_PRINTF(2,
|
|
("vectx_close: caller=%s,name='%s',rc=%d,severity=%d\n",
|
|
caller,ctx->vec_path, rc, severity));
|
|
if (rc == VE_FINGERPRINT_WRONG) {
|
|
printf("Unverified: %s\n", ve_error_get());
|
|
#if !defined(UNIT_TEST) && !defined(DEBUG_VECTX)
|
|
/* we are generally called with VE_MUST */
|
|
if (severity > VE_WANT)
|
|
panic("cannot continue");
|
|
#endif
|
|
} else if (severity > VE_WANT) {
|
|
printf("%serified %s\n", (rc <= 0) ? "Unv" : "V",
|
|
ctx->vec_path);
|
|
}
|
|
free(ctx);
|
|
return ((rc < 0) ? rc : 0);
|
|
}
|