root/lib/libsecureboot/verify_file.c
/*-
 * Copyright (c) 2017-2020, Juniper Networks, Inc.
 *
 * 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 COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "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 COPYRIGHT
 * OWNER OR CONTRIBUTORS 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.
 */
/*
 * Routines to verify files loaded.
 */

#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
/*
 * define MANIFEST_SKIP to Skip - in tests/tvo.c so that
 * tvo can control the value we use in find_manifest()
 */
extern char *Destdir;
extern size_t DestdirLen;
extern char *Skip;
# undef MANIFEST_SKIP
# define MANIFEST_SKIP Skip
# undef VE_DEBUG_LEVEL
#endif

/*
 * We sometimes need to know if input is verified or not.
 * The extra slot is for tracking most recently opened.
 */
#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;        /* have we loaded anything? */

enum {
        VE_VERBOSE_SILENT,              /* only report errors */
        VE_VERBOSE_UNVERIFIED,          /* all unverified files */
        VE_VERBOSE_MUST,                /* report VE_MUST */
        VE_VERBOSE_ALL,                 /* report all */
        VE_VERBOSE_DEBUG,               /* extra noise */
};

#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

/**
 * @brief set ve status for fd
 */
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;
}

/**
 * @brief get ve status of fd
 *
 * What we return depends on ve_status_state.
 *
 * @return
 *      @li ve_status[fd] if ve_status_state is valid
 *      @li ve_status[SOPEN_MAX] if ve_status_state is none
 *      @li VE_NOT_CHECKED if ve_status_state uninitialized
 */
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]);  /* most recent */
}

/**
 * @brief track verify status
 *
 * occasionally loader will make multiple calls
 * for the same file, we need only check it once.
 */
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);
}

/* most recent first, since most likely to see repeated calls. */
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));
}


/**
 * @brief
 * load specified manifest if verified
 */
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);
                }
                /* loader has no sense of time */
                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; /* we are verifying! */
                        DEBUG_PRINTF(3, ("loaded: %s %s %s\n",
                                name, prefix, skip));
                        rc = VE_VERIFIED;
                } else {
                        rc = VE_FINGERPRINT_WRONG;
                        add_verify_status(stp, rc);     /* remember */
                }
        }
        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] == '.') {
                        /* skip /../ */
                        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             /* very unlikely */
                        skip = MANIFEST_SKIP_ALWAYS;
#else
#ifdef MANIFEST_SKIP                    /* rare */
                        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;

        /*
         * Some files like *.conf and *.hints may be unsigned,
         * a *.tgz is expected to have its own signed manifest.
         * We allow *.conf to get VE_WANT, but files we expect
         * to always be unverified get VE_TRY and we will not
         * report them.
         */
        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;              /* 0 if not verifying */

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) {
                /* anything caller wants verified must be */
                *accept_no_fp = VE_WANT;
                Verbose = VE_VERBOSE_ALL;
                /* treat self test failure as fatal */
                if (!ve_self_tests()) {
                        panic("verify self tests failed");
                }
        } else if (strcmp(tweak, "modules") == 0) {
                /* modules/kernel must be verified */
                *accept_no_fp = VE_MUST;
        } else if (strcmp(tweak, "try") == 0) {
                /* best effort: always accept no fp */
                *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) {
                /* content is trust anchor to add or revoke */
                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;
}


/**
 * @brief report verification status
 *
 * @param[in] path
 *      path we attempted to verify
 *
 * @param[in] severity
 *      indicator of how to handle case of missing fingerprint
 *
 * @param[in] status
 *      result of verification
 *      0 not a file to be verified, > 0 success, < 0 error
 *
 * @param[in] stp
 *      pointer to struct stat, used in extra info to be output
 *
 * The output is dictated by combinations of the above and the setting
 * of Verbose:
 *
 * VE_VERBOSE_SILENT
 *      report only failure to verify if severity is VE_WANT or higher.
 *
 * VE_VERBOSE_UNVERIFIED
 *      report any unverified file.
 *
 * VE_VERBOSE_MUST
 *      report verified only if severity is VE_MUST or higher.
 *
 * VE_VERBOSE_ALL
 *      report all verified files.
 *
 * VE_VERBOSE_DEBUG
 *      if stp is not NULL report dev,inode for path
 */
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);
                }
        }
}


/**
 * @brief prepare to verify an open file
 *
 * @param[in] fd
 *      open descriptor
 *
 * @param[in] filename
 *      path we opened and will use to lookup fingerprint
 *
 * @param[in] stp
 *      stat pointer so we can check file type
 */
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();
                /* initialize ve_status with default result */
                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);
}

/**
 * @brief verify an open file
 *
 * @param[in] fd
 *      open descriptor
 *
 * @param[in] filename
 *      path we opened and will use to lookup fingerprint
 *
 * @param[in] off
 *      current offset in fd, must be restored on return
 *
 * @param[in] severity
 *      indicator of how to handle case of missing fingerprint
 *
 * We look for a signed manifest relative to the filename
 * just opened and verify/load it if needed.
 *
 * We then use verify_fd() in libve to actually verify that hash for
 * open file.  If it returns < 0 we look at the severity arg to decide
 * what to do about it.
 *
 * If verify_fd() returns VE_FINGERPRINT_NONE we accept it if severity
 * is < accept_no_fp.
 *
 * @return >= 0 on success < 0 on failure
 */
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
                /*
                 * Only update pcr with things that must verify
                 * these tend to be processed in a more deterministic
                 * order, which makes our pseudo pcr more useful.
                 */
                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) { /* not a kernel or module */
                                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);

                /* recheck debug/verbose level next time we are called */
                if (rc == VE_UNVERIFIED_OK) {
                        check_verbose = 1;
                }
        }
#ifdef LOADER_VERIEXEC_TESTING
        else if (rc != VE_FINGERPRINT_WRONG) {
                /*
                 * We have not loaded any manifest and
                 * not because of verication failure.
                 * Most likely reason is we have none.
                 * Allow boot to proceed if we are just testing.
                 */
                return (VE_UNVERIFIED_OK);
        }
#endif
        if (rc == VE_FINGERPRINT_WRONG && severity > accept_no_fp)
                panic("cannot continue");
        ve_status_set(fd, rc);
        return (rc);
}

/**
 * @brief get hex string for pcr value and export
 *
 * In case we are doing measured boot, provide
 * value of the "pcr" data we have accumulated.
 */
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'; /* clobber newline */
                        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) {
                                        /*
                                         * bump kenv_mvallen
                                         * roundup to multiple of 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
}

/*
 * For tftp and http we need to hash pathname
 * to be able to fake stat(2) data.
 */
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;
}