#include <sys/param.h>
#include <string.h>
#include <sys/queue.h>
#include <sys/kenv.h>
#include "libsecureboot.h"
#include <verify_file.h>
#include <manifests.h>
#ifdef UNIT_TEST
# include <err.h>
# define panic warn
extern char *Destdir;
extern size_t DestdirLen;
extern char *Skip;
# undef MANIFEST_SKIP
# define MANIFEST_SKIP Skip
# undef VE_DEBUG_LEVEL
#endif
#ifndef SOPEN_MAX
#define SOPEN_MAX 64
#endif
static int ve_status[SOPEN_MAX+1];
static int ve_status_state;
struct verify_status;
static struct verify_status *verified_files = NULL;
static int loaded_manifests = 0;
enum {
VE_VERBOSE_SILENT,
VE_VERBOSE_UNVERIFIED,
VE_VERBOSE_MUST,
VE_VERBOSE_ALL,
VE_VERBOSE_DEBUG,
};
#ifndef VE_VERBOSE_DEFAULT
# define VE_VERBOSE_DEFAULT VE_VERBOSE_MUST
#endif
static int Verbose = VE_VERBOSE_DEFAULT;
#define VE_STATUS_NONE 1
#define VE_STATUS_VALID 2
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;
}
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]);
}
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;
int rc = VE_NOT_CHECKED;
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) {
rc = vsp->vs_status;
break;
}
}
}
DEBUG_PRINTF(4, ("%s: dev=%lld,ino=%llu,status=%d\n",
__func__, (long long)stp->st_dev,
(unsigned long long)stp->st_ino, rc));
return (rc);
}
void
add_verify_status(struct stat *stp, int status)
{
struct verify_status *vsp;
vsp = malloc(sizeof(struct verify_status));
if (vsp) {
vsp->vs_next = verified_files;
vsp->vs_dev = stp->st_dev;
vsp->vs_ino = stp->st_ino;
vsp->vs_status = status;
verified_files = vsp;
}
DEBUG_PRINTF(4, ("%s: dev=%lld,ino=%llu,status=%d\n",
__func__, (long long)stp->st_dev,
(unsigned long long)stp->st_ino, status));
}
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);
}
ve_utc_set(stp->st_mtime);
content = (char *)verify_signed(name, VerifyFlags);
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;
DEBUG_PRINTF(3, ("loaded: %s %s %s\n",
name, prefix, skip));
rc = VE_VERIFIED;
} else {
rc = VE_FINGERPRINT_WRONG;
add_verify_status(stp, rc);
}
}
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);
if (*tp[0] == '.') {
if (prefix[0] == '\0' || prefix[1] == '\0')
continue;
}
DEBUG_PRINTF(5, ("looking for %s\n", buf));
if (stat(buf, &st) == 0 && st.st_size > 0) {
#ifdef MANIFEST_SKIP_ALWAYS
skip = MANIFEST_SKIP_ALWAYS;
#else
#ifdef MANIFEST_SKIP
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
static int
severity_guess(const char *filename)
{
const char *cp;
if ((cp = strrchr(filename, '.'))) {
if (strcmp(cp, ".cookie") == 0 ||
strcmp(cp, ".hints") == 0 ||
strcmp(cp, ".order") == 0 ||
strcmp(cp, ".tgz") == 0)
return (VE_TRY);
if (strcmp(cp, ".4th") == 0 ||
strcmp(cp, ".lua") == 0 ||
strcmp(cp, ".rc") == 0)
return (VE_MUST);
}
return (VE_WANT);
}
static int Verifying = -1;
static void
verify_tweak(int fd, off_t off, struct stat *stp,
char *tweak, int *accept_no_fp)
{
if (strcmp(tweak, "off") == 0) {
Verifying = 0;
} else if (strcmp(tweak, "strict") == 0) {
*accept_no_fp = VE_WANT;
Verbose = VE_VERBOSE_ALL;
if (!ve_self_tests()) {
panic("verify self tests failed");
}
} else if (strcmp(tweak, "modules") == 0) {
*accept_no_fp = VE_MUST;
} else if (strcmp(tweak, "try") == 0) {
*accept_no_fp = VE_MUST + 1;
} else if (strcmp(tweak, "verbose") == 0) {
Verbose = VE_VERBOSE_ALL;
} else if (strcmp(tweak, "quiet") == 0) {
Verbose = VE_VERBOSE_UNVERIFIED;
VerifyFlags = 0;
} else if (strcmp(tweak, "silent") == 0) {
Verbose = VE_VERBOSE_SILENT;
VerifyFlags = 0;
} else if (strncmp(tweak, "trust", 5) == 0) {
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;
}
void
verify_report(const char *path, int severity, int status, struct stat *stp)
{
if (status < 0 || status == VE_FINGERPRINT_IGNORE) {
if (Verbose < VE_VERBOSE_ALL && severity < VE_WANT)
return;
if (Verbose >= VE_VERBOSE_UNVERIFIED || severity > VE_TRY ||
status <= VE_FINGERPRINT_WRONG) {
if (Verbose == VE_VERBOSE_DEBUG && stp != NULL)
printf("Unverified %s %llu,%llu\n",
ve_error_get(),
(long long)stp->st_dev,
(long long)stp->st_ino);
else
printf("Unverified %s\n", ve_error_get());
}
} else if (status > 0 && Verbose >= VE_VERBOSE_MUST) {
if (severity >= VE_MUST || Verbose >= VE_VERBOSE_ALL) {
if (Verbose == VE_VERBOSE_DEBUG && stp != NULL)
printf("Unverified %s %llu,%llu\n",
path,
(long long)stp->st_dev,
(long long)stp->st_ino);
else
printf("Verified %s\n", path);
}
}
}
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();
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=%llu\n",
caller, fd, filename, (long long)off, (long long)stp->st_dev,
(unsigned long long)stp->st_ino));
rc = is_verified(stp);
if (rc == VE_NOT_CHECKED) {
rc = find_manifest(filename);
if (rc == VE_VERIFIED)
rc = VE_NOT_CHECKED;
} else {
ve_status_set(fd, rc);
}
return (rc);
}
int
verify_file(int fd, const char *filename, off_t off, int severity,
const char *caller)
{
static int check_verbose = 1;
static int accept_no_fp = ACCEPT_NO_FP_DEFAULT;
struct stat st;
char *cp;
int rc;
if (check_verbose) {
check_verbose = 0;
Verbose = getenv_int("VE_VERBOSE", VE_VERBOSE_DEFAULT);
VerifyFlags = getenv_int("VE_VERIFY_FLAGS",
Verbose ? VEF_VERBOSE : 0);
#ifndef UNIT_TEST
ve_debug_set(getenv_int("VE_DEBUG_LEVEL", VE_DEBUG_LEVEL));
#endif
}
rc = verify_prep(fd, filename, off, &st, caller);
if (!rc)
return (0);
if (rc != VE_FINGERPRINT_WRONG && loaded_manifests) {
if (rc != VE_NOT_CHECKED && rc != VE_FINGERPRINT_NONE)
return (rc);
if (severity <= VE_GUESS)
severity = severity_guess(filename);
#ifdef VE_PCR_SUPPORT
ve_pcr_updating_set((severity == VE_MUST));
#endif
#ifdef UNIT_TEST
if (DestdirLen > 0 &&
strncmp(filename, Destdir, DestdirLen) == 0) {
filename += DestdirLen;
}
#endif
rc = verify_fd(fd, filename, off, &st);
verify_report(filename, severity, rc, &st);
if (rc >= 0) {
if (severity < VE_MUST) {
if ((cp = strrchr(filename, '/'))) {
cp++;
if (strncmp(cp, "loader.ve.", 10) == 0) {
cp += 10;
verify_tweak(fd, off, &st, cp,
&accept_no_fp);
}
}
}
add_verify_status(&st, rc);
ve_status_set(fd, rc);
return (rc);
}
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);
if (rc == VE_UNVERIFIED_OK) {
check_verbose = 1;
}
}
#ifdef LOADER_VERIEXEC_TESTING
else if (rc != VE_FINGERPRINT_WRONG) {
return (VE_UNVERIFIED_OK);
}
#endif
if (rc == VE_FINGERPRINT_WRONG && severity > accept_no_fp)
panic("cannot continue");
ve_status_set(fd, rc);
return (rc);
}
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';
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));
if ((hlen = strlen(hinfo)) > KENV_MVALLEN) {
char mvallen[16];
hlen += KENV_MVALLEN -
(hlen % KENV_MVALLEN);
if (snprintf(mvallen, sizeof(mvallen),
"%d", (int) hlen) < (int)sizeof(mvallen))
setenv("kenv_mvallen", mvallen, 1);
}
free(hinfo);
}
}
}
#endif
}
int
hash_string(char *s, size_t n, char *buf, size_t bufsz)
{
br_hash_compat_context mctx;
const br_hash_class *md;
switch (bufsz) {
case br_sha1_SIZE:
md = &br_sha1_vtable;
break;
case br_sha256_SIZE:
md = &br_sha256_vtable;
break;
default:
if (bufsz < br_sha1_SIZE)
return -1;
md = &br_sha1_vtable;
bufsz = br_sha1_SIZE;
break;
}
if (n == 0)
n = strlen(s);
md->init(&mctx.vtable);
md->update(&mctx.vtable, s, n);
md->out(&mctx.vtable, buf);
return bufsz;
}