Changeset View
Standalone View
lib/libve/veopen.c
- This file was added.
Property | Old Value | New Value |
---|---|---|
svn:eol-style | null | native \ No newline at end of property |
svn:keywords | null | FreeBSD=%H \ No newline at end of property |
svn:mime-type | null | text/plain \ No newline at end of property |
/*- | |||||
* Copyright (c) 2017, 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 <sys/cdefs.h> | |||||
__FBSDID("$FreeBSD$"); | |||||
#include <sys/queue.h> | |||||
#include "libve-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; | |||||
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) | |||||
*cp = '\0'; | |||||
else { | |||||
free(nfip->fi_prefix); | |||||
free(nfip); | |||||
return; | |||||
} | |||||
} | |||||
#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 = strdup(skip); | |||||
nfip->fi_skip_len = strlen(skip); | |||||
} 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) { | |||||
cem: This is not going to be pretty with any significant number of verified files. | |||||
Not Done Inline ActionsThe loader does not deal with significant numbers of files. sjg: The loader does not deal with significant numbers of files.
Nor does it look at them more than… | |||||
Not Done Inline ActionsIf it does not need them more than once, why even keep them on a list? cem: If it does not need them more than once, why even keep them on a list? | |||||
Not Done Inline ActionsPerhaps a pointer to my slides would help.Adding verification to FreeBSD loader slides sjg: Perhaps a pointer to my slides would help.[[ https://www.bsdcan. | |||||
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)); | |||||
} | |||||
char * | |||||
fingerprint_info_lookup(int fd, const char *path) | |||||
{ | |||||
char pbuf[MAXPATHLEN+1]; | |||||
char nbuf[MAXPATHLEN+1]; | |||||
struct stat st; | |||||
Not Done Inline ActionsThis is a lot of stack — is this file userspace-only? cem: This is a lot of stack — is this file userspace-only? | |||||
Not Done Inline ActionsIt is a lot, and I re-worked it at one point to use malloc, but IIRC the heap handling in libsa is very crude, the stack has not (yet) been an issue and makes the code much simpler. sjg: It is a lot, and I re-worked it at one point to use malloc, but IIRC the heap handling in libsa… | |||||
Not Done Inline ActionsI'm ok with this if this code is not used in the kernel. cem: I'm ok with this if this code is not used in the kernel. | |||||
Not Done Inline ActionsAs I mentioned somewhere, by using an md_image for initial rootfs and having the loader verify that, you can avoid the need for any of this in the kernel sjg: As I mentioned somewhere, by using an md_image for initial rootfs and having the loader verify… | |||||
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]; | |||||
while (cp > &pbuf[1]) { | |||||
if (ep) { | |||||
*ep = '/'; | |||||
cp -= 2; | |||||
} | |||||
nlen = plen = 0; /* keep gcc quiet */ | |||||
if (cp > pbuf) { | |||||
for ( ; cp >= pbuf && *cp != '/'; cp--) | |||||
; /* nothing */ | |||||
if (cp > pbuf) { | |||||
ep = cp++; | |||||
*ep = '\0'; | |||||
} | |||||
plen = ep - pbuf; | |||||
nlen = n - plen - 1; | |||||
} | |||||
if (cp == pbuf) { | |||||
prefix = "/"; | |||||
plen = 1; | |||||
nlen = n - 1; | |||||
cp++; | |||||
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", | |||||
Done Inline Actionsstyle: missing space between comma and following parenthesis. cem: style: missing space between comma and following parenthesis. | |||||
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; | |||||
/* | |||||
* 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) { | |||||
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++) | |||||
Done Inline Actionsstyle: the semi-colon goes on its own line cem: style: the semi-colon goes on its own line | |||||
; /* nothing */ | |||||
return (cp); | |||||
} else { | |||||
DEBUG_PRINTF(3, | |||||
("Ignoring prefix=%s\n", fip->fi_prefix)); | |||||
} | |||||
} | |||||
} | |||||
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, "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); | |||||
Not Done Inline ActionsWhy are we comparing a printed hash at all? (Also, strcmp is not constant time.) cem: Why are we comparing a printed hash at all?
(Also, `strcmp` is not constant time.) | |||||
Not Done Inline ActionsBecause the manifest records the printed hash. Nothing here is subject to timing attacks - there's no "authentication" going on. sjg: Because the manifest records the printed hash.
This makes them simple to generate and simple… | |||||
Not Done Inline ActionsIt's fine (well, bloats space, which might impact embedded) to a have a text manifest. But maybe we should store the binary version of the manifest contents after parsing? Something that checks if a hash matches a known value and returns OK or WRONG looks like authentication — can you elaborate on why it isn't? Is it intended that the manifest hashes are public information? cem: It's fine (well, bloats space, which might impact embedded) to a have a text manifest. But… | |||||
Not Done Inline ActionsWe are talking about the loader. Everything in the manifest is public information. sjg: We are talking about the loader.
Everything it does is tossed after a few seconds.
Optimal… | |||||
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 | |||||
Not Done Inline ActionsIs this file also in loader context? cem: Is this file also in loader context? | |||||
Not Done Inline ActionsAs the title implies this is all loader sjg: As the title implies this is all loader | |||||
* 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_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); | |||||
} | |||||
Done Inline Actionsstyle: return (foo); cem: style: return (foo); | |||||
Not Done Inline ActionsNone of the previous portion of this comment adds anything of value for the reader beyond the information already present in the function name and parameter types and names below. cem: None of the previous portion of this comment adds anything of value for the reader beyond the… | |||||
Not Done Inline ActionsMinimal doxygen markup to allow generating api docs. sjg: Minimal doxygen markup to allow generating api docs. | |||||
Not Done Inline ActionsAnd? It's still spam. If doxygen can't tell you what function types and parameters are without the comment, maybe it's not a helpful tool. Other options that don't go stale with source code changes are ctags, cscope, or OpenGrok. cem: And? It's still spam.
If doxygen can't tell you what function types and parameters are… |
This is not going to be pretty with any significant number of verified files.