mirror of https://github.com/F-Stack/f-stack.git
476 lines
11 KiB
C
476 lines
11 KiB
C
/*-
|
|
* 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 <security/mac_veriexec/mac_veriexec.h>
|
|
#include <security/mac_veriexec/mac_veriexec_internal.h>
|
|
|
|
/* 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, 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);
|
|
(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(&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,
|
|
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);
|