Page MenuHomeFreeBSD

D19281.id54181.diff
No OneTemporary

D19281.id54181.diff

Index: lib/libsecureboot/h/verify_file.h
===================================================================
--- lib/libsecureboot/h/verify_file.h
+++ lib/libsecureboot/h/verify_file.h
@@ -40,6 +40,8 @@
int ve_status_get(int);
void ve_efi_init(void);
int load_manifest(const char *, const char *, const char *, struct stat *);
+int pass_manifest(const char *, const char *);
+int pass_manifest_export_envs(void);
int verify_file(int, const char *, off_t, int);
void verify_pcr_export(void);
Index: lib/libsecureboot/verify_file.c
===================================================================
--- lib/libsecureboot/verify_file.c
+++ lib/libsecureboot/verify_file.c
@@ -60,6 +60,13 @@
struct verify_status;
struct verify_status *verified_files = NULL;
static int loaded_manifests = 0; /* have we loaded anything? */
+/*
+ * Values to pass to kernel by envs.
+ */
+static char manifest_path[MAXPATHLEN];
+static char manifest_prefix[MAXPATHLEN];
+static char manifest_hash[2 * br_sha256_SIZE + 2];
+static int manifest_present = 0;
#define VE_STATUS_NONE 1
#define VE_STATUS_VALID 2
@@ -139,6 +146,112 @@
verified_files = vsp;
}
+/*
+ * Verify and pass manifest path and digest to kernel through envs.
+ * The paths in manifest can be either absolute,
+ * or "prefix", if exists will be added to the ones that are not.
+ */
+int
+pass_manifest(const char *path, const char *prefix)
+{
+ char *content;
+ struct stat st;
+ unsigned char digest[br_sha256_SIZE];
+ const br_hash_class *md;
+ br_hash_compat_context ctx;
+ int rc;
+
+ content = NULL;
+ md = &br_sha256_vtable;
+
+ if (strnlen(path, MAXPATHLEN) == MAXPATHLEN ||
+ strnlen(prefix, MAXPATHLEN) == MAXPATHLEN)
+ return (EINVAL);
+
+ rc = stat(path, &st);
+ if (rc != 0)
+ goto out;
+
+ if (!S_ISREG(st.st_mode)) {
+ rc = EINVAL;
+ goto out;
+ }
+
+ rc = is_verified(&st);
+
+ if (rc != VE_NOT_CHECKED && rc != VE_VERIFIED) {
+ rc = EPERM;
+ goto out;
+ }
+
+ if (rc == VE_VERIFIED)
+ content = read_file(path, NULL);
+ else
+ content = (char *)verify_signed(path, VEF_VERBOSE);
+
+ if (content == NULL) {
+ rc = EIO;
+ goto out;
+ }
+
+ md->init(&ctx.vtable);
+ md->update(&ctx.vtable, content, st.st_size);
+ md->out(&ctx.vtable, digest);
+
+ if (prefix == NULL)
+ manifest_prefix[0] = '\0';
+ else
+ strcpy(manifest_prefix, prefix);
+
+ strcpy(manifest_path, path);
+
+ hexdigest(manifest_hash, 2 * br_sha256_SIZE + 2,
+ digest, br_sha256_SIZE);
+ manifest_hash[2*br_sha256_SIZE] = '\0';
+
+ manifest_present = 1;
+ rc = 0;
+
+out:
+ if (content != NULL)
+ free(content);
+
+ return (rc);
+}
+
+/*
+ * Set appropriate envs to inform kernel about manifest location and digest.
+ * This should be called right before boot so that envs can't be replaced.
+ */
+int
+pass_manifest_export_envs()
+{
+ int rc;
+
+ /* If we have nothing to pass make sure that envs are empty. */
+ if (!manifest_present) {
+ unsetenv("veriexec.manifest_path");
+ unsetenv("veriexec.manifest_hash");
+ unsetenv("veriexec.manifest_prefix");
+ return (0);
+ }
+
+ rc = setenv("veriexec.manifest_path", manifest_path, 1);
+ if (rc != 0)
+ return (rc);
+
+ rc = setenv("veriexec.manifest_hash", manifest_hash, 1);
+ if (rc != 0) {
+ unsetenv("veriexec.manifest_path");
+ return (rc);
+ }
+
+ if (manifest_prefix[0] != '\0')
+ rc = setenv("veriexec.manifest_prefix", manifest_prefix, 1);
+
+ return (rc);
+}
+
/**
* @brief
* load specified manifest if verified
Index: stand/common/boot.c
===================================================================
--- stand/common/boot.c
+++ stand/common/boot.c
@@ -108,6 +108,7 @@
#ifdef LOADER_VERIEXEC
verify_pcr_export(); /* for measured boot */
+ pass_manifest_export_envs();
#endif
/* Call the exec handler from the loader matching the kernel */
Index: stand/common/module.c
===================================================================
--- stand/common/module.c
+++ stand/common/module.c
@@ -151,6 +151,9 @@
}
#ifdef LOADER_VERIEXEC
+ if (strncmp(typestr, "pass_manifest", 13) == 0) {
+ return (pass_manifest(argv[1], prefix));
+ }
if (strncmp(typestr, "manifest", 8) == 0) {
return (load_manifest(argv[1], prefix, skip, NULL));
}
Index: sys/conf/files
===================================================================
--- sys/conf/files
+++ sys/conf/files
@@ -4876,6 +4876,7 @@
security/mac_veriexec/mac_veriexec.c optional mac_veriexec
security/mac_veriexec/veriexec_fingerprint.c optional mac_veriexec
security/mac_veriexec/veriexec_metadata.c optional mac_veriexec
+security/mac_veriexec/veriexec_manifest_env.c optional mac_veriexec
security/mac_veriexec/mac_veriexec_rmd160.c optional mac_veriexec_rmd160
security/mac_veriexec/mac_veriexec_sha1.c optional mac_veriexec_sha1
security/mac_veriexec/mac_veriexec_sha256.c optional mac_veriexec_sha256
Index: sys/modules/mac_veriexec/Makefile
===================================================================
--- sys/modules/mac_veriexec/Makefile
+++ sys/modules/mac_veriexec/Makefile
@@ -15,7 +15,8 @@
SRCS += \
mac_veriexec.c \
veriexec_fingerprint.c \
- veriexec_metadata.c
+ veriexec_metadata.c \
+ veriexec_manifest_env.c
EXPORT_SYMS+= ve_mutex \
mac_veriexec_in_state \
Index: sys/security/mac_veriexec/veriexec_manifest_env.c
===================================================================
--- /dev/null
+++ sys/security/mac_veriexec/veriexec_manifest_env.c
@@ -0,0 +1,439 @@
+/*-
+ * Copyright (c) 2019 Stormshield.
+ * Copyright (c) 2019 Semihalf.
+ *
+ * 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 AUTHOR ``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 AUTHOR 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$");
+
+#include <sys/param.h>
+#include <sys/ctype.h>
+#include <sys/eventhandler.h>
+#include <sys/fcntl.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/namei.h>
+#include <sys/proc.h>
+#include <sys/systm.h>
+#include <sys/vnode.h>
+
+#include <crypto/sha2/sha256.h>
+#include <crypto/sha2/sha384.h>
+#include <crypto/sha2/sha512.h>
+
+#include "mac_veriexec.h"
+#include "mac_veriexec_internal.h"
+
+/* The following is based on sbin/veriexec */
+struct fingerprint_type {
+ const char *fp_type;
+ int fp_size;
+};
+
+static const struct fingerprint_type fp_table[] = {
+ {"sha256=", SHA256_DIGEST_LENGTH},
+#if MAXFINGERPRINTLEN >= SHA384_DIGEST_LENGTH
+ {"sha384=", SHA384_DIGEST_LENGTH},
+#endif
+#if MAXFINGERPRINTLEN >= SHA512_DIGEST_LENGTH
+ {"sha512=", SHA512_DIGEST_LENGTH},
+#endif
+ {NULL, 0}
+};
+
+extern struct mtx ve_mutex;
+
+static unsigned char hexchar_to_byte(unsigned char c);
+static int hexstring_to_bin(unsigned char *buf);
+
+static int get_fp(const char *entry, char **type, unsigned char **digest);
+static int verify_digest(const char *data, size_t len,
+ const unsigned char *expected_hash);
+
+static int open_file(const char *path, struct nameidata *nid);
+static char *read_manifest(char *path, unsigned char *digest);
+static int parse_entry(char *entry, char *prefix);
+static int parse_manifest(char *path, unsigned char *hash, char *prefix);
+
+static unsigned char
+hexchar_to_byte(unsigned char c)
+{
+
+ if (isdigit(c))
+ return (c - '0');
+
+ return (isupper(c) ? c - 'A' + 10 : c - 'a' + 10);
+}
+
+static int
+hexstring_to_bin(unsigned char *buf)
+{
+ size_t i, len;
+ unsigned char byte;
+
+ len = strlen(buf);
+ for (i = 0; i < len / 2; i++) {
+ if (!isxdigit(buf[2 * i]) || !isxdigit(buf[2 * i + 1]))
+ return (EINVAL);
+
+ byte = hexchar_to_byte(buf[2 * i]) << 4;
+ byte += hexchar_to_byte(buf[2 * i + 1]);
+ buf[i] = byte;
+ }
+ return (0);
+}
+
+/*
+ * Parse a single line of manifest looking for a digest and its type.
+ * We expect it to be in form of "path shaX=hash".
+ * The line will be split into path, hash type and hash value.
+ */
+static int
+get_fp(const char *entry, char **type, unsigned char **digest)
+{
+ char *delimiter;
+ char *fp_type;
+ char *prev_fp_type;
+ size_t min_len;
+ int i;
+
+ delimiter = NULL;
+ fp_type = NULL;
+ prev_fp_type = NULL;
+
+ for (i = 0; fp_table[i].fp_type; i++) {
+ fp_type = strstr(entry, fp_table[i].fp_type);
+ /* Look for the last "shaX=hash" in line */
+ while (fp_type != NULL) {
+ prev_fp_type = fp_type;
+ fp_type++;
+ fp_type = strstr(fp_type, fp_table[i].fp_type);
+ }
+ fp_type = prev_fp_type;
+ if (fp_type != NULL) {
+ if (fp_type == entry || fp_type[-1] != ' ')
+ return (EINVAL);
+
+ /*
+ * The entry should contain at least
+ * fp_type and digest in hexadecimal form.
+ */
+ min_len = strlen(fp_table[i].fp_type) +
+ 2 * fp_table[i].fp_size;
+
+ if (strnlen(fp_type, min_len) < min_len)
+ return (EINVAL);
+
+ delimiter = &fp_type[strlen(fp_table[i].fp_type)];
+
+ /*
+ * Make sure that digest is followed by
+ * some kind of delimiter.
+ */
+ if (delimiter[2 * fp_table[i].fp_size] != '\n' &&
+ delimiter[2 * fp_table[i].fp_size] != '\0' &&
+ delimiter[2 * fp_table[i].fp_size] != ' ')
+ return (EINVAL);
+
+ /*
+ * Split entry into three parts:
+ * path, fp_type and digest.
+ */
+ delimiter[-1] = '\0';
+ delimiter[2 * fp_table[i].fp_size] = '\0';
+ fp_type[-1] = '\0';
+ break;
+ }
+ }
+
+ if (fp_type == NULL)
+ return (EINVAL);
+
+ if (type != NULL)
+ *type = fp_type;
+
+ if (digest != NULL)
+ *digest = delimiter;
+
+ return (0);
+}
+
+/*
+ * Currently we verify manifest using sha256.
+ * In future another env with hash type could be introduced.
+ */
+static int
+verify_digest(const char *data, size_t len, const unsigned char *expected_hash)
+{
+ SHA256_CTX ctx;
+ unsigned char hash[SHA256_DIGEST_LENGTH];
+
+ SHA256_Init(&ctx);
+ SHA256_Update(&ctx, data, len);
+ SHA256_Final(hash, &ctx);
+
+ return (memcmp(expected_hash, hash, SHA256_DIGEST_LENGTH));
+}
+
+
+static int
+open_file(const char *path, struct nameidata *nid)
+{
+ int flags, rc;
+
+ flags = FREAD;
+
+ pwd_ensure_dirs();
+
+ NDINIT(nid, LOOKUP, 0, UIO_SYSSPACE, path, curthread);
+ rc = vn_open(nid, &flags, 0, NULL);
+ NDFREE(nid, NDF_ONLY_PNBUF);
+ if (rc != 0)
+ return (rc);
+
+ return (0);
+}
+
+/*
+ * Read the manifest from location specified in path and verify its digest.
+ */
+static char*
+read_manifest(char *path, unsigned char *digest)
+{
+ struct nameidata nid;
+ struct vattr va;
+ char *data;
+ ssize_t bytes_read, resid;
+ int rc;
+
+ data = NULL;
+ bytes_read = 0;
+
+ rc = open_file(path, &nid);
+ if (rc != 0)
+ goto fail;
+
+ rc = VOP_GETATTR(nid.ni_vp, &va, curthread->td_ucred);
+ if (rc != 0)
+ goto fail;
+
+ data = (char *)malloc(va.va_size + 1, M_VERIEXEC, M_WAITOK);
+
+ while (bytes_read < va.va_size) {
+ rc = vn_rdwr(
+ UIO_READ, nid.ni_vp, data,
+ va.va_size - bytes_read, bytes_read,
+ UIO_SYSSPACE, IO_NODELOCKED,
+ curthread->td_ucred, NOCRED, &resid, curthread);
+ if (rc != 0)
+ goto fail;
+
+ bytes_read = va.va_size - resid;
+ }
+
+ data[bytes_read] = '\0';
+
+ VOP_UNLOCK(nid.ni_vp, 0);
+ (void)vn_close(nid.ni_vp, FREAD, curthread->td_ucred, curthread);
+
+ /*
+ * If digest is wrong someone might be trying to fool us.
+ */
+ if (verify_digest(data, va.va_size, digest))
+ panic("Manifest hash doesn't match expected value!");
+
+ return (data);
+
+fail:
+ if (data != NULL)
+ free(data, M_VERIEXEC);
+
+ return (NULL);
+}
+
+/*
+ * Process single line.
+ * First split it into path, digest_type and digest.
+ * Then try to open the file and insert its fingerprint into metadata store.
+ */
+static int
+parse_entry(char *entry, char *prefix)
+{
+ struct nameidata nid;
+ struct vattr va;
+ char path[MAXPATHLEN];
+ char *fp_type;
+ unsigned char *digest;
+ int rc, is_exec;
+
+ fp_type = NULL;
+ digest = NULL;
+
+ rc = get_fp(entry, &fp_type, &digest);
+ if (rc != 0)
+ return (rc);
+
+ rc = hexstring_to_bin(digest);
+ if (rc != 0)
+ return (rc);
+
+ if (strnlen(entry, MAXPATHLEN) == MAXPATHLEN)
+ return (EINVAL);
+
+ /* If the path is not absolute prepend it with a prefix */
+ if (prefix != NULL && entry[0] != '/') {
+ rc = snprintf(path, MAXPATHLEN, "%s/%s",
+ prefix, entry);
+ if (rc < 0)
+ return (-rc);
+ } else {
+ strcpy(path, entry);
+ }
+
+ NDINIT(&nid, LOOKUP, FOLLOW, UIO_SYSSPACE, path, curthread);
+
+ rc = open_file(path, &nid);
+ NDFREE(&nid, NDF_ONLY_PNBUF);
+ if (rc != 0)
+ return (rc);
+
+ rc = VOP_GETATTR(nid.ni_vp, &va, curthread->td_ucred);
+ if (rc != 0)
+ goto out;
+
+ is_exec = (va.va_mode & VEXEC);
+
+ mtx_lock(&ve_mutex);
+ rc = mac_veriexec_metadata_add_file(
+ is_exec == 0,
+ va.va_fsid, va.va_fileid, va.va_gen,
+ digest, 0, fp_type, 1);
+ mtx_unlock(&ve_mutex);
+
+out:
+ VOP_UNLOCK(nid.ni_vp, 0);
+ vn_close(nid.ni_vp, FREAD, curthread->td_ucred, curthread);
+ return (rc);
+}
+
+/*
+ * Look for manifest in env that have beed passed by loader.
+ * This routine should be called right after the rootfs is mounted.
+ */
+static int
+parse_manifest(char *path, unsigned char *hash, char *prefix)
+{
+ char *data;
+ char *entry;
+ char *next_entry;
+ int rc, success_count;
+
+ data = NULL;
+ success_count = 0;
+ rc = 0;
+
+ data = read_manifest(path, hash);
+ if (data == NULL) {
+ rc = EIO;
+ goto out;
+ }
+
+ entry = data;
+ while (entry != NULL) {
+ next_entry = strchr(entry, '\n');
+ if (next_entry != NULL) {
+ *next_entry = '\0';
+ next_entry++;
+ }
+ if (entry[0] == '\n' || entry[0] == '\0') {
+ entry = next_entry;
+ continue;
+ }
+ if ((rc = parse_entry(entry, prefix)))
+ printf("Warning: Failed to parse entry"
+ " with rc:%d, entry:\"%s\"\n", rc, entry);
+ else
+ success_count++;
+
+ entry = next_entry;
+ }
+ rc = 0;
+
+out:
+ if (data != NULL)
+ free(data, M_VERIEXEC);
+
+ if (success_count == 0)
+ rc = EINVAL;
+
+ return (rc);
+}
+
+static void
+parse_manifest_event(void *dummy)
+{
+ char *manifest_path;
+ char *manifest_prefix;
+ unsigned char *manifest_hash;
+ int rc;
+
+ /* If the envs are not set fail silently */
+ manifest_path = kern_getenv("veriexec.manifest_path");
+ if (manifest_path == NULL)
+ return;
+
+ manifest_hash = kern_getenv("veriexec.manifest_hash");
+ if (manifest_hash == NULL) {
+ freeenv(manifest_path);
+ return;
+ }
+
+ manifest_prefix = kern_getenv("veriexec.manifest_prefix");
+
+ if (strlen(manifest_hash) != 2 * SHA256_DIGEST_LENGTH)
+ panic("veriexec.manifest_hash has incorrect size");
+
+ rc = hexstring_to_bin(manifest_hash);
+ if (rc != 0)
+ panic("mac_veriexec: veriexec.loader.manifest_hash"
+ " doesn't contain a hash in hexadecimal form");
+
+ rc = parse_manifest(manifest_path, manifest_hash, manifest_prefix);
+ if (rc != 0)
+ panic("mac_veriexec: Failed to parse manifest err=%d", rc);
+
+ mtx_lock(&ve_mutex);
+ mac_veriexec_set_state(
+ VERIEXEC_STATE_LOADED | VERIEXEC_STATE_ACTIVE |
+ VERIEXEC_STATE_ENFORCE | VERIEXEC_STATE_LOCKED);
+ mtx_unlock(&ve_mutex);
+
+ freeenv(manifest_path);
+ freeenv(manifest_hash);
+ if (manifest_prefix != NULL)
+ freeenv(manifest_prefix);
+}
+
+EVENTHANDLER_DEFINE(mountroot, parse_manifest_event, NULL, 0);

File Metadata

Mime Type
text/plain
Expires
Sun, Feb 1, 9:25 AM (4 h, 10 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
28331475
Default Alt Text
D19281.id54181.diff (15 KB)

Event Timeline