/*- * 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 #include /* The following are based on sbin/veriexec */ struct fingerprint_type { const char *fp_type; int fp_size; }; struct fp_flag { const char *flag_name; int flag; }; 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} }; static const struct fp_flag flags_table[] = { {"indirect", VERIEXEC_INDIRECT}, {"no_ptrace", VERIEXEC_NOTRACE}, {"trusted", VERIEXEC_TRUSTED}, {"no_fips", VERIEXEC_NOFIPS}, {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_flags(const char *entry); static int get_fp(const char *entry, char **type, unsigned char **digest, int *flags); 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); } static int get_flags(const char *entry) { int i; int result = 0; for (i = 0; flags_table[i].flag_name != NULL; i++) if (strstr(entry, flags_table[i].flag_name) != NULL) result |= flags_table[i].flag; return (result); } /* * 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, int *flags) { char *delimiter; char *local_digest; 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 != NULL; 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); local_digest = &fp_type[strlen(fp_table[i].fp_type)]; delimiter = &local_digest[2 * fp_table[i].fp_size]; /* * Make sure that digest is followed by * some kind of delimiter. */ if (*delimiter != '\n' && *delimiter != '\0' && *delimiter != ' ') return (EINVAL); /* * Does the entry contain flags we need to parse? */ if (*delimiter == ' ' && flags != NULL) *flags = get_flags(delimiter); /* * Split entry into three parts: * path, fp_type and digest. */ local_digest[-1] = '\0'; *delimiter = '\0'; fp_type[-1] = '\0'; break; } } if (fp_type == NULL) return (EINVAL); if (type != NULL) *type = fp_type; if (digest != NULL) *digest = local_digest; 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); rc = vn_open(nid, &flags, 0, NULL); NDFREE_PNBUF(nid); 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); (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, flags; fp_type = NULL; digest = NULL; flags = 0; rc = get_fp(entry, &fp_type, &digest, &flags); 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); } rc = open_file(path, &nid); NDFREE_PNBUF(&nid); 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, NULL, 0, flags, fp_type, 1); mtx_unlock(&ve_mutex); out: VOP_UNLOCK(nid.ni_vp); 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("mac_veriexec_parser: 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_LOCKED | VERIEXEC_STATE_ENFORCE); 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);