diff --git a/lib/libsecureboot/h/libsecureboot.h b/lib/libsecureboot/h/libsecureboot.h index 581b72b411d2..33f98bca0479 100644 --- a/lib/libsecureboot/h/libsecureboot.h +++ b/lib/libsecureboot/h/libsecureboot.h @@ -1,96 +1,97 @@ /*- * 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. */ /* * $FreeBSD$ */ #ifndef _LIBSECUREBOOT_H_ #define _LIBSECUREBOOT_H_ #include #ifdef _STANDALONE #include #else #include #include #include #include #include #include #endif #include unsigned char * read_fd(int, size_t); #ifndef NEED_BRSSL_H unsigned char * read_file(const char *, size_t *); #endif extern int DebugVe; #define DEBUG_PRINTF(n, x) if (DebugVe >= n) printf x int ve_trust_init(void); size_t ve_trust_anchors_add_buf(unsigned char *, size_t); size_t ve_trust_anchors_revoke(unsigned char *, size_t); int ve_trust_add(const char *); void ve_debug_set(int); void ve_anchor_verbose_set(int); int ve_anchor_verbose_get(void); void ve_utc_set(time_t utc); char *ve_error_get(void); int ve_error_set(const char *, ...) __printflike(1,2); int ve_self_tests(void); void fingerprint_info_add(const char *, const char *, const char *, const char *, struct stat *); int ve_check_hash(br_hash_compat_context *, const br_hash_class *, const char *, const char *, size_t); char * hexdigest(char *, size_t, unsigned char *, size_t); int verify_fd(int, const char *, off_t, struct stat *); int verify_open(const char *, int); unsigned char *verify_signed(const char *, int); unsigned char *verify_sig(const char *, int); unsigned char *verify_asc(const char *, int); /* OpenPGP */ void ve_pcr_init(void); -void ve_pcr_update(unsigned char *, size_t); +void ve_pcr_update(const char *, unsigned char *, size_t); ssize_t ve_pcr_get(unsigned char *, size_t); int ve_pcr_updating_get(void); void ve_pcr_updating_set(int); +char * ve_pcr_hashed_get(int); /* flags for verify_{asc,sig,signed} */ #define VEF_VERBOSE 1 #define VE_FINGERPRINT_OK 1 #define VE_FINGERPRINT_IGNORE 2 /* errors from verify_fd */ #define VE_FINGERPRINT_NONE -2 #define VE_FINGERPRINT_WRONG -3 #define VE_FINGERPRINT_UNKNOWN -4 /* may not be an error */ #endif /* _LIBSECUREBOOT_H_ */ diff --git a/lib/libsecureboot/vectx.c b/lib/libsecureboot/vectx.c index 908e24fb554c..433df00f244c 100644 --- a/lib/libsecureboot/vectx.c +++ b/lib/libsecureboot/vectx.c @@ -1,356 +1,364 @@ /*- * 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 __FBSDID("$FreeBSD$"); #ifndef _STANDALONE /* Avoid unwanted userlandish components */ #define _KERNEL #include #undef _KERNEL #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 */ size_t vec_size; /* size of path */ size_t vec_hashsz; /* size of hash */ int vec_fd; /* file descriptor */ int vec_status; /* verification status */ }; /** * @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,name='%s',prep_rc=%d\n", - caller,path, rc)); + ("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; 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 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 { n = read(ctx->vec_fd, &bp[off], nbytes - off); if (n < 0) return (n); if (n > 0) { /* we may have seeked backwards! */ delta = ctx->vec_hashed - ctx->vec_off; if (delta > 0) { x = MIN(delta, n); off += x; n -= x; ctx->vec_off += x; } if (n > 0) { ctx->vec_md->update(&ctx->vec_ctx.vtable, &bp[off], n); off += n; ctx->vec_off += n; ctx->vec_hashed += n; } } } 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 */ if (whence == SEEK_END && off <= 0) { whence = SEEK_SET; off += ctx->vec_size; } else if (whence == SEEK_CUR) { whence = SEEK_SET; off += ctx->vec_off; } if (whence != SEEK_SET || (size_t)off > ctx->vec_size) { printf("ERROR: %s: unsupported operation: whence=%d off=%lld -> %lld\n", __func__, whence, (long long)ctx->vec_off, (long long)off); return (-1); } if (off < ctx->vec_hashed) { /* 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; 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)); - if (severity > VE_WANT || rc == VE_FINGERPRINT_WRONG) - printf("%serified %s\n", (rc <= 0) ? "Unv" : "V", - ctx->vec_path); + if (rc == VE_FINGERPRINT_WRONG) { + printf("Unverified: %s\n", ve_error_get()); #if !defined(UNIT_TEST) && !defined(DEBUG_VECTX) - /* we are generally called with VE_MUST */ - if (severity > VE_WANT && rc == VE_FINGERPRINT_WRONG) - panic("cannot continue"); + /* we are generally called with VE_MUST */ + if (severity > VE_WANT) + panic("cannot continue"); #endif + } else if (severity > VE_WANT) { + printf("%serified %s\n", (rc <= 0) ? "Unv" : "V", + ctx->vec_path); + } free(ctx); return ((rc < 0) ? rc : 0); } diff --git a/lib/libsecureboot/veopen.c b/lib/libsecureboot/veopen.c index 6ecf85c44af1..da6291504c4c 100644 --- a/lib/libsecureboot/veopen.c +++ b/lib/libsecureboot/veopen.c @@ -1,461 +1,463 @@ /*- * 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. */ #include __FBSDID("$FreeBSD$"); #include #include "libsecureboot-priv.h" struct fingerprint_info { char *fi_prefix; /**< manifest entries relative to */ char *fi_skip; /**< manifest entries prefixed with */ const char *fi_data; /**< manifest data */ size_t fi_prefix_len; /**< length of prefix */ size_t fi_skip_len; /**< length of skip */ dev_t fi_dev; /**< device id */ LIST_ENTRY(fingerprint_info) entries; }; static LIST_HEAD(, fingerprint_info) fi_list; static void fingerprint_info_init(void) { static int once; if (once) return; LIST_INIT(&fi_list); once = 1; } /** * @brief * add manifest data to list * * list is kept sorted by longest prefix. * * @param[in] prefix * path that all manifest entries are resolved via * * @param[in] skip * optional prefix within manifest entries which should be skipped * * @param[in] data * manifest data */ void fingerprint_info_add(const char *filename, const char *prefix, const char *skip, const char *data, struct stat *stp) { struct fingerprint_info *fip, *nfip, *lfip; char *cp; int n; fingerprint_info_init(); nfip = malloc(sizeof(struct fingerprint_info)); if (prefix) { nfip->fi_prefix = strdup(prefix); } else { if (!filename) { free(nfip); return; } nfip->fi_prefix = strdup(filename); cp = strrchr(nfip->fi_prefix, '/'); - if (cp) + if (cp == nfip->fi_prefix) { + cp[1] = '\0'; + } else if (cp) { *cp = '\0'; - else { + } else { free(nfip->fi_prefix); free(nfip); return; } } /* collapse any trailing ..[/] */ n = 0; - while ((cp = strrchr(nfip->fi_prefix, '/')) != NULL) { + while ((cp = strrchr(nfip->fi_prefix, '/')) > nfip->fi_prefix) { if (cp[1] == '\0') { /* trailing "/" */ *cp = '\0'; continue; } if (strcmp(&cp[1], "..") == 0) { n++; *cp = '\0'; continue; } if (n > 0) { n--; *cp = '\0'; } if (n == 0) break; } #ifdef UNIT_TEST nfip->fi_dev = 0; #else nfip->fi_dev = stp->st_dev; #endif nfip->fi_data = data; nfip->fi_prefix_len = strlen(nfip->fi_prefix); if (skip) { nfip->fi_skip_len = strlen(skip); if (nfip->fi_skip_len) nfip->fi_skip = strdup(skip); else nfip->fi_skip = NULL; } else { nfip->fi_skip = NULL; nfip->fi_skip_len = 0; } if (LIST_EMPTY(&fi_list)) { LIST_INSERT_HEAD(&fi_list, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s at head\n", nfip->fi_prefix_len, nfip->fi_prefix)); return; } LIST_FOREACH(fip, &fi_list, entries) { if (nfip->fi_prefix_len >= fip->fi_prefix_len) { LIST_INSERT_BEFORE(fip, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s before %zu %s\n", nfip->fi_prefix_len, nfip->fi_prefix, fip->fi_prefix_len, fip->fi_prefix)); return; } lfip = fip; } LIST_INSERT_AFTER(lfip, nfip, entries); DEBUG_PRINTF(4, ("inserted %zu %s after %zu %s\n", nfip->fi_prefix_len, nfip->fi_prefix, lfip->fi_prefix_len, lfip->fi_prefix)); } #ifdef MANIFEST_SKIP_MAYBE /* * Deal with old incompatible boot/manifest * if fp[-1] is '/' and start of entry matches * MANIFEST_SKIP_MAYBE, we want it. */ static char * maybe_skip(char *fp, struct fingerprint_info *fip, size_t *nplenp) { char *tp; tp = fp - sizeof(MANIFEST_SKIP_MAYBE); if (tp >= fip->fi_data) { DEBUG_PRINTF(3, ("maybe: %.48s\n", tp)); if ((tp == fip->fi_data || tp[-1] == '\n') && strncmp(tp, MANIFEST_SKIP_MAYBE, sizeof(MANIFEST_SKIP_MAYBE) - 1) == 0) { fp = tp; *nplenp += sizeof(MANIFEST_SKIP_MAYBE); } } return (fp); } #endif char * fingerprint_info_lookup(int fd, const char *path) { char pbuf[MAXPATHLEN+1]; char nbuf[MAXPATHLEN+1]; struct stat st; struct fingerprint_info *fip; char *cp, *ep, *fp, *np; const char *prefix; size_t n, plen, nlen, nplen; dev_t dev = 0; fingerprint_info_init(); n = strlcpy(pbuf, path, sizeof(pbuf)); if (n >= sizeof(pbuf)) return (NULL); #ifndef UNIT_TEST if (fstat(fd, &st) == 0) dev = st.st_dev; #endif /* * get the first entry - it will have longest prefix * so we can can work out how to initially split path */ fip = LIST_FIRST(&fi_list); if (!fip) return (NULL); prefix = pbuf; ep = NULL; cp = &pbuf[fip->fi_prefix_len]; do { if (ep) { *ep = '/'; cp -= 2; if (cp < pbuf) break; } nlen = plen = 0; /* keep gcc quiet */ if (cp > pbuf) { for ( ; cp >= pbuf && *cp != '/'; cp--) ; /* nothing */ if (cp > pbuf) { ep = cp++; *ep = '\0'; } else { cp = pbuf; } if (ep) { plen = ep - pbuf; nlen = n - plen - 1; } } if (cp == pbuf) { prefix = "/"; plen = 1; if (*cp == '/') { nlen = n - 1; cp++; } else nlen = n; ep = NULL; } DEBUG_PRINTF(2, ("looking for %s %zu %s\n", prefix, plen, cp)); LIST_FOREACH(fip, &fi_list, entries) { DEBUG_PRINTF(4, ("at %zu %s\n", fip->fi_prefix_len, fip->fi_prefix)); if (fip->fi_prefix_len < plen) { DEBUG_PRINTF(3, ("skipping prefix=%s %zu %zu\n", fip->fi_prefix, fip->fi_prefix_len, plen)); break; } if (fip->fi_prefix_len == plen) { if (fip->fi_dev != 0 && fip->fi_dev != dev) { DEBUG_PRINTF(3, ( "skipping dev=%ld != %ld\n", (long)fip->fi_dev, (long)dev)); continue; } if (strcmp(prefix, fip->fi_prefix)) { DEBUG_PRINTF(3, ( "skipping prefix=%s\n", fip->fi_prefix)); continue; } DEBUG_PRINTF(3, ("checking prefix=%s\n", fip->fi_prefix)); if (fip->fi_skip_len) { np = nbuf; nplen = snprintf(nbuf, sizeof(nbuf), "%s/%s", fip->fi_skip, cp); nplen = MIN(nplen, sizeof(nbuf) - 1); } else { np = cp; nplen = nlen; } DEBUG_PRINTF(3, ("lookup: '%s'\n", np)); if (!(fp = strstr(fip->fi_data, np))) continue; #ifdef MANIFEST_SKIP_MAYBE if (fip->fi_skip_len == 0 && fp > fip->fi_data && fp[-1] == '/') { fp = maybe_skip(fp, fip, &nplen); } #endif /* * when we find a match: * fp[nplen] will be space and * fp will be fip->fi_data or * fp[-1] will be \n */ if (!((fp == fip->fi_data || fp[-1] == '\n') && fp[nplen] == ' ')) { do { fp++; fp = strstr(fp, np); if (fp) { #ifdef MANIFEST_SKIP_MAYBE if (fip->fi_skip_len == 0 && fp > fip->fi_data && fp[-1] == '/') { fp = maybe_skip(fp, fip, &nplen); } #endif DEBUG_PRINTF(3, ("fp[-1]=%#x fp[%zu]=%#x fp=%.78s\n", fp[-1], nplen, fp[nplen], fp)); } } while (fp != NULL && !(fp[-1] == '\n' && fp[nplen] == ' ')); if (!fp) continue; } DEBUG_PRINTF(2, ("found %.78s\n", fp)); /* we have a match! */ for (cp = &fp[nplen]; *cp == ' '; cp++) ; /* nothing */ return (cp); } else { DEBUG_PRINTF(3, ("Ignoring prefix=%s\n", fip->fi_prefix)); } } } while (cp > &pbuf[1]); return (NULL); } static int verify_fingerprint(int fd, const char *path, const char *cp, off_t off) { unsigned char buf[PAGE_SIZE]; const br_hash_class *md; br_hash_compat_context mctx; size_t hlen; int n; if (strncmp(cp, "no_hash", 7) == 0) { return (VE_FINGERPRINT_IGNORE); } else if (strncmp(cp, "sha256=", 7) == 0) { md = &br_sha256_vtable; hlen = br_sha256_SIZE; cp += 7; #ifdef VE_SHA1_SUPPORT } else if (strncmp(cp, "sha1=", 5) == 0) { md = &br_sha1_vtable; hlen = br_sha1_SIZE; cp += 5; #endif #ifdef VE_SHA384_SUPPORT } else if (strncmp(cp, "sha384=", 7) == 0) { md = &br_sha384_vtable; hlen = br_sha384_SIZE; cp += 7; #endif #ifdef VE_SHA512_SUPPORT } else if (strncmp(cp, "sha512=", 7) == 0) { md = &br_sha512_vtable; hlen = br_sha512_SIZE; cp += 7; #endif } else { ve_error_set("%s: no supported fingerprint", path); return (VE_FINGERPRINT_UNKNOWN); } md->init(&mctx.vtable); if (off) lseek(fd, 0, SEEK_SET); do { n = read(fd, buf, sizeof(buf)); if (n < 0) return (n); if (n > 0) md->update(&mctx.vtable, buf, n); } while (n > 0); lseek(fd, off, SEEK_SET); return (ve_check_hash(&mctx, md, path, cp, hlen)); } /** * @brief * verify an open file * * @param[in] fd * open descriptor * * @param[in] path * pathname to open * * @param[in] off * current offset * * @return 0, VE_FINGERPRINT_OK or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG */ int verify_fd(int fd, const char *path, off_t off, struct stat *stp) { struct stat st; char *cp; int rc; if (!stp) { if (fstat(fd, &st) == 0) stp = &st; } if (stp && !S_ISREG(stp->st_mode)) return (0); /* not relevant */ cp = fingerprint_info_lookup(fd, path); if (!cp) { ve_error_set("%s: no entry", path); return (VE_FINGERPRINT_NONE); } rc = verify_fingerprint(fd, path, cp, off); switch (rc) { case VE_FINGERPRINT_OK: case VE_FINGERPRINT_IGNORE: case VE_FINGERPRINT_UNKNOWN: return (rc); default: return (VE_FINGERPRINT_WRONG); } } /** * @brief * open a file if it can be verified * * @param[in] path * pathname to open * * @param[in] flags * flags for open * * @return fd or VE_FINGERPRINT_NONE, VE_FINGERPRINT_WRONG */ int verify_open(const char *path, int flags) { int fd; int rc; if ((fd = open(path, flags)) >= 0) { if ((rc = verify_fd(fd, path, 0, NULL)) < 0) { close(fd); fd = rc; } } return (fd); } diff --git a/lib/libsecureboot/vepcr.c b/lib/libsecureboot/vepcr.c index a97cb7245832..88128647b086 100644 --- a/lib/libsecureboot/vepcr.c +++ b/lib/libsecureboot/vepcr.c @@ -1,104 +1,169 @@ /*- * 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 __FBSDID("$FreeBSD$"); +#include #include "libsecureboot-priv.h" /* * To support measured boot without putting a ton * of extra code in the loader, we just maintain * a hash of all the hashes we (attempt to) verify. * The loader can export this for kernel or rc script * to feed to a TPM pcr register - hence the name ve_pcr. * * NOTE: in the current standard the TPM pcr register size is for SHA1, * the fact that we provide a SHA256 hash should not matter * as long as we are consistent - it can be truncated or hashed * before feeding to TPM. */ static const br_hash_class *pcr_md = NULL; static br_hash_compat_context pcr_ctx; static size_t pcr_hlen = 0; -static int pcr_updating; +static int pcr_updating = -1; + +struct hashed_info { + const char *hi_path; + const char *hi_basename; + STAILQ_ENTRY(hashed_info) entries; +}; + +static STAILQ_HEAD(, hashed_info) hi_list; + /** * @brief initialize pcr context * * Real TPM registers only hold a SHA1 hash * but we use SHA256 */ void ve_pcr_init(void) { - pcr_updating = 0; - pcr_hlen = br_sha256_SIZE; - pcr_md = &br_sha256_vtable; - pcr_md->init(&pcr_ctx.vtable); + if (pcr_updating < 0) { + pcr_updating = 0; + pcr_hlen = br_sha256_SIZE; + pcr_md = &br_sha256_vtable; + pcr_md->init(&pcr_ctx.vtable); + STAILQ_INIT(&hi_list); + } } /** * @brief get pcr_updating state */ int ve_pcr_updating_get(void) { return (pcr_updating); } /** * @brief set pcr_updating state */ void ve_pcr_updating_set(int updating) { pcr_updating = updating; } /** * @brief update pcr context */ void -ve_pcr_update(unsigned char *data, size_t dlen) +ve_pcr_update(const char *path, unsigned char *data, size_t dlen) { - if (pcr_updating != 0 && pcr_md != NULL) + struct hashed_info *hip; + + if (pcr_updating > 0 && pcr_md != NULL) { pcr_md->update(&pcr_ctx.vtable, data, dlen); + /* if mallocs fail, measured boot will likely fail too */ + if ((hip = malloc(sizeof(struct hashed_info)))) { + hip->hi_path = strdup(path); + if (!hip->hi_path) { + free(hip); + return; + } + hip->hi_basename = strrchr(hip->hi_path, '/'); + if (hip->hi_basename) { + hip->hi_basename++; + } else { + hip->hi_basename = hip->hi_path; + } + STAILQ_INSERT_TAIL(&hi_list, hip, entries); + } + } } /** * @brief get pcr result */ ssize_t ve_pcr_get(unsigned char *buf, size_t sz) { if (!pcr_md) return (-1); if (sz < pcr_hlen) return (-1); pcr_md->out(&pcr_ctx.vtable, buf); return (pcr_hlen); } +/** + * @brief get list of paths in prc + */ +char * +ve_pcr_hashed_get(int flags) +{ + const char *cp; + char *hinfo; + struct hashed_info *hip; + size_t nbytes; + size_t x; + int n; + + n = 0; + nbytes = x = 0; + hinfo = NULL; + STAILQ_FOREACH(hip, &hi_list, entries) { + nbytes += 1 + strlen(flags ? hip->hi_basename : hip->hi_path); + } + if (nbytes > 1) { + hinfo = malloc(nbytes + 2); + if (hinfo) { + STAILQ_FOREACH(hip, &hi_list, entries) { + cp = flags ? hip->hi_basename : hip->hi_path; + n = snprintf(&hinfo[x], nbytes - x, "%s,", cp); + x += n; + } + if (x > 0) { + hinfo[x-1] = '\0'; + } + } + } + return hinfo; +} diff --git a/lib/libsecureboot/verify_file.c b/lib/libsecureboot/verify_file.c index eee749667759..20fc0ae4ae78 100644 --- a/lib/libsecureboot/verify_file.c +++ b/lib/libsecureboot/verify_file.c @@ -1,526 +1,540 @@ /*- * 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. */ /* * Routines to verify files loaded. */ #include __FBSDID("$FreeBSD$"); #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. */ static int ve_status[SOPEN_MAX+1]; static int ve_status_state; struct verify_status; struct verify_status *verified_files = NULL; static int loaded_manifests = 0; /* have we loaded anything? */ #define VE_STATUS_NONE 1 #define VE_STATUS_VALID 2 /** * @brief set ve status for fd */ static 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; - 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); + 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); + } } return (VE_NOT_CHECKED); } /* 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)); vsp->vs_next = verified_files; vsp->vs_dev = stp->st_dev; vsp->vs_ino = stp->st_ino; vsp->vs_status = status; verified_files = vsp; } /** * @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, VEF_VERBOSE); 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); 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 #ifndef VE_VERBOSE_DEFAULT # define VE_VERBOSE_DEFAULT 0 #endif static int severity_guess(const char *filename) { const char *cp; /* Some files like *.conf and *.hints may be unsigned */ if ((cp = strrchr(filename, '.'))) { if (strcmp(cp, ".conf") == 0 || strcmp(cp, ".cookie") == 0 || strcmp(cp, ".hints") == 0) return (VE_TRY); } 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, int *verbose) { 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 = 1; /* warn of anything unverified */ /* 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 = 1; } else if (strcmp(tweak, "quiet") == 0) { *verbose = 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 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(); #ifndef UNIT_TEST ve_debug_set(getenv_int("VE_DEBUG_LEVEL", VE_DEBUG_LEVEL)); #endif /* 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, - ("caller=%s,fd=%d,name='%s',off=%lld,dev=%lld,ino=%lld\n", + ("verify_prep: caller=%s,fd=%d,name='%s',off=%lld,dev=%lld,ino=%lld\n", caller, fd, filename, (long long)off, (long long)stp->st_dev, (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); } 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 once; static int accept_no_fp = ACCEPT_NO_FP_DEFAULT; static int verbose = VE_VERBOSE_DEFAULT; struct stat st; char *cp; int rc; rc = verify_prep(fd, filename, off, &st, caller); if (!rc) return (0); if (!once) { once++; verbose = getenv_int("VE_VERBOSE", VE_VERBOSE_DEFAULT); } if (rc != VE_FINGERPRINT_WRONG && loaded_manifests) { 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 if ((rc = verify_fd(fd, filename, off, &st)) >= 0) { if (verbose || severity > VE_WANT) { #if defined(VE_DEBUG_LEVEL) && VE_DEBUG_LEVEL > 0 printf("%serified %s %llu,%llu\n", (rc == VE_FINGERPRINT_IGNORE) ? "Unv" : "V", filename, (long long)st.st_dev, (long long)st.st_ino); #else printf("%serified %s\n", (rc == VE_FINGERPRINT_IGNORE) ? "Unv" : "V", filename); #endif } 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, &verbose); } } } add_verify_status(&st, rc); ve_status_set(fd, rc); return (rc); } if (severity || verbose || rc == VE_FINGERPRINT_WRONG) printf("Unverified: %s\n", ve_error_get()); 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); } #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)); + free(hinfo); + } } } #endif } diff --git a/lib/libsecureboot/vets.c b/lib/libsecureboot/vets.c index 7d974fc54115..3a82592ea699 100644 --- a/lib/libsecureboot/vets.c +++ b/lib/libsecureboot/vets.c @@ -1,1017 +1,1023 @@ /*- * 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. */ #include __FBSDID("$FreeBSD$"); /** * @file vets.c - trust store * @brief verify signatures * * We leverage code from BearSSL www.bearssl.org */ #include #include #define NEED_BRSSL_H #include "libsecureboot-priv.h" #include #include #ifndef TRUST_ANCHOR_STR # define TRUST_ANCHOR_STR ta_PEM #endif #define SECONDS_PER_DAY 86400 +#define SECONDS_PER_YEAR 365 * SECONDS_PER_DAY +#ifndef VE_UTC_MAX_JUMP +# define VE_UTC_MAX_JUMP 20 * SECONDS_PER_YEAR +#endif #define X509_DAYS_TO_UTC0 719528 int DebugVe = 0; typedef VECTOR(br_x509_certificate) cert_list; typedef VECTOR(hash_data) digest_list; static anchor_list trust_anchors = VEC_INIT; static anchor_list forbidden_anchors = VEC_INIT; static digest_list forbidden_digests = VEC_INIT; static int anchor_verbose = 0; void ve_anchor_verbose_set(int n) { anchor_verbose = n; } int ve_anchor_verbose_get(void) { return (anchor_verbose); } void ve_debug_set(int n) { DebugVe = n; } static char ebuf[512]; char * ve_error_get(void) { return (ebuf); } int ve_error_set(const char *fmt, ...) { int rc; va_list ap; va_start(ap, fmt); ebuf[0] = '\0'; rc = 0; if (fmt) { #ifdef STAND_H vsprintf(ebuf, fmt, ap); /* no vsnprintf in libstand */ ebuf[sizeof(ebuf) - 1] = '\0'; rc = strlen(ebuf); #else rc = vsnprintf(ebuf, sizeof(ebuf), fmt, ap); #endif } va_end(ap); return (rc); } /* this is the time we use for verifying certs */ static time_t ve_utc = 0; /** * @brief * set ve_utc used for certificate verification * * @param[in] utc - * time - ignored unless greater than current value. + * time - ignored unless greater than current value + * and not a leap of 20 years or more. */ void ve_utc_set(time_t utc) { - if (utc > ve_utc) { + if (utc > ve_utc && + (ve_utc == 0 || (utc - ve_utc) < VE_UTC_MAX_JUMP)) { DEBUG_PRINTF(2, ("Set ve_utc=%jd\n", (intmax_t)utc)); ve_utc = utc; } } static void free_cert_contents(br_x509_certificate *xc) { xfree(xc->data); } /* * a bit of a dance to get commonName from a certificate */ static char * x509_cn_get(br_x509_certificate *xc, char *buf, size_t len) { br_x509_minimal_context mc; br_name_element cn; unsigned char cn_oid[4]; int err; if (buf == NULL) return (buf); /* * We want the commonName field * the OID we want is 2,5,4,3 - but DER encoded */ cn_oid[0] = 3; cn_oid[1] = 0x55; cn_oid[2] = 4; cn_oid[3] = 3; cn.oid = cn_oid; cn.buf = buf; cn.len = len; cn.buf[0] = '\0'; br_x509_minimal_init(&mc, &br_sha256_vtable, NULL, 0); br_x509_minimal_set_name_elements(&mc, &cn, 1); /* the below actually does the work - updates cn.status */ mc.vtable->start_chain(&mc.vtable, NULL); mc.vtable->start_cert(&mc.vtable, xc->data_len); mc.vtable->append(&mc.vtable, xc->data, xc->data_len); mc.vtable->end_cert(&mc.vtable); /* we don' actually care about cert status - just its name */ err = mc.vtable->end_chain(&mc.vtable); if (!cn.status) buf = NULL; return (buf); } /* ASN parsing related defines */ #define ASN1_PRIMITIVE_TAG 0x1F #define ASN1_INF_LENGTH 0x80 #define ASN1_LENGTH_MASK 0x7F /* * Get TBS part of certificate. * Since BearSSL doesn't provide any API to do this, * it has to be implemented here. */ static void* X509_to_tbs(unsigned char* cert, size_t* output_size) { unsigned char *result; size_t tbs_size; int size, i; if (cert == NULL) return (NULL); /* Strip two sequences to get to the TBS section */ for (i = 0; i < 2; i++) { /* * XXX: We don't need to support extended tags since * they should not be present in certificates. */ if ((*cert & ASN1_PRIMITIVE_TAG) == ASN1_PRIMITIVE_TAG) return (NULL); cert++; if (*cert == ASN1_INF_LENGTH) return (NULL); size = *cert & ASN1_LENGTH_MASK; tbs_size = 0; /* Size can either be stored on a single or multiple bytes */ if (*cert & (ASN1_LENGTH_MASK + 1)) { cert++; while (*cert == 0 && size > 0) { cert++; size--; } while (size-- > 0) { tbs_size <<= 8; tbs_size |= *(cert++); } } if (i == 0) result = cert; } tbs_size += (cert - result); if (output_size != NULL) *output_size = tbs_size; return (result); } void ve_forbidden_digest_add(hash_data *digest, size_t num) { while (num--) VEC_ADD(forbidden_digests, digest[num]); } static size_t ve_anchors_add(br_x509_certificate *xcs, size_t num, anchor_list *anchors, const char *anchors_name) { br_x509_trust_anchor ta; size_t u; for (u = 0; u < num; u++) { if (certificate_to_trust_anchor_inner(&ta, &xcs[u]) < 0) { break; } VEC_ADD(*anchors, ta); if (anchor_verbose && anchors_name) { char buf[64]; char *cp; cp = x509_cn_get(&xcs[u], buf, sizeof(buf)); if (cp) { printf("x509_anchor(%s) %s\n", cp, anchors_name); } } } return (u); } /** * @brief * add certs to our trust store */ size_t ve_trust_anchors_add(br_x509_certificate *xcs, size_t num) { return (ve_anchors_add(xcs, num, &trust_anchors, "trusted")); } size_t ve_forbidden_anchors_add(br_x509_certificate *xcs, size_t num) { return (ve_anchors_add(xcs, num, &forbidden_anchors, "forbidden")); } /** * @brief add trust anchors in buf * * Assume buf contains x509 certificates, but if not and * we support OpenPGP try adding as that. * * @return number of anchors added */ size_t ve_trust_anchors_add_buf(unsigned char *buf, size_t len) { br_x509_certificate *xcs; size_t num; num = 0; xcs = parse_certificates(buf, len, &num); if (xcs != NULL) { num = ve_trust_anchors_add(xcs, num); #ifdef VE_OPENPGP_SUPPORT } else { num = openpgp_trust_add_buf(buf, len); #endif } return (num); } /** * @brief revoke trust anchors in buf * * Assume buf contains x509 certificates, but if not and * we support OpenPGP try revoking keyId * * @return number of anchors revoked */ size_t ve_trust_anchors_revoke(unsigned char *buf, size_t len) { br_x509_certificate *xcs; size_t num; num = 0; xcs = parse_certificates(buf, len, &num); if (xcs != NULL) { num = ve_forbidden_anchors_add(xcs, num); #ifdef VE_OPENPGP_SUPPORT } else { if (buf[len - 1] == '\n') buf[len - 1] = '\0'; num = openpgp_trust_revoke((char *)buf); #endif } return (num); } /** * @brief * initialize our trust_anchors from ta_PEM */ int ve_trust_init(void) { static int once = -1; if (once >= 0) return (once); once = 0; /* to be sure */ - ve_utc_set(time(NULL)); #ifdef BUILD_UTC - ve_utc_set(BUILD_UTC); /* just in case */ + ve_utc_set(BUILD_UTC); /* ensure sanity */ #endif + ve_utc_set(time(NULL)); ve_error_set(NULL); /* make sure it is empty */ #ifdef VE_PCR_SUPPORT ve_pcr_init(); #endif #ifdef TRUST_ANCHOR_STR ve_trust_anchors_add_buf(__DECONST(unsigned char *, TRUST_ANCHOR_STR), sizeof(TRUST_ANCHOR_STR)); #endif once = (int) VEC_LEN(trust_anchors); #ifdef VE_OPENPGP_SUPPORT once += openpgp_trust_init(); #endif return (once); } /** * if we can verify the certificate chain in "certs", * return the public key and if "xcp" is !NULL the associated * certificate */ static br_x509_pkey * verify_signer_xcs(br_x509_certificate *xcs, size_t num, br_name_element *elts, size_t num_elts, anchor_list *anchors) { br_x509_minimal_context mc; br_x509_certificate *xc; size_t u; cert_list chain = VEC_INIT; const br_x509_pkey *tpk; br_x509_pkey *pk; unsigned int usages; int err; DEBUG_PRINTF(5, ("verify_signer: %zu certs in chain\n", num)); VEC_ADDMANY(chain, xcs, num); if (VEC_LEN(chain) == 0) { ve_error_set("ERROR: no/invalid certificate chain\n"); return (NULL); } DEBUG_PRINTF(5, ("verify_signer: %zu trust anchors\n", VEC_LEN(*anchors))); br_x509_minimal_init(&mc, &br_sha256_vtable, &VEC_ELT(*anchors, 0), VEC_LEN(*anchors)); #ifdef VE_ECDSA_SUPPORT br_x509_minimal_set_ecdsa(&mc, &br_ec_prime_i31, &br_ecdsa_i31_vrfy_asn1); #endif #ifdef VE_RSA_SUPPORT br_x509_minimal_set_rsa(&mc, &br_rsa_i31_pkcs1_vrfy); #endif #if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT) /* This is deprecated! do not enable unless you absoultely have to */ br_x509_minimal_set_hash(&mc, br_sha1_ID, &br_sha1_vtable); #endif br_x509_minimal_set_hash(&mc, br_sha256_ID, &br_sha256_vtable); #ifdef VE_SHA384_SUPPORT br_x509_minimal_set_hash(&mc, br_sha384_ID, &br_sha384_vtable); #endif #ifdef VE_SHA512_SUPPORT br_x509_minimal_set_hash(&mc, br_sha512_ID, &br_sha512_vtable); #endif br_x509_minimal_set_name_elements(&mc, elts, num_elts); #ifdef _STANDALONE /* * Clock is probably bogus so we use ve_utc. */ mc.days = (ve_utc / SECONDS_PER_DAY) + X509_DAYS_TO_UTC0; mc.seconds = (ve_utc % SECONDS_PER_DAY); #endif mc.vtable->start_chain(&mc.vtable, NULL); for (u = 0; u < VEC_LEN(chain); u ++) { xc = &VEC_ELT(chain, u); mc.vtable->start_cert(&mc.vtable, xc->data_len); mc.vtable->append(&mc.vtable, xc->data, xc->data_len); mc.vtable->end_cert(&mc.vtable); switch (mc.err) { case 0: case BR_ERR_X509_OK: case BR_ERR_X509_EXPIRED: break; default: printf("u=%zu mc.err=%d\n", u, mc.err); break; } } err = mc.vtable->end_chain(&mc.vtable); pk = NULL; if (err) { ve_error_set("Validation failed, err = %d", err); } else { tpk = mc.vtable->get_pkey(&mc.vtable, &usages); if (tpk != NULL) { pk = xpkeydup(tpk); } } VEC_CLEAR(chain); return (pk); } /* * Check if digest of one of the certificates from verified chain * is present in the forbidden database. * Since UEFI allows to store three types of digests * all of them have to be checked separately. */ static int check_forbidden_digests(br_x509_certificate *xcs, size_t num) { unsigned char sha256_digest[br_sha256_SIZE]; unsigned char sha384_digest[br_sha384_SIZE]; unsigned char sha512_digest[br_sha512_SIZE]; void *tbs; hash_data *digest; br_hash_compat_context ctx; const br_hash_class *md; size_t tbs_len, i; int have_sha256, have_sha384, have_sha512; if (VEC_LEN(forbidden_digests) == 0) return (0); /* * Iterate through certificates, extract their To-Be-Signed section, * and compare its digest against the ones in the forbidden database. */ while (num--) { tbs = X509_to_tbs(xcs[num].data, &tbs_len); if (tbs == NULL) { printf("Failed to obtain TBS part of certificate\n"); return (1); } have_sha256 = have_sha384 = have_sha512 = 0; for (i = 0; i < VEC_LEN(forbidden_digests); i++) { digest = &VEC_ELT(forbidden_digests, i); switch (digest->hash_size) { case br_sha256_SIZE: if (!have_sha256) { have_sha256 = 1; md = &br_sha256_vtable; md->init(&ctx.vtable); md->update(&ctx.vtable, tbs, tbs_len); md->out(&ctx.vtable, sha256_digest); } if (!memcmp(sha256_digest, digest->data, br_sha256_SIZE)) return (1); break; case br_sha384_SIZE: if (!have_sha384) { have_sha384 = 1; md = &br_sha384_vtable; md->init(&ctx.vtable); md->update(&ctx.vtable, tbs, tbs_len); md->out(&ctx.vtable, sha384_digest); } if (!memcmp(sha384_digest, digest->data, br_sha384_SIZE)) return (1); break; case br_sha512_SIZE: if (!have_sha512) { have_sha512 = 1; md = &br_sha512_vtable; md->init(&ctx.vtable); md->update(&ctx.vtable, tbs, tbs_len); md->out(&ctx.vtable, sha512_digest); } if (!memcmp(sha512_digest, digest->data, br_sha512_SIZE)) return (1); break; } } } return (0); } static br_x509_pkey * verify_signer(const char *certs, br_name_element *elts, size_t num_elts) { br_x509_certificate *xcs; br_x509_pkey *pk; size_t num; pk = NULL; ve_trust_init(); xcs = read_certificates(certs, &num); if (xcs == NULL) { ve_error_set("cannot read certificates\n"); return (NULL); } /* * Check if either * 1. There is a direct match between cert from forbidden_anchors * and a cert from chain. * 2. CA that signed the chain is found in forbidden_anchors. */ if (VEC_LEN(forbidden_anchors) > 0) pk = verify_signer_xcs(xcs, num, elts, num_elts, &forbidden_anchors); if (pk != NULL) { ve_error_set("Certificate is on forbidden list\n"); xfreepkey(pk); pk = NULL; goto out; } pk = verify_signer_xcs(xcs, num, elts, num_elts, &trust_anchors); if (pk == NULL) goto out; /* * Check if hash of tbs part of any certificate in chain * is on the forbidden list. */ if (check_forbidden_digests(xcs, num)) { ve_error_set("Certificate hash is on forbidden list\n"); xfreepkey(pk); pk = NULL; } out: free_certificates(xcs, num); return (pk); } /** * we need a hex digest including trailing newline below */ char * hexdigest(char *buf, size_t bufsz, unsigned char *foo, size_t foo_len) { char const hex2ascii[] = "0123456789abcdef"; size_t i; /* every binary byte is 2 chars in hex + newline + null */ if (bufsz < (2 * foo_len) + 2) return (NULL); for (i = 0; i < foo_len; i++) { buf[i * 2] = hex2ascii[foo[i] >> 4]; buf[i * 2 + 1] = hex2ascii[foo[i] & 0x0f]; } buf[i * 2] = 0x0A; /* we also want a newline */ buf[i * 2 + 1] = '\0'; return (buf); } /** * @brief * verify file against sigfile using pk * * When we generated the signature in sigfile, * we hashed (sha256) file, and sent that to signing server * which hashed (sha256) that hash. * * To verify we need to replicate that result. * * @param[in] pk * br_x509_pkey * * @paramp[in] file * file to be verified * * @param[in] sigfile * signature (PEM encoded) * * @return NULL on error, otherwise content of file. */ #ifdef VE_ECDSA_SUPPORT static unsigned char * verify_ec(br_x509_pkey *pk, const char *file, const char *sigfile) { #ifdef VE_ECDSA_HASH_AGAIN char *hex, hexbuf[br_sha512_SIZE * 2 + 2]; #endif unsigned char rhbuf[br_sha512_SIZE]; br_sha256_context ctx; unsigned char *fcp, *scp; size_t flen, slen, plen; pem_object *po; const br_ec_impl *ec; br_ecdsa_vrfy vrfy; if ((fcp = read_file(file, &flen)) == NULL) return (NULL); if ((scp = read_file(sigfile, &slen)) == NULL) { free(fcp); return (NULL); } if ((po = decode_pem(scp, slen, &plen)) == NULL) { free(fcp); free(scp); return (NULL); } br_sha256_init(&ctx); br_sha256_update(&ctx, fcp, flen); br_sha256_out(&ctx, rhbuf); #ifdef VE_ECDSA_HASH_AGAIN hex = hexdigest(hexbuf, sizeof(hexbuf), rhbuf, br_sha256_SIZE); /* now hash that */ if (hex) { br_sha256_init(&ctx); br_sha256_update(&ctx, hex, strlen(hex)); br_sha256_out(&ctx, rhbuf); } #endif ec = br_ec_get_default(); vrfy = br_ecdsa_vrfy_asn1_get_default(); if (!vrfy(ec, rhbuf, br_sha256_SIZE, &pk->key.ec, po->data, po->data_len)) { free(fcp); fcp = NULL; } free(scp); return (fcp); } #endif #if defined(VE_RSA_SUPPORT) || defined(VE_OPENPGP_SUPPORT) /** * @brief verify an rsa digest * * @return 0 on failure */ int verify_rsa_digest (br_rsa_public_key *pkey, const unsigned char *hash_oid, unsigned char *mdata, size_t mlen, unsigned char *sdata, size_t slen) { br_rsa_pkcs1_vrfy vrfy; unsigned char vhbuf[br_sha512_SIZE]; vrfy = br_rsa_pkcs1_vrfy_get_default(); if (!vrfy(sdata, slen, hash_oid, mlen, pkey, vhbuf) || memcmp(vhbuf, mdata, mlen) != 0) { return (0); /* fail */ } return (1); /* ok */ } #endif /** * @brief * verify file against sigfile using pk * * When we generated the signature in sigfile, * we hashed (sha256) file, and sent that to signing server * which hashed (sha256) that hash. * * Or (deprecated) we simply used sha1 hash directly. * * To verify we need to replicate that result. * * @param[in] pk * br_x509_pkey * * @paramp[in] file * file to be verified * * @param[in] sigfile * signature (PEM encoded) * * @return NULL on error, otherwise content of file. */ #ifdef VE_RSA_SUPPORT static unsigned char * verify_rsa(br_x509_pkey *pk, const char *file, const char *sigfile) { unsigned char rhbuf[br_sha512_SIZE]; const unsigned char *hash_oid; const br_hash_class *md; br_hash_compat_context mctx; unsigned char *fcp, *scp; size_t flen, slen, plen, hlen; pem_object *po; if ((fcp = read_file(file, &flen)) == NULL) return (NULL); if ((scp = read_file(sigfile, &slen)) == NULL) { free(fcp); return (NULL); } if ((po = decode_pem(scp, slen, &plen)) == NULL) { free(fcp); free(scp); return (NULL); } switch (po->data_len) { #if defined(UNIT_TEST) && defined(VE_DEPRECATED_RSA_SHA1_SUPPORT) case 256: // this is our old deprecated sig method md = &br_sha1_vtable; hlen = br_sha1_SIZE; hash_oid = BR_HASH_OID_SHA1; break; #endif default: md = &br_sha256_vtable; hlen = br_sha256_SIZE; hash_oid = BR_HASH_OID_SHA256; break; } md->init(&mctx.vtable); md->update(&mctx.vtable, fcp, flen); md->out(&mctx.vtable, rhbuf); if (!verify_rsa_digest(&pk->key.rsa, hash_oid, rhbuf, hlen, po->data, po->data_len)) { free(fcp); fcp = NULL; } free(scp); return (fcp); } #endif /** * @brief * verify a signature and return content of signed file * * @param[in] sigfile * file containing signature * we derrive path of signed file and certificate change from * this. * * @param[in] flags * only bit 1 significant so far * * @return NULL on error otherwise content of signed file */ unsigned char * verify_sig(const char *sigfile, int flags) { br_x509_pkey *pk; br_name_element cn; char cn_buf[80]; unsigned char cn_oid[4]; char pbuf[MAXPATHLEN]; char *cp; unsigned char *ucp; size_t n; DEBUG_PRINTF(5, ("verify_sig: %s\n", sigfile)); n = strlcpy(pbuf, sigfile, sizeof(pbuf)); if (n > (sizeof(pbuf) - 5) || strcmp(&sigfile[n - 3], "sig") != 0) return (NULL); cp = strcpy(&pbuf[n - 3], "certs"); /* * We want the commonName field * the OID we want is 2,5,4,3 - but DER encoded */ cn_oid[0] = 3; cn_oid[1] = 0x55; cn_oid[2] = 4; cn_oid[3] = 3; cn.oid = cn_oid; cn.buf = cn_buf; cn.len = sizeof(cn_buf); pk = verify_signer(pbuf, &cn, 1); if (!pk) { printf("cannot verify: %s: %s\n", pbuf, ve_error_get()); return (NULL); } for (; cp > pbuf; cp--) { if (*cp == '.') { *cp = '\0'; break; } } switch (pk->key_type) { #ifdef VE_ECDSA_SUPPORT case BR_KEYTYPE_EC: ucp = verify_ec(pk, pbuf, sigfile); break; #endif #ifdef VE_RSA_SUPPORT case BR_KEYTYPE_RSA: ucp = verify_rsa(pk, pbuf, sigfile); break; #endif default: ucp = NULL; /* not supported */ } xfreepkey(pk); if (!ucp) { printf("Unverified %s (%s)\n", pbuf, cn.status ? cn_buf : "unknown"); } else if ((flags & 1) != 0) { printf("Verified %s signed by %s\n", pbuf, cn.status ? cn_buf : "someone we trust"); } return (ucp); } /** * @brief verify hash matches * * We have finished hashing a file, * see if we got the desired result. * * @param[in] ctx * pointer to hash context * * @param[in] md * pointer to hash class * * @param[in] path * name of the file we are checking * * @param[in] want * the expected result * * @param[in] hlen * size of hash output * * @return 0 on success */ int ve_check_hash(br_hash_compat_context *ctx, const br_hash_class *md, const char *path, const char *want, size_t hlen) { char hexbuf[br_sha512_SIZE * 2 + 2]; unsigned char hbuf[br_sha512_SIZE]; char *hex; int rc; int n; md->out(&ctx->vtable, hbuf); #ifdef VE_PCR_SUPPORT - ve_pcr_update(hbuf, hlen); + ve_pcr_update(path, hbuf, hlen); #endif hex = hexdigest(hexbuf, sizeof(hexbuf), hbuf, hlen); if (!hex) return (VE_FINGERPRINT_WRONG); n = 2*hlen; if ((rc = strncmp(hex, want, n))) { ve_error_set("%s: %.*s != %.*s", path, n, hex, n, want); rc = VE_FINGERPRINT_WRONG; } return (rc ? rc : VE_FINGERPRINT_OK); } #ifdef VE_HASH_KAT_STR static int test_hash(const br_hash_class *md, size_t hlen, const char *hname, const char *s, size_t slen, const char *want) { br_hash_compat_context mctx; md->init(&mctx.vtable); md->update(&mctx.vtable, s, slen); return (ve_check_hash(&mctx, md, hname, want, hlen) != VE_FINGERPRINT_OK); } #endif #define ve_test_hash(n, N) \ printf("Testing hash: " #n "\t\t\t\t%s\n", \ test_hash(&br_ ## n ## _vtable, br_ ## n ## _SIZE, #n, \ VE_HASH_KAT_STR, VE_HASH_KAT_STRLEN(VE_HASH_KAT_STR), \ vh_ ## N) ? "Failed" : "Passed") /** * @brief * run self tests on hash and signature verification * * Test that the hash methods (SHA1 and SHA256) work. * Test that we can verify a certificate for each supported * Root CA. * * @return cached result. */ int ve_self_tests(void) { static int once = -1; #ifdef VERIFY_CERTS_STR br_x509_certificate *xcs; br_x509_pkey *pk; br_name_element cn; char cn_buf[80]; unsigned char cn_oid[4]; size_t num; size_t u; #endif if (once >= 0) return (once); once = 0; DEBUG_PRINTF(5, ("Self tests...\n")); #ifdef VE_HASH_KAT_STR #ifdef VE_SHA1_SUPPORT ve_test_hash(sha1, SHA1); #endif #ifdef VE_SHA256_SUPPORT ve_test_hash(sha256, SHA256); #endif #ifdef VE_SHA384_SUPPORT ve_test_hash(sha384, SHA384); #endif #ifdef VE_SHA512_SUPPORT ve_test_hash(sha512, SHA512); #endif #endif #ifdef VERIFY_CERTS_STR xcs = parse_certificates(__DECONST(unsigned char *, VERIFY_CERTS_STR), sizeof(VERIFY_CERTS_STR), &num); if (xcs != NULL) { /* * We want the commonName field * the OID we want is 2,5,4,3 - but DER encoded */ cn_oid[0] = 3; cn_oid[1] = 0x55; cn_oid[2] = 4; cn_oid[3] = 3; cn.oid = cn_oid; cn.buf = cn_buf; for (u = 0; u < num; u ++) { cn.len = sizeof(cn_buf); if ((pk = verify_signer_xcs(&xcs[u], 1, &cn, 1, &trust_anchors)) != NULL) { free_cert_contents(&xcs[u]); once++; printf("Testing verify certificate: %s\tPassed\n", cn.status ? cn_buf : ""); xfreepkey(pk); } } if (!once) printf("Testing verify certificate:\t\t\tFailed\n"); xfree(xcs); } #endif /* VERIFY_CERTS_STR */ #ifdef VE_OPENPGP_SUPPORT if (!openpgp_self_tests()) once++; #endif return (once); } diff --git a/stand/common/install.c b/stand/common/install.c index 0ba73e491b68..032006ec4fd3 100644 --- a/stand/common/install.c +++ b/stand/common/install.c @@ -1,355 +1,366 @@ /*- * Copyright (c) 2008-2014, Juniper Networks, Inc. * 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 ``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 "bootstrap.h" extern struct in_addr servip; extern int pkgfs_init(const char *, struct fs_ops *); extern void pkgfs_cleanup(void); COMMAND_SET(install, "install", "install software package", command_install); static char *inst_kernel; static char **inst_modules; static char *inst_rootfs; static char *inst_loader_rc; static int setpath(char **what, char *val) { char *path; size_t len; int rel; len = strlen(val) + 1; rel = (val[0] != '/') ? 1 : 0; path = malloc(len + rel); if (path == NULL) return (ENOMEM); path[0] = '/'; strcpy(path + rel, val); *what = path; return (0); } static int setmultipath(char ***what, char *val) { char *s, *v; int count, error, idx; count = 0; v = val; do { count++; s = strchr(v, ','); v = (s == NULL) ? NULL : s + 1; } while (v != NULL); *what = calloc(count + 1, sizeof(char *)); if (*what == NULL) return (ENOMEM); for (idx = 0; idx < count; idx++) { s = strchr(val, ','); if (s != NULL) *s++ = '\0'; error = setpath(*what + idx, val); if (error) return (error); val = s; } return (0); } static int read_metatags(int fd) { char buf[1024]; char *p, *tag, *val; ssize_t fsize; int error; fsize = read(fd, buf, sizeof(buf)); if (fsize == -1) return (errno); /* * Assume that if we read a whole buffer worth of data, we * haven't read the entire file. In other words, the buffer * size must always be larger than the file size. That way * we can append a '\0' and use standard string operations. * Return an error if this is not possible. */ if (fsize == sizeof(buf)) return (ENOMEM); buf[fsize] = '\0'; error = 0; tag = buf; while (!error && *tag != '\0') { val = strchr(tag, '='); if (val == NULL) { error = EINVAL; break; } *val++ = '\0'; p = strchr(val, '\n'); if (p == NULL) { error = EINVAL; break; } *p++ = '\0'; if (strcmp(tag, "KERNEL") == 0) error = setpath(&inst_kernel, val); else if (strcmp(tag, "MODULES") == 0) error = setmultipath(&inst_modules, val); else if (strcmp(tag, "ROOTFS") == 0) error = setpath(&inst_rootfs, val); else if (strcmp(tag, "LOADER_RC") == 0) error = setpath(&inst_loader_rc, val); tag = p; } return (error); } static void cleanup(void) { u_int i; if (inst_kernel != NULL) { free(inst_kernel); inst_kernel = NULL; } if (inst_modules != NULL) { i = 0; while (inst_modules[i] != NULL) free(inst_modules[i++]); free(inst_modules); inst_modules = NULL; } if (inst_rootfs != NULL) { free(inst_rootfs); inst_rootfs = NULL; } if (inst_loader_rc != NULL) { free(inst_loader_rc); inst_loader_rc = NULL; } pkgfs_cleanup(); } /* * usage: install URL * where: URL = (tftp|file)://[host]/ */ static int install(char *pkgname) { static char buf[256]; struct fs_ops *proto; struct preloaded_file *fp; char *s, *currdev; const char *devname; int error, fd, i, local; s = strstr(pkgname, "://"); if (s == NULL) goto invalid_url; i = s - pkgname; if (i == 4 && !strncasecmp(pkgname, "tftp", i)) { devname = "net0"; proto = &tftp_fsops; local = 0; } else if (i == 4 && !strncasecmp(pkgname, "file", i)) { currdev = getenv("currdev"); if (currdev != NULL && strcmp(currdev, "pxe0:") == 0) { devname = "pxe0"; proto = NULL; +#ifdef HOSTPROG + } else if (currdev != NULL && strcmp(currdev, "host0:") == 0) { + extern struct fs_ops host_fsops; + + devname = "host0"; + proto = &host_fsops; +#endif } else { devname = "disk1"; proto = &dosfs_fsops; } local = 1; } else goto invalid_url; s += 3; if (*s == '\0') goto invalid_url; if (*s != '/' ) { if (local) goto invalid_url; pkgname = strchr(s, '/'); if (pkgname == NULL) goto invalid_url; *pkgname = '\0'; servip.s_addr = inet_addr(s); if (servip.s_addr == htonl(INADDR_NONE)) goto invalid_url; setenv("serverip", inet_ntoa(servip), 1); + if (proto == &tftp_fsops) { + tftpip.s_addr = servip.s_addr; + } + *pkgname = '/'; } else pkgname = s; if (strlen(devname) + strlen(pkgname) + 2 > sizeof(buf)) { command_errmsg = "package name too long"; return (CMD_ERROR); } sprintf(buf, "%s:%s", devname, pkgname); setenv("install_package", buf, 1); error = pkgfs_init(buf, proto); if (error) { command_errmsg = "cannot open package"; goto fail; } /* * Point of no return: unload anything that may have been * loaded and prune the environment from harmful variables. */ unload(); unsetenv("vfs.root.mountfrom"); /* * read the metatags file. */ fd = open("/metatags", O_RDONLY); if (fd != -1) { error = read_metatags(fd); close(fd); if (error) { command_errmsg = "cannot load metatags"; goto fail; } } s = (inst_kernel == NULL) ? "/kernel" : inst_kernel; error = mod_loadkld(s, 0, NULL); if (error) { command_errmsg = "cannot load kernel from package"; goto fail; } /* If there is a loader.rc in the package, execute it */ s = (inst_loader_rc == NULL) ? "/loader.rc" : inst_loader_rc; fd = open(s, O_RDONLY); if (fd != -1) { close(fd); error = inter_include(s); if (error == CMD_ERROR) goto fail; } i = 0; while (inst_modules != NULL && inst_modules[i] != NULL) { error = mod_loadkld(inst_modules[i], 0, NULL); if (error) { command_errmsg = "cannot load module(s) from package"; goto fail; } i++; } s = (inst_rootfs == NULL) ? "/install.iso" : inst_rootfs; if (file_loadraw(s, "mfs_root", 1) == NULL) { error = errno; command_errmsg = "cannot load root file system"; goto fail; } cleanup(); fp = file_findfile(NULL, NULL); if (fp != NULL) file_formats[fp->f_loader]->l_exec(fp); error = CMD_ERROR; command_errmsg = "unable to start installation"; fail: sprintf(buf, "%s (error %d)", command_errmsg, error); cleanup(); unload(); exclusive_file_system = NULL; command_errmsg = buf; /* buf is static. */ return (CMD_ERROR); invalid_url: command_errmsg = "invalid URL"; return (CMD_ERROR); } static int command_install(int argc, char *argv[]) { int argidx; unsetenv("install_format"); argidx = 1; while (1) { if (argc == argidx) { command_errmsg = "usage: install [--format] "; return (CMD_ERROR); } if (!strcmp(argv[argidx], "--format")) { setenv("install_format", "yes", 1); argidx++; continue; } break; } return (install(argv[argidx])); } diff --git a/stand/libsa/pkgfs.c b/stand/libsa/pkgfs.c index e78ba3302e1f..df87a7035153 100644 --- a/stand/libsa/pkgfs.c +++ b/stand/libsa/pkgfs.c @@ -1,792 +1,809 @@ /*- * Copyright (c) 2007-2014, Juniper Networks, Inc. * 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 __FBSDID("$FreeBSD$"); #include "stand.h" #include #include #include #include #ifdef PKGFS_DEBUG #define DBG(x) printf x #else #define DBG(x) #endif static int pkg_open(const char *, struct open_file *); static int pkg_close(struct open_file *); static int pkg_read(struct open_file *, void *, size_t, size_t *); static off_t pkg_seek(struct open_file *, off_t, int); static int pkg_stat(struct open_file *, struct stat *); static int pkg_readdir(struct open_file *, struct dirent *); +static off_t pkg_atol(const char *, unsigned); struct fs_ops pkgfs_fsops = { "pkg", pkg_open, pkg_close, pkg_read, null_write, pkg_seek, pkg_stat, pkg_readdir }; #define PKG_BUFSIZE 512 -#define PKG_MAXCACHESZ 4096 +#define PKG_MAXCACHESZ 16384 #define PKG_FILEEXT ".tgz" /* * Layout of POSIX 'ustar' header. */ struct ustar_hdr { char ut_name[100]; char ut_mode[8]; char ut_uid[8]; char ut_gid[8]; char ut_size[12]; char ut_mtime[12]; char ut_checksum[8]; char ut_typeflag[1]; char ut_linkname[100]; char ut_magic[6]; /* For POSIX: "ustar\0" */ char ut_version[2]; /* For POSIX: "00" */ char ut_uname[32]; char ut_gname[32]; char ut_rdevmajor[8]; char ut_rdevminor[8]; union { struct { char prefix[155]; } posix; struct { char atime[12]; char ctime[12]; char offset[12]; char longnames[4]; char unused[1]; struct gnu_sparse { char offset[12]; char numbytes[12]; } sparse[4]; char isextended[1]; char realsize[12]; } gnu; } u; u_char __padding[12]; }; struct package; struct tarfile { struct package *tf_pkg; struct tarfile *tf_next; struct ustar_hdr tf_hdr; off_t tf_ofs; off_t tf_size; off_t tf_fp; size_t tf_cachesz; void *tf_cache; }; struct package { struct package *pkg_chain; int pkg_fd; off_t pkg_ofs; z_stream pkg_zs; struct tarfile *pkg_first; struct tarfile *pkg_last; u_char pkg_buf[PKG_BUFSIZE]; }; static struct package *package = NULL; static int new_package(int, struct package **); void pkgfs_cleanup(void) { struct package *chain; struct tarfile *tf, *tfn; while (package != NULL) { inflateEnd(&package->pkg_zs); close(package->pkg_fd); tf = package->pkg_first; while (tf != NULL) { tfn = tf->tf_next; if (tf->tf_cachesz > 0) free(tf->tf_cache); free(tf); tf = tfn; } chain = package->pkg_chain; free(package); package = chain; } } int pkgfs_init(const char *pkgname, struct fs_ops *proto) { struct package *pkg; int error, fd; pkg = NULL; if (proto != &pkgfs_fsops) pkgfs_cleanup(); exclusive_file_system = proto; fd = open(pkgname, O_RDONLY); exclusive_file_system = NULL; if (fd == -1) return (errno); error = new_package(fd, &pkg); if (error) { close(fd); return (error); } if (pkg == NULL) return (EDOOFUS); pkg->pkg_chain = package; package = pkg; exclusive_file_system = &pkgfs_fsops; return (0); } static int get_mode(struct tarfile *); static int get_zipped(struct package *, void *, size_t); static int new_package(int, struct package **); static struct tarfile *scan_tarfile(struct package *, struct tarfile *); static int pkg_open(const char *fn, struct open_file *f) { struct tarfile *tf; if (fn == NULL || f == NULL) return (EINVAL); if (package == NULL) return (ENXIO); /* * We can only read from a package, so reject request to open * for write-only or read-write. */ if (f->f_flags != F_READ) return (EPERM); /* * Scan the file headers for the named file. We stop scanning * at the first filename that has the .pkg extension. This is * a package within a package. We assume we have all the files * we need up-front and without having to dig within nested * packages. * * Note that we preserve streaming properties as much as possible. */ while (*fn == '/') fn++; /* * Allow opening of the root directory for use by readdir() * to support listing files in the package. */ if (*fn == '\0') { f->f_fsdata = NULL; return (0); } tf = scan_tarfile(package, NULL); while (tf != NULL) { if (strcmp(fn, tf->tf_hdr.ut_name) == 0) { f->f_fsdata = tf; tf->tf_fp = 0; /* Reset the file pointer. */ return (0); } tf = scan_tarfile(package, tf); } return (errno); } static int pkg_close(struct open_file *f) { struct tarfile *tf; tf = (struct tarfile *)f->f_fsdata; if (tf == NULL) return (0); /* * Free up the cache if we read all of the file. */ if (tf->tf_fp == tf->tf_size && tf->tf_cachesz > 0) { free(tf->tf_cache); tf->tf_cachesz = 0; } return (0); } static int pkg_read(struct open_file *f, void *buf, size_t size, size_t *res) { struct tarfile *tf; char *p; off_t fp; size_t sz; tf = (struct tarfile *)f->f_fsdata; if (tf == NULL) { if (res != NULL) *res = size; return (EBADF); } fp = tf->tf_fp; p = buf; sz = 0; while (size > 0) { sz = tf->tf_size - fp; if (fp < tf->tf_cachesz && tf->tf_cachesz < tf->tf_size) sz = tf->tf_cachesz - fp; if (size < sz) sz = size; if (sz == 0) break; if (fp < tf->tf_cachesz) { /* Satisfy the request from cache. */ memcpy(p, tf->tf_cache + fp, sz); fp += sz; p += sz; size -= sz; continue; } if (get_zipped(tf->tf_pkg, p, sz) == -1) { sz = -1; break; } fp += sz; p += sz; size -= sz; if (tf->tf_cachesz != 0) continue; tf->tf_cachesz = (sz <= PKG_MAXCACHESZ) ? sz : PKG_MAXCACHESZ; tf->tf_cache = malloc(tf->tf_cachesz); if (tf->tf_cache != NULL) memcpy(tf->tf_cache, buf, tf->tf_cachesz); else tf->tf_cachesz = 0; } tf->tf_fp = fp; if (res != NULL) *res = size; return ((sz == -1) ? errno : 0); } static off_t pkg_seek(struct open_file *f, off_t ofs, int whence) { char buf[512]; struct tarfile *tf; off_t delta; + off_t nofs; size_t sz, res; int error; tf = (struct tarfile *)f->f_fsdata; if (tf == NULL) { errno = EBADF; return (-1); } switch (whence) { case SEEK_SET: delta = ofs - tf->tf_fp; break; case SEEK_CUR: delta = ofs; break; case SEEK_END: delta = tf->tf_size - tf->tf_fp + ofs; break; default: errno = EINVAL; return (-1); } if (delta < 0) { + /* seeking backwards - ok if within cache */ + if (tf->tf_cachesz > 0 && tf->tf_fp <= tf->tf_cachesz) { + nofs = tf->tf_fp + delta; + if (nofs >= 0) { + tf->tf_fp = nofs; + return (tf->tf_fp); + } + } DBG(("%s: negative file seek (%jd)\n", __func__, (intmax_t)delta)); errno = ESPIPE; return (-1); } while (delta > 0 && tf->tf_fp < tf->tf_size) { sz = (delta > sizeof(buf)) ? sizeof(buf) : delta; error = pkg_read(f, buf, sz, &res); if (error != 0) { errno = error; return (-1); } delta -= sz - res; } return (tf->tf_fp); } static int pkg_stat(struct open_file *f, struct stat *sb) { struct tarfile *tf; tf = (struct tarfile *)f->f_fsdata; if (tf == NULL) return (EBADF); memset(sb, 0, sizeof(*sb)); sb->st_mode = get_mode(tf); + if ((sb->st_mode & S_IFMT) == 0) { + /* tar file bug - assume regular file */ + sb->st_mode |= S_IFREG; + } sb->st_size = tf->tf_size; sb->st_blocks = (tf->tf_size + 511) / 512; + sb->st_mtime = pkg_atol(tf->tf_hdr.ut_mtime, 12); + sb->st_dev = (off_t)tf->tf_pkg; + sb->st_ino = tf->tf_ofs; /* unique per tf_pkg */ return (0); } static int pkg_readdir(struct open_file *f, struct dirent *d) { struct tarfile *tf; tf = (struct tarfile *)f->f_fsdata; if (tf != NULL) return (EBADF); tf = scan_tarfile(package, NULL); if (tf == NULL) return (ENOENT); d->d_fileno = 0; d->d_reclen = sizeof(*d); d->d_type = DT_REG; memcpy(d->d_name, tf->tf_hdr.ut_name, sizeof(d->d_name)); return (0); } /* * Low-level support functions. */ static int get_byte(struct package *pkg, off_t *op) { int c; if (pkg->pkg_zs.avail_in == 0) { c = read(pkg->pkg_fd, pkg->pkg_buf, PKG_BUFSIZE); if (c <= 0) return (-1); pkg->pkg_zs.avail_in = c; pkg->pkg_zs.next_in = pkg->pkg_buf; } c = *pkg->pkg_zs.next_in; pkg->pkg_zs.next_in++; pkg->pkg_zs.avail_in--; (*op)++; return (c); } static int get_zipped(struct package *pkg, void *buf, size_t bufsz) { int c; pkg->pkg_zs.next_out = buf; pkg->pkg_zs.avail_out = bufsz; while (pkg->pkg_zs.avail_out) { if (pkg->pkg_zs.avail_in == 0) { c = read(pkg->pkg_fd, pkg->pkg_buf, PKG_BUFSIZE); if (c <= 0) { errno = EIO; return (-1); } pkg->pkg_zs.avail_in = c; pkg->pkg_zs.next_in = pkg->pkg_buf; } c = inflate(&pkg->pkg_zs, Z_SYNC_FLUSH); if (c != Z_OK && c != Z_STREAM_END) { errno = EIO; return (-1); } } pkg->pkg_ofs += bufsz; return (0); } static int cache_data(struct tarfile *tf) { struct package *pkg; size_t sz; if (tf == NULL) { DBG(("%s: no file to cache data for?\n", __func__)); errno = EINVAL; return (-1); } pkg = tf->tf_pkg; if (pkg == NULL) { DBG(("%s: no package associated with file?\n", __func__)); errno = EINVAL; return (-1); } if (tf->tf_ofs != pkg->pkg_ofs) { DBG(("%s: caching after partial read of file %s?\n", __func__, tf->tf_hdr.ut_name)); errno = EINVAL; return (-1); } /* We don't cache everything... */ if (tf->tf_size > PKG_MAXCACHESZ) { errno = ENOMEM; return (-1); } /* All files are padded to a multiple of 512 bytes. */ sz = (tf->tf_size + 0x1ff) & ~0x1ff; tf->tf_cache = malloc(sz); if (tf->tf_cache == NULL) { DBG(("%s: could not allocate %d bytes\n", __func__, (int)sz)); errno = ENOMEM; return (-1); } tf->tf_cachesz = sz; return (get_zipped(pkg, tf->tf_cache, sz)); } /* * Note that this implementation does not (and should not!) obey * locale settings; you cannot simply substitute strtol here, since * it does obey locale. */ static off_t pkg_atol8(const char *p, unsigned char_cnt) { int64_t l, limit, last_digit_limit; int digit, sign, base; base = 8; limit = INT64_MAX / base; last_digit_limit = INT64_MAX % base; while (*p == ' ' || *p == '\t') p++; if (*p == '-') { sign = -1; p++; } else sign = 1; l = 0; digit = *p - '0'; while (digit >= 0 && digit < base && char_cnt-- > 0) { if (l>limit || (l == limit && digit > last_digit_limit)) { l = UINT64_MAX; /* Truncate on overflow. */ break; } l = (l * base) + digit; digit = *++p - '0'; } return (sign < 0) ? -l : l; } /* * Parse a base-256 integer. This is just a straight signed binary * value in big-endian order, except that the high-order bit is * ignored. Remember that "int64_t" may or may not be exactly 64 * bits; the implementation here tries to avoid making any assumptions * about the actual size of an int64_t. It does assume we're using * twos-complement arithmetic, though. */ static int64_t pkg_atol256(const char *_p, unsigned char_cnt) { int64_t l, upper_limit, lower_limit; const unsigned char *p = (const unsigned char *)_p; upper_limit = INT64_MAX / 256; lower_limit = INT64_MIN / 256; /* Pad with 1 or 0 bits, depending on sign. */ if ((0x40 & *p) == 0x40) l = (int64_t)-1; else l = 0; l = (l << 6) | (0x3f & *p++); while (--char_cnt > 0) { if (l > upper_limit) { l = INT64_MAX; /* Truncate on overflow */ break; } else if (l < lower_limit) { l = INT64_MIN; break; } l = (l << 8) | (0xff & (int64_t)*p++); } return (l); } static off_t pkg_atol(const char *p, unsigned char_cnt) { /* * Technically, GNU pkg considers a field to be in base-256 * only if the first byte is 0xff or 0x80. */ if (*p & 0x80) return (pkg_atol256(p, char_cnt)); return (pkg_atol8(p, char_cnt)); } static int get_mode(struct tarfile *tf) { return (pkg_atol(tf->tf_hdr.ut_mode, sizeof(tf->tf_hdr.ut_mode))); } /* GZip flag byte */ #define ASCII_FLAG 0x01 /* bit 0 set: file probably ascii text */ #define HEAD_CRC 0x02 /* bit 1 set: header CRC present */ #define EXTRA_FIELD 0x04 /* bit 2 set: extra field present */ #define ORIG_NAME 0x08 /* bit 3 set: original file name present */ #define COMMENT 0x10 /* bit 4 set: file comment present */ #define RESERVED 0xE0 /* bits 5..7: reserved */ static int new_package(int fd, struct package **pp) { struct package *pkg; off_t ofs; int flags, i, error; pkg = malloc(sizeof(*pkg)); if (pkg == NULL) return (ENOMEM); bzero(pkg, sizeof(*pkg)); pkg->pkg_fd = fd; /* * Parse the header. */ error = EFTYPE; ofs = 0; /* Check megic. */ if (get_byte(pkg, &ofs) != 0x1f || get_byte(pkg, &ofs) != 0x8b) goto fail; /* Check method. */ if (get_byte(pkg, &ofs) != Z_DEFLATED) goto fail; /* Check flags. */ flags = get_byte(pkg, &ofs); if (flags & RESERVED) goto fail; /* Skip time, xflags and OS code. */ for (i = 0; i < 6; i++) { if (get_byte(pkg, &ofs) == -1) goto fail; } /* Skip extra field. */ if (flags & EXTRA_FIELD) { i = (get_byte(pkg, &ofs) & 0xff) | ((get_byte(pkg, &ofs) << 8) & 0xff); while (i-- > 0) { if (get_byte(pkg, &ofs) == -1) goto fail; } } /* Skip original file name. */ if (flags & ORIG_NAME) { do { i = get_byte(pkg, &ofs); } while (i != 0 && i != -1); if (i == -1) goto fail; } /* Print the comment if it's there. */ if (flags & COMMENT) { while (1) { i = get_byte(pkg, &ofs); if (i == -1) goto fail; if (i == 0) break; putchar(i); } } /* Skip the CRC. */ if (flags & HEAD_CRC) { if (get_byte(pkg, &ofs) == -1) goto fail; if (get_byte(pkg, &ofs) == -1) goto fail; } /* * Done parsing the ZIP header. Spkgt the inflation engine. */ error = inflateInit2(&pkg->pkg_zs, -15); if (error != Z_OK) goto fail; *pp = pkg; return (0); fail: free(pkg); return (error); } static struct tarfile * scan_tarfile(struct package *pkg, struct tarfile *last) { char buf[512]; struct tarfile *cur; off_t ofs; size_t sz; cur = (last != NULL) ? last->tf_next : pkg->pkg_first; if (cur == NULL) { ofs = (last != NULL) ? last->tf_ofs + last->tf_size : pkg->pkg_ofs; ofs = (ofs + 0x1ff) & ~0x1ff; /* Check if we've reached EOF. */ if (ofs < pkg->pkg_ofs) { errno = ENOSPC; return (NULL); } if (ofs != pkg->pkg_ofs) { if (last != NULL && pkg->pkg_ofs == last->tf_ofs) { if (cache_data(last) == -1) return (NULL); } else { sz = ofs - pkg->pkg_ofs; while (sz != 0) { if (sz > sizeof(buf)) sz = sizeof(buf); if (get_zipped(pkg, buf, sz) == -1) return (NULL); sz = ofs - pkg->pkg_ofs; } } } cur = malloc(sizeof(*cur)); if (cur == NULL) return (NULL); memset(cur, 0, sizeof(*cur)); cur->tf_pkg = pkg; while (1) { if (get_zipped(pkg, &cur->tf_hdr, sizeof(cur->tf_hdr)) == -1) { free(cur); return (NULL); } /* * There are always 2 empty blocks appended to * a PKG. It marks the end of the archive. */ if (strncmp(cur->tf_hdr.ut_magic, "ustar", 5) != 0) { free(cur); errno = ENOSPC; return (NULL); } cur->tf_ofs = pkg->pkg_ofs; cur->tf_size = pkg_atol(cur->tf_hdr.ut_size, sizeof(cur->tf_hdr.ut_size)); if (cur->tf_hdr.ut_name[0] != '+') break; /* * Skip package meta-files. */ ofs = cur->tf_ofs + cur->tf_size; ofs = (ofs + 0x1ff) & ~0x1ff; while (pkg->pkg_ofs < ofs) { if (get_zipped(pkg, buf, sizeof(buf)) == -1) { free(cur); return (NULL); } } } if (last != NULL) last->tf_next = cur; else pkg->pkg_first = cur; pkg->pkg_last = cur; } return (cur); } diff --git a/stand/userboot/test/test.c b/stand/userboot/test/test.c index aaff67717685..301069a4d953 100644 --- a/stand/userboot/test/test.c +++ b/stand/userboot/test/test.c @@ -1,475 +1,480 @@ /*- * Copyright (c) 2011 Google, Inc. * 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. * * $FreeBSD$ */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include char *host_base = NULL; struct termios term, oldterm; char *image; size_t image_size; uint64_t regs[16]; uint64_t pc; int *disk_fd; int disk_index = -1; void test_exit(void *arg, int v); /* * Console i/o */ void test_putc(void *arg, int ch) { char c = ch; write(1, &c, 1); } int test_getc(void *arg) { char c; if (read(0, &c, 1) == 1) return c; return -1; } int test_poll(void *arg) { int n; if (ioctl(0, FIONREAD, &n) >= 0) return (n > 0); return (0); } /* * Host filesystem i/o */ struct test_file { int tf_isdir; size_t tf_size; struct stat tf_stat; union { int fd; DIR *dir; } tf_u; }; int test_open(void *arg, const char *filename, void **h_return) { struct stat st; struct test_file *tf; char path[PATH_MAX]; if (!host_base) return (ENOENT); strlcpy(path, host_base, PATH_MAX); if (path[strlen(path) - 1] == '/') path[strlen(path) - 1] = 0; strlcat(path, filename, PATH_MAX); tf = malloc(sizeof(struct test_file)); if (stat(path, &tf->tf_stat) < 0) { free(tf); return (errno); } tf->tf_size = st.st_size; if (S_ISDIR(tf->tf_stat.st_mode)) { tf->tf_isdir = 1; tf->tf_u.dir = opendir(path); if (!tf->tf_u.dir) goto out; *h_return = tf; return (0); } if (S_ISREG(tf->tf_stat.st_mode)) { tf->tf_isdir = 0; tf->tf_u.fd = open(path, O_RDONLY); if (tf->tf_u.fd < 0) goto out; *h_return = tf; return (0); } out: free(tf); return (EINVAL); } int test_close(void *arg, void *h) { struct test_file *tf = h; if (tf->tf_isdir) closedir(tf->tf_u.dir); else close(tf->tf_u.fd); free(tf); return (0); } int test_isdir(void *arg, void *h) { struct test_file *tf = h; return (tf->tf_isdir); } int test_read(void *arg, void *h, void *dst, size_t size, size_t *resid_return) { struct test_file *tf = h; ssize_t sz; if (tf->tf_isdir) return (EINVAL); sz = read(tf->tf_u.fd, dst, size); if (sz < 0) return (EINVAL); *resid_return = size - sz; return (0); } int test_readdir(void *arg, void *h, uint32_t *fileno_return, uint8_t *type_return, size_t *namelen_return, char *name) { struct test_file *tf = h; struct dirent *dp; if (!tf->tf_isdir) return (EINVAL); dp = readdir(tf->tf_u.dir); if (!dp) return (ENOENT); /* * Note: d_namlen is in the range 0..255 and therefore less * than PATH_MAX so we don't need to test before copying. */ *fileno_return = dp->d_fileno; *type_return = dp->d_type; *namelen_return = dp->d_namlen; memcpy(name, dp->d_name, dp->d_namlen); name[dp->d_namlen] = 0; return (0); } int test_seek(void *arg, void *h, uint64_t offset, int whence) { struct test_file *tf = h; if (tf->tf_isdir) return (EINVAL); if (lseek(tf->tf_u.fd, offset, whence) < 0) return (errno); return (0); } int -test_stat(void *arg, void *h, int *mode_return, int *uid_return, int *gid_return, - uint64_t *size_return) +test_stat(void *arg, void *h, struct stat *stp) { struct test_file *tf = h; - *mode_return = tf->tf_stat.st_mode; - *uid_return = tf->tf_stat.st_uid; - *gid_return = tf->tf_stat.st_gid; - *size_return = tf->tf_stat.st_size; + if (!stp) + return (-1); + memset(stp, 0, sizeof(struct stat)); + stp->st_mode = tf->tf_stat.st_mode; + stp->st_uid = tf->tf_stat.st_uid; + stp->st_gid = tf->tf_stat.st_gid; + stp->st_size = tf->tf_stat.st_size; + stp->st_ino = tf->tf_stat.st_ino; + stp->st_dev = tf->tf_stat.st_dev; + stp->st_mtime = tf->tf_stat.st_mtime; return (0); } /* * Disk image i/o */ int test_diskread(void *arg, int unit, uint64_t offset, void *dst, size_t size, size_t *resid_return) { ssize_t n; if (unit > disk_index || disk_fd[unit] == -1) return (EIO); n = pread(disk_fd[unit], dst, size, offset); if (n < 0) return (errno); *resid_return = size - n; return (0); } int test_diskioctl(void *arg, int unit, u_long cmd, void *data) { struct stat sb; if (unit > disk_index || disk_fd[unit] == -1) return (EBADF); switch (cmd) { case DIOCGSECTORSIZE: *(u_int *)data = 512; break; case DIOCGMEDIASIZE: if (fstat(disk_fd[unit], &sb) == 0) *(off_t *)data = sb.st_size; else return (ENOTTY); break; default: return (ENOTTY); } return (0); } /* * Guest virtual machine i/o * * Note: guest addresses are kernel virtual */ int test_copyin(void *arg, const void *from, uint64_t to, size_t size) { to &= 0x7fffffff; if (to > image_size) return (EFAULT); if (to + size > image_size) size = image_size - to; memcpy(&image[to], from, size); return(0); } int test_copyout(void *arg, uint64_t from, void *to, size_t size) { from &= 0x7fffffff; if (from > image_size) return (EFAULT); if (from + size > image_size) size = image_size - from; memcpy(to, &image[from], size); return(0); } void test_setreg(void *arg, int r, uint64_t v) { if (r < 0 || r >= 16) return; regs[r] = v; } void test_setmsr(void *arg, int r, uint64_t v) { } void test_setcr(void *arg, int r, uint64_t v) { } void test_setgdt(void *arg, uint64_t v, size_t sz) { } void test_exec(void *arg, uint64_t pc) { printf("Execute at 0x%"PRIx64"\n", pc); test_exit(arg, 0); } /* * Misc */ void test_delay(void *arg, int usec) { usleep(usec); } void test_exit(void *arg, int v) { tcsetattr(0, TCSAFLUSH, &oldterm); exit(v); } void test_getmem(void *arg, uint64_t *lowmem, uint64_t *highmem) { *lowmem = 128*1024*1024; *highmem = 0; } char * test_getenv(void *arg, int idx) { static char *vars[] = { "foo=bar", "bar=barbar", NULL }; return (vars[idx]); } struct loader_callbacks cb = { .putc = test_putc, .getc = test_getc, .poll = test_poll, .open = test_open, .close = test_close, .isdir = test_isdir, .read = test_read, .readdir = test_readdir, .seek = test_seek, .stat = test_stat, .diskread = test_diskread, .diskioctl = test_diskioctl, .copyin = test_copyin, .copyout = test_copyout, .setreg = test_setreg, .setmsr = test_setmsr, .setcr = test_setcr, .setgdt = test_setgdt, .exec = test_exec, .delay = test_delay, .exit = test_exit, .getmem = test_getmem, .getenv = test_getenv, }; void usage() { printf("usage: [-b ] [-d ] [-h \n"); exit(1); } int main(int argc, char** argv) { void *h; void (*func)(struct loader_callbacks *, void *, int, int) __dead2; int opt; const char *userboot_obj = "/boot/userboot.so"; while ((opt = getopt(argc, argv, "b:d:h:")) != -1) { switch (opt) { case 'b': userboot_obj = optarg; break; case 'd': disk_index++; disk_fd = reallocarray(disk_fd, disk_index + 1, sizeof (int)); disk_fd[disk_index] = open(optarg, O_RDONLY); if (disk_fd[disk_index] < 0) err(1, "Can't open disk image '%s'", optarg); break; case 'h': host_base = optarg; break; case '?': usage(); } } h = dlopen(userboot_obj, RTLD_LOCAL); if (!h) { printf("%s\n", dlerror()); return (1); } func = dlsym(h, "loader_main"); if (!func) { printf("%s\n", dlerror()); return (1); } image_size = 128*1024*1024; image = malloc(image_size); tcgetattr(0, &term); oldterm = term; term.c_iflag &= ~(ICRNL); term.c_lflag &= ~(ICANON|ECHO); tcsetattr(0, TCSAFLUSH, &term); func(&cb, NULL, USERBOOT_VERSION_3, disk_index + 1); } diff --git a/stand/userboot/userboot.h b/stand/userboot/userboot.h index e8817aeb39a2..de0cdb6605c8 100644 --- a/stand/userboot/userboot.h +++ b/stand/userboot/userboot.h @@ -1,225 +1,224 @@ /*- * Copyright (c) 2011 Doug Rabson * 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. * * $FreeBSD$ */ /* * USERBOOT interface versions */ #define USERBOOT_VERSION_1 1 #define USERBOOT_VERSION_2 2 #define USERBOOT_VERSION_3 3 /* * Version 4 added more generic callbacks for setting up * registers and descriptors. The callback structure is * backward compatible (new callbacks have been added at * the tail end). */ #define USERBOOT_VERSION_4 4 /* * Version 5 added a callback for indicating that the guest * should be restarted with a different interpreter. The callback * structure is still backward compatible. */ #define USERBOOT_VERSION_5 5 /* * Exit codes from the loader */ #define USERBOOT_EXIT_QUIT 1 #define USERBOOT_EXIT_REBOOT 2 struct loader_callbacks { /* * Console i/o */ /* * Wait until a key is pressed on the console and then return it */ int (*getc)(void *arg); /* * Write the character ch to the console */ void (*putc)(void *arg, int ch); /* * Return non-zero if a key can be read from the console */ int (*poll)(void *arg); /* * Host filesystem i/o */ /* * Open a file in the host filesystem */ int (*open)(void *arg, const char *filename, void **h_return); /* * Close a file */ int (*close)(void *arg, void *h); /* * Return non-zero if the file is a directory */ int (*isdir)(void *arg, void *h); /* * Read size bytes from a file. The number of bytes remaining * in dst after reading is returned in *resid_return */ int (*read)(void *arg, void *h, void *dst, size_t size, size_t *resid_return); /* * Read an entry from a directory. The entry's inode number is * returned in *fileno_return, its type in *type_return and * the name length in *namelen_return. The name itself is * copied to the buffer name which must be at least PATH_MAX * in size. */ int (*readdir)(void *arg, void *h, uint32_t *fileno_return, uint8_t *type_return, size_t *namelen_return, char *name); /* * Seek to a location within an open file */ int (*seek)(void *arg, void *h, uint64_t offset, int whence); /* * Return some stat(2) related information about the file */ - int (*stat)(void *arg, void *h, int *mode_return, - int *uid_return, int *gid_return, uint64_t *size_return); + int (*stat)(void *arg, void *h, struct stat *stp); /* * Disk image i/o */ /* * Read from a disk image at the given offset */ int (*diskread)(void *arg, int unit, uint64_t offset, void *dst, size_t size, size_t *resid_return); /* * Guest virtual machine i/o */ /* * Copy to the guest address space */ int (*copyin)(void *arg, const void *from, uint64_t to, size_t size); /* * Copy from the guest address space */ int (*copyout)(void *arg, uint64_t from, void *to, size_t size); /* * Set a guest register value */ void (*setreg)(void *arg, int, uint64_t); /* * Set a guest MSR value */ void (*setmsr)(void *arg, int, uint64_t); /* * Set a guest CR value */ void (*setcr)(void *arg, int, uint64_t); /* * Set the guest GDT address */ void (*setgdt)(void *arg, uint64_t, size_t); /* * Transfer control to the guest at the given address */ void (*exec)(void *arg, uint64_t pc); /* * Misc */ /* * Sleep for usec microseconds */ void (*delay)(void *arg, int usec); /* * Exit with the given exit code */ void (*exit)(void *arg, int v); /* * Return guest physical memory map details */ void (*getmem)(void *arg, uint64_t *lowmem, uint64_t *highmem); /* * ioctl interface to the disk device */ int (*diskioctl)(void *arg, int unit, u_long cmd, void *data); /* * Returns an environment variable in the form "name=value". * * If there are no more variables that need to be set in the * loader environment then return NULL. * * 'num' is used as a handle for the callback to identify which * environment variable to return next. It will begin at 0 and * each invocation will add 1 to the previous value of 'num'. */ char * (*getenv)(void *arg, int num); /* * Version 4 additions. */ int (*vm_set_register)(void *arg, int vcpu, int reg, uint64_t val); int (*vm_set_desc)(void *arg, int vcpu, int reg, uint64_t base, u_int limit, u_int access); /* * Version 5 addition. */ void (*swap_interpreter)(void *arg, const char *interp); }; diff --git a/stand/userboot/userboot/conf.c b/stand/userboot/userboot/conf.c index 3a4417f576a4..af63528163d5 100644 --- a/stand/userboot/userboot/conf.c +++ b/stand/userboot/userboot/conf.c @@ -1,107 +1,112 @@ /*- * Copyright (c) 1997 * Matthias Drochner. 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed for the NetBSD Project * by Matthias Drochner. * 4. The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * 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. * * $NetBSD: conf.c,v 1.2 1997/03/22 09:03:29 thorpej Exp $ */ #include __FBSDID("$FreeBSD$"); #include #include "libuserboot.h" #if defined(USERBOOT_ZFS_SUPPORT) #include "libzfs.h" #endif /* * We could use linker sets for some or all of these, but * then we would have to control what ended up linked into * the bootstrap. So it's easier to conditionalise things * here. * * XXX rename these arrays to be consistent and less namespace-hostile */ /* Exported for libstand */ struct devsw *devsw[] = { &host_dev, &userboot_disk, #if defined(USERBOOT_ZFS_SUPPORT) &zfs_dev, #endif NULL }; struct fs_ops *file_system[] = { &host_fsops, &ufs_fsops, &cd9660_fsops, #if defined(USERBOOT_ZFS_SUPPORT) &zfs_fsops, #endif &gzipfs_fsops, &bzipfs_fsops, NULL }; +/* to keep libsa happy */ +struct netif_driver *netif_drivers[] = { + NULL +}; + /* Exported for i386 only */ /* * Sort formats so that those that can detect based on arguments * rather than reading the file go first. */ extern struct file_format i386_elf; extern struct file_format i386_elf_obj; extern struct file_format amd64_elf; extern struct file_format amd64_elf_obj; struct file_format *file_formats[] = { &i386_elf, &i386_elf_obj, &amd64_elf, &amd64_elf_obj, NULL }; /* * Consoles * * We don't prototype these in libuserboot.h because they require * data structures from bootstrap.h as well. */ extern struct console userboot_console; extern struct console userboot_comconsole; struct console *consoles[] = { &userboot_console, &userboot_comconsole, NULL }; diff --git a/stand/userboot/userboot/host.c b/stand/userboot/userboot/host.c index 861856544aa2..c9ac01576e82 100644 --- a/stand/userboot/userboot/host.c +++ b/stand/userboot/userboot/host.c @@ -1,192 +1,184 @@ /*- * Copyright (c) 2011 Google, Inc. * 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 __FBSDID("$FreeBSD$"); /* * Read from the host filesystem */ #include #include #include #include #include #include #include #include "libuserboot.h" /* * Open a file. */ static int host_open(const char *upath, struct open_file *f) { if (f->f_dev != &host_dev) return (EINVAL); return (CALLBACK(open, upath, &f->f_fsdata)); } static int host_close(struct open_file *f) { CALLBACK(close, f->f_fsdata); f->f_fsdata = (void *)0; return (0); } /* * Copy a portion of a file into memory. */ static int host_read(struct open_file *f, void *start, size_t size, size_t *resid) { return (CALLBACK(read, f->f_fsdata, start, size, resid)); } static off_t host_seek(struct open_file *f, off_t offset, int where) { return (CALLBACK(seek, f->f_fsdata, offset, where)); } static int host_stat(struct open_file *f, struct stat *sb) { - int mode; - int uid; - int gid; - uint64_t size; - - CALLBACK(stat, f->f_fsdata, &mode, &uid, &gid, &size); - sb->st_mode = mode; - sb->st_uid = uid; - sb->st_gid = gid; - sb->st_size = size; + + CALLBACK(stat, f->f_fsdata, sb); return (0); } static int host_readdir(struct open_file *f, struct dirent *d) { uint32_t fileno; uint8_t type; size_t namelen; int rc; rc = CALLBACK(readdir, f->f_fsdata, &fileno, &type, &namelen, d->d_name); if (rc) return (rc); d->d_fileno = fileno; d->d_type = type; d->d_namlen = namelen; return (0); } static int host_dev_init(void) { return (0); } static int host_dev_print(int verbose) { char line[80]; printf("%s devices:", host_dev.dv_name); if (pager_output("\n") != 0) return (1); snprintf(line, sizeof(line), " host%d: Host filesystem\n", 0); return (pager_output(line)); } /* * 'Open' the host device. */ static int host_dev_open(struct open_file *f, ...) { va_list args; struct devdesc *dev; va_start(args, f); dev = va_arg(args, struct devdesc*); va_end(args); return (0); } static int host_dev_close(struct open_file *f) { return (0); } static int host_dev_strategy(void *devdata, int rw, daddr_t dblk, size_t size, char *buf, size_t *rsize) { return (ENOSYS); } struct fs_ops host_fsops = { "host", host_open, host_close, host_read, null_write, host_seek, host_stat, host_readdir }; struct devsw host_dev = { .dv_name = "host", .dv_type = DEVT_NET, .dv_init = host_dev_init, .dv_strategy = host_dev_strategy, .dv_open = host_dev_open, .dv_close = host_dev_close, .dv_ioctl = noioctl, .dv_print = host_dev_print, .dv_cleanup = NULL }; diff --git a/usr.sbin/bhyveload/bhyveload.c b/usr.sbin/bhyveload/bhyveload.c index 3764642c76ad..1b3be71745da 100644 --- a/usr.sbin/bhyveload/bhyveload.c +++ b/usr.sbin/bhyveload/bhyveload.c @@ -1,836 +1,841 @@ /*- * SPDX-License-Identifier: BSD-2-Clause-FreeBSD AND BSD-2-Clause * * Copyright (c) 2011 NetApp, Inc. * 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 NETAPP, INC ``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 NETAPP, INC 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. * * $FreeBSD$ */ /*- * Copyright (c) 2011 Google, Inc. * 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. * * $FreeBSD$ */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "userboot.h" #define MB (1024 * 1024UL) #define GB (1024 * 1024 * 1024UL) #define BSP 0 #define NDISKS 32 static char *host_base; static struct termios term, oldterm; static int disk_fd[NDISKS]; static int ndisks; static int consin_fd, consout_fd; static int need_reinit; static void *loader_hdl; static char *loader; static int explicit_loader; static jmp_buf jb; static char *vmname, *progname; static struct vmctx *ctx; static uint64_t gdtbase, cr3, rsp; static void cb_exit(void *arg, int v); /* * Console i/o callbacks */ static void cb_putc(void *arg, int ch) { char c = ch; (void) write(consout_fd, &c, 1); } static int cb_getc(void *arg) { char c; if (read(consin_fd, &c, 1) == 1) return (c); return (-1); } static int cb_poll(void *arg) { int n; if (ioctl(consin_fd, FIONREAD, &n) >= 0) return (n > 0); return (0); } /* * Host filesystem i/o callbacks */ struct cb_file { int cf_isdir; size_t cf_size; struct stat cf_stat; union { int fd; DIR *dir; } cf_u; }; static int cb_open(void *arg, const char *filename, void **hp) { struct cb_file *cf; char path[PATH_MAX]; if (!host_base) return (ENOENT); strlcpy(path, host_base, PATH_MAX); if (path[strlen(path) - 1] == '/') path[strlen(path) - 1] = 0; strlcat(path, filename, PATH_MAX); cf = malloc(sizeof(struct cb_file)); if (stat(path, &cf->cf_stat) < 0) { free(cf); return (errno); } cf->cf_size = cf->cf_stat.st_size; if (S_ISDIR(cf->cf_stat.st_mode)) { cf->cf_isdir = 1; cf->cf_u.dir = opendir(path); if (!cf->cf_u.dir) goto out; *hp = cf; return (0); } if (S_ISREG(cf->cf_stat.st_mode)) { cf->cf_isdir = 0; cf->cf_u.fd = open(path, O_RDONLY); if (cf->cf_u.fd < 0) goto out; *hp = cf; return (0); } out: free(cf); return (EINVAL); } static int cb_close(void *arg, void *h) { struct cb_file *cf = h; if (cf->cf_isdir) closedir(cf->cf_u.dir); else close(cf->cf_u.fd); free(cf); return (0); } static int cb_isdir(void *arg, void *h) { struct cb_file *cf = h; return (cf->cf_isdir); } static int cb_read(void *arg, void *h, void *buf, size_t size, size_t *resid) { struct cb_file *cf = h; ssize_t sz; if (cf->cf_isdir) return (EINVAL); sz = read(cf->cf_u.fd, buf, size); if (sz < 0) return (EINVAL); *resid = size - sz; return (0); } static int cb_readdir(void *arg, void *h, uint32_t *fileno_return, uint8_t *type_return, size_t *namelen_return, char *name) { struct cb_file *cf = h; struct dirent *dp; if (!cf->cf_isdir) return (EINVAL); dp = readdir(cf->cf_u.dir); if (!dp) return (ENOENT); /* * Note: d_namlen is in the range 0..255 and therefore less * than PATH_MAX so we don't need to test before copying. */ *fileno_return = dp->d_fileno; *type_return = dp->d_type; *namelen_return = dp->d_namlen; memcpy(name, dp->d_name, dp->d_namlen); name[dp->d_namlen] = 0; return (0); } static int cb_seek(void *arg, void *h, uint64_t offset, int whence) { struct cb_file *cf = h; if (cf->cf_isdir) return (EINVAL); if (lseek(cf->cf_u.fd, offset, whence) < 0) return (errno); return (0); } static int -cb_stat(void *arg, void *h, int *mode, int *uid, int *gid, uint64_t *size) +cb_stat(void *arg, void *h, struct stat *sbp) { struct cb_file *cf = h; - *mode = cf->cf_stat.st_mode; - *uid = cf->cf_stat.st_uid; - *gid = cf->cf_stat.st_gid; - *size = cf->cf_stat.st_size; + memset(sbp, 0, sizeof(struct stat)); + sbp->st_mode = cf->cf_stat.st_mode; + sbp->st_uid = cf->cf_stat.st_uid; + sbp->st_gid = cf->cf_stat.st_gid; + sbp->st_size = cf->cf_stat.st_size; + sbp->st_mtime = cf->cf_stat.st_mtime; + sbp->st_dev = cf->cf_stat.st_dev; + sbp->st_ino = cf->cf_stat.st_ino; + return (0); } /* * Disk image i/o callbacks */ static int cb_diskread(void *arg, int unit, uint64_t from, void *to, size_t size, size_t *resid) { ssize_t n; if (unit < 0 || unit >= ndisks ) return (EIO); n = pread(disk_fd[unit], to, size, from); if (n < 0) return (errno); *resid = size - n; return (0); } static int cb_diskioctl(void *arg, int unit, u_long cmd, void *data) { struct stat sb; if (unit < 0 || unit >= ndisks) return (EBADF); switch (cmd) { case DIOCGSECTORSIZE: *(u_int *)data = 512; break; case DIOCGMEDIASIZE: if (fstat(disk_fd[unit], &sb) != 0) return (ENOTTY); if (S_ISCHR(sb.st_mode) && ioctl(disk_fd[unit], DIOCGMEDIASIZE, &sb.st_size) != 0) return (ENOTTY); *(off_t *)data = sb.st_size; break; default: return (ENOTTY); } return (0); } /* * Guest virtual machine i/o callbacks */ static int cb_copyin(void *arg, const void *from, uint64_t to, size_t size) { char *ptr; to &= 0x7fffffff; ptr = vm_map_gpa(ctx, to, size); if (ptr == NULL) return (EFAULT); memcpy(ptr, from, size); return (0); } static int cb_copyout(void *arg, uint64_t from, void *to, size_t size) { char *ptr; from &= 0x7fffffff; ptr = vm_map_gpa(ctx, from, size); if (ptr == NULL) return (EFAULT); memcpy(to, ptr, size); return (0); } static void cb_setreg(void *arg, int r, uint64_t v) { int error; enum vm_reg_name vmreg; vmreg = VM_REG_LAST; switch (r) { case 4: vmreg = VM_REG_GUEST_RSP; rsp = v; break; default: break; } if (vmreg == VM_REG_LAST) { printf("test_setreg(%d): not implemented\n", r); cb_exit(NULL, USERBOOT_EXIT_QUIT); } error = vm_set_register(ctx, BSP, vmreg, v); if (error) { perror("vm_set_register"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } } static void cb_setmsr(void *arg, int r, uint64_t v) { int error; enum vm_reg_name vmreg; vmreg = VM_REG_LAST; switch (r) { case MSR_EFER: vmreg = VM_REG_GUEST_EFER; break; default: break; } if (vmreg == VM_REG_LAST) { printf("test_setmsr(%d): not implemented\n", r); cb_exit(NULL, USERBOOT_EXIT_QUIT); } error = vm_set_register(ctx, BSP, vmreg, v); if (error) { perror("vm_set_msr"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } } static void cb_setcr(void *arg, int r, uint64_t v) { int error; enum vm_reg_name vmreg; vmreg = VM_REG_LAST; switch (r) { case 0: vmreg = VM_REG_GUEST_CR0; break; case 3: vmreg = VM_REG_GUEST_CR3; cr3 = v; break; case 4: vmreg = VM_REG_GUEST_CR4; break; default: break; } if (vmreg == VM_REG_LAST) { printf("test_setcr(%d): not implemented\n", r); cb_exit(NULL, USERBOOT_EXIT_QUIT); } error = vm_set_register(ctx, BSP, vmreg, v); if (error) { perror("vm_set_cr"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } } static void cb_setgdt(void *arg, uint64_t base, size_t size) { int error; error = vm_set_desc(ctx, BSP, VM_REG_GUEST_GDTR, base, size - 1, 0); if (error != 0) { perror("vm_set_desc(gdt)"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } gdtbase = base; } static void cb_exec(void *arg, uint64_t rip) { int error; if (cr3 == 0) error = vm_setup_freebsd_registers_i386(ctx, BSP, rip, gdtbase, rsp); else error = vm_setup_freebsd_registers(ctx, BSP, rip, cr3, gdtbase, rsp); if (error) { perror("vm_setup_freebsd_registers"); cb_exit(NULL, USERBOOT_EXIT_QUIT); } cb_exit(NULL, 0); } /* * Misc */ static void cb_delay(void *arg, int usec) { usleep(usec); } static void cb_exit(void *arg, int v) { tcsetattr(consout_fd, TCSAFLUSH, &oldterm); exit(v); } static void cb_getmem(void *arg, uint64_t *ret_lowmem, uint64_t *ret_highmem) { *ret_lowmem = vm_get_lowmem_size(ctx); *ret_highmem = vm_get_highmem_size(ctx); } struct env { char *str; /* name=value */ SLIST_ENTRY(env) next; }; static SLIST_HEAD(envhead, env) envhead; static void addenv(char *str) { struct env *env; env = malloc(sizeof(struct env)); env->str = str; SLIST_INSERT_HEAD(&envhead, env, next); } static char * cb_getenv(void *arg, int num) { int i; struct env *env; i = 0; SLIST_FOREACH(env, &envhead, next) { if (i == num) return (env->str); i++; } return (NULL); } static int cb_vm_set_register(void *arg, int vcpu, int reg, uint64_t val) { return (vm_set_register(ctx, vcpu, reg, val)); } static int cb_vm_set_desc(void *arg, int vcpu, int reg, uint64_t base, u_int limit, u_int access) { return (vm_set_desc(ctx, vcpu, reg, base, limit, access)); } static void cb_swap_interpreter(void *arg, const char *interp_req) { /* * If the user specified a loader but we detected a mismatch, we should * not try to pivot to a different loader on them. */ free(loader); if (explicit_loader == 1) { perror("requested loader interpreter does not match guest userboot"); cb_exit(NULL, 1); } if (interp_req == NULL || *interp_req == '\0') { perror("guest failed to request an interpreter"); cb_exit(NULL, 1); } if (asprintf(&loader, "/boot/userboot_%s.so", interp_req) == -1) err(EX_OSERR, "malloc"); need_reinit = 1; longjmp(jb, 1); } static struct loader_callbacks cb = { .getc = cb_getc, .putc = cb_putc, .poll = cb_poll, .open = cb_open, .close = cb_close, .isdir = cb_isdir, .read = cb_read, .readdir = cb_readdir, .seek = cb_seek, .stat = cb_stat, .diskread = cb_diskread, .diskioctl = cb_diskioctl, .copyin = cb_copyin, .copyout = cb_copyout, .setreg = cb_setreg, .setmsr = cb_setmsr, .setcr = cb_setcr, .setgdt = cb_setgdt, .exec = cb_exec, .delay = cb_delay, .exit = cb_exit, .getmem = cb_getmem, .getenv = cb_getenv, /* Version 4 additions */ .vm_set_register = cb_vm_set_register, .vm_set_desc = cb_vm_set_desc, /* Version 5 additions */ .swap_interpreter = cb_swap_interpreter, }; static int altcons_open(char *path) { struct stat sb; int err; int fd; /* * Allow stdio to be passed in so that the same string * can be used for the bhyveload console and bhyve com-port * parameters */ if (!strcmp(path, "stdio")) return (0); err = stat(path, &sb); if (err == 0) { if (!S_ISCHR(sb.st_mode)) err = ENOTSUP; else { fd = open(path, O_RDWR | O_NONBLOCK); if (fd < 0) err = errno; else consin_fd = consout_fd = fd; } } return (err); } static int disk_open(char *path) { int fd; if (ndisks >= NDISKS) return (ERANGE); fd = open(path, O_RDONLY); if (fd < 0) return (errno); disk_fd[ndisks] = fd; ndisks++; return (0); } static void usage(void) { fprintf(stderr, "usage: %s [-S][-c ] [-d ] [-e ]\n" " %*s [-h ] [-m memsize[K|k|M|m|G|g|T|t]] \n", progname, (int)strlen(progname), ""); exit(1); } int main(int argc, char** argv) { void (*func)(struct loader_callbacks *, void *, int, int); uint64_t mem_size; int opt, error, memflags; progname = basename(argv[0]); memflags = 0; mem_size = 256 * MB; consin_fd = STDIN_FILENO; consout_fd = STDOUT_FILENO; while ((opt = getopt(argc, argv, "CSc:d:e:h:l:m:")) != -1) { switch (opt) { case 'c': error = altcons_open(optarg); if (error != 0) errx(EX_USAGE, "Could not open '%s'", optarg); break; case 'd': error = disk_open(optarg); if (error != 0) errx(EX_USAGE, "Could not open '%s'", optarg); break; case 'e': addenv(optarg); break; case 'h': host_base = optarg; break; case 'l': if (loader != NULL) errx(EX_USAGE, "-l can only be given once"); loader = strdup(optarg); if (loader == NULL) err(EX_OSERR, "malloc"); explicit_loader = 1; break; case 'm': error = vm_parse_memsize(optarg, &mem_size); if (error != 0) errx(EX_USAGE, "Invalid memsize '%s'", optarg); break; case 'C': memflags |= VM_MEM_F_INCORE; break; case 'S': memflags |= VM_MEM_F_WIRED; break; case '?': usage(); } } argc -= optind; argv += optind; if (argc != 1) usage(); vmname = argv[0]; need_reinit = 0; error = vm_create(vmname); if (error) { if (errno != EEXIST) { perror("vm_create"); exit(1); } need_reinit = 1; } ctx = vm_open(vmname); if (ctx == NULL) { perror("vm_open"); exit(1); } /* * setjmp in the case the guest wants to swap out interpreter, * cb_swap_interpreter will swap out loader as appropriate and set * need_reinit so that we end up in a clean state once again. */ setjmp(jb); if (need_reinit) { error = vm_reinit(ctx); if (error) { perror("vm_reinit"); exit(1); } } vm_set_memflags(ctx, memflags); error = vm_setup_memory(ctx, mem_size, VM_MMAP_ALL); if (error) { perror("vm_setup_memory"); exit(1); } if (loader == NULL) { loader = strdup("/boot/userboot.so"); if (loader == NULL) err(EX_OSERR, "malloc"); } if (loader_hdl != NULL) dlclose(loader_hdl); loader_hdl = dlopen(loader, RTLD_LOCAL); if (!loader_hdl) { printf("%s\n", dlerror()); free(loader); return (1); } func = dlsym(loader_hdl, "loader_main"); if (!func) { printf("%s\n", dlerror()); free(loader); return (1); } tcgetattr(consout_fd, &term); oldterm = term; cfmakeraw(&term); term.c_cflag |= CLOCAL; tcsetattr(consout_fd, TCSAFLUSH, &term); addenv("smbios.bios.vendor=BHYVE"); addenv("boot_serial=1"); func(&cb, NULL, USERBOOT_VERSION_5, ndisks); free(loader); return (0); }