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 +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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);