The vectx API, computes the hash for verifying a file as it is read. This avoids the overhead of reading files twice - once to verify, then again to load. For doing an install via loader, avoiding the need to rewind large files is critical. This API is only used for modules, kernel and mdimage as these are the biggest files read by the loader. The reduction in boot time depends on how expensive the I/O is on any given platform. On a fast VM we see 6% improvement. For install via loader the first file to be verified is likely to be the kernel, so some of the prep work (finding manifest etc) done by verify_file() needs to be factored so it can be reused for vectx_open(). For missing or unrecognized fingerprint entries, we fail in vectx_open() unless verifying is disabled. Otherwise fingerprint check happens in vectx_close() and since this API is only used for files which must be verified (VE_MUST) we panic if we get an incorrect hash. Reviewed by: imp,tsoome MFC after: 1 week Sponsored by: Juniper Networks Differential Revision: https://reviews.freebsd.org//D23827
357 lines
8.7 KiB
C
357 lines
8.7 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,name='%s',prep_rc=%d\n",
|
|
caller,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.
|
|
*
|
|
* @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
|
|
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 (severity > VE_WANT || rc == VE_FINGERPRINT_WRONG)
|
|
printf("%serified %s\n", (rc <= 0) ? "Unv" : "V",
|
|
ctx->vec_path);
|
|
#if !defined(UNIT_TEST) && !defined(DEBUG_VECTX)
|
|
/* we are generally called with VE_MUST */
|
|
if (severity > VE_WANT && rc == VE_FINGERPRINT_WRONG)
|
|
panic("cannot continue");
|
|
#endif
|
|
free(ctx);
|
|
return ((rc < 0) ? rc : 0);
|
|
}
|