diff --git a/lib/libsecureboot/Makefile.inc b/lib/libsecureboot/Makefile.inc index ff40b919bad3..b09e44edc1b0 100644 --- a/lib/libsecureboot/Makefile.inc +++ b/lib/libsecureboot/Makefile.inc @@ -1,173 +1,173 @@ .if empty(BEARSSL) .include "../libbearssl/Makefile.inc" .endif .if !target(_${__this}_) _${__this}_: libsecureboot_src:= ${.PARSEDIR} CFLAGS+= -I${libsecureboot_src}/h CFLAGS+= -DHAVE_BR_X509_TIME_CHECK .PATH: ${.PARSEDIR} SRCS+= \ readfile.c \ brf.c \ vesigned.c \ vets.c .if ${.CURDIR:M*libsecureboot*} != "" SRCS+= veta.c .endif CFLAGS+= ${XCFLAGS.${.TARGET:T:R}:U} # we use a couple of files from ${BEARSSL}/tools BRSSL_CFLAGS+= -I${BEARSSL}/tools BRSSL_SRCS+= \ ${BEARSSL}/tools/xmem.c \ ${BEARSSL}/tools/vector.c BRSSL_DEPS= \ brf.c \ vets.c \ veta.c .if ${MK_LOADER_EFI_SECUREBOOT} != "no" BRSSL_DEPS+= \ efi_init.c \ efi_variables.c .endif # we do not need/want nested objdirs OBJS_SRCS_FILTER = T R SRCS+= ${BRSSL_SRCS} # extract the last cert from a chain (should be rootCA) _LAST_PEM_USE: .USE sed "1,`grep -n .-END ${.ALLSRC:M*.pem} | tail -2 | head -1 | sed 's,:.*,,'`d" ${.ALLSRC:M*.pem} > ${.TARGET} # extract 2nd last cert from chain - we use this for self-test _2ndLAST_PEM_USE: .USE sed -n "`grep -n .-BEGIN ${.ALLSRC:M*.pem} | tail -2 | \ sed 's,:.*,,' | xargs | (read a b; echo $$a,$$(($$b - 1)))`p" ${.ALLSRC:M*.pem} > ${.TARGET} +# rules to populate the [tv]*.pem files we use to generate ta.h +# and can add/alter VE_*_LIST as desired. +.-include "local.trust.mk" + # list of hashes we support VE_HASH_LIST?= SHA256 # list of signatures we support # some people don't trust ECDSA VE_SIGNATURE_LIST?= RSA # this list controls our search for signatures so will not be sorted # note: for X509 signatures we assume we can replace the trailing # "sig" with "certs" to find the certificate chain # eg. for manifest.esig we use manifest.ecerts VE_SIGNATURE_EXT_LIST?= sig # needs to be yes for FIPS 140-2 compliance VE_SELF_TESTS?= no -# rules to populate the [tv]*.pem files we use to generate ta.h -# and can add/alter VE_*_LIST as desired. -.-include "local.trust.mk" - # this is what we use as our trust anchor CFLAGS+= -I. -DTRUST_ANCHOR_STR=ta_PEM .if ${VE_SELF_TESTS} != "no" XCFLAGS.vets+= -DVERIFY_CERTS_STR=vc_PEM .endif # clean these up VE_HASH_LIST:= ${VE_HASH_LIST:tu:O:u} VE_SIGNATURE_LIST:= ${VE_SIGNATURE_LIST:tu:O:u} # define what we are supporting CFLAGS+= ${VE_HASH_LIST:@H@-DVE_$H_SUPPORT@} \ ${VE_SIGNATURE_LIST:@S@-DVE_$S_SUPPORT@} .if ${VE_SIGNATURE_LIST:MOPENPGP} != "" .include "openpgp/Makefile.inc" .endif .if ${VE_SELF_TESTS} != "no" # The input used for hash KATs # we use a string by default so it is independent of any other test VE_HASH_KAT_STRLEN?= strlen .if ${VE_HASH_KAT_STRLEN} == "strlen" VE_HASH_KAT_STR?= self-tests-are-good VE_HASH_KAT_STR_INPUT= echo -n XCFLAGS.vets+= -DVE_HASH_KAT_STR=\"${VE_HASH_KAT_STR}\" .else VE_HASH_KAT_STR?= vc_PEM VE_HASH_KAT_STR_INPUT= cat VE_HASH_KAT_STRLEN= sizeof XCFLAGS.vets+= -DVE_HASH_KAT_STR=${VE_HASH_KAT_STR} .endif XCFLAGS.vets+= -DVE_HASH_KAT_STRLEN=${VE_HASH_KAT_STRLEN} .endif # this should be updated occassionally this is 2019-01-01Z SOURCE_DATE_EPOCH?= 1546329600 .if ${MK_REPRODUCIBLE_BUILD} == "yes" BUILD_UTC?= ${SOURCE_DATE_EPOCH} .endif # BUILD_UTC provides a basis for the loader's notion of time # By default we use the mtime of BUILD_UTC_FILE .if empty(BUILD_UTC_FILE) BUILD_UTC_FILE:= ${.PARSEDIR:tA}/${.PARSEFILE} .endif # you can of course set BUILD_UTC to any value you like .if ${MAKE_VERSION} > 20230509 BUILD_UTC?= ${BUILD_UTC_FILE:mtime} .else BUILD_UTC?= ${${STAT:Ustat} -L -f %m ${BUILD_UTC_FILE}:L:sh} .endif # Generate ta.h containing one or more PEM encoded trust anchors in ta_PEM. # # If we are doing self-tests, we define another arrary vc_PEM # containing certificates that we can verify for each trust anchor. # This is typically a subordinate CA cert. # Finally we generate a hash of VE_HASH_KAT_STR # using each supported hash method # to use as a Known Answer Test (needed for FIPS 140-2) # TA_PEM_LIST ?= ${.ALLSRC:N*crl*:Mt*.pem} VC_PEM_LIST ?= ${.ALLSRC:N*crl*:Mv*.pem} vets.o vets.po vets.pico: ta.h ta.h: @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ cat ${TA_PEM_LIST:O:u} /dev/null | \ file2c -sx 'static const char ta_PEM[] = {' '};'; \ echo "${.newline}${VE_HASH_LIST:O:u:@H@static char vh_$H[] = \"`${VE_HASH_KAT_STR_INPUT} ${VE_HASH_KAT_STR} | ${$H:U${H:tl}}`\";${.newline}@}"; ) > ${.TARGET} .if ${VE_SELF_TESTS} != "no" ( cat ${VC_PEM_LIST:O:u} /dev/null | \ file2c -sx 'static const char vc_PEM[] = {' '};'; echo ) >> ${.TARGET} .endif echo '#define BUILD_UTC ${BUILD_UTC}' >> ${.TARGET} ${.OODATE:MNOMETA_CMP} # This header records our preference for signature extensions. vesigned.o vesigned.po vesigned.pico: vse.h vse.h: @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ echo "static const char *signature_exts[] = {"; \ echo '${VE_SIGNATURE_EXT_LIST:O:u:@e@"$e",${.newline}@}'; \ echo 'NULL };' ) > ${.TARGET} .for s in ${BRSSL_SRCS} ${BRSSL_DEPS} .ifdef BRSSL_SED $s: brssl.h .endif XCFLAGS.${s:R}+= ${BRSSL_CFLAGS} .endfor .endif diff --git a/lib/libsecureboot/Makefile.libsa.inc b/lib/libsecureboot/Makefile.libsa.inc index 76e0a91bc20a..907c8e8f7533 100644 --- a/lib/libsecureboot/Makefile.libsa.inc +++ b/lib/libsecureboot/Makefile.libsa.inc @@ -1,57 +1,60 @@ BRSSL_CFLAGS+= -DNO_STDIO .include "Makefile.inc" # for "measured boot" # loader puts the equivalent of TPM's PCR register into kenv # this is not as good but *way* simpler than talking to TPM CFLAGS+= -DVE_PCR_SUPPORT # sources that only apply to libsa SRCS+= \ vectx.c \ veopen.c \ vepcr.c \ verify_file.c \ # Build library with support for the UEFI based authentication .if ${MK_LOADER_EFI_SECUREBOOT} == "yes" SRCS+= \ efi/efi_variables.c \ efi/efi_init.c # Add includes required by efi part CFLAGS+= \ -I${SRCTOP}/stand/efi/include \ -I${SRCTOP}/lib/libsecureboot/efi/include \ -I${SRCTOP}/stand/efi/include/${MACHINE} .endif .if ${MK_LOADER_VERIEXEC_PASS_MANIFEST} == "yes" SRCS+= \ pass_manifest.c .endif # this is the list of paths (relative to a file # that we need to verify) used to find a signed manifest. # the signature extensions in VE_SIGNATURE_EXT_LIST # will be applied to each. VE_MANIFEST_LIST?= manifest ../manifest verify_file.o: manifests.h manifests.h: @( echo '/* Autogenerated - DO NOT EDIT!!! */'; echo; \ echo "static const char *manifest_names[] = {"; \ echo '${VE_MANIFEST_LIST:@m@"$m",${.newline}@}'; \ echo 'NULL };' ) > ${.TARGET} +# only add these if set XCFLAGS.verify_file+= \ - -DVE_DEBUG_LEVEL=${VE_DEBUG_LEVEL:U0} \ - -DVE_VERBOSE_DEFAULT=${VE_VERBOSE_DEFAULT:U0} \ + ${VE_DEBUG_LEVEL \ + VE_VERBOSE_DEFAULT \ + VE_VERIFY_FLAGS \ + :L:@v@${$v:S,^,-D$v=,}@} .if !empty(MANIFEST_SKIP_ALWAYS) XCFLAGS.verify_file+= -DMANIFEST_SKIP_ALWAYS=\"${MANIFEST_SKIP_ALWAYS}\" .elif !empty(MANIFEST_SKIP) XCFLAGS.verify_file+= -DMANIFEST_SKIP=\"${MANIFEST_SKIP}\" .endif diff --git a/lib/libsecureboot/h/verify_file.h b/lib/libsecureboot/h/verify_file.h index 88d758b27af4..f918ed6d0e38 100644 --- a/lib/libsecureboot/h/verify_file.h +++ b/lib/libsecureboot/h/verify_file.h @@ -1,66 +1,67 @@ /*- * Copyright (c) 2017-2018, 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. */ #ifndef _VERIFY_FILE_H_ #define _VERIFY_FILE_H_ #define VE_GUESS -1 /* let verify_file work it out */ #define VE_TRY 0 /* we don't mind if unverified */ #define VE_WANT 1 /* we want this verified */ #define VE_MUST 2 /* this must be verified */ #define VE_NOT_CHECKED -42 #define VE_VERIFIED 1 /* all good */ #define VE_UNVERIFIED_OK 0 /* not verified but that's ok */ #define VE_NOT_VERIFYING 2 /* we are not verifying */ /* suitable buf size for hash_string */ #ifndef SHA_DIGEST_LENGTH # define SHA_DIGEST_LENGTH 20 #endif struct stat; int verify_prep(int, const char *, off_t, struct stat *, const char *); void ve_debug_set(int); char *ve_error_get(void); void ve_efi_init(void); +void ve_status_set(int, int); int ve_status_get(int); int load_manifest(const char *, const char *, const char *, struct stat *); int pass_manifest(const char *, const char *); int pass_manifest_export_envs(void); void verify_report(const char *, int, int, struct stat *); int verify_file(int, const char *, off_t, int, const char *); void verify_pcr_export(void); int hash_string(char *s, size_t n, char *buf, size_t bufsz); int is_verified(struct stat *); void add_verify_status(struct stat *, int); struct vectx; struct vectx* vectx_open(int, const char *, off_t, struct stat *, int *, const char *); ssize_t vectx_read(struct vectx *, void *, size_t); off_t vectx_lseek(struct vectx *, off_t, int); int vectx_close(struct vectx *, int, const char *); #endif /* _VERIFY_FILE_H_ */ diff --git a/lib/libsecureboot/local.trust.mk b/lib/libsecureboot/local.trust.mk index 7b1e5f7ee97b..b009139a2f68 100644 --- a/lib/libsecureboot/local.trust.mk +++ b/lib/libsecureboot/local.trust.mk @@ -1,127 +1,108 @@ # Consider this file an example. # # For Junos this is how we obtain trust anchor .pems # the signing server (http://www.crufty.net/sjg/blog/signing-server.htm) # for each key will provide the appropriate certificate chain on request -# force these for Junos -#MANIFEST_SKIP_ALWAYS= boot -VE_HASH_LIST= \ - SHA1 \ - SHA256 \ - SHA384 \ - SHA512 - -VE_SIGNATURE_LIST= \ - ECDSA \ - RSA - -VE_SIGNATURE_EXT_LIST= \ - esig \ - rsig - -VE_SELF_TESTS= yes - -.if ${MACHINE} == "host" && ${.CURDIR:T} == "tests" - -VE_SIGNATURE_LIST+= \ - DEPRECATED_RSA_SHA1 +# allow site control +.-include "site.trust.mk" -VE_SIGNATURE_EXT_LIST+= \ - sig -.endif +#VE_DEBUG_LEVEL?=3 +#VE_VERBOSE_DEFAULT?=2 -# add OpenPGP support - possibly dormant -VE_SIGNATURE_LIST+= OPENPGP -VE_SIGNATURE_EXT_LIST+= asc +VE_HASH_LIST?= \ + SHA256 \ + SHA384 \ -# allow site override of all the above -.-include "site.trust.mk" +VE_SELF_TESTS?= yes -SIGNER ?= ${SB_TOOLS_PATH:U/volume/buildtools/bin}/sign.py +# client for the signing server above +SIGNER?= /opt/sigs/sign.py .if exists(${SIGNER}) -SIGN_HOST ?= ${SB_SITE:Usvl}-junos-signer.juniper.net -ECDSA_PORT:= ${133%y:L:gmtime} -SIGN_ECDSA= ${PYTHON} ${SIGNER} -u ${SIGN_HOST}:${ECDSA_PORT} -h sha256 -RSA2_PORT:= ${163%y:L:gmtime} -SIGN_RSA2= ${PYTHON} ${SIGNER} -u ${SIGN_HOST}:${RSA2_PORT} -h sha256 +OPENPGP_SIGNER?= ${SIGNER:H}/openpgp-sign.py +OPENPGP_SIGN_FLAGS= -a +OPENPGP_SIGN_HOST?= localhost +SIGN_HOST ?= localhost + +# A list of name/ext/url tuples. +# name should be one of ECDSA, OPENPGP or RSA, they can be repeated +# Order of ext list implies runtime preference so do not sort! +VE_SIGN_URL_LIST?= \ + ECDSA/esig/${SIGN_HOST}:${133%y:L:localtime} \ + RSA/rsig/${SIGN_HOST}:${163%y:L:localtime} \ + OPENPGP/asc/${OPENPGP_SIGN_HOST}:1234 \ + +.for sig ext url in ${VE_SIGN_URL_LIST:@x@${x:H:H} ${x:H:T} ${x:T}@} +SIGN_${sig}:= ${PYTHON} ${${sig}_SIGNER:U${SIGNER}} -u ${url} ${${sig}_SIGN_FLAGS:U-h sha256} + +VE_SIGNATURE_LIST+= ${sig} +VE_SIGNATURE_EXT_LIST+= ${ext} + +_SIGN_${sig}_USE: .USE + ${SIGN_${sig}} ${.ALLSRC} + +_TA_${sig}_USE: .USE + ${SIGN_${sig}} -C ${.TARGET} + +.if ${sig} == "OPENPGP" +ta_${sig:tl}.${ext}: _TA_${sig}_USE +ta_${ext}.h: ta_${sig:tl}.${ext} +.else +${ext:S/sig/certs/}.pem: _TA_${sig}_USE +# the last cert in the chain is the one we want +ta_${ext}.pem: ${ext:S/sig/certs/}.pem _LAST_PEM_USE +ta.h: ta_${ext}.pem +.if ${VE_SELF_TESTS} != "no" +# we use the 2nd last cert to test verification +vc_${ext}.pem: ${ext:S/sig/certs/}.pem _2ndLAST_PEM_USE +ta.h: vc_${ext}.pem +.endif +.endif +.endfor -# deal with quirk of our .esig format -XCFLAGS.vets+= -DVE_ECDSA_HASH_AGAIN +# cleanup duplicates +VE_SIGNATURE_LIST:= ${VE_SIGNATURE_LIST:O:u} -.if !empty(OPENPGP_SIGN_URL) +.if target(ta_asc.h) XCFLAGS.opgp_key+= -DHAVE_TA_ASC_H -VE_SIGNATURE_LIST+= OPENPGP -VE_SIGNATURE_EXT_LIST+= asc - -SIGN_OPENPGP= ${PYTHON} ${SIGNER:H}/openpgp-sign.py -a -u ${OPENPGP_SIGN_URL} - -ta_openpgp.asc: - ${SIGN_OPENPGP} -C ${.TARGET} - -ta_asc.h: ta_openpgp.asc - .if ${VE_SELF_TESTS} != "no" # for self test vc_openpgp.asc: ta_openpgp.asc ${SIGN_OPENPGP} ${.ALLSRC:M*.asc} mv ta_openpgp.asc.asc ${.TARGET} ta_asc.h: vc_openpgp.asc .endif .endif -rcerts.pem: - ${SIGN_RSA2} -C ${.TARGET} - -ecerts.pem: - ${SIGN_ECDSA} -C ${.TARGET} - -.if ${VE_SIGNATURE_LIST:tu:MECDSA} != "" -# the last cert in the chain is the one we want -ta_ec.pem: ecerts.pem _LAST_PEM_USE -ta.h: ta_ec.pem -.if ${VE_SELF_TESTS} != "no" -# these are for verification self test -vc_ec.pem: ecerts.pem _2ndLAST_PEM_USE -ta.h: vc_ec.pem -.endif -.endif - -.if ${VE_SIGNATURE_LIST:tu:MRSA} != "" -ta_rsa.pem: rcerts.pem _LAST_PEM_USE -ta.h: ta_rsa.pem -.if ${VE_SELF_TESTS} != "no" -vc_rsa.pem: rcerts.pem _2ndLAST_PEM_USE -ta.h: vc_rsa.pem -.endif -.endif - -# we take the mtime of this as our baseline time -#BUILD_UTC_FILE= ecerts.pem -#VE_DEBUG_LEVEL=3 -#VE_VERBOSE_DEFAULT=1 - .else +VE_SIGNATURE_LIST?= RSA + # you need to provide t*.pem or t*.asc files for each trust anchor +# below assumes they are named ta_${ext}.pem eg ta_esig.pem for ECDSA .if empty(TRUST_ANCHORS) TRUST_ANCHORS!= cd ${.CURDIR} && 'ls' -1 *.pem t*.asc 2> /dev/null .endif .if empty(TRUST_ANCHORS) && ${MK_LOADER_EFI_SECUREBOOT} != "yes" .error Need TRUST_ANCHORS see ${.PARSEDIR}/README.rst .endif + .if ${TRUST_ANCHORS:T:Mt*.pem} != "" ta.h: ${TRUST_ANCHORS:M*.pem} +VE_SIGNATURE_EXT_LIST?= ${TRUST_ANCHORS:T:Mt*.pem:R:S/ta_//} +.if ${VE_SIGNATURE_EXT_LIST:Mesig} != "" +VE_SIGNATURE_LIST+= ECDSA +.endif .endif + .if ${TRUST_ANCHORS:T:Mt*.asc} != "" VE_SIGNATURE_LIST+= OPENPGP VE_SIGNATURE_EXT_LIST+= asc ta_asc.h: ${TRUST_ANCHORS:M*.asc} .endif # we take the mtime of this as our baseline time BUILD_UTC_FILE?= ${TRUST_ANCHORS:[1]} .endif - diff --git a/lib/libsecureboot/vectx.c b/lib/libsecureboot/vectx.c index dba728421ce4..2d56830cd81d 100644 --- a/lib/libsecureboot/vectx.c +++ b/lib/libsecureboot/vectx.c @@ -1,404 +1,416 @@ /*- * Copyright (c) 2018, 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. */ #include #ifndef _STANDALONE /* Avoid unwanted userlandish components */ #define _KERNEL #include #undef _KERNEL #endif #ifdef VECTX_DEBUG static int vectx_debug = VECTX_DEBUG; # define DEBUG_PRINTF(n, x) if (vectx_debug >= n) printf x #endif #include "libsecureboot-priv.h" #include /** * @file vectx.c * @brief api to verify file while reading * * This API allows the hash of a file to be computed as it is read. * Key to this is seeking by reading. * * On close an indication of the verification result is returned. */ struct vectx { br_hash_compat_context vec_ctx; /* hash ctx */ const br_hash_class *vec_md; /* hash method */ const char *vec_path; /* path we are verifying */ const char *vec_want; /* hash value we want */ off_t vec_off; /* current offset */ off_t vec_hashed; /* where we have hashed to */ off_t vec_size; /* size of path */ size_t vec_hashsz; /* size of hash */ int vec_fd; /* file descriptor */ int vec_status; /* verification status */ int vec_closing; /* we are closing */ }; /** * @brief * verify an open file as we read it * * If the file has no fingerprint to match, we will still return a * verification context containing little more than the file * descriptor, and an error code in @c error. * * @param[in] fd * open descriptor * * @param[in] path * pathname to open * * @param[in] off * current offset * * @param[in] stp * pointer to struct stat * * @param[out] error * @li 0 all is good * @li ENOMEM out of memory * @li VE_FINGERPRINT_NONE no entry found * @li VE_FINGERPRINT_UNKNOWN no fingerprint in entry * * @return ctx or NULL on error. * NULL is only returned for non-files or out-of-memory. */ struct vectx * vectx_open(int fd, const char *path, off_t off, struct stat *stp, int *error, const char *caller) { struct vectx *ctx; struct stat st; size_t hashsz; char *cp; int rc; if (!stp) stp = &st; rc = verify_prep(fd, path, off, stp, __func__); DEBUG_PRINTF(2, ("vectx_open: caller=%s,fd=%d,name='%s',prep_rc=%d\n", caller, fd, path, rc)); switch (rc) { case VE_FINGERPRINT_NONE: case VE_FINGERPRINT_UNKNOWN: case VE_FINGERPRINT_WRONG: *error = rc; return (NULL); } ctx = malloc(sizeof(struct vectx)); if (!ctx) goto enomem; ctx->vec_fd = fd; ctx->vec_path = path; ctx->vec_size = stp->st_size; ctx->vec_off = 0; ctx->vec_hashed = 0; ctx->vec_want = NULL; ctx->vec_status = 0; ctx->vec_hashsz = hashsz = 0; ctx->vec_closing = 0; if (rc == 0) { /* we are not verifying this */ *error = 0; return (ctx); } cp = fingerprint_info_lookup(fd, path); if (!cp) { ctx->vec_status = VE_FINGERPRINT_NONE; ve_error_set("%s: no entry", path); } else { if (strncmp(cp, "no_hash", 7) == 0) { ctx->vec_status = VE_FINGERPRINT_IGNORE; hashsz = 0; } else if (strncmp(cp, "sha256=", 7) == 0) { ctx->vec_md = &br_sha256_vtable; hashsz = br_sha256_SIZE; cp += 7; #ifdef VE_SHA1_SUPPORT } else if (strncmp(cp, "sha1=", 5) == 0) { ctx->vec_md = &br_sha1_vtable; hashsz = br_sha1_SIZE; cp += 5; #endif #ifdef VE_SHA384_SUPPORT } else if (strncmp(cp, "sha384=", 7) == 0) { ctx->vec_md = &br_sha384_vtable; hashsz = br_sha384_SIZE; cp += 7; #endif #ifdef VE_SHA512_SUPPORT } else if (strncmp(cp, "sha512=", 7) == 0) { ctx->vec_md = &br_sha512_vtable; hashsz = br_sha512_SIZE; cp += 7; #endif } else { ctx->vec_status = VE_FINGERPRINT_UNKNOWN; ve_error_set("%s: no supported fingerprint", path); } } *error = ctx->vec_status; ctx->vec_hashsz = hashsz; ctx->vec_want = cp; if (hashsz > 0) { ctx->vec_md->init(&ctx->vec_ctx.vtable); if (off > 0) { lseek(fd, 0, SEEK_SET); vectx_lseek(ctx, off, SEEK_SET); } } DEBUG_PRINTF(2, ("vectx_open: caller=%s,name='%s',hashsz=%lu,status=%d\n", caller, path, (unsigned long)ctx->vec_hashsz, ctx->vec_status)); return (ctx); enomem: /* unlikely */ *error = ENOMEM; free(ctx); return (NULL); } /** * @brief * read bytes from file and update hash * * It is critical that all file I/O comes through here. * We keep track of current offset. * We also track what offset we have hashed to, * so we won't replay data if we seek backwards. * * @param[in] pctx * pointer to ctx * * @param[in] buf * * @param[in] nbytes * * @return bytes read or error. */ ssize_t vectx_read(struct vectx *ctx, void *buf, size_t nbytes) { unsigned char *bp = buf; int d; int n; int delta; int x; size_t off; if (ctx->vec_hashsz == 0) /* nothing to do */ return (read(ctx->vec_fd, buf, nbytes)); off = 0; do { /* * Do this in reasonable chunks so * we don't timeout if doing tftp */ x = nbytes - off; x = MIN(PAGE_SIZE, x); d = n = read(ctx->vec_fd, &bp[off], x); if (ctx->vec_closing && n < x) { DEBUG_PRINTF(3, ("%s: read %d off=%ld hashed=%ld size=%ld\n", __func__, n, (long)ctx->vec_off, (long)ctx->vec_hashed, (long)ctx->vec_size)); } if (n < 0) { return (n); } if (d > 0) { /* we may have seeked backwards! */ delta = ctx->vec_hashed - ctx->vec_off; if (delta > 0) { x = MIN(delta, d); off += x; d -= x; ctx->vec_off += x; } if (d > 0) { if (ctx->vec_closing && d < PAGE_SIZE) { DEBUG_PRINTF(3, ("%s: update %ld + %d\n", __func__, (long)ctx->vec_hashed, d)); } ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], d); off += d; ctx->vec_off += d; ctx->vec_hashed += d; } } } while (n > 0 && off < nbytes); return (off); } /** * @brief * vectx equivalent of lseek * * When seeking forwards we actually call vectx_read * to reach the desired offset. * * We support seeking backwards. * * @param[in] pctx * pointer to ctx * * @param[in] off * desired offset * * @param[in] whence * We try to convert whence to ``SEEK_SET``. * We do not support ``SEEK_DATA`` or ``SEEK_HOLE``. * * @return offset or error. */ off_t vectx_lseek(struct vectx *ctx, off_t off, int whence) { unsigned char buf[PAGE_SIZE]; size_t delta; ssize_t n; if (ctx->vec_hashsz == 0) /* nothing to do */ return (lseek(ctx->vec_fd, off, whence)); /* * Convert whence to SEEK_SET */ DEBUG_PRINTF(3, ("%s(%s, %ld, %d)\n", __func__, ctx->vec_path, (long)off, whence)); if (whence == SEEK_END && off <= 0) { - if (ctx->vec_closing && ctx->vec_hashed < ctx->vec_size) { - DEBUG_PRINTF(3, ("%s: SEEK_END %ld\n", - __func__, - (long)(ctx->vec_size - ctx->vec_hashed))); + if (ctx->vec_size < 0) { + if (ctx->vec_closing) { + /* size unknown - read until EOF */ + do { + n = vectx_read(ctx, buf, PAGE_SIZE); + if (n < 0) + return (n); + } while (n > 0); + return (ctx->vec_off); + } + } else { + if (ctx->vec_closing && ctx->vec_hashed < ctx->vec_size) { + DEBUG_PRINTF(3, ("%s: SEEK_END %ld\n", + __func__, + (long)(ctx->vec_size - ctx->vec_hashed))); + } + whence = SEEK_SET; + off += ctx->vec_size; } - whence = SEEK_SET; - off += ctx->vec_size; } else if (whence == SEEK_CUR) { whence = SEEK_SET; off += ctx->vec_off; } if (whence != SEEK_SET || - off > ctx->vec_size) { + (off > ctx->vec_size && ctx->vec_size > 0)) { printf("ERROR: %s: unsupported operation: whence=%d off=%ld -> %ld\n", __func__, whence, (long)ctx->vec_off, (long)off); return (-1); } if (off < ctx->vec_hashed) { #ifdef _STANDALONE struct open_file *f = fd2open_file(ctx->vec_fd); if (f != NULL && strncmp(f->f_ops->fs_name, "tftp", 4) == 0) { /* we cannot rewind if we've hashed much of the file */ if (ctx->vec_hashed > ctx->vec_size / 5) return (-1); /* refuse! */ } #endif /* seeking backwards! just do it */ ctx->vec_off = lseek(ctx->vec_fd, off, whence); return (ctx->vec_off); } n = 0; do { delta = off - ctx->vec_off; if (delta > 0) { delta = MIN(PAGE_SIZE, delta); n = vectx_read(ctx, buf, delta); if (n < 0) return (n); } } while (ctx->vec_off < off && n > 0); return (ctx->vec_off); } /** * @brief * check that hashes match and cleanup * * We have finished reading file, compare the hash with what * we wanted. * * Be sure to call this before closing the file, since we may * need to seek to the end to ensure hashing is complete. * * @param[in] pctx * pointer to ctx * * @return 0 or an error. */ int vectx_close(struct vectx *ctx, int severity, const char *caller) { int rc; ctx->vec_closing = 1; if (ctx->vec_hashsz == 0) { rc = ctx->vec_status; } else { #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 /* make sure we have hashed it all */ vectx_lseek(ctx, 0, SEEK_END); rc = ve_check_hash(&ctx->vec_ctx, ctx->vec_md, ctx->vec_path, ctx->vec_want, ctx->vec_hashsz); } DEBUG_PRINTF(2, ("vectx_close: caller=%s,name='%s',rc=%d,severity=%d\n", caller,ctx->vec_path, rc, severity)); verify_report(ctx->vec_path, severity, rc, NULL); if (rc == VE_FINGERPRINT_WRONG) { #if !defined(UNIT_TEST) && !defined(DEBUG_VECTX) /* we are generally called with VE_MUST */ if (severity > VE_WANT) panic("cannot continue"); #endif } free(ctx); return ((rc < 0) ? rc : 0); } diff --git a/lib/libsecureboot/verify_file.c b/lib/libsecureboot/verify_file.c index c123ea9e1088..753204a33b6a 100644 --- a/lib/libsecureboot/verify_file.c +++ b/lib/libsecureboot/verify_file.c @@ -1,671 +1,690 @@ /*- * 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 #include #include #include #include "libsecureboot.h" #include #include #ifdef UNIT_TEST # include # 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 */ -static void +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) - return (vsp->vs_status); + stp->st_ino == vsp->vs_ino) { + rc = vsp->vs_status; + break; + } } } - return (VE_NOT_CHECKED); + 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, ".conf") == 0 || - strcmp(cp, ".cookie") == 0 || + 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); - DEBUG_PRINTF(4,("verify_prep: is_verified()->%d\n", rc)); 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", VEF_VERBOSE); + 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) + 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; } diff --git a/stand/common/interp.c b/stand/common/interp.c index b71e0858e702..0f142902b4ac 100644 --- a/stand/common/interp.c +++ b/stand/common/interp.c @@ -1,199 +1,207 @@ /*- * Copyright (c) 1998 Michael Smith * All rights reserved. * * 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 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 AUTHOR 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. */ #include /* * Simple commandline interpreter, toplevel and misc. * * XXX may be obsoleted by BootFORTH or some other, better, interpreter. */ #include #include #include "bootstrap.h" +#ifdef LOADER_VERIEXEC +#include +#endif + #define MAXARGS 20 /* maximum number of arguments allowed */ const char * volatile interp_identifier; /* * Interactive mode */ void interact(void) { static char input[256]; /* big enough? */ TSENTER(); /* * Because interp_identifier is volatile, it cannot be optimized out by * the compiler as it's considered an externally observable event. This * prevents the compiler from optimizing out our carefully placed * $Interpreter:4th string that userboot may use to determine that * we need to switch interpreters. */ interp_identifier = bootprog_interp; interp_init(); printf("\n"); /* * Before interacting, we might want to autoboot. */ autoboot_maybe(); /* * Not autobooting, go manual */ printf("\nType '?' for a list of commands, 'help' for more detailed help.\n"); if (getenv("prompt") == NULL) setenv("prompt", "${interpret}", 1); if (getenv("interpret") == NULL) setenv("interpret", "OK", 1); for (;;) { input[0] = '\0'; interp_emit_prompt(); ngets(input, sizeof(input)); +#ifdef LOADER_VERIEXEC + /* some settings should be restritcted */ + ve_status_set(-1, VE_UNVERIFIED_OK); +#endif interp_run(input); } } /* * Read commands from a file, then execute them. * * We store the commands in memory and close the source file so that the media * holding it can safely go away while we are executing. * * Commands may be prefixed with '@' (so they aren't displayed) or '-' (so * that the script won't stop if they fail). */ COMMAND_SET(include, "include", "read commands from a file", command_include); static int command_include(int argc, char *argv[]) { int i; int res; char **argvbuf; /* * Since argv is static, we need to save it here. */ argvbuf = (char**) calloc((u_int)argc, sizeof(char*)); for (i = 0; i < argc; i++) argvbuf[i] = strdup(argv[i]); res=CMD_OK; for (i = 1; (i < argc) && (res == CMD_OK); i++) res = interp_include(argvbuf[i]); for (i = 0; i < argc; i++) free(argvbuf[i]); free(argvbuf); return(res); } /* * Emit the current prompt; use the same syntax as the parser * for embedding environment variables. Does not accept input. */ void interp_emit_prompt(void) { char *pr, *p, *cp, *ev; if ((cp = getenv("prompt")) == NULL) cp = ">"; pr = p = strdup(cp); while (*p != 0) { if ((*p == '$') && (*(p+1) == '{')) { for (cp = p + 2; (*cp != 0) && (*cp != '}'); cp++) ; *cp = 0; ev = getenv(p + 2); if (ev != NULL) printf("%s", ev); p = cp + 1; continue; } putchar(*p++); } putchar(' '); free(pr); } static struct bootblk_command * interp_lookup_cmd(const char *cmd) { struct bootblk_command **cmdp; /* search the command set for the command */ SET_FOREACH(cmdp, Xcommand_set) { if (((*cmdp)->c_name != NULL) && !strcmp(cmd, (*cmdp)->c_name)) return (*cmdp); } return (NULL); } /* * Perform a builtin command */ int interp_builtin_cmd(int argc, char *argv[]) { int result; struct bootblk_command *cmd; if (argc < 1) return (CMD_OK); /* set return defaults; a successful command will override these */ command_errmsg = command_errbuf; strcpy(command_errbuf, "no error message"); result = CMD_ERROR; cmd = interp_lookup_cmd(argv[0]); if (cmd != NULL && cmd->c_fn) { result = cmd->c_fn(argc, argv); } else { command_errmsg = "unknown command"; } return (result); } /* * Return true if the builtin command exists */ bool interp_has_builtin_cmd(const char *cmd) { return (interp_lookup_cmd(cmd) != NULL); } diff --git a/stand/efi/loader/version.veriexec b/stand/efi/loader/version.veriexec new file mode 100644 index 000000000000..5c9292310c04 --- /dev/null +++ b/stand/efi/loader/version.veriexec @@ -0,0 +1,7 @@ +NOTE ANY CHANGES YOU MAKE TO THE BOOTBLOCKS HERE. The format of this +file is important. Make sure the current version number is on line 6. + +2.1: SMBIOS 3 support +2.0: Secure boot support +1.1: Keep in sync with i386 version. +0.1: Initial i386 version. Derived from ia64. diff --git a/stand/veriexec.mk b/stand/veriexec.mk index 930e933be0a9..a0ff7d1e8489 100644 --- a/stand/veriexec.mk +++ b/stand/veriexec.mk @@ -1,9 +1,12 @@ .if ${MK_LOADER_VERIEXEC} != "no" +.if exists(${VERSION_FILE}.veriexec) +VERSION_FILE:= ${VERSION_FILE}.veriexec +.endif CFLAGS+= -DLOADER_VERIEXEC -I${SRCTOP}/lib/libsecureboot/h .if ${MK_LOADER_VERIEXEC_VECTX} != "no" CFLAGS+= -DLOADER_VERIEXEC_VECTX .endif .if ${MK_LOADER_VERIEXEC_PASS_MANIFEST} != "no" CFLAGS+= -DLOADER_VERIEXEC_PASS_MANIFEST .endif .endif