Index: pefs_kmod/sbin/pefs/pefs_checksum.c =================================================================== --- pefs_kmod/sbin/pefs/pefs_checksum.c (revision 0) +++ pefs_kmod/sbin/pefs/pefs_checksum.c (revision 240588) @@ -0,0 +1,2348 @@ +/*- + * Copyright (c) 2012 Efstratios Karatzas + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "pefs_ctl.h" + +//#define PEFS_INTEGRITY_DEBUG +#if defined (PEFS_INTEGRITY_DEBUG) +#define dprintf(a) printf a +#else +#define dprintf(a) (void)0 +#endif + +#define PEFS_EXTEND 1 +#define PEFS_NOEXTEND 2 +#define PEFS_REALLOC 3 + +#define PEFS_SIGNATURE_MAX_LENGTH 512 +#define PEFS_CHECKSUM_FILE_VERSION 0xDD +#define PEFS_HASH_BYTE_ALIGNMENT 512 +#define PEFS_EXTRA_TABLE_SIZE 15 + +#define PEFS_PLEN 1024 +#define PEFS_SEED_LEN 20 + +#define PEFS_BUFISZE 512 + +/* tail that contains a single file's checksums */ +TAILQ_HEAD(checksum_head, checksum); +/* tail that contains all file headers that require integrity checking */ +TAILQ_HEAD(file_header_head, file_header); +/* tail for all the file_headers that refer to the same inode */ +TAILQ_HEAD(hardlink_fh_head, file_header); + +/* RB tree for hardlink counters */ +RB_HEAD(hardlink_head, hardlink_counter); +RB_PROTOTYPE(hardlink_head, hardlink_counter, hardlink_entries, pefs_rb_cmp); + +/* on disk size of .pefs.checksum's unique file header */ +#define PEFS_CFH_SIZE 16 +/* on disk size of a single file header (also a bucket in cuckoo hashing) */ +#define PEFS_FH_SIZE 16 + +/* + * This struct is used to check if all hardlinks for a given inode are + * supplied by the user. + */ +struct hardlink_counter { + /* inode number for the file in question */ + ino_t inode; + /* total hardlinks of the file */ + uint32_t total_links; + /* how many links are found in user supplied list */ + uint32_t links_found; + /* file headers of the links we have found */ + struct hardlink_fh_head file_headers; + /* entry in hardlink RB tree */ + RB_ENTRY(hardlink_counter) hardlink_entries; +}; + +/* + * This is the unique file header of the .pefs.checksum file, found in + * the beginning of the file. + */ +struct checksum_file_header { + uint8_t version; + uint8_t reserved; + uint8_t hash_len; + uint8_t hash_algo[8]; + uint8_t offset_to_hash_table; + uint32_t hash_table_size; +}; + +struct checksum { + unsigned char *hash; + TAILQ_ENTRY(checksum) checksum_entries; +}; + +union file_id { + uint64_t fid_num; + uint8_t fid_str[8]; +}; + +/* XXXgpf: [TODO] turns offsets to uint64_t? */ +struct file_header { + /* + * on disk information + */ + + /* the number of hashes for the file */ + uint32_t nhashes; + /* in file offset to start of checksums */ + uint32_t offset_to_checksums; + /* id is MAC tweak from filename (first 64 bits) */ + union file_id fid; + + /* + * in memory information + */ + + /* fullpath for this file */ + char path[MAXPATHLEN + 1]; + /* fullpath for this file's parent dir */ + char dirpath[MAXPATHLEN + 1]; + /* filename */ + char filename[MAXNAMLEN + 1]; + /*fullpath to this symlink's immediate next target */ + char *target_path; + /* file descriptors for the file and its parent dir */ + int fd, pfd; + /* mark that this entry was found during "verify" action */ + int found; + /* this file's checksums */ + struct checksum_head checksums; + /* entry in global file header tail */ + TAILQ_ENTRY(file_header) file_header_entries; + /* entry in hardlink counter */ + TAILQ_ENTRY(file_header) fh_hardlink_entries; +}; + +struct bucket { + struct file_header * fhp; +}; + +/* + * This cuckoo hashing implementation requires 2 tables, each + * with his own hash function: pefs_hash1() & pefs_hash2() + */ +struct cuckoo_hash_table { + struct bucket *buckets1; /* table1 */ + struct bucket *buckets2; /* table2 */ + uint32_t size; /* how many buckets in each table */ + uint32_t nelements; /* total number of elements <= size */ +}; + +static int +pefs_is_prime(uint32_t num) +{ + uint32_t i, sq; + + if ((num % 2 == 0) || (num % 3 == 0)) + return 0; + + sq = sqrt(num); + /* All other primes are in the form of 6k+-1 */ + for (i = 5; i <= sq; i+=6) { + if ((num % i == 0) || (num % (i+2) == 0)) + return 0; + } + + return 1; +} + +static int +pefs_next_prime(uint32_t num) +{ + uint32_t i; + + if (num == 2 || num == 3) + return num; + + if (num % 2 == 0) + num+=1; + + for (i = num;; i+=2) { + if (pefs_is_prime(i)) + return i; + } + + return 0; +} + +static struct file_header * +pefs_allocate_file_header(void) +{ + struct file_header *fhp; + + fhp = malloc(sizeof(struct file_header)); + if (fhp == NULL) { + pefs_warn("memory allocation error"); + return (NULL); + } + + fhp->nhashes = 0; + fhp->offset_to_checksums = 0; + fhp->fid.fid_num = 0; + fhp->fd = -1; + fhp->pfd = -1; + fhp->found = 0; + + fhp->target_path = NULL; + + fhp->path[0] = '\0'; + fhp->dirpath[0] = '\0'; + fhp->filename[0] = '\0'; + + TAILQ_INIT(&(fhp->checksums)); + + return (fhp); +} + +static void +pefs_close_files(struct file_header *fhp) +{ + if (fhp->fd >= 0) { + close(fhp->fd); + fhp->fd = -1; + } + if (fhp->pfd >= 0) { + close(fhp->pfd); + fhp->pfd = -1; + } +} + +static void +pefs_free_file_header(struct file_header *fhp) +{ + struct checksum *csp, *tcsp; + if (fhp != NULL) { + pefs_close_files(fhp); + TAILQ_FOREACH_SAFE(csp, &(fhp->checksums), checksum_entries, tcsp) { + TAILQ_REMOVE(&(fhp->checksums), csp, checksum_entries); + if (csp->hash != NULL) + free(csp->hash); + free(csp); + } + if (fhp->target_path != NULL) + free(fhp->target_path); + free(fhp); + } +} + +static int +pefs_compute_symlink_checksum(struct file_header *fhp, const EVP_MD *md, + uint8_t hash_len, int flags) +{ + struct pefs_xslink_ctext xsl; + EVP_MD_CTX mdctx; + char *buf; + int error, i, md_len; + size_t buf_len; + struct checksum *csp; + + if ((flags & PEFS_UNMOUNTED) != 0 || (flags & PEFS_NOKEY) != 0) { + buf = fhp->target_path; + buf_len = strnlen(fhp->target_path, MAXPATHLEN + 1); + } + else { + /* feed parent directory to ioctl() */ + strlcpy(xsl.pxsl_filename, fhp->filename, sizeof(xsl.pxsl_filename)); + xsl.pxsl_namelen = strnlen(xsl.pxsl_filename, + sizeof(xsl.pxsl_filename)); + + error = ioctl(fhp->pfd, PEFS_GETSLINKCTEXT, &xsl); + if (error != 0) { + pefs_warn("error retrieving symlink's ciphertext of %s", fhp->path); + return (PEFS_ERR_IO); + } + + dprintf(("read %d bytes from kernel\n\n", xsl.pxsl_target_len)); + dprintf(("printing contents of buf:")); + for (i=0; i < (int)xsl.pxsl_target_len; i++) + dprintf(("%c", xsl.pxsl_target[i])); + dprintf(("!\n")); + buf = xsl.pxsl_target; + buf_len = xsl.pxsl_target_len; + } + EVP_MD_CTX_init(&mdctx); + EVP_DigestInit_ex(&mdctx, md, NULL); + EVP_DigestUpdate(&mdctx, buf, buf_len); + + csp = malloc(sizeof(struct checksum)); + if (csp == NULL) { + pefs_warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + csp->hash = malloc(hash_len); + if (csp->hash == NULL) { + pefs_warn("memory allocation error"); + free(csp); + return (PEFS_ERR_SYS); + } + + EVP_DigestFinal_ex(&mdctx, csp->hash, &md_len); + + dprintf(("Digest is: ")); + for (i = 0; i < md_len; i++) dprintf(("%02x", csp->hash[i])); + dprintf(("\n")); + + EVP_MD_CTX_cleanup(&mdctx); + TAILQ_INSERT_TAIL(&(fhp->checksums), csp, checksum_entries); + fhp->nhashes++; + + return (0); +} + +static int +pefs_compute_file_checksums(struct file_header *fhp, const EVP_MD *md, + uint8_t hash_len, int flags) +{ + struct pefs_xsector_ctext xsct; + char sector_buf[PEFS_SECTOR_SIZE]; + EVP_MD_CTX mdctx; + struct stat sb; + off_t offset, resid; + FILE * fp; + char *buf; + uint32_t bytes_to_read; + size_t buf_len; + int error, i, md_len; + struct checksum *csp; + + if (fhp->target_path != NULL) + return (pefs_compute_symlink_checksum(fhp, md, hash_len, flags)); + + /* XXXgpf: what happens if file size > 2^64? */ + if (fstat(fhp->fd, &sb) != 0) { + warn("cannot stat file %s", fhp->path); + return (PEFS_ERR_SYS); + } + + resid = sb.st_size; + if (resid == 0) { + pefs_warn("empty files are not allowed: %s", fhp->path); + return (PEFS_ERR_GENERIC); + } + + fp = NULL; + if ((flags & PEFS_UNMOUNTED) != 0 || (flags & PEFS_NOKEY) != 0) { + fp = fdopen(fhp->fd, "r"); + if (fp == NULL) { + pefs_warn("could not open file for reading: %s", fhp->path); + return (PEFS_ERR_IO); + } + } + + offset = 0; + xsct.pxsct_offset = 0; + while (resid > 0) { + if (resid > PEFS_SECTOR_SIZE) + bytes_to_read = PEFS_SECTOR_SIZE; + else + bytes_to_read = resid; + + if ((flags & PEFS_UNMOUNTED) != 0 || (flags & PEFS_NOKEY) != 0) { + fread(sector_buf, bytes_to_read, sizeof(char), fp); + if (ferror(fp)) { + pefs_warn("error reading from file: %s", fhp->path); + return (PEFS_ERR_IO); + } + buf = sector_buf; + buf_len = bytes_to_read; + resid-=bytes_to_read; + } + else { + resid-=bytes_to_read; + xsct.pxsct_ctext_len = bytes_to_read; + error = ioctl(fhp->fd, PEFS_GETSECTORCTEXT, &xsct); + if (error != 0) { + pefs_warn("error retrieving ciphertext of %s", fhp->path); + return (PEFS_ERR_IO); + } + xsct.pxsct_offset+= xsct.pxsct_ctext_len; + buf = xsct.pxsct_ctext; + buf_len = xsct.pxsct_ctext_len; + } + + EVP_MD_CTX_init(&mdctx); + EVP_DigestInit_ex(&mdctx, md, NULL); + EVP_DigestUpdate(&mdctx, buf, buf_len); + + dprintf(("read %d bytes\n\n", buf_len)); + dprintf(("printing contents of buffer:")); + for (i=0; i < (int)buf_len; i++) dprintf(("%c", buf[i])); + dprintf(("!\n")); + + csp = malloc(sizeof(struct checksum)); + if (csp == NULL) { + pefs_warn("memory allocation error"); + EVP_MD_CTX_cleanup(&mdctx); + return (PEFS_ERR_SYS); + } + csp->hash = malloc(hash_len); + if (csp->hash == NULL) { + pefs_warn("memory allocation error"); + free(csp); + EVP_MD_CTX_cleanup(&mdctx); + return (PEFS_ERR_SYS); + } + + EVP_DigestFinal_ex(&mdctx, csp->hash, &md_len); + + dprintf(("Digest is: ")); + for (i = 0; i < md_len; i++) dprintf(("%02x", csp->hash[i])); + dprintf(("\n")); + + EVP_MD_CTX_cleanup(&mdctx); + + TAILQ_INSERT_TAIL(&(fhp->checksums), csp, checksum_entries); + fhp->nhashes++; + } + + /* + * XXXgpf: [TODO] better move the fp into file_header struct and deal with + * closing it during pefs_file_close. + */ + if (fp != NULL) { + fclose(fp); + fhp->fd = -1; + } + + return (0); +} + +static void +pefs_init_hash_table(struct cuckoo_hash_table *chtp) +{ + chtp->size = 0; + chtp->nelements = 0; + chtp->buckets1 = NULL; + chtp->buckets2 = NULL; +} + +static int +pefs_allocate_hash_table(struct cuckoo_hash_table *chtp, uint32_t nelements, + int alloc_flag) +{ + uint32_t i; + + if (alloc_flag == PEFS_EXTEND) { + /* + * spending 15% more space for each table lowers the chance to fall into + * an infinite loop during cuckoo insertion to about 1.5%. + */ + chtp->size = pefs_next_prime(nelements + + ((nelements * PEFS_EXTRA_TABLE_SIZE)/100)); + chtp->nelements = nelements; + if (chtp->size < chtp->nelements) { + pefs_warn("numeric overflow while computing hash table size"); + return (PEFS_ERR_GENERIC); + } + } + else if (alloc_flag == PEFS_NOEXTEND) { + chtp->size = nelements; + chtp->nelements = nelements; + } + /* re-alloc the hash tables in case of infinite loop during cuckoo insert */ + else if (alloc_flag == PEFS_REALLOC) { + chtp->size = pefs_next_prime(chtp->size + 1); + if (chtp->size < chtp->nelements) { + pefs_warn("numeric overflow while computing new hash table size"); + return (PEFS_ERR_GENERIC); + } + free(chtp->buckets1); + free(chtp->buckets2); + } + + dprintf(("hash table elem:%u\tsize: %u\n", chtp->nelements, chtp->size)); + + chtp->buckets1 = malloc (chtp->size * sizeof(struct bucket)); + if (chtp->buckets1 == NULL) { + pefs_warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + + for (i = 0; i < chtp->size; i++) + chtp->buckets1[i].fhp = NULL; + + chtp->buckets2 = malloc (chtp->size * sizeof(struct bucket)); + if (chtp->buckets2 == NULL) { + pefs_warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + + for (i = 0; i < chtp->size; i++) + chtp->buckets2[i].fhp = NULL; + + return (0); +} + +static void +pefs_free_hash_table(struct cuckoo_hash_table *chtp) +{ + struct bucket *bp; + uint32_t i; + + if (chtp->buckets1 != NULL) { + for (i = 0; i < chtp->size; i++) { + bp = &chtp->buckets1[i]; + pefs_free_file_header(bp->fhp); + } + free(chtp->buckets1); + } + + if (chtp->buckets2 != NULL) { + for (i = 0; i < chtp->size; i++) { + bp = &chtp->buckets2[i]; + pefs_free_file_header(bp->fhp); + } + free(chtp->buckets2); + } +} + +static void +pefs_free_file_header_tail(struct file_header_head *fh_headp) +{ + struct file_header *fhp, *tfhp; + + TAILQ_FOREACH_SAFE(fhp, fh_headp, file_header_entries, tfhp) { + TAILQ_REMOVE(fh_headp, fhp, file_header_entries); + pefs_free_file_header(fhp); + } +} + +static uint32_t +pefs_hash1(struct cuckoo_hash_table *chtp, struct file_header *fhp) +{ + uint32_t nbucket; + + nbucket = fhp->fid.fid_num % chtp->size; + dprintf(("hash1: goto bucket %d\n", nbucket)); + return (nbucket); +} + +static uint32_t +pefs_hash2(struct cuckoo_hash_table *chtp, struct file_header *fhp) +{ + uint32_t nbucket; + + nbucket = fnv_64_buf(&(fhp->fid.fid_num), sizeof(fhp->fid.fid_num), + FNV1_64_INIT) % chtp->size; + dprintf(("hash2: goto bucket %d\n", nbucket)); + + return (nbucket); +} + +static struct file_header * +pefs_cuckoo_lookup(struct cuckoo_hash_table *chtp, struct file_header *fhp) +{ + struct file_header *elem, *elem1, *elem2; + int pos1, pos2; + + elem = fhp; + pos1 = pefs_hash1(chtp, elem); + elem1 = chtp->buckets1[pos1].fhp; + if (elem1 != NULL) { + if (elem1->fid.fid_num == elem->fid.fid_num) { + return (elem1); + } + } + + pos2 = pefs_hash2(chtp, elem); + elem2 = chtp->buckets2[pos2].fhp; + if (elem2 != NULL) { + if (elem2->fid.fid_num == elem->fid.fid_num) { + return (elem2); + } + } + + return (NULL); +} + +static int +pefs_cuckoo_insert(struct cuckoo_hash_table *chtp, + struct file_header *fhp) +{ + struct file_header *elem, *elem1, *elem2, *res; + uint32_t i, max_tries, pos1, pos2; + + max_tries = chtp->size; + elem = fhp; + + /* file_id collision check */ + res = pefs_cuckoo_lookup(chtp, elem); + if (res != NULL) { + pefs_warn("file identifier collision detected between files: %s & %s", + res->path, elem->path); + return (PEFS_ERR_EXIST); + } + + for (i = 0; i < max_tries; i++) { + pos1 = pefs_hash1(chtp, elem); + elem1 = chtp->buckets1[pos1].fhp; + /* do the cuckoo! */ + chtp->buckets1[pos1].fhp = elem; + if (elem1 == NULL) + return 0; + pos2 = pefs_hash2(chtp, elem1); + elem2 = chtp->buckets2[pos2].fhp; + /* do the cuckoo! */ + chtp->buckets2[pos2].fhp = elem1; + if (elem2 == NULL) + return 0; + else + elem = elem2; + } + + /* XXXgpf: should be left as a warning during development phase */ + pefs_warn("cuckoo_insert resulted in infinite loop!"); + return (PEFS_ERR_GENERIC); +} + +static int +pefs_add_to_hash_table(struct cuckoo_hash_table *chtp, + struct file_header *fhp) +{ + return (pefs_cuckoo_insert(chtp, fhp)); +} + +/* XXXgpf: for debugging purposes */ +static void +pefs_print_hash_tables(struct cuckoo_hash_table *chtp, uint8_t hash_len) +{ + struct file_header *fhp; + struct checksum *csp; + uint32_t i,j; + + dprintf(("\n+++Printing Hash Table 1+++\n\n")); + for (i = 0; i < chtp->size; i++) { + fhp = chtp->buckets1[i].fhp; + dprintf(("\nbucket %d with element: %d\n", i, fhp == NULL ? 0 : 1)); + if (fhp != NULL) { + dprintf(("\tid = %llu\tnhashes = %d\n", fhp->fid.fid_num, + fhp->nhashes)); + if (fhp->path[0] == '/') + dprintf(("\tpath = %s\n", fhp->path)); + TAILQ_FOREACH(csp, &(fhp->checksums), checksum_entries) { + dprintf(("\t\tdigest=")); + for (j = 0; j < hash_len; j++) + dprintf(("%02x", csp->hash[j])); + dprintf(("\n")); + } + } + } + + dprintf(("\n+++Printing Hash Table 2+++\n\n")); + for (i = 0; i < chtp->size; i++) { + fhp = chtp->buckets2[i].fhp; + dprintf(("\nbucket %d with element: %d\n", i, fhp == NULL ? 0 : 1)); + if (fhp != NULL) { + dprintf(("\tid = %llu\tnhashes = %d\n", fhp->fid.fid_num, + fhp->nhashes)); + if (fhp->path[0] == '/') + dprintf(("\tpath = %s\n", fhp->path)); + TAILQ_FOREACH(csp, &(fhp->checksums), checksum_entries) { + dprintf(("\t\tdigest=")); + for (j = 0; j < hash_len; j++) + dprintf(("%02x", csp->hash[j])); + dprintf(("\n")); + } + } + } +} + +/* + * pefs encrypted filename = XBase64(checksum || E(tweak || filename)) + * We use filename mac (checksum) as file_id. This way, should a filesystem + * be dump/restored, there will be no need to recreate .pefs.checksum because + * filenames remain the same. + * + * file id used is checksum = VMAC(E(tweak || filename)) + */ +static int +pefs_get_file_id(struct file_header *fhp, int flags) +{ + struct pefs_xnamecsum xncs; + uint64_t temp; + char *enc, *buf; + int error, r; + size_t buf_len, enc_len; + + if ((flags & PEFS_NOKEY) != 0 || (flags & PEFS_UNMOUNTED) != 0) { + /* in this case, we already have the encrypted filename */ + enc = fhp->filename; + enc_len = strnlen(fhp->filename, sizeof(fhp->filename)); + enc++; + enc_len--; + buf_len = MAXNAMLEN + 1; + buf = malloc(buf_len); + if (buf == NULL) { + pefs_warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + + r = pefs_name_pton(enc, enc_len, buf, buf_len); + if (r <= 0) { + pefs_warn("failed to extract file id from encrypted filename: %s", + fhp->filename); + error = PEFS_ERR_GENERIC; + } + else { + memcpy(fhp->fid.fid_str, buf, sizeof(temp)); + error = 0; + } + + free(buf); + return (error); + } + + strlcpy(xncs.pxnc_filename, fhp->filename, sizeof(xncs.pxnc_filename)); + xncs.pxnc_namelen = strnlen(xncs.pxnc_filename, sizeof(xncs.pxnc_filename)); + + /* feed parent directory to ioctl() */ + error = ioctl(fhp->pfd, PEFS_GETNAMECSUM, &xncs); + + if (error == 0) + memcpy(fhp->fid.fid_str, xncs.pxnc_csum, sizeof(xncs.pxnc_csum)); + else + pefs_warn("failed to fetch file id from kernel for filename: %s", + fhp->filename); + + return (error); +} + +static void +pefs_symlink_warn(struct cuckoo_hash_table *chtp, struct file_header_head *fhhp) +{ + struct stat sb; + struct file_header targetfh; + char dirbuf[MAXPATHLEN + 1], namebuf[MAXNAMLEN + 1]; + struct file_header *fhp, *res; + char *dirnamep, *namep; + int error; + + TAILQ_FOREACH(fhp, fhhp, file_header_entries) { + /* + * If fhp == symlink and target file == regular file || symlink, + * then grab target's filename MAC and look it up in our hash table. + * Print a warning message if it is not found. + * symlink target referes to the file that is immediately pointed to by + * our symlink. therefore in a syml1->syml2->file example, we only check + * syml2 if we are supplied syml1. This is by choise so that user will + * receive warning for intermediate parts of a symlink chain. + * + * XXXgpf: perhaps relax restrictions on warning messages + */ + if (fhp->target_path != NULL) { + if (lstat(fhp->target_path, &sb) != 0) { + warn("cannot stat file %s", fhp->target_path); + continue; + } + + if (S_ISLNK(sb.st_mode) == 0 && S_ISREG(sb.st_mode) == 0) + continue; + + /* retrieve dirpath & filaname */ + strlcpy(targetfh.path, fhp->target_path, sizeof(targetfh.path)); + strlcpy(dirbuf, targetfh.path, sizeof(dirbuf)); + strlcpy(namebuf, targetfh.path, sizeof(namebuf)); + + dirnamep = dirname(dirbuf); + if (dirnamep == NULL) { + pefs_warn("failed to extract dirname of %s", targetfh.path); + continue; + } + strlcpy(targetfh.dirpath, dirnamep, sizeof(targetfh.dirpath)); + + namep = basename(namebuf); + if (namep == NULL) { + pefs_warn("failed to extract filename of %s", targetfh.path); + continue; + } + strlcpy(targetfh.filename, namep, sizeof(targetfh.filename)); + + targetfh.pfd = -1; + targetfh.pfd = open(targetfh.dirpath, O_RDONLY); + if (targetfh.pfd < 0) { + warn("cannot open %s", targetfh.dirpath); + continue; + } + + error = pefs_get_file_id(&targetfh, 0); + if (error == 0) { + res = pefs_cuckoo_lookup(chtp, &targetfh); + if (res == NULL) + pefs_warn("target file %s of symlink %s was not " + "found in inputlist", targetfh.path, fhp->path); + } + pefs_close_files(&targetfh); + } + } +} + +/* XXXgpf: for debugging purposes */ +static void +pefs_hardlink_print(struct hardlink_head *hlc_headp) +{ + struct hardlink_counter *hlcp; + struct file_header *fhp; + + dprintf(("\n+++Printing RB tree+++\n\n")); + RB_FOREACH(hlcp, hardlink_head, hlc_headp) { + dprintf(("inode %d\ttotal links %d\tlinks found %d\n", + hlcp->inode, hlcp->total_links, hlcp->links_found)); + TAILQ_FOREACH(fhp, &(hlcp->file_headers), fh_hardlink_entries) { + dprintf(("\tpath: %s\n", fhp->path)); + } + } +} + +static void +pefs_hardlink_warn(struct hardlink_head *hlc_headp) +{ + struct hardlink_counter *hlcp; + struct file_header *fhp; + int i; + + RB_FOREACH(hlcp, hardlink_head, hlc_headp) { + if (hlcp->total_links > hlcp->links_found) { + pefs_warn("%d hard link(s) of total %d were found in input list " + "for file with inode: %d", + hlcp->links_found, hlcp->total_links, hlcp->inode); + i = 1; + TAILQ_FOREACH(fhp, &(hlcp->file_headers), fh_hardlink_entries) { + pefs_warn("link %d: %s", i++, fhp->path); + } + } + } +} + +static int +pefs_hardlink_insert(struct hardlink_head *hlc_headp, struct file_header *fhp, + struct stat *sbp) +{ + struct hardlink_counter find, *res, *new_hlcp; + + find.inode = sbp->st_ino; + res = RB_FIND(hardlink_head, hlc_headp, &find); + + if (res != NULL) { + res->links_found++; + TAILQ_INSERT_TAIL(&(res->file_headers), fhp, fh_hardlink_entries); + } + else { + new_hlcp = malloc(sizeof(struct hardlink_counter)); + if (new_hlcp == NULL) { + warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + + new_hlcp->inode = sbp->st_ino; + new_hlcp->total_links = sbp->st_nlink; + new_hlcp->links_found = 1; + TAILQ_INIT(&(new_hlcp->file_headers)); + TAILQ_INSERT_TAIL(&(new_hlcp->file_headers), fhp, fh_hardlink_entries); + + RB_INSERT(hardlink_head, hlc_headp, new_hlcp); + } + + return (0); +} + +static int +pefs_hardlink_cmp(struct hardlink_counter *hlcp1, struct hardlink_counter *hlcp2) +{ + if (hlcp1->inode < hlcp2->inode) + return -1; + else if (hlcp1->inode > hlcp2->inode) + return 1; + else + return 0; +} + +static void +pefs_hardlink_free(struct hardlink_head *hlc_headp) +{ + struct hardlink_counter *cur, *next; + + for (cur = RB_MIN(hardlink_head, hlc_headp); cur != NULL; cur = next) { + next = RB_NEXT(hardlink_head, hlc_headp, cur); + RB_REMOVE(hardlink_head, hlc_headp, cur); + free(cur); + } +} + +/* open a file and perform various semantic checks on it */ +static int +pefs_open_semantic_checks(struct file_header *fhp, struct statfs *fsp, + struct hardlink_head *hlc_headp, int flags) +{ + char dirbuf[MAXPATHLEN + 1], namebuf[MAXNAMLEN + 1]; + char sbuf[MAXPATHLEN + 1]; + struct stat sb; + struct statfs this_fs; + char *dirnamep, *namep; + size_t target_path_size; + int error, nchars; + + /* retrieve dirpath & filename */ + strlcpy(dirbuf, fhp->path, sizeof(dirbuf)); + strlcpy(namebuf, fhp->path, sizeof(namebuf)); + + dirnamep = dirname(dirbuf); + if (dirnamep == NULL) { + pefs_warn("failed to extract dirname of %s", fhp->path); + return (PEFS_ERR_GENERIC); + } + strlcpy(fhp->dirpath, dirnamep, sizeof(fhp->dirpath)); + + namep = basename(namebuf); + if (namep == NULL) { + pefs_warn("failed to extract filename of %s", fhp->path); + return (PEFS_ERR_GENERIC); + } + strlcpy(fhp->filename, namep, sizeof(fhp->filename)); + + dprintf(("path = %s!\ndirname = %s!\nbasename = %s!\n", fhp->path, + fhp->dirpath, fhp->filename)); + + if (lstat(fhp->path, &sb) != 0) { + warn("cannot stat file %s", fhp->path); + return (PEFS_ERR_SYS); + } + + if ((sb.st_flags & SF_IMMUTABLE) == 0 && + (flags & PEFS_SETIMMUTABLE) == 0 && + (flags & PEFS_VERIFY) == 0 && + (flags & PEFS_GETID) == 0) { + pefs_warn("file %s does not have schg flag", fhp->path); + return (PEFS_ERR_SYS); + } + + if (S_ISLNK(sb.st_mode) != 0) { + fhp->pfd = open(fhp->dirpath, O_RDONLY); + if (fhp->pfd < 0) { + warn("cannot open %s", fhp->dirpath); + return (PEFS_ERR_IO); + } + + if ((sb.st_flags & SF_IMMUTABLE) == 0 && + (flags & PEFS_SETIMMUTABLE) != 0) { + dprintf(("setting immutable flag %s\n", fhp->path)); + error = lchflags(fhp->path, SF_IMMUTABLE); + if (error != 0) { + warn("cannot set schg flag to file %s", fhp->path); + return (PEFS_ERR_SYS); + } + } + + nchars = readlink(fhp->path, sbuf, sizeof(sbuf)); + if (nchars == -1) { + warn("readlink failed: %s", fhp->path); + return (PEFS_ERR_SYS); + } + + /* + * Target_path can be used to tell if user has supplied target_file + * in input file-list, since symlinks are not traversed. User will have + * to provide fullpaths for both symlink & target file if he wants + * integrity checking for both. However, we will print warning messages + * in case target file is not provided in user supplied input list. + * + * Target referes to the file immediately pointed to by our symlink, not + * the final target of a possible symlink chain. + */ + target_path_size = MAXPATHLEN + 1; + fhp->target_path = malloc(target_path_size); + if (fhp->target_path == NULL) { + warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + sbuf[nchars] = '\0'; + + /* turn relative paths to absolute paths */ + if (sbuf[0] != '/' && (flags & PEFS_UNMOUNTED) == 0 && + (flags & PEFS_NOKEY) == 0) + snprintf(fhp->target_path, target_path_size, "%s/%s", + fhp->dirpath, sbuf); + else + strlcpy(fhp->target_path, sbuf, target_path_size); + + /* + * The only semantic check that's performed on target file is an attempt + * to lstat() the file, in order to make sure the file exists. This is + * intentional since target file is allowed to reside on a different + * filesystem or in the same filesystem, but not be a regular file or a + * symlink. + * e.g. a directory + */ + if ((flags & PEFS_NOKEY) == 0 && (flags & PEFS_UNMOUNTED) == 0) { + /* + * If target_path is encrypted due to no key or unmounted fs, + * lstat will always fail. + */ + if (lstat(fhp->target_path, &sb) != 0) { + warn("cannot stat symlink's target file %s", fhp->target_path); + return (PEFS_ERR_SYS); + } + } + return (0); + } + + fhp->fd = open(fhp->path, O_RDONLY | O_NOFOLLOW); + if (fhp->fd < 0) { + warn("cannot open %s", fhp->path); + return (PEFS_ERR_IO); + } + + fhp->pfd = open(fhp->dirpath, O_RDONLY); + if (fhp->pfd < 0) { + warn("cannot open %s", fhp->dirpath); + return (PEFS_ERR_IO); + } + + if (fstat(fhp->fd, &sb) != 0) { + warn("cannot stat file %s", fhp->path); + return (PEFS_ERR_SYS); + } + + if (S_ISREG(sb.st_mode) == 0 && (flags & PEFS_GETID) == 0) { + pefs_warn("filename: %s is not a regular file", fhp->path); + return (PEFS_ERR_INVALID); + } + + if ((sb.st_flags & SF_IMMUTABLE) == 0 && + (flags & PEFS_SETIMMUTABLE) != 0) { + dprintf(("setting immutable flag %s\n", fhp->path)); + error = fchflags(fhp->fd, SF_IMMUTABLE); + if (error != 0) { + warn("cannot set schg flag to file %s", fhp->path); + return (PEFS_ERR_SYS); + } + } + + if ((flags & PEFS_UNMOUNTED) == 0 && (fsp != NULL)) { + if (fstatfs(fhp->fd, &this_fs) == -1) { + pefs_warn("statfs failed: %s: %s", fhp->path, strerror(errno)); + return (PEFS_ERR_SYS); + } + + if ((fsp->f_fsid.val[0] != this_fs.f_fsid.val[0]) || + (fsp->f_fsid.val[1] != this_fs.f_fsid.val[1])) { + pefs_warn("filename: %s does not reside in filesystem %s", + fhp->path, fsp->f_mntonname); + return (PEFS_ERR_INVALID); + } + } + + /* Keep all hardlink file headers in a rb tree */ + if (sb.st_nlink > 1 && hlc_headp != NULL) + return (pefs_hardlink_insert(hlc_headp, fhp, &sb)); + + return (0); +} + +static struct file_header * +pefs_next_file(FILE *fpin, int *error, int *nfiles) +{ + char buf[MAXPATHLEN + 1]; + struct file_header *fhp; + + if (fgets(buf, sizeof(buf), fpin) == NULL) { + if (feof(fpin)) + *error = 0; + else { + *error = PEFS_ERR_IO; + warn("error reading input"); + } + return (NULL); + } + + if (buf[strnlen(buf, sizeof(buf)) - 1] == '\n') + buf[strnlen(buf, sizeof(buf)) - 1] = '\0'; + dprintf(("\nnext file entry=%s!\n", buf)); + + fhp = pefs_allocate_file_header(); + if (fhp == NULL) { + *error = PEFS_ERR_SYS; + return (NULL); + } + + strlcpy(fhp->path, buf, sizeof(fhp->path)); + (*nfiles)++; + + return (fhp); +} + +/* + * This function creates the in memory database that will be later written to + * the checksum file. + * A) For each file entry: + * A1) semantic checks: + * A1a) file should reside in pefs filesystem & file should be + * regular file or symbolic link. + * A1b) if symlink, acquire and save the absolute path of the symlink's + * target. Try to lstat() the target but don't do anything else. + * A1c) If hardlink, save a reference to this file entry in our + * rb tree that uses i-node numbers as keys and is used in + * part 'C' to print warnings. + * A1d) Open and store file descriptors to file & parent_directory. + * A2) the file_id is retrieved. (filename MAC) + * A3) list of checksums is computed for the file's 4k blocks. + * A4) file entry is added to the universal fh_head. + * B) Print warnings for hardlinks if the number of links found in inputlist + * isn't equal to the number of total inode links. + * C) Hash tables are allocated. + * D) Cuckoo insertion: + * We try to populate our hash tables using the cuckoo algorithm. Should we fall + * into an infinite loop during insertion, we re-allocate larger hash tables + * and try again until we succeed. The possibility to fail twice in a row is + * 1.5% * 1.5% = 0.0225% + * E) For each symlink found in input list, print warnings if its target file + * was not found in input list since symlinks are not traversed. + */ +static int +pefs_create_in_memory_db(FILE *fpin, const EVP_MD *md, uint8_t hash_len, + struct cuckoo_hash_table *chtp, char *fsroot, int flags) +{ + struct statfs fs; + struct file_header_head fh_head; + struct hardlink_head hlc_head; + struct file_header *fhp; + int error; + uint32_t nfiles; + + nfiles = 0; + if (statfs(fsroot, &fs) == -1) { + pefs_warn("statfs failed: %s: %s", fsroot, strerror(errno)); + return (PEFS_ERR_SYS); + } + + TAILQ_INIT(&fh_head); + RB_INIT(&hlc_head); + while((fhp = pefs_next_file(fpin, &error, &nfiles)) != NULL) { + error = pefs_open_semantic_checks(fhp, &fs, &hlc_head, flags); + if (error != 0) { + pefs_free_file_header(fhp); + return (error); + } + + error = pefs_get_file_id(fhp, flags); + if (error != 0) { + pefs_free_file_header(fhp); + return (error); + } + + error = pefs_compute_file_checksums(fhp, md, hash_len, flags); + if (error != 0) { + pefs_free_file_header(fhp); + return (error); + } + + TAILQ_INSERT_TAIL(&fh_head, fhp, file_header_entries); + pefs_close_files(fhp); + } + + /* checking I/O error from pefs_next_file() */ + if (error != 0) + return (error); + + pefs_hardlink_print(&hlc_head); + pefs_hardlink_warn(&hlc_head); + pefs_hardlink_free(&hlc_head); + + error = pefs_allocate_hash_table(chtp, nfiles, PEFS_EXTEND); + if (error != 0) + return (error); + +cuckoo_insert: + TAILQ_FOREACH(fhp, &fh_head, file_header_entries) { + error = pefs_add_to_hash_table(chtp, fhp); + /* collision error */ + if (error == PEFS_ERR_EXIST) + return (error); + /* + * cuckoo insertion algorithm fell into an infinite loop! + * Create new, larger hash tables where size = next_prime(old_size) + * and try again. + */ + else if (error != 0) { + dprintf(("fell into an infinite loop!\n")); + error = pefs_allocate_hash_table(chtp, nfiles, PEFS_REALLOC); + if (error != 0) + return (error); + goto cuckoo_insert; + } + } + pefs_print_hash_tables(chtp, hash_len); + pefs_symlink_warn(chtp, &fh_head); + + return (error); +} + +static int +pefs_write_checksum_file_header(int fdout, struct checksum_file_header *cfhp) +{ + int rval; + uint32_t bytes, hash_table_size; + + rval = lseek(fdout, PEFS_SIGNATURE_MAX_LENGTH, SEEK_SET); + if (rval == -1) { + warn("lseek error while writing to .pefs.checksum"); + return (PEFS_ERR_SYS); + } + + bytes = write(fdout, &(cfhp->version), sizeof(cfhp->version)); + if (bytes != sizeof(cfhp->version)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = write(fdout, &(cfhp->reserved), sizeof(cfhp->reserved)); + if (bytes != sizeof(cfhp->reserved)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = write(fdout, &(cfhp->hash_len), sizeof(cfhp->hash_len)); + if (bytes != sizeof(cfhp->hash_len)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = write(fdout, cfhp->hash_algo, sizeof(cfhp->hash_algo)); + if (bytes != sizeof(cfhp->hash_algo)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = write(fdout, &(cfhp->offset_to_hash_table), + sizeof(cfhp->offset_to_hash_table)); + if (bytes != sizeof(cfhp->offset_to_hash_table)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + + hash_table_size = htole32(cfhp->hash_table_size); + bytes = write(fdout, &hash_table_size, sizeof(hash_table_size)); + if (bytes != sizeof(hash_table_size)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + + return (0); +} + +static int +pefs_write_file_header(int fdout, struct file_header *fhp, + uint32_t *buckets_offset) +{ + uint32_t nhashes, offset_to_checksums; + int bytes; + + nhashes = htole32(fhp->nhashes); + bytes = pwrite(fdout, &nhashes, sizeof(nhashes), *buckets_offset); + if (bytes != sizeof(nhashes)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + (*buckets_offset)+= sizeof(nhashes); + + offset_to_checksums = htole32(fhp->offset_to_checksums); + bytes = pwrite(fdout, &offset_to_checksums, + sizeof(offset_to_checksums), *buckets_offset); + if (bytes != sizeof(offset_to_checksums)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + (*buckets_offset)+= sizeof(offset_to_checksums); + + bytes = pwrite(fdout, fhp->fid.fid_str, sizeof(fhp->fid.fid_str), + *buckets_offset); + if (bytes != sizeof(fhp->fid.fid_str)) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + (*buckets_offset)+= sizeof(fhp->fid.fid_str); + + return (0); +} + +static int +pefs_write_bucket(int fdout, struct bucket *bp, uint32_t *buckets_offset) +{ + struct file_header emptyfh; + struct file_header *fhp; + + fhp = bp->fhp; + /* Empty files aren't allowed so nhashes == 0 symbolizes an empty bucket */ + if (fhp == NULL) { + emptyfh.nhashes = 0; + emptyfh.fid.fid_num = 0; + emptyfh.offset_to_checksums = 0; + fhp = &emptyfh; + } + + return (pefs_write_file_header(fdout, fhp, buckets_offset)); +} + +static int +pefs_write_hash(int fdout, struct checksum *csp, uint32_t *hashes_offset, + uint8_t hash_len) +{ + int bytes; + + bytes = pwrite(fdout, csp->hash, hash_len, *hashes_offset); + if (bytes != hash_len) { + warn("error writing to .pefs.checksum"); + return (PEFS_ERR_IO); + } + (*hashes_offset)+= hash_len; + + return (0); +} + +/* + * All data member writes are done separately so as to avoid alignment problems. + * Writes are always in little endian byte order, except file_id which is + * always treated as big endian. + * + * First 512 bytes of .pefs.checksum are reserved for the file's digital + * signature. + * + * After that, the next 16 bytes of .pefs.checksum are filled with + * .pefs.checksum's global file header. Right after this header lies the + * 'index' part of our database. + * This index is later kept in kernel memory. + * + * Index: + * Both hash tables of cuckoo algorithm are written to the file sequentially. + * The first hash table corresponds to hash1() and the second hash table to + * hash2(). Each bucket (cell) of a hash table contains at most one + * entry(=file_header). + * The size of an entry is 16 bytes. + * + * hash table entries end at the following offset: + * 16 + hash_table_size * 2 * 16 + * + * Checksums: + * The last part of .pefs.checksum is filled with the actual checksums. + * The offset where the first checksum starts is a 512 aligned address. + * Each hash table file header entry contains an offset that points to the + * beginning of a chain of checksums for that particular file's 4k blocks. + */ +static int +pefs_write_checksum_file(int fdout, struct checksum_file_header *cfhp, + struct cuckoo_hash_table *chtp) +{ + struct bucket *bp; + struct checksum *csp; + struct file_header *fhp; + uint32_t i, buckets_offset, hashes_offset; + int error; + + error = pefs_write_checksum_file_header(fdout, cfhp); + if (error != 0) + return (error); + + /* this points to where the buckets start */ + buckets_offset = PEFS_SIGNATURE_MAX_LENGTH + cfhp->offset_to_hash_table; + + /* this points to where the buckets stop and the checksums start */ + hashes_offset = buckets_offset; + hashes_offset+= chtp->size * PEFS_FH_SIZE * 2; + if (hashes_offset % PEFS_HASH_BYTE_ALIGNMENT != 0) + hashes_offset+= PEFS_HASH_BYTE_ALIGNMENT - (hashes_offset % + PEFS_HASH_BYTE_ALIGNMENT); + + for (i = 0; i < chtp->size; i++) { + bp = &chtp->buckets1[i]; + if (bp->fhp != NULL) + bp->fhp->offset_to_checksums = hashes_offset; + error = pefs_write_bucket(fdout, bp, &buckets_offset); + if (error != 0) + return (error); + + fhp = bp->fhp; + if (fhp != NULL) { + TAILQ_FOREACH(csp, &(fhp->checksums), checksum_entries) { + error = pefs_write_hash(fdout, csp, &hashes_offset, + cfhp->hash_len); + if (error != 0) + return (error); + } + } + } + + for (i = 0; i < chtp->size; i++) { + bp = &chtp->buckets2[i]; + if (bp->fhp != NULL) + bp->fhp->offset_to_checksums = hashes_offset; + error = pefs_write_bucket(fdout, bp, &buckets_offset); + if (error != 0) + return (error); + + fhp = bp->fhp; + if (fhp != NULL) { + TAILQ_FOREACH(csp, &(fhp->checksums), checksum_entries) { + error = pefs_write_hash(fdout, csp, &hashes_offset, + cfhp->hash_len); + if (error != 0) + return (error); + } + } + } + + return (0); +} + +static void +pefs_init_checksum_file_header(struct checksum_file_header *cfhp, + const char *algo, uint8_t hash_len, struct cuckoo_hash_table *chtp) +{ + cfhp->hash_len = hash_len; + cfhp->hash_table_size = chtp->size; + cfhp->version = PEFS_CHECKSUM_FILE_VERSION; + strlcpy(cfhp->hash_algo, algo, sizeof(cfhp->hash_algo)); + cfhp->offset_to_hash_table = PEFS_CFH_SIZE; +} + +/* read dsa privkey from file */ +static EVP_PKEY * +pefs_read_dsa_privkey(FILE *pk_fp) +{ + DSA *dsa; + EVP_PKEY *pkey; + int rval; + + dsa = PEM_read_DSAPrivateKey(pk_fp, NULL, NULL, NULL); + if (dsa == NULL) { + pefs_warn("error reading dsa private key"); + return (NULL); + } + + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + pefs_warn("error allocating a pkey"); + DSA_free(dsa); + return (NULL); + } + + rval = EVP_PKEY_assign_DSA(pkey, dsa); + if (rval != 1) { + pefs_warn("error assigning dsa key"); + EVP_PKEY_free(pkey); + DSA_free(dsa); + return (NULL); + } + + return (pkey); +} + +/* Sign .pefs.checksum. Signature is placed at the beginning of the file. */ +static int +pefs_sign_file(int fd, FILE *pkfp) +{ + unsigned char buf[PEFS_BUFISZE]; + EVP_MD_CTX ctx; + const EVP_MD *md; + EVP_PKEY *pkey; + unsigned char *sign; + unsigned int sign_len; + int bytes, error, rval; + + pkey = pefs_read_dsa_privkey(pkfp); + if (pkey == NULL) + return (PEFS_ERR_SYS); + + md = EVP_dss1(); + if (md == NULL) { + pefs_warn("error acquiring digest type"); + EVP_PKEY_free(pkey); + return (PEFS_ERR_GENERIC); + } + + /* generate digital signature */ + EVP_SignInit(&ctx, md); + + error = lseek(fd, PEFS_SIGNATURE_MAX_LENGTH, SEEK_SET); + if (error == -1) { + warn("lseek error while signing file"); + EVP_PKEY_free(pkey); + return (PEFS_ERR_SYS); + } + + while ((bytes = read(fd, buf, sizeof(buf))) > 0) { + rval = EVP_SignUpdate(&ctx, buf, bytes); + if (rval != 1) { + pefs_warn("error updating sign data"); + EVP_PKEY_free(pkey); + return (PEFS_ERR_SYS); + } + } + + if (bytes == -1) { + warn("read error"); + EVP_PKEY_free(pkey); + return (PEFS_ERR_IO); + } + + sign = malloc(EVP_PKEY_size(pkey)); + if (sign == NULL) { + pefs_warn("memory allocation error"); + EVP_PKEY_free(pkey); + return (PEFS_ERR_SYS); + } + + rval = EVP_SignFinal(&ctx, sign, &sign_len, pkey); + if (rval != 1) { + pefs_warn("error generating signature"); + free(sign); + EVP_PKEY_free(pkey); + return (PEFS_ERR_SYS); + } + + /* write digital signature to beginning of .pefs.checksum */ + bytes = pwrite(fd, sign, sign_len, 0); + if (bytes == -1) { + pefs_warn("error writing signature"); + free(sign); + EVP_PKEY_free(pkey); + return (PEFS_ERR_IO); + } + + free(sign); + EVP_PKEY_free(pkey); + + return (0); +} + +/* read dsa pubkey from file */ +static EVP_PKEY * +pefs_read_dsa_pubkey(FILE *pk_fp) +{ + DSA *dsa; + EVP_PKEY *pkey; + int rval; + + dsa = PEM_read_DSA_PUBKEY(pk_fp, NULL, NULL, NULL); + if (dsa == NULL) { + pefs_warn("error reading dsa pubkey"); + return (NULL); + } + + pkey = EVP_PKEY_new(); + if (pkey == NULL) { + pefs_warn("error allocating a pkey"); + DSA_free(dsa); + return (NULL); + } + + rval = EVP_PKEY_assign_DSA(pkey, dsa); + if (rval != 1) { + pefs_warn("error assigning dsa key"); + EVP_PKEY_free(pkey); + DSA_free(dsa); + return (NULL); + } + + return (pkey); +} + +/* verify digital signature of .pefs.checksum */ +static int +pefs_verify_signature(int fd, FILE *pk_fp) +{ + unsigned char buf[PEFS_BUFISZE]; + EVP_MD_CTX ctx; + const EVP_MD *md; + EVP_PKEY *pkey; + unsigned char *sign; + int bytes, error, rval, sign_len; + + pkey = pefs_read_dsa_pubkey(pk_fp); + if (pkey == NULL) + return (PEFS_ERR_SYS); + + sign = malloc(EVP_PKEY_size(pkey)); + if (sign == NULL) { + pefs_warn("memory allocation error"); + EVP_PKEY_free(pkey); + return (PEFS_ERR_SYS); + } + + /* read signature from .pefs.checksum */ + sign_len = pread(fd, sign, EVP_PKEY_size(pkey), 0); + if (sign_len == -1) { + warn("error reading signature"); + free(sign); + return (PEFS_ERR_IO); + } + + md = EVP_dss1(); + if (md == NULL) { + pefs_warn("error acquiring digest type"); + free(sign); + EVP_PKEY_free(pkey); + return (PEFS_ERR_GENERIC); + } + + /* process .pefs.checksum & verify the signature */ + EVP_VerifyInit(&ctx, md); + + error = lseek(fd, PEFS_SIGNATURE_MAX_LENGTH, SEEK_SET); + if (error == -1) { + warn("lseek error while verifying file"); + free(sign); + EVP_PKEY_free(pkey); + return (PEFS_ERR_SYS); + } + + while ((bytes = read(fd, buf, sizeof(buf))) > 0) { + rval = EVP_VerifyUpdate(&ctx, buf, bytes); + if (rval != 1) { + pefs_warn("error updating sign data"); + free(sign); + EVP_PKEY_free(pkey); + return (PEFS_ERR_SYS); + } + } + + if (bytes == -1) { + warn("read error"); + free(sign); + EVP_PKEY_free(pkey); + return (PEFS_ERR_IO); + } + + rval = EVP_VerifyFinal(&ctx, sign, sign_len, pkey); + if (rval == 0) { + pefs_warn("file signature is not verified by public key"); + free(sign); + EVP_PKEY_free(pkey); + return (PEFS_ERR_GENERIC); + } + else if (rval == -1) { + pefs_warn("error verifying signature"); + free(sign); + EVP_PKEY_free(pkey); + return (PEFS_ERR_GENERIC); + } + + free(sign); + EVP_PKEY_free(pkey); + + return (0); +} + +/* + * If .pefs.checksum is created inside pefs mounted fs, then it will obtain an + * encrypted filename & encrypted data, which is unacceptable. User should + * create checksum file outside of filesystem and then copy it by hand. + */ +static int +pefs_open_checksum_file(int *fdp, char *fsroot, char *csm_path) +{ + struct statfs pefs_fs, checksum_fs; + int fd; + + *fdp = -1; + + /* create checksum file */ + fd = open(csm_path, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR); + if (fd == -1) { + warn("cannot open %s", csm_path); + return (PEFS_ERR_IO); + } + + *fdp = fd; + + if (statfs(fsroot, &pefs_fs) == -1) { + pefs_warn("statfs failed: %s: %s", fsroot, strerror(errno)); + return (PEFS_ERR_SYS); + } + + if (statfs(csm_path, &checksum_fs) == -1) { + pefs_warn("statfs failed: %s: %s", csm_path, strerror(errno)); + return (PEFS_ERR_SYS); + } + + if ((pefs_fs.f_fsid.val[0] == checksum_fs.f_fsid.val[0]) && + (pefs_fs.f_fsid.val[1] == checksum_fs.f_fsid.val[1])) { + pefs_warn("%s must be created outside of pefs mounted filesystem %s", + csm_path, pefs_fs.f_mntonname); + return (PEFS_ERR_INVALID); + } + + return (0); +} + +/* + * An in memory database is created from entries in fpin. This database is + * later written to file ".pefs.checksum" which is created under csm_path. + * algo is used as a cryptographic hash function that produces checksums + * for 4k blocks of each file. When we are done with .pefs.checksum, we + * sign it and place the signature at the beginning of .pefs.checksum. + */ +int +pefs_create_checksum_file(FILE *fpin, char *fsroot, char *csm_path, + FILE *pk_fp, const char *algo, int flags) +{ + struct cuckoo_hash_table checksum_hash_table; + struct checksum_file_header cfh; + const EVP_MD *md; + int error, fdout; + uint8_t hash_len; + + OpenSSL_add_all_digests(); + md = EVP_get_digestbyname(algo); + + if(md == NULL) { + pefs_warn("Unknown message digest %s\n", algo); + return (PEFS_ERR_INVALID); + } + hash_len = EVP_MD_size(md); + + pefs_init_hash_table(&checksum_hash_table); + + error = pefs_open_checksum_file(&fdout, fsroot, csm_path); + if (error != 0) + goto out; + + error = pefs_create_in_memory_db(fpin, md, hash_len, + &checksum_hash_table, fsroot, flags); + if (error != 0) + goto out; + + pefs_init_checksum_file_header(&cfh, algo, hash_len, &checksum_hash_table); + + error = pefs_write_checksum_file(fdout, &cfh, &checksum_hash_table); + if (error != 0) + goto out; + + error = pefs_sign_file(fdout, pk_fp); + +out: + if (fdout >= 0) { + close(fdout); + if (error != 0) + unlink(csm_path); + } + pefs_free_hash_table(&checksum_hash_table); + + return (error); +} + +static int +pefs_read_checksum_file_header(int fdin, struct checksum_file_header *cfhp) +{ + int rval; + uint32_t bytes, hash_table_size; + + rval = lseek(fdin, PEFS_SIGNATURE_MAX_LENGTH, SEEK_SET); + if (rval == -1) { + warn("lseek error while reading checksum file header"); + return (PEFS_ERR_SYS); + } + + bytes = read(fdin, &(cfhp->version), sizeof(cfhp->version)); + if (bytes != sizeof(cfhp->version)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = read(fdin, &(cfhp->reserved), sizeof(cfhp->reserved)); + if (bytes != sizeof(cfhp->reserved)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = read(fdin, &(cfhp->hash_len), sizeof(cfhp->hash_len)); + if (bytes != sizeof(cfhp->hash_len)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = read(fdin, cfhp->hash_algo, sizeof(cfhp->hash_algo)); + if (bytes != sizeof(cfhp->hash_algo)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = read(fdin, &(cfhp->offset_to_hash_table), + sizeof(cfhp->offset_to_hash_table)); + if (bytes != sizeof(cfhp->offset_to_hash_table)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + + bytes = read(fdin, &hash_table_size, sizeof(hash_table_size)); + if (bytes != sizeof(hash_table_size)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + cfhp->hash_table_size = le32toh(hash_table_size); + + dprintf(("+++printing checksum file header info+++\n")); + dprintf(("version %X\nhash_len %d\nhash_algo %s\n", + cfhp->version, cfhp->hash_len, cfhp->hash_algo)); + dprintf(("offset to hash table %d\nhash table size %d\n", + cfhp->offset_to_hash_table, cfhp->hash_table_size)); + + return (0); +} + +static int +pefs_read_file_header(int fdin, struct file_header *fhp, + uint32_t *buckets_offset) +{ + uint32_t nhashes, offset_to_checksums; + int bytes; + + bytes = pread(fdin, &nhashes, sizeof(nhashes), *buckets_offset); + if (bytes != sizeof(nhashes)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + fhp->nhashes = le32toh(nhashes); + (*buckets_offset)+= sizeof(nhashes); + + bytes = pread(fdin, &offset_to_checksums, sizeof(offset_to_checksums), + *buckets_offset); + if (bytes != sizeof(offset_to_checksums)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + fhp->offset_to_checksums = le32toh(offset_to_checksums); + (*buckets_offset)+= sizeof(offset_to_checksums); + + bytes = pread(fdin, fhp->fid.fid_str, sizeof(fhp->fid.fid_str), + *buckets_offset); + if (bytes != sizeof(fhp->fid.fid_str)) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + (*buckets_offset)+= sizeof(fhp->fid.fid_str); + + dprintf(("\nfile header offset = %d\n", *fh_offset)); + dprintf(("\n++priting file header info++\n")); + dprintf(("nhashes %d\noffset_to_checksums %u\n", + fhp->nhashes, fhp->offset_to_checksums)); + dprintf(("file id %llu\n", fhp->file_id)); + + return (0); +} + +static int +pefs_read_bucket(int fdin, struct bucket *bp, uint32_t *buckets_offset) +{ + struct file_header *fhp; + int error; + + dprintf(("bucket offset = %d\n", *buckets_offset)); + fhp = pefs_allocate_file_header(); + if (fhp == NULL) + return (PEFS_ERR_SYS); + + error = pefs_read_file_header(fdin, fhp, buckets_offset); + if (error != 0) + return (error); + + if (fhp->nhashes == 0) { + pefs_free_file_header(fhp); + fhp = NULL; + } + bp->fhp = fhp; + + dprintf(("\n++priting bucket info++\n")); + + return (0); +} + +static int +pefs_read_hash(int fdin, struct checksum *csp, uint32_t *hashes_offset, + uint8_t hash_len) +{ + int bytes; + + bytes = pread(fdin, csp->hash, hash_len, *hashes_offset); + if (bytes != hash_len) { + warn("error reading from .pefs.checksum"); + return (PEFS_ERR_IO); + } + (*hashes_offset)+= hash_len; + + dprintf(("hashes offset = %d\n", *hashes_offset)); + dprintf(("hash %s\n", csp->hash)); + + return (0); +} + +static void +pefs_add_to_file_header(struct file_header *fhp, struct checksum *csp) +{ + TAILQ_INSERT_TAIL(&(fhp->checksums), csp, checksum_entries); +} + +/* Create in memory checksum database by reading .pefs.checksum file. */ +static int +pefs_read_checksum_file(int fdin, struct checksum_file_header *cfhp, + struct cuckoo_hash_table *chtp) +{ + struct bucket *bp; + struct checksum *csp; + struct file_header *fhp; + uint32_t i, k, buckets_offset, hashes_offset; + int error; + + /* this points to where the buckets start */ + buckets_offset = PEFS_SIGNATURE_MAX_LENGTH + cfhp->offset_to_hash_table; + + for (i = 0; i < chtp->size; i++) { + bp = &chtp->buckets1[i]; + error = pefs_read_bucket(fdin, bp, &buckets_offset); + if (error != 0) + return (error); + + fhp = bp->fhp; + if (fhp != NULL) { + hashes_offset = fhp->offset_to_checksums; + + for (k = 0; k < fhp->nhashes; k++) { + csp = malloc(sizeof(struct checksum)); + if (csp == NULL) { + pefs_warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + csp->hash = malloc(cfhp->hash_len); + if (csp->hash == NULL) { + pefs_warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + + error = pefs_read_hash(fdin, csp, &hashes_offset, + cfhp->hash_len); + if (error != 0) + return (error); + + pefs_add_to_file_header(fhp, csp); + } + } + } + + for (i = 0; i < chtp->size; i++) { + bp = &chtp->buckets2[i]; + error = pefs_read_bucket(fdin, bp, &buckets_offset); + if (error != 0) + return (error); + + fhp = bp->fhp; + if (fhp != NULL) { + hashes_offset = fhp->offset_to_checksums; + + for (k = 0; k < fhp->nhashes; k++) { + csp = malloc(sizeof(struct checksum)); + if (csp == NULL) { + pefs_warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + csp->hash = malloc(cfhp->hash_len); + if (csp->hash == NULL) { + pefs_warn("memory allocation error"); + return (PEFS_ERR_SYS); + } + + error = pefs_read_hash(fdin, csp, &hashes_offset, + cfhp->hash_len); + if (error != 0) + return (error); + + pefs_add_to_file_header(fhp, csp); + } + } + } + + return (0); +} + +static int +pefs_compare_checksums(struct file_header *fhp, struct file_header *indexfhp, + uint8_t hash_len) +{ + struct checksum *csp1, *csp2; + uint32_t i; + int error, cmp; + + dprintf(("comparing hashes for file with fid: %llu\n", + fhp->fid.fid_num)); + + error = 0; + if (fhp->nhashes != indexfhp->nhashes) { + pefs_warn("number of hashes differ between on disk file and %s values " + "for file %s: %u vs %u", + PEFS_FILE_CHECKSUM, fhp->path, fhp->nhashes, indexfhp->nhashes); + error = PEFS_ERR_CHECKSUM; + } + + csp1 = TAILQ_FIRST(&fhp->checksums); + csp2 = TAILQ_FIRST(&indexfhp->checksums); + i = 1; + while (csp1 != NULL && csp2 != NULL) { + cmp = memcmp(csp1->hash, csp2->hash, hash_len); + if (cmp != 0) { + pefs_warn("checksum no: %u differs between on disk file and %s " + "values for file %s", + i, PEFS_FILE_CHECKSUM, fhp->path); + error = PEFS_ERR_CHECKSUM; + } + csp1 = TAILQ_NEXT(csp1, checksum_entries); + csp2 = TAILQ_NEXT(csp2, checksum_entries); + i++; + } + + return (error); +} + +/* + * Traverse the entire filesystem and for every regular file or symbolic link, + * look it up in .pefs.checksum index and verify its checksums. + * + * This function will try to avoid returning due to errors encountered when + * checksums mismatch or immutable flags are missing so as to print as many + * warnings as possible. + */ +static int +pefs_traverse_fs(struct cuckoo_hash_table *chtp, const EVP_MD *md, + uint8_t hash_len, DIR *dirp, char *path, struct statfs *fsp, + struct hardlink_head *hlc_headp, struct file_header_head *fh_headp, + int flags, int *checksum_error) +{ + char tmpath[MAXPATHLEN]; + struct stat sb; + DIR *dirtmp; + struct dirent *sdp; + struct file_header *fhp, *indexfhp; + int error; + + while (dirp) { + sdp = readdir(dirp); + if (sdp != NULL) { + if (strcmp(sdp->d_name, "..") == 0 || + strcmp(sdp->d_name, ".") == 0 || + strcmp(sdp->d_name, ".pefs.db") == 0 || + strcmp(sdp->d_name, ".pefs.conf") == 0 || + strcmp(sdp->d_name, ".pefs.checksum") == 0) + continue; + + dprintf(("dirent: %s\n", sdp->d_name)); + snprintf(tmpath, sizeof(tmpath), "%s/%s", path, sdp->d_name); + switch (sdp->d_type) { + case DT_DIR: + dirtmp = opendir(tmpath); + if (dirtmp == NULL) { + pefs_warn("failed to open dir: %s", tmpath); + closedir(dirp); + return (PEFS_ERR_SYS); + } + error = pefs_traverse_fs(chtp, md, hash_len, dirtmp, tmpath, + fsp, hlc_headp, fh_headp, flags, checksum_error); + if (error != 0) { + closedir(dirp); + return (PEFS_ERR_SYS); + } + break; + /* + * Look up the file and verify its checksums. + * Total number of checksums should be the same and checksums + * should match. + * Also, take care of hardlinks & symlink warnings. + * After the traversal is done, we should have found all of the + * entries in the checksum file. + */ + /* FALLTHROUGH */ + case DT_REG: + case DT_LNK: + fhp = pefs_allocate_file_header(); + if (fhp == NULL) { + closedir(dirp); + return (PEFS_ERR_SYS); + } + strlcpy(fhp->path, tmpath, sizeof(fhp->path)); + + error = pefs_open_semantic_checks(fhp, fsp, NULL, flags); + if (error != 0) { + closedir(dirp); + pefs_free_file_header(fhp); + return (error); + } + + error = pefs_get_file_id(fhp, flags); + if (error != 0) { + closedir(dirp); + pefs_free_file_header(fhp); + return (error); + } + + indexfhp = pefs_cuckoo_lookup(chtp, fhp); + if (indexfhp == NULL) { + pefs_free_file_header(fhp); + break; + } + indexfhp->found = 1; + + error = pefs_compute_file_checksums(fhp, md, hash_len, flags); + if (error != 0) { + closedir(dirp); + pefs_free_file_header(fhp); + return (error); + } + + error = lstat(fhp->path, &sb); + if (error != 0) { + warn("cannot stat file %s", fhp->path); + closedir(dirp); + pefs_free_file_header(fhp); + return (PEFS_ERR_SYS); + } + + if ((sb.st_flags & SF_IMMUTABLE) == 0) { + pefs_warn("file %s does not have schg flag", fhp->path); + *checksum_error = PEFS_ERR_CHECKSUM; + } + + error = pefs_hardlink_insert(hlc_headp, fhp, &sb); + if (error != 0) { + closedir(dirp); + pefs_free_file_header(fhp); + return (error); + } + + /* + * if error encountered during pefs_compare_checksums, + * keep on traversing the fs to find other errors as well. + */ + error = pefs_compare_checksums(fhp, indexfhp, hash_len); + if (error != 0) + *checksum_error = error; + + TAILQ_INSERT_TAIL(fh_headp, fhp, file_header_entries); + pefs_close_files(fhp); + break; + default: + break; + } + } + else { + closedir(dirp); + return (0); + } + } + + /* unreachable, just to silence compiler */ + pefs_warn("invalid dirp argument for dir: %s", path); + return (PEFS_ERR_SYS); +} + +static int +pefs_found_all_entries(struct cuckoo_hash_table *chtp) +{ + struct file_header *fhp; + uint32_t i; + int error; + + error = 0; + for (i = 0; i < chtp->size; i++) { + fhp = chtp->buckets1[i].fhp; + if (fhp != NULL) + if (fhp->found != 1) { + pefs_warn("file with file id %llu was not found in " + "filesystem but exists in %s", + fhp->fid.fid_num, PEFS_FILE_CHECKSUM); + error = PEFS_ERR_NOENT; + } + } + + for (i = 0; i < chtp->size; i++) { + fhp = chtp->buckets2[i].fhp; + if (fhp != NULL) + if (fhp->found != 1) { + pefs_warn("file with file id %llu was not found in " + "filesystem but exists in %s", + fhp->fid.fid_num, PEFS_FILE_CHECKSUM); + error = PEFS_ERR_NOENT; + } + } + + return (error); +} + +/* + * Verify the contents of a .pefs.checksum file. + * A) .pefs.checksum is read into memory. + * B) The entire filesystem is traversed in order to check each and every file. + * C) warning messages are produced for hardlinks and symbolic links. + * D) check that every file in .pefs.checksum was actually found in filesystem. + * E) verify the file's signature with the user supplied public key + */ +int +pefs_verify_checksum(int fdin, FILE *pk_fp, char *fsroot, int flags) +{ + struct statfs fs; + struct checksum_file_header cfh; + struct cuckoo_hash_table cht; + struct file_header_head fh_head; + struct hardlink_head hlc_head; + const EVP_MD *md; + DIR *dirp; + int error, checksum_error; + uint8_t hash_len; + + RB_INIT(&hlc_head); + TAILQ_INIT(&fh_head); + checksum_error = 0; + + if (statfs(fsroot, &fs) == -1) { + pefs_warn("statfs failed: %s: %s", fsroot, strerror(errno)); + return (PEFS_ERR_SYS); + } + + error = pefs_read_checksum_file_header(fdin, &cfh); + if (error != 0) + return (error); + + OpenSSL_add_all_digests(); + md = EVP_get_digestbyname(cfh.hash_algo); + + if(md == NULL) { + pefs_warn("Unknown message digest %s", cfh.hash_algo); + return (PEFS_ERR_INVALID); + } + hash_len = EVP_MD_size(md); + + error = pefs_allocate_hash_table(&cht, cfh.hash_table_size, PEFS_NOEXTEND); + if (error != 0) + return (error); + + error = pefs_read_checksum_file(fdin, &cfh, &cht); + if (error != 0) + goto out; + + /* pefs_print_hash_tables(&cht, hash_len); */ + + dirp = opendir(fsroot); + if (dirp == NULL) { + pefs_warn("failed to open dir %s", fsroot); + goto out; + } + + error = pefs_traverse_fs(&cht, md, hash_len, dirp, fsroot, &fs, &hlc_head, + &fh_head, flags, &checksum_error); + if (error != 0) + goto out; + + /* pefs_hardlink_print(&hlc_head); */ + pefs_hardlink_warn(&hlc_head); + if ((flags & PEFS_UNMOUNTED) == 0 && (flags & PEFS_NOKEY) == 0) + pefs_symlink_warn(&cht, &fh_head); + + error = pefs_found_all_entries(&cht); + if (error == 0 && checksum_error != 0) + error = checksum_error; + + error = pefs_verify_signature(fdin, pk_fp); + +out: + pefs_free_hash_table(&cht); + pefs_hardlink_free(&hlc_head); + pefs_free_file_header_tail(&fh_head); + + return (error); +} + +/* retrieve and then print the name checksum ID for a given filename */ +int +pefs_filename_to_id(char *file_path, int flags) +{ + struct file_header *fhp; + int error; + + fhp = pefs_allocate_file_header(); + if (fhp == NULL) { + error = PEFS_ERR_SYS; + goto out; + } + + strlcpy(fhp->path, file_path, sizeof(fhp->path)); + + error = pefs_open_semantic_checks(fhp, NULL, NULL, flags); + if (error != 0) + goto out; + + error = pefs_get_file_id(fhp, flags); + if (error != 0) + goto out; + + printf("id: %llu\n", fhp->fid.fid_num); + +out: + pefs_free_file_header(fhp); + return (error); +} + +RB_GENERATE(hardlink_head, hardlink_counter, hardlink_entries, + pefs_hardlink_cmp); Index: pefs_kmod/sbin/pefs/pefs_ctl.c =================================================================== --- pefs_kmod/sbin/pefs/pefs_ctl.c (revision 235719) +++ pefs_kmod/sbin/pefs/pefs_ctl.c (revision 240588) @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -74,6 +76,9 @@ static int pefs_getkey(int argc, char *argv[]); static int pefs_showchains(int argc, char *argv[]); static int pefs_showalgs(int argc, char *argv[]); +static int pefs_addchecksum(int argc, char *argv[]); +static int pefs_verify(int argc, char *argv[]); +static int pefs_nameid(int argc, char *argv[]); typedef int (*command_func_t)(int argc, char **argv); typedef int (*keyop_func_t)(struct pefs_keychain_head *kch, int fd, @@ -100,9 +105,16 @@ { "delchain", pefs_delchain }, { "showchains", pefs_showchains }, { "showalgs", pefs_showalgs }, + { "addchecksum", pefs_addchecksum}, + { "verify", pefs_verify}, + { "nameid", pefs_nameid}, { NULL, NULL }, }; + +/* XXXgpf: [TODO] should probably add more at a later point */ +const char *supported_digests[] = {"sha256","sha512"}; + void pefs_warn(const char *fmt, ...) { @@ -991,6 +1003,334 @@ return (0); } +/* + * XXXgpf: Instead of a man page entry: + * + * pefs addchecksum [-s] [-a alg] [-i inputfile] [-k pkey_file] [-d dirpath] \ + * filesystem + * + * $command creates .pefs.checksum db file for filesystem. + * This file will contain all checksums necessary to check integrity + * of files upon access. + * + * alg is the name of the algorithm to be used as a cryptographic + * hash function; supported algorithms: sha256, sha512. sha256 is + * used by default. + * + * inputfile contains list of files that need integrity checking. If + * the argument is not supplied, input is read from stdin by default. + * These files should be either regular files or symbolic links. + * Symlinks are not traversed. + * + * dirpath defines where .pefs.checksum should be created. By default, + * .pefs.checksum is created under $PWD. dirpath should be a directory, + * outside of target pefs filesystem. + * + * pkey_file is the file that contains the private key that will be used + * by the DSA signing algorithm. Key should be in PEM format. + * + * -f symbolizes that $command should set immutable flag schg for every file + * in inputlist if the flag is not already set. + * + * When $command is run, filesystem must be mounted with pefs, and + * user must have supplied the necessary pefs key(s). + * + */ +static int +pefs_addchecksum(int argc, char *argv[]) +{ + char fsroot[MAXPATHLEN + 1]; + char csm_path[MAXPATHLEN + 1]; + struct stat sb; + FILE *fpin, *pk_fp; + int error, flags, i, j; + const char *algo; + + flags = 0; + fpin = stdin; + pk_fp = NULL; + /* by default use sha256 */ + algo = supported_digests[0]; + /* by default create checksum file under $PWD */ + snprintf(csm_path, sizeof(csm_path), "./%s", PEFS_FILE_CHECKSUM); + + while ((i = getopt(argc, argv, "sa:i:k:d:")) != -1) + switch(i) { + case 'a': + for (j=0; j < PEFS_SUPPORTED_DIGESTS; j++) + if (strcmp(supported_digests[j], optarg) == 0) { + algo = supported_digests[j]; + break; + } + + if (j == PEFS_SUPPORTED_DIGESTS) { + pefs_warn("invalid digestname: %s", optarg); + error = PEFS_ERR_INVALID; + goto out; + } + break; + case 's': + flags|= PEFS_SETIMMUTABLE; + break; + case 'i': + fpin = fopen(optarg, "r"); + if (fpin == NULL) { + warn("cannot open inputfile: %s", optarg); + error = PEFS_ERR_INVALID; + goto out; + } + break; + case 'k': + pk_fp = fopen(optarg, "r"); + if (pk_fp == NULL) { + warn("error opening privkey file %s", optarg); + error = PEFS_ERR_SYS; + goto out; + } + break; + case 'd': + if (stat(optarg, &sb) != 0) { + warn("cannot stat file %s", optarg); + error = PEFS_ERR_INVALID; + goto out; + } + + if (S_ISDIR(sb.st_mode) == 0) { + pefs_warn("filename: %s is not a directory", optarg); + error = PEFS_ERR_INVALID; + goto out; + } + + snprintf(csm_path, sizeof(csm_path), "%s/%s", optarg, + PEFS_FILE_CHECKSUM); + break; + default: + if (fpin != NULL) + fclose(fpin); + pefs_usage(); + } + argc -= optind; + argv += optind; + + if (pk_fp == NULL) { + pefs_warn("user must provide a file containing the private key"); + return (PEFS_ERR_INVALID); + } + + initfsroot(argc, argv, 0, fsroot, sizeof(fsroot)); + + error = pefs_create_checksum_file(fpin, fsroot, csm_path, pk_fp, + algo, flags); + +out: + if (fpin != NULL) + fclose(fpin); + if (pk_fp != NULL) + fclose(pk_fp); + + return (error); +} + +/* + * XXXgpf: Instead of a man page entry: + * + * pefs verify [-u/-n] [-k pkey_file] checksumpath filesystem + * + * $command verifies the contents of a .pefs.checksum file. It scans the + * entire filesystem and checks that every entry in .pefs.checksum is + * found in the filesystem with the same checksums. + * + * $command will try to produce the same warning messages as addchecksum + * concerning hardlinks and symbolic links. + * + * -n flag should be used if filesystem is mounted but key has not + * been provided yet. + * + * -u flag should be used if filesystem is unmounted. + * + * flags -u and -n are mutually exclusive. + * + * pkey_file is the file containing the public key that is used to verify + * .pefs.checksum's signature by the DSA algorithm. + * + * By default, pefs will assume that filesystem is mounted and user + * has provided key. + */ +static int +pefs_verify(int argc, char *argv[]) +{ + struct stat sb; + char fsroot[MAXPATHLEN + 1]; + FILE *pk_fp; + int error, fdin, flags, i; + + fdin = -1; + flags = PEFS_VERIFY; + pk_fp = NULL; + while ((i = getopt(argc, argv, "k:nu")) != -1) + switch(i) { + case 'k': + pk_fp = fopen(optarg, "r"); + if (pk_fp == NULL) { + warn("error opening pubkey file %s", optarg); + error = PEFS_ERR_SYS; + goto out; + } + break; + case 'n': + flags|= PEFS_NOKEY; + if ((flags & PEFS_UNMOUNTED) != 0) { + pefs_warn("flags -u and -n are mutually exclusive"); + return (PEFS_ERR_INVALID); + } + break; + case 'u': + flags|= PEFS_UNMOUNTED; + if ((flags & PEFS_NOKEY) != 0) { + pefs_warn("flags -u and -n are mutually exclusive"); + return (PEFS_ERR_INVALID); + } + break; + default: + pefs_usage(); + } + argc -= optind; + argv += optind; + + if (pk_fp == NULL) { + pefs_warn("user must provide a file containing the public key"); + return (PEFS_ERR_INVALID); + } + + if (argc != 2) { + if (argc < 2) + warnx("too few arguments"); + else + warnx("too many arguments"); + pefs_usage(); + } + + fdin = open(argv[0], O_RDONLY); + if (fdin == -1) { + warn("cannot open %s file: %s", PEFS_FILE_CHECKSUM, argv[0]); + error = PEFS_ERR_INVALID; + goto out; + } + + argc -=1; + argv +=1; + + if ((flags & PEFS_UNMOUNTED) == 0) + initfsroot(argc, argv, 0, fsroot, sizeof(fsroot)); + else { + /* + * XXXgpf: should i also check that fsroot path belongs to + * a non pefs filesystem? + */ + strlcpy(fsroot, argv[0], sizeof(fsroot)); + if (stat(fsroot, &sb) != 0) { + warn("cannot stat fs root: %s", fsroot); + error = PEFS_ERR_NOENT; + goto out; + } + + if (S_ISDIR(sb.st_mode) == 0) { + pefs_warn("fs root is not a directory: %s", fsroot); + error = PEFS_ERR_SYS; + goto out; + } + } + + error = pefs_verify_checksum(fdin, pk_fp, fsroot, flags); + if (error == 0) + printf("integrity verification ok!\n"); + else + pefs_warn("integrity verification encountered error(s)"); + +out: + if (fdin >= 0) + close(fdin); + if (pk_fp != NULL) + fclose(pk_fp); + return (error); +} + +/* + * XXXgpf: Instead of a man page entry: + * + * pefs nameid [-u/-n] filepath + * + * $command prints out the identifier for an encrypted pefs filename where + * pefs encrypted filename = XBase64(checksum || E(tweak || filename)). + * + * The id is the name checksum, meaning VMAC(E(tweak || filename)). + * + * This identifier is used as a primary key when a specific filename is handled + * by pefs for integrity checking purposes. + * + * Some warning messages produced by /sbin/pefs refer to files by their internal + * ID and not their unencrypted fullpath; e.g. when verifying an unmounted pefs + * filesystem. Therefore this command can be used to map fullpaths to internal + * IDs. + * + * -n flag should be used if filesystem is mounted but key has not been + * provided yet. + * + * -u flag should be used if filesystem is unmounted. + * + * In both of these scenarios the "filepath" that is provided by the user should + * be the encrypted filepath. + * + * flags -u and -n are mutually exclusive. + * + * filepath may refer to any kind of file that is encrypted by pefs filesystem, + * such as directories, regular files, symlinks, etc. + * + * Symlinks are not traversed. + */ +static int +pefs_nameid(int argc, char *argv[]) +{ + char file_path[MAXPATHLEN + 1]; + int error, flags, i; + + flags = PEFS_GETID; + while ((i = getopt(argc, argv, "nu")) != -1) + switch(i) { + case 'n': + flags|= PEFS_NOKEY; + if ((flags & PEFS_UNMOUNTED) != 0) { + pefs_warn("flags -u and -n are mutually exclusive"); + return (PEFS_ERR_INVALID); + } + break; + case 'u': + flags|= PEFS_UNMOUNTED; + if ((flags & PEFS_NOKEY) != 0) { + pefs_warn("flags -u and -n are mutually exclusive"); + return (PEFS_ERR_INVALID); + } + break; + default: + pefs_usage(); + } + argc -= optind; + argv += optind; + + if (argc != 1) { + if (argc < 1) + warnx("too few arguments"); + else + warnx("too many arguments"); + pefs_usage(); + } + + strlcpy(file_path, argv[0], sizeof(file_path)); + error = pefs_filename_to_id(file_path, flags); + + return (error); +} + static void pefs_usage_alg(void) { @@ -1016,6 +1356,9 @@ " pefs randomchain [-fv] [-n min] [-N max] filesystem\n" " pefs showchains [-fp] [-i iterations] [-k keyfile] filesystem\n" " pefs showalgs\n" +" pefs addchecksum [-s] [-a algo] [-i inputfile] [-k pkey_file] [-d dirpath] filesystem\n" +" pefs verify [-n/u] [-k pkey_file] [checksumpath filesystem]\n" +" pefs nameid [-u/-n] [filepath]" ); exit(PEFS_ERR_USAGE); } Index: pefs_kmod/sbin/pefs/pefs.8 =================================================================== --- pefs_kmod/sbin/pefs/pefs.8 (revision 235719) +++ pefs_kmod/sbin/pefs/pefs.8 (revision 240588) @@ -104,6 +104,25 @@ .Pp .Nm .Cm showalgs +.Pp +.Nm +.Cm addchecksum +.Op Fl s +.Op Fl a Ar alg +.Op Fl i Ar input_file +.Op Fl k Ar privatekey_file +.Op Fl d Ar dirpath +.Ar filesystem +.Nm +.Cm verify +.Op Fl u|n +.Op Fl k Ar publickey_file +.Ar checksum_file +.Ar filesystem +.Nm +.Cm nameid +.Op Fl u|n +.Ar filepath .Sh DESCRIPTION The .Nm @@ -227,6 +246,76 @@ Print all elements of the key chain staring with given parent key. .It Cm showalgs Print list of all supported algorithms. +.It Cm addchecksum Ar filesystem +Create +.Em .pefs.checksum +db file for +.Ar filesystem. +The algorithm that will be used as a hash function (sha256 by default) is +set by +.Fl a Ar alg . +The file that contains the private key in PEM format for the DSA signing +algorithm must be provided using +.Fl k Ar privatekey_file . +The list of files is read from stdin unless +.Fl i Ar input_file +is used. Files should be either regular files or symbolic links. Symlinks +are not traversed. +All files that need integrity checking must have the immutable flag (schg) set; +.Fl s +can be used to let +.Nm +turn it on for files that do not. +.Fl d Ar dirpath +can be used to specify under which directory the resulting +.Em .pefs.checksum +file should be placed. Otherwise, it is created under $PWD. +.It Cm verify Ar checksumpath filesystem +Verify the contents of a +.Em .pefs.checksum +file. This command scans the entire +.Ar filesystem +and checks that every entry in +.Em .pefs.checkum +is found and produces the same checksums. The command will try to produce +the same warning messages as +.Cm addchecksum +concerning hardlinks and symbolic links. It will also try to produce as many +warning messages as possible before failing. If +.Ar filesystem +is mounted but the key has not been supplied yet, +.Fl n +flag should be used. If the pefs +.Ar filesystem +is unmounted, the +.Fl u +flag should be used instead. By default, +.Nm +will assume that the filesystem is mounted and user has provided the +necessary key(s) using +.Cm addkey . +The file that contains the public key in PEM format must be provided using +.Fl k Ar privatekey_file . +.It Cm nameid Ar filepath +Print the identifier for an encrypted pefs filename where filename = +XBase64(checksum || E(tweak || filename)). The id is the name checksum, +meaning VMAC(E(tweak || filename)). This identifier is used as a primary key +when a filename is handled by +.Nm +for integrity checking purposes. Some warning messages produced by +.Nm +refer to files by their internal ID and not their decrypted fullpath; e.g. +when verifying an unmounted pefs filesystem. Therefore, this command can be +used to map fullpaths to internal IDs. If the pefs +.Ar filesystem +is unmounted, the +.Fl u +flag should be used instead. By default, +.Nm +will assume that the filesystem is mounted and user has provided the +necessary key(s) using +.Cm addkey . +Symlinks are not traversed. .El .Pp .Ss COMMAND OPTIONS @@ -248,11 +337,18 @@ .It Fl C Disables key chain lookup. By default if chain is found, keys it consists of are also used for operation. +.It Fl d Ar dirpath +specifies under which directory the resulting +.Em .pefs.checksum +file should be placed. .It Fl i Ar iterations Number of .Ar iterations to use with PKCS#5v2. -If this option is not specified default value of 50000 is used. +If this option is not specified default value of 50000 is used. In case of +.Cm addchecksum +, it may be used to specify the file that contains the list of full filenames +that require integrity checking. .It Fl I Ar iterations Specifies number of .Ar iterations @@ -270,9 +366,15 @@ Specifies a file which contains part of the key. If .Ar keyfile -is given as -, standard input will be used. +is given as -, standard input will be used. In case of integrity +checking actions, this specifies either the public or the private key +that is used by the signing algorithm. .It Fl K Ar keyfile Specifies a file which contains part of the secondary/child key. +.It Fl n +Specifies that the pefs +.Ar filesystem +is mounted but user has not provided the necessary key(s) yet. .It Fl o Ar options Mount options passed to .Xr mount 8 @@ -281,10 +383,19 @@ Do not ask for passphrase. .It Fl P Do not ask for passphrase for secondary/child key. +.It Fl s +Is used to let +.Cm addchecksum +turn on the schg immutable flag for files that need integrity checking but +lack the schg flag. .It Fl t Test-only mode. Do not perform actual operation but check if it can be performed. Usable for scripting. +.It Fl u +Specifies that the pefs +.Ar filesystem +is unmounted. .It Fl v Verbose mode. .It Fl x @@ -369,6 +480,14 @@ before loading .Nm kernel module. +.It Va vfs.pefs.exec.enable +If this flag is set to 1, the system allows execution of code that derives +solely from files with the immutable flag (schg) set. This flag is temporary +as this functionality should be controlled by securelevel. +.It Va vfs.pefs.exec.enable.noscript +Same as the above except for when user is trying to execute a script. In +that case, only the interpreter will be checked for the schg flag, not the +script file. .El .Sh EXAMPLES Encrypting a directory: Index: pefs_kmod/sbin/pefs/pefs_ctl.h =================================================================== --- pefs_kmod/sbin/pefs/pefs_ctl.h (revision 235719) +++ pefs_kmod/sbin/pefs/pefs_ctl.h (revision 240588) @@ -28,6 +28,8 @@ #include +struct EVP_MD; + #define PEFS_FSTYPE "pefs" #define PEFS_KLD PEFS_FSTYPE @@ -36,9 +38,18 @@ #define PEFS_KDF_ITERATIONS 50000 +#define PEFS_BLOCKSIZE 4096 + #define PEFS_FILE_KEYCHAIN ".pefs.db" #define PEFS_FILE_KEYCONF ".pefs.conf" +#define PEFS_FILE_CHECKSUM ".pefs.checksum" +#define PEFS_NOKEY 0x0001 +#define PEFS_UNMOUNTED 0x0002 +#define PEFS_SETIMMUTABLE 0x0004 +#define PEFS_VERIFY 0x0010 +#define PEFS_GETID 0x0020 + #define PEFS_KEYCONF_ALG_IND 0 #define PEFS_KEYCONF_ITERATIONS_IND 1 @@ -47,6 +58,8 @@ #define PEFS_KEYENC_MAC_SIZE (PEFS_KEY_SIZE / 2) +#define PEFS_SUPPORTED_DIGESTS 2 + #define PEFS_ERR_GENERIC 1 #define PEFS_ERR_USAGE 2 #define PEFS_ERR_IO 3 @@ -54,6 +67,7 @@ #define PEFS_ERR_NOENT 5 #define PEFS_ERR_EXIST 6 #define PEFS_ERR_INVALID 7 +#define PEFS_ERR_CHECKSUM 8 #define PEFS_FS_IGNORE_TYPE 0x0001 @@ -77,6 +91,8 @@ void pefs_warn(const char *, ...) __printf0like(1, 2); int pefs_getfsroot(const char *path, int flags, char *fsroot, size_t size); +int pefs_getfsroots(const char *path, int flags, char *fsroot, + char * fromfsroot, size_t size); int pefs_key_generate(struct pefs_xkey *xk, const char *passphrase, struct pefs_keyparam *kp); @@ -85,7 +101,14 @@ int pefs_key_decrypt(struct pefs_xkeyenc *xe, const struct pefs_xkey *xk_parent); uintmax_t pefs_keyid_as_int(char *keyid); +int pefs_create_checksum_file(FILE *fpin, char *fsroot, char *csm_path, + FILE *pk_fp, const char *algo, int flags); +int pefs_verify_checksum(int fdin, FILE *pk_fp, char *fsroot, int flags); +int pefs_filename_to_id(char *file_path, int flags); +int pefs_name_pton(char const *src, size_t srclen, u_char *target, + size_t targsize); + const char * pefs_alg_name(struct pefs_xkey *xk); void pefs_alg_list(FILE *stream); Index: pefs_kmod/sbin/pefs/pefs_key.c =================================================================== --- pefs_kmod/sbin/pefs/pefs_key.c (revision 235719) +++ pefs_kmod/sbin/pefs/pefs_key.c (revision 240588) @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include Index: pefs_kmod/sbin/pefs/pefs_keychain.c =================================================================== --- pefs_kmod/sbin/pefs/pefs_keychain.c (revision 235719) +++ pefs_kmod/sbin/pefs/pefs_keychain.c (revision 240588) @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include Index: pefs_kmod/sbin/pefs/pefs_subr.c =================================================================== --- pefs_kmod/sbin/pefs/pefs_subr.c (revision 235719) +++ pefs_kmod/sbin/pefs/pefs_subr.c (revision 240588) @@ -32,6 +32,7 @@ #include #include #include +#include #include #include @@ -73,3 +74,73 @@ return (0); } + +#define Assert(Cond) (void)0 + +/* + * Algorithm is standard base64 with few exceptions: + * - file system friendly alphabet + * - no paddings and whitespace skip + */ +static const char Base64[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_"; +int +pefs_name_pton(char const *src, size_t srclen, u_char *target, size_t targsize) +{ + int tarindex, state, ch; + char *pos; + + state = 0; + tarindex = 0; + + while ((ch = *src++) != '\0' && srclen-- > 0) { + if (target && (size_t)tarindex >= targsize) + return (-1); + + pos = strchr(Base64, ch); + if (pos == 0) /* A non-base64 character. */ + return (-1); + + switch (state) { + case 0: + if (target) { + target[tarindex] = (pos - Base64) << 2; + } + state = 1; + break; + case 1: + if (target) { + target[tarindex] |= (pos - Base64) >> 4; + if ((size_t)tarindex + 1 < targsize) + target[tarindex+1] = + ((pos - Base64) & 0x0f) << 4 ; + } + tarindex++; + state = 2; + break; + case 2: + if (target) { + target[tarindex] |= (pos - Base64) >> 2; + if ((size_t)tarindex + 1 < targsize) + target[tarindex+1] = + ((pos - Base64) & 0x03) << 6; + } + tarindex++; + state = 3; + break; + case 3: + if (target) { + target[tarindex] |= (pos - Base64); + } + tarindex++; + state = 0; + break; + default: + return (-1); + } + } + + if (tarindex == 0) + return (-1); + return (tarindex); +} Index: pefs_kmod/sbin/pefs/Makefile =================================================================== --- pefs_kmod/sbin/pefs/Makefile (revision 235719) +++ pefs_kmod/sbin/pefs/Makefile (revision 240588) @@ -5,7 +5,7 @@ .PATH: ${SYS}/crypto/hmac ${SYS}/crypto/rijndael ${SYS}/crypto/sha2 PROG= pefs -SRCS= pefs_ctl.c pefs_key.c pefs_keychain.c pefs_subr.c +SRCS= pefs_ctl.c pefs_key.c pefs_keychain.c pefs_subr.c pefs_checksum.c SRCS+= hmac_sha512.c sha2.c SRCS+= rijndael-api.c rijndael-api-fst.c rijndael-alg-fst.c SRCS+= pkcs5v2.c @@ -17,7 +17,7 @@ DEBUG_FLAGS+= -g DPADD= ${LIBUTIL} -LDADD= -lutil +LDADD= -lutil -lcrypto BINDIR?= /sbin Index: pefs_kmod/sys/fs/pefs/pefs.h =================================================================== --- pefs_kmod/sys/fs/pefs/pefs.h (revision 235719) +++ pefs_kmod/sys/fs/pefs/pefs.h (revision 240588) @@ -48,6 +48,25 @@ char pxk_key[PEFS_KEY_SIZE]; }; +struct pefs_xnamecsum { + uint32_t pxnc_namelen; + char pxnc_csum[PEFS_NAME_CSUM_SIZE]; + char pxnc_filename[MAXNAMLEN + 1]; +}; + +struct pefs_xsector_ctext { + off_t pxsct_offset; + uint32_t pxsct_ctext_len; + char pxsct_ctext[PEFS_SECTOR_SIZE]; +}; + +struct pefs_xslink_ctext { + uint32_t pxsl_namelen; + uint32_t pxsl_target_len; + char pxsl_filename[MAXPATHLEN + 1]; + char pxsl_target[PEFS_SECTOR_SIZE]; +}; + #ifdef _IO #define PEFS_GETKEY _IOWR('p', 0, struct pefs_xkey) #define PEFS_ADDKEY _IOWR('p', 1, struct pefs_xkey) @@ -55,6 +74,9 @@ #define PEFS_DELKEY _IOWR('p', 3, struct pefs_xkey) #define PEFS_FLUSHKEYS _IO('p', 4) #define PEFS_GETNODEKEY _IOWR('p', 5, struct pefs_xkey) +#define PEFS_GETNAMECSUM _IOWR('p', 6, struct pefs_xnamecsum) +#define PEFS_GETSECTORCTEXT _IOWR('p', 7, struct pefs_xsector_ctext) +#define PEFS_GETSLINKCTEXT _IOWR('p', 8, struct pefs_xslink_ctext) #endif #ifdef _KERNEL @@ -98,6 +120,8 @@ #define PN_WANTRECYCLE 0x000100 #define PN_LOCKBUF_SMALL 0x001000 #define PN_LOCKBUF_LARGE 0x002000 +#define PN_NO_CHECKSUM 0x000010 +#define PN_WRONG_CHECKSUM 0x000020 struct pefs_node { LIST_ENTRY(pefs_node) pn_listentry; @@ -109,17 +133,32 @@ void *pn_buf_large; int pn_flags; struct pefs_tkey pn_tkey; + char *pn_checksum_index_entry; }; #define PM_ROOT_CANRECURSE 0x01 #define PM_DIRCACHE 0x02 #define PM_ASYNCRECLAIM 0x04 +#define PM_CHECKSUM 0x08 +struct pefs_checksum { + uint8_t pcs_version; + uint8_t pcs_reserved; + uint8_t pcs_hash_len; + uint8_t pcs_hash_algo; + uint8_t pcs_hash_algo_name[8]; + uint8_t pcs_offset_to_hash_table; + uint32_t pcs_hash_table_size; + char *pcs_table1, *pcs_table2; + struct vnode *pcs_checksumvp; +}; + struct pefs_mount { struct mount *pm_lowervfs; struct vnode *pm_rootvp; struct mtx pm_keys_lock; struct pefs_key_head pm_keys; + struct pefs_checksum pm_checksum; int pm_flags; }; Index: pefs_kmod/sys/fs/pefs/pefs_mac.c =================================================================== --- pefs_kmod/sys/fs/pefs/pefs_mac.c (revision 0) +++ pefs_kmod/sys/fs/pefs/pefs_mac.c (revision 240588) @@ -0,0 +1,218 @@ +/*- + * Copyright (c) 2012 Efstratios Karatzas + * 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 + +/* + * XXXgpf: + * The problem with this MAC hook is that the hook is called before + * do_execve() checks if our executable requires an interpreter. + * Therefore, the script file will itself be checked for the schg flag. + * + * We could: + * + * a) allow this because it's a feature! During development of a script, + * user will have to pass it as an argument to the interpreter and when it's + * complete, continue calling it like that or add the schg flag. + * + * b) add a brand new MAC hook that will be called at the precise point + * in do_execve() where only the interpreter or the regular executable + * will be checked for the schg flag. [don't seem the other devs will go + * for us modifying MAC framework though] + * + * c) duplicate code from do_execve() and perform the check ourselves. It + * could be done I guess but I'm not sure since image activators seem to have + * their own custom functions that are called in order to figure out whether + * the interpreted flag should be turned on. Don't know how much they are + * allowed to tamper with imgp, besides that flag. + * + */ +static int +pefs_vnode_check_exec(struct ucred *cred, struct vnode *vp, + struct label *vplabel, struct image_params *imgp, + struct label *execlabel) +{ + int enabled, rval; + size_t enabled_len; + + rval = kernel_sysctlbyname(curthread, "vfs.pefs.exec.enable", + &enabled, &enabled_len, NULL, 0, NULL, 0); + + if (rval == 0 && enabled != 0) { + if ((imgp->attr->va_flags & SF_IMMUTABLE) == 0) { + return (EPERM); + } + } + + return (0); +} + +/* + * XXXgpf: + * + * This new hook is placed in do_execve(), found in sys/kern/kern_exec.c. + * Its purpose is to only check the interpreter for the schg flag in case + * there's an executable that requires an interpreter. + * + * It is placed after exec_check_permissions() and it will be called after + * we've looped back for the interpreter. Therefore, only either the interpeter + * or the regular executable itself will ever be checked by this hook; we'll + * never check the script file itself. + * + * It uses a different dbg sysctl var than the above hook for obvious reasons. + */ +static int +pefs_vnode_check_exec_noscript(struct ucred *cred, struct vnode *vp, + struct label *vplabel, struct image_params *imgp, + struct label *execlabel) +{ + int enabled, rval; + size_t enabled_len; + + rval = kernel_sysctlbyname(curthread, "vfs.pefs.exec.enable_noscript", + &enabled, &enabled_len, NULL, 0, NULL, 0); + + if (rval == 0 && enabled != 0) { + if ((imgp->attr->va_flags & SF_IMMUTABLE) == 0) + return (EPERM); + } + + return (0); +} + + +/* + * XXXgpf: Check if schg is turned on when we mmap() a vnode with + * the PROT_EXEC flag. + */ +static int +pefs_vnode_check_mmap(struct ucred *cred, struct vnode *vp, + struct label *vplabel, int prot, int flags) +{ + struct vattr va; + int enabled1, enabled2, error, rval1, rval2; + size_t enabled_len; + + if ((prot & PROT_EXEC) == 0) + return (0); + + rval1 = kernel_sysctlbyname(curthread, "vfs.pefs.exec.enable", + &enabled1, &enabled_len, NULL, 0, NULL, 0); + + rval2 = kernel_sysctlbyname(curthread, "vfs.pefs.exec.enable_noscript", + &enabled2, &enabled_len, NULL, 0, NULL, 0); + + if ((rval1 == 0 && enabled1 != 0) || (rval2 == 0 && enabled2 != 0)) { + error = VOP_GETATTR(vp, &va, cred); + if (error != 0) + return (EACCES); + + if ((va.va_flags & SF_IMMUTABLE) == 0) + return (EACCES); + } + + return (0); +} + +/* + * XXXgpf: + * + * Checking mmap(2) with the above MAC hook is not enough if we wish to + * prevent user from mmaping a file and then executing those pages due to + * mprotect(2). + * + * I did notice the existance of mac_vnode_check_mprotect(), but unfortunately + * it's not used anywhere in the kernel for some reason(?)! If it ever comes + * back into action, I believe it would be preferable to the following solution. + * + * My alternative solution was to set the MAXPROT flag of the mapped area + * during mmap(). If we are mapping a file and we need schg protection, we + * remove VM_PROT_EXECUTE from MAXPROT which in turn causes following attempts + * to mprotect() with PROT_EXEC to fail. + */ +static void +pefs_vnode_set_mmap_maxprot(struct ucred *cred, struct vnode *vp, + struct label *vplabel, vm_prot_t *maxprotp, int flags) +{ + int enabled1, enabled2, rval1, rval2; + size_t enabled_len; + + if ((*maxprotp & VM_PROT_EXECUTE) == 0) + return; + + rval1 = kernel_sysctlbyname(curthread, "vfs.pefs.exec.enable", + &enabled1, &enabled_len, NULL, 0, NULL, 0); + + rval2 = kernel_sysctlbyname(curthread, "vfs.pefs.exec.enable_noscript", + &enabled2, &enabled_len, NULL, 0, NULL, 0); + + if ((rval1 == 0 && enabled1 != 0) || (rval2 == 0 && enabled2 != 0)) + *maxprotp &= ~VM_PROT_EXECUTE; +} + +static struct mac_policy_ops pefs_ops = +{ + .mpo_vnode_check_exec = pefs_vnode_check_exec, + .mpo_vnode_check_exec_noscript = pefs_vnode_check_exec_noscript, + .mpo_vnode_check_mmap = pefs_vnode_check_mmap, + .mpo_vnode_set_mmap_maxprot = pefs_vnode_set_mmap_maxprot, +}; + +MAC_POLICY_SET(&pefs_ops, mac_pefs, "pefs exec protection", + MPC_LOADTIME_FLAG_UNLOADOK, NULL); + +/* XXXgpf: declare our debugging sysctls for schg execution control */ +SYSCTL_NODE(_vfs_pefs, OID_AUTO, exec, CTLFLAG_RW, 0, + "PEFS kern_exec.c stuff"); + +int pefs_exec_enable = 0; +SYSCTL_INT(_vfs_pefs_exec, OID_AUTO, enable, CTLFLAG_RW, + &pefs_exec_enable, 0, "Enable exec protection"); + +int pefs_exec_enable_noscript = 0; +SYSCTL_INT(_vfs_pefs_exec, OID_AUTO, enable_noscript, CTLFLAG_RW, + &pefs_exec_enable_noscript, 0, "Enable exec protection [no scripts]"); Index: pefs_kmod/sys/fs/pefs/pefs_checksum.c =================================================================== --- pefs_kmod/sys/fs/pefs/pefs_checksum.c (revision 0) +++ pefs_kmod/sys/fs/pefs/pefs_checksum.c (revision 240588) @@ -0,0 +1,409 @@ +/*- + * Copyright (c) 2012 Efstratios Karatzas + * 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 + +const char *pefs_checksum_supported_digests[] = {"sha256","sha512"}; +uint8_t pefs_checksum_supported_hash_lengths[] = {32, 64}; + +/* sanitize .pefs.checkum's global file header that's read during VFS_MOUNT() */ +int +pefs_sanitize_checksum_header(struct pefs_checksum *pcs) +{ + int error, i; + + error = 0; + for (i=0; i < PEFS_CHECKSUM_SUPPORTED_DIGESTS; i++) + if (strncmp(pefs_checksum_supported_digests[i], pcs->pcs_hash_algo_name, + sizeof(pcs->pcs_hash_algo_name)) == 0) + break; + + pcs->pcs_hash_algo = i; + switch(pcs->pcs_hash_algo) { + /* FALLTHROUGH */ + case (PEFS_SHA256): + case (PEFS_SHA512): + dprintf(("digest: %s\n", pcs->pcs_hash_algo_name)); + if (pcs->pcs_hash_len != pefs_checksum_supported_hash_lengths[i]) { + dprintf(("pefs_sanitize invalid algo len %u\n", pcs->pcs_hash_len)); + error = EINVAL; + } + break; + default: + dprintf(("pefs_sanitize invalid algo %s\n", pcs->pcs_hash_algo_name)); + error = ENODEV; + break; + } + + return (error); +} + +static uint32_t +pefs_checksum_hash1(struct pefs_checksum *pc, + struct pefs_checksum_index_entry *pcie) +{ + uint32_t nbucket; + + nbucket = pcie->pcie_fid.pcfi_num % pc->pcs_hash_table_size; + dprintf(("hash1: goto bucket %d\n", nbucket)); + return (nbucket); +} + +static uint32_t +pefs_checksum_hash2(struct pefs_checksum *pc, + struct pefs_checksum_index_entry *pcie) +{ + uint32_t nbucket; + + nbucket = fnv_64_buf(&(pcie->pcie_fid.pcfi_num), + sizeof(pcie->pcie_fid.pcfi_num), FNV1_64_INIT) % + pc->pcs_hash_table_size; + dprintf(("hash2: goto bucket %d\n", nbucket)); + + return (nbucket); +} + +/* fill out pcie from data pointed to by p */ +static void +pefs_get_index_entry(char *p, struct pefs_checksum_index_entry *pcie) +{ + MPASS(p != NULL); + + memcpy(&(pcie->pcie_nhashes), p, sizeof(pcie->pcie_nhashes)); + pcie->pcie_nhashes = le32toh(pcie->pcie_nhashes); + if (pcie->pcie_nhashes != 0) { + p+=sizeof(pcie->pcie_nhashes); + + memcpy(&(pcie->pcie_offset), p, sizeof(pcie->pcie_offset)); + pcie->pcie_offset = le32toh(pcie->pcie_offset); + p+=sizeof(pcie->pcie_offset); + + memcpy(pcie->pcie_fid.pcfi_str, p, sizeof(pcie->pcie_fid.pcfi_str)); + } +} + +static int +pefs_checksum_index_lookup(struct pefs_checksum_index_entry *pcie, + struct vnode *vp) +{ + struct pefs_checksum_index_entry target_pcie; + struct pefs_mount *pm = VFS_TO_PEFS(vp->v_mount); + struct pefs_checksum *pcs = &(pm->pm_checksum); + struct pefs_node *pn = VP_TO_PN(vp); + char *start; + uint32_t pos; + + pos = pefs_checksum_hash1(pcs, pcie); + start = &(pcs->pcs_table1[pos * PEFS_HT_CELL_SIZE]); + pefs_get_index_entry(start, &target_pcie); + + if (!PEFS_EMPTY_INDEX_ENTRY(&target_pcie)) { + dprintf(("cell %d:\n", pos)); + dprintf(("\thashes = %d\n\toffset = %d\n\tfile id = %llu\n", + target_pcie.pcie_nhashes, target_pcie.pcie_offset, + target_pcie.pcie_fid.pcfi_num)); + + if (target_pcie.pcie_fid.pcfi_num == pcie->pcie_fid.pcfi_num) { + pn->pn_checksum_index_entry = start; + dprintf(("checksum lookup: found1!\n")); + return (0); + } + } + + pos = pefs_checksum_hash2(pcs, pcie); + start = &(pcs->pcs_table2[pos * PEFS_HT_CELL_SIZE]); + pefs_get_index_entry(start, &target_pcie); + + if (!PEFS_EMPTY_INDEX_ENTRY(&target_pcie)) { + dprintf(("cell %d:\n", pos)); + dprintf(("\thashes = %d\n\toffset = %d\n\tfile id = %llu\n", + target_pcie.pcie_nhashes, target_pcie.pcie_offset, + target_pcie.pcie_fid.pcfi_num)); + + if (target_pcie.pcie_fid.pcfi_num == pcie->pcie_fid.pcfi_num) { + pn->pn_checksum_index_entry = start; + dprintf(("checksum lookup: found2!\n")); + return (0); + } + } + + pn->pn_checksum_index_entry = NULL; + dprintf(("checksum lookup: not found!\n")); + return (ENOENT); +} + +void +pefs_checksum_lookup(char *enc_name, size_t enc_name_len, + struct componentname *cnp, struct vnode *vp) +{ + struct vattr va; + struct pefs_checksum_index_entry pcie; + struct pefs_node *pn = VP_TO_PN(vp); + struct ucred *cred = vp->v_mount->mnt_cred; + char *buf; + size_t buf_len; + int error, r; + + dprintf(("gpf: checksum code @ lookup\n")); + if ((cnp != NULL && cnp->cn_nameiop != LOOKUP) || (vp->v_type != VREG && + vp->v_type != VLNK) + || ((pn->pn_flags & PN_NO_CHECKSUM) != 0)) + goto not_found; + + if (strncmp(enc_name, ".pefs.db", enc_name_len) == 0 || + strncmp(enc_name, ".pefs.conf", enc_name_len) == 0 || + strncmp(enc_name, ".pefs.checksum", enc_name_len) == 0) + goto not_found; + + enc_name++; + enc_name_len--; + buf_len = MAXNAMLEN + 1; + buf = malloc(buf_len, M_TEMP, M_WAITOK); + + r = pefs_name_pton(enc_name, enc_name_len, buf, buf_len); + if (r <= 0) { + /* XXXgpf: I sincerely doubt an error can occur here */ + error = EINVAL; + dprintf(("name_pton error: %d\n", error)); + } + else { + memcpy(pcie.pcie_fid.pcfi_str, buf, sizeof(pcie.pcie_fid.pcfi_str)); + dprintf(("id to lookup: %llu\n", pcie.pcie_fid.pcfi_num)); + error = pefs_checksum_index_lookup(&pcie, vp); + if (error != 0) { + free(buf, M_TEMP); + goto not_found; + } + } + /* + * Check to see if schg flag is set, if not mark the vnode so that all + * read access is denied. + * + * XXXgpf: [TODO] I should make sure that the PN_WRONG_CHECKSUM flag is + * used in other parts of the pefs codebase to deny access to the file + * data. + */ + error = VOP_GETATTR(vp, &va, cred); + if (error != 0) { + dprintf(("unable to retrieve attributes of %llu\n", + pcie.pcie_fid.pcfi_num)); + pn->pn_flags|= PN_WRONG_CHECKSUM; + } + else { + if ((va.va_flags & SF_IMMUTABLE) == 0) { + dprintf(("schg not set for %llu\n", pcie.pcie_fid.pcfi_num)); + pn->pn_flags|= PN_WRONG_CHECKSUM; + } + } + + free(buf, M_TEMP); + return; + +not_found: + pn->pn_flags|= PN_NO_CHECKSUM; +} + +static int +pefs_generate_checksum(struct vnode *vp, char *data, ssize_t data_len, + unsigned char **digest, ssize_t *digest_len) +{ + struct pefs_mount *pm = VFS_TO_PEFS(vp->v_mount); + struct pefs_checksum *pcs = &(pm->pm_checksum); + unsigned char *dig; + + switch(pcs->pcs_hash_algo) { + case (PEFS_SHA256): + *digest_len = SHA256_DIGEST_STRING_LENGTH; + dig = malloc(*digest_len, M_TEMP, M_WAITOK); + /* + * XXXgpf: Does this interface work for any length input? + * [TODO] Also, I should either use a different interface or store the + * checksums in hex during .pefs.checksum creation because turning + * them to hex at this point every single time we have a read is + * just silly. + */ + SHA256_Data(data, data_len, dig); + break; + case (PEFS_SHA512): + *digest_len = SHA512_DIGEST_STRING_LENGTH; + dig = malloc(*digest_len, M_TEMP, M_WAITOK); + SHA512_Data(data, data_len, dig); + break; + default: + dprintf(("pefs_sanitize invalid algo %s\n", pcs->pcs_hash_algo_name)); + return (ENODEV); + } + + *digest = dig; + return (0); +} + +static int +pefs_retrieve_checksum(struct vnode *vp, struct pefs_checksum_index_entry *pcie, + off_t block_offset, unsigned char **digest, ssize_t digest_len) +{ + struct pefs_mount *pm = VFS_TO_PEFS(vp->v_mount); + struct pefs_checksum *pcs = &(pm->pm_checksum); + struct ucred *cred = vp->v_mount->mnt_cred; + struct uio *puio; + struct pefs_chunk pc; + unsigned char *dig; + off_t checksum_offset; + int error, i; + + pefs_chunk_create(&pc, NULL, pcs->pcs_hash_len); + checksum_offset = pcie->pcie_offset + pcs->pcs_hash_len * + (block_offset / PEFS_SECTOR_SIZE); + puio = pefs_chunk_uio(&pc, checksum_offset, UIO_READ); + + /* XXXgpf: gleb says I should use vn_rdwr instead of VOP_READ */ + error = VOP_READ(pcs->pcs_checksumvp, puio, IO_UNIT, cred); + if (error != 0) { + dprintf(("pefs_retrieve_checksum read error %d\n", error)); + return (error); + } + + dig = malloc(digest_len, M_TEMP, M_WAITOK); + for (i=0; i < pcs->pcs_hash_len; i++) + sprintf(&dig[i*2],"%02x", ((unsigned char *)pc.pc_base)[i]); + dig[i*2] = '\0'; + + pefs_chunk_free(&pc, NULL); + *digest = dig; + + return (0); +} + +static int +pefs_compare_checksums(unsigned char *digest1, unsigned char *digest2, + ssize_t digest_len) +{ + int error; + + dprintf(("compare dig1: %s\n", digest1)); + dprintf(("compare dig2: %s\n", digest2)); + + error = memcmp(digest1, digest2, digest_len); + if (error != 0) { + dprintf(("checksum mismatch!\n")); + error = EAUTH; + } + + return (error); +} + +int +pefs_integrity_check(struct vnode *vp, off_t offset, u_quad_t fsize, + struct pefs_chunk *pc) +{ + struct pefs_checksum_index_entry pcie; + struct pefs_node *pn = VP_TO_PN(vp); + ssize_t digest_len, resid; + unsigned char *digest1, *digest2; + char *buf, *end; + long *p; + int error; + + dprintf(("integrity checking!\noffset %llu\n", offset)); + + if ((pn->pn_flags & PN_WRONG_CHECKSUM) != 0) + return (EAUTH); + + pefs_get_index_entry(pn->pn_checksum_index_entry, &pcie); + + dprintf(("id: %llu\n", pcie.pcie_fid.pcfi_num)); + + buf = (char *)pc->pc_base; + end = buf + pc->pc_size; + + if ((fsize > pcie.pcie_nhashes * PEFS_SECTOR_SIZE) || + (fsize < (pcie.pcie_nhashes - 1) * PEFS_SECTOR_SIZE)) { + dprintf(("file size differs from the one in .pefs.checksum\n")); + pn->pn_flags|= PN_WRONG_CHECKSUM; + return (EAUTH); + } + + while (buf < end) { + if ((end - buf) >= PEFS_SECTOR_SIZE) { + p = (long *)buf; + resid = PEFS_SECTOR_SIZE / sizeof(long); + for (; resid > 0; resid--) + if (*(p++) != 0) + break; + if (resid == 0) { + bzero(buf, PEFS_SECTOR_SIZE); + offset += PEFS_SECTOR_SIZE; + buf += PEFS_SECTOR_SIZE; + continue; + } + resid = PEFS_SECTOR_SIZE; + } + else + resid = end - buf; + + error = pefs_generate_checksum(vp, buf, resid, &digest1, &digest_len); + if (error != 0) + return (error); + + error = pefs_retrieve_checksum(vp, &pcie, offset, &digest2, digest_len); + if (error != 0) { + free(digest1, M_TEMP); + return (error); + } + + error = pefs_compare_checksums(digest1, digest2, digest_len); + free(digest1, M_TEMP); + free(digest2, M_TEMP); + if (error != 0) { + pn->pn_flags|= PN_WRONG_CHECKSUM; + return (error); + } + + buf += resid; + offset += resid; + } + + return (0); +} Index: pefs_kmod/sys/fs/pefs/pefs_vnops.c =================================================================== --- pefs_kmod/sys/fs/pefs/pefs_vnops.c (revision 235719) +++ pefs_kmod/sys/fs/pefs/pefs_vnops.c (revision 240588) @@ -64,6 +64,7 @@ #include #include +#include #include #define DIRENT_MINSIZE (sizeof(struct dirent) - (MAXNAMLEN + 1)) @@ -474,6 +475,7 @@ pefs_lookup(struct vop_cachedlookup_args *ap) { struct componentname *cnp = ap->a_cnp; + struct pefs_mount *pm; struct vnode *vp = NULL; struct vnode *lvp = NULL; struct vnode *dvp = ap->a_dvp; @@ -484,8 +486,8 @@ int nokey_lookup, skip_lookup; int error; - PEFSDEBUG("pefs_lookup: op=%lx, name=%.*s\n", - cnp->cn_nameiop, (int)cnp->cn_namelen, cnp->cn_nameptr); + dprintf(("pefs_lookup: op=%lx, name=%.*s\n", + cnp->cn_nameiop, (int)cnp->cn_namelen, cnp->cn_nameptr)); pefs_enccn_init(&enccn); @@ -560,6 +562,22 @@ if (error != 0) { vput(lvp); } else { + pm = VFS_TO_PEFS(vp->v_mount); + if ((pm->pm_flags & PM_CHECKSUM) != 0) { + if (nokey_lookup) { + dprintf(("cnp name=%.*s\n",(int)cnp->cn_namelen, + cnp->cn_nameptr)); + pefs_checksum_lookup(cnp->cn_nameptr, + cnp->cn_namelen, cnp, vp); + } + else { + dprintf(("enccnp name=%.*s\n", + (int)enccn.pec_cn.cn_namelen, + enccn.pec_cn.cn_nameptr)); + pefs_checksum_lookup(enccn.pec_cn.cn_nameptr, + enccn.pec_cn.cn_namelen, cnp, vp); + } + } *ap->a_vpp = vp; if ((cnp->cn_flags & MAKEENTRY) && cnp->cn_nameiop != CREATE) @@ -1901,6 +1919,7 @@ return (error); error = pefs_read_int(vp, uio, ioflag, cred, fsize); + return (error); } @@ -1910,6 +1929,7 @@ { struct vnode *lvp = PEFS_LOWERVP(vp); struct uio *puio; + struct pefs_mount *pm = VFS_TO_PEFS(vp->v_mount); struct pefs_node *pn = VP_TO_PN(vp); struct pefs_chunk pc; struct sf_buf *sf; @@ -1966,6 +1986,11 @@ /* XXX assert full buffer is read */ pefs_chunk_setsize(&pc, done); + if ((pm->pm_flags & PM_CHECKSUM) != 0 && PEFS_NEEDS_CHECKING(pn)) { + error = pefs_integrity_check(vp, poffset, fsize, &pc); + if (error != 0) + break; + } pefs_data_decrypt(&pn->pn_tkey, poffset, &pc); if (nocopy == 0) { error = pefs_chunk_copy(&pc, bskip, uio); @@ -2355,15 +2380,28 @@ static int pefs_ioctl(struct vop_ioctl_args *ap) { + struct pefs_enccn enccn; + struct componentname cn; + struct pefs_chunk pc; + struct nameidata nd, *ndp = &nd; + u_quad_t fsize; struct vnode *vp = ap->a_vp; + struct vnode *lvp = PEFS_LOWERVP(vp); + struct vnode *svp, *slvp; struct pefs_xkey *xk = ap->a_data; + struct pefs_xnamecsum *xncs = ap->a_data; + struct pefs_xsector_ctext *xsct = ap->a_data; + struct pefs_xslink_ctext *xsl = ap->a_data; struct ucred *cred = ap->a_cred; struct thread *td = ap->a_td; struct mount *mp = vp->v_mount; struct pefs_mount *pm = VFS_TO_PEFS(mp); struct pefs_node *pn; struct pefs_key *pk; - int error = 0, i; + struct uio *puio; + char *enc, *buf; + size_t enc_len, buf_len; + int error = 0, i, r; if (mp->mnt_cred->cr_uid != cred->cr_uid) { error = priv_check_cred(cred, PRIV_VFS_ADMIN, 0); @@ -2465,6 +2503,179 @@ if (pefs_key_remove_all(pm)) pefs_flushkey(mp, td, PEFS_FLUSHKEY_ALL, NULL); break; + case PEFS_GETSECTORCTEXT: + vn_lock(vp, LK_EXCLUSIVE); + + if (vp->v_type != VREG) { + dprintf(("pefs_ioctl: PEFS_GETSECTORCTEXT vp is not a reg file\n")); + VOP_UNLOCK(vp, 0); + return (EOPNOTSUPP); + } + + error = pefs_getsize(vp, &fsize, cred); + if (error != 0) { + VOP_UNLOCK(vp, 0); + return (error); + } + + if (xsct->pxsct_ctext_len > PEFS_SECTOR_SIZE || + xsct->pxsct_ctext_len == 0 || + xsct->pxsct_ctext_len > fsize) { + dprintf(("pefs_ioctl: PEFS_GETSECTORCTEXT invalid len: %d\n", + xsct->pxsct_ctext_len)); + VOP_UNLOCK(vp, 0); + return (EINVAL); + } + + if (xsct->pxsct_offset > (fsize - xsct->pxsct_ctext_len)) { + dprintf(("pefs_ioctl: PEFS_GETSECTORCTEXT invalid offset: %llu\n", + xsct->pxsct_offset)); + VOP_UNLOCK(vp, 0); + return (EINVAL); + } + + pn = VP_TO_PN(vp); + pefs_chunk_create(&pc, pn, xsct->pxsct_ctext_len); + puio = pefs_chunk_uio(&pc, xsct->pxsct_offset, UIO_READ); + + error = VOP_READ(lvp, puio, IO_UNIT, cred); + + if (error == 0) + memcpy(xsct->pxsct_ctext, pc.pc_base, xsct->pxsct_ctext_len); + + pefs_chunk_free(&pc, pn); + VOP_UNLOCK(vp, 0); + break; + case PEFS_GETNAMECSUM: + vn_lock(vp, LK_EXCLUSIVE); + if (vp->v_type != VDIR) { + dprintf(("pefs_ioctl: PEFS_GETNAMEMAC vp is not a directory\n")); + VOP_UNLOCK(vp, 0); + return (ENOTDIR); + } + + if (strnlen(xncs->pxnc_filename, sizeof(xncs->pxnc_filename)) != + xncs->pxnc_namelen) { + dprintf(("pefs_ioctl: PEFS_GETNAMEMAC incorrect pxnc_namelen %d\n", + xncs->pxnc_namelen)); + VOP_UNLOCK(vp, 0); + return (EINVAL); + } + + if (strchr(xncs->pxnc_filename, '/') != NULL) { + dprintf(("pefs_ioctl: PEFS_GETNAMEMAC pxnc_filename contains '/'\n"); + VOP_UNLOCK(vp, 0)); + return (EINVAL); + } + + pefs_enccn_init(&enccn); + if (pefs_no_keys(vp)) { + enc = xncs->pxnc_filename; + enc_len = xncs->pxnc_namelen; + error = 0; + } + else { + cn.cn_nameiop = LOOKUP; + cn.cn_thread = td; + cn.cn_cred = cred; + cn.cn_lkflags = 0; + cn.cn_flags = 0; + cn.cn_nameptr = xncs->pxnc_filename; + cn.cn_namelen = xncs->pxnc_namelen; + + /* XXXgpf: does this lookup rely solely on present cache data? */ + error = pefs_enccn_lookup(&enccn, vp, &cn); + if (error == 0) { + enc = enccn.pec_cn.cn_nameptr; + enc_len = enccn.pec_cn.cn_namelen; + } + } + VOP_UNLOCK(vp, 0); + + if (error == 0) { + if (enc[0] != '.' || enc_len <= 1) { + pefs_enccn_free(&enccn); + error = EINVAL; + break; + } + enc++; + enc_len--; + buf_len = MAXNAMLEN + 1; + buf = malloc(buf_len, M_TEMP, M_WAITOK); + + r = pefs_name_pton(enc, enc_len, buf, buf_len); + if (r <= 0) + error = EINVAL; + else + memcpy(xncs->pxnc_csum, buf, sizeof(xncs->pxnc_csum)); + + pefs_enccn_free(&enccn); + free(buf, M_TEMP); + } + break; + case PEFS_GETSLINKCTEXT: + if (vp->v_type != VDIR) { + dprintf(("pefs_ioctl: PEFS_GETSLINKCTEXT vp is not a directory\n")); + VOP_UNLOCK(vp, 0); + return (ENOTDIR); + } + + if (strnlen(xsl->pxsl_filename, sizeof(xsl->pxsl_filename)) != + xsl->pxsl_namelen) { + dprintf(("pefs_ioctl: PEFS_GETSLINKCTEXT incorrect namelen %d\n", + xsl->pxsl_namelen)); + VOP_UNLOCK(vp, 0); + return (EINVAL); + } + + if (strchr(xsl->pxsl_filename, '/') != NULL) { + dprintf(("pefs_ioctl: PEFS_GETSLINKCTEXT filename contains '/'\n")); + VOP_UNLOCK(vp, 0); + return (EINVAL); + } + + /* grab a vnodep for our symlink */ + NDINIT_ATVP(ndp, LOOKUP, MPSAFE | LOCKPARENT | LOCKLEAF | NOFOLLOW, + UIO_SYSSPACE, xsl->pxsl_filename, vp, td); + error = namei(ndp); + if (error != 0) { + dprintf(("pefs_ioctl: PEFS_GETSLINKCTEXT namei error %d\n", error)); + return (error); + } + NDFREE(ndp, NDF_ONLY_PNBUF); + svp = ndp->ni_vp; + + if (ndp->ni_dvp != vp) { + dprintf(("pefs_ioctl: PEFS_GETSLINKCTEXT namei wrong parent\n")); + vput(svp); + VOP_UNLOCK(vp, 0); + return (ENOENT); + } + + if (svp->v_type != VLNK) { + dprintf(("pefs_ioctl: PEFS_GETSLINKCTEXT svp is not a symlink\n")); + vput(svp); + VOP_UNLOCK(vp, 0); + return (EFTYPE); + } + + /* VOP_READLINK our slvp */ + slvp = PEFS_LOWERVP(svp); + + pn = VP_TO_PN(svp); + pefs_chunk_create(&pc, pn, MAXPATHLEN + 1); + puio = pefs_chunk_uio(&pc, 0, UIO_READ); + + error = VOP_READLINK(slvp, puio, cred); + + xsl->pxsl_target_len = pc.pc_size - pc.pc_uio.uio_resid; + if (error == 0) + memcpy(xsl->pxsl_target, pc.pc_base, xsl->pxsl_target_len); + + pefs_chunk_free(&pc, pn); + vput(svp); + VOP_UNLOCK(vp, 0); + break; default: error = ENOTTY; break; Index: pefs_kmod/sys/fs/pefs/pefs_checksum.h =================================================================== --- pefs_kmod/sys/fs/pefs/pefs_checksum.h (revision 0) +++ pefs_kmod/sys/fs/pefs/pefs_checksum.h (revision 240588) @@ -0,0 +1,81 @@ +/*- + * Copyright (c) 2012 Efstratios Karatzas + * 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$ + */ + +#define PEFS_FILE_CHECKSUM ".pefs.checksum" + +#define PEFS_CFH_SIZE 16 /* file header of .pefs.checksum file */ +#define PEFS_HT_CELL_SIZE 16 /* hash table cell(bucket) size */ + +#define PEFS_SIGNATURE_MAX_LENGTH 512 + +#define PEFS_CHECKSUM_SUPPORTED_DIGESTS 2 + +#define PEFS_SHA256 0 +#define PEFS_SHA512 1 + +/* + * XXXgpf: use this instead of PEFS_DEBUG so as to lower the number of debugging + * messages during code development. + */ +//#define PEFS_INTEGRITY_DEBUG +#if defined (PEFS_INTEGRITY_DEBUG) +#define dprintf(a) printf a +#else +#define dprintf(a) (void)0 +#endif + +union pefs_checksum_file_id { + uint64_t pcfi_num; + uint8_t pcfi_str[8]; +}; + +struct pefs_checksum_index_entry { + uint32_t pcie_nhashes; + uint32_t pcie_offset; + union pefs_checksum_file_id pcie_fid; +}; + +static __inline int +PEFS_EMPTY_INDEX_ENTRY(struct pefs_checksum_index_entry *pcie) +{ + MPASS(pcie != NULL); + return ((pcie->pcie_nhashes == 0) ? 1 : 0); +} + +static __inline int +PEFS_NEEDS_CHECKING(struct pefs_node *pn) +{ + MPASS(pn != NULL); + return ((pn->pn_checksum_index_entry != NULL) ? 1 : 0); +} + +void pefs_checksum_lookup(char *enc_name, size_t enc_name_len, + struct componentname *cnp, struct vnode *vp); +int pefs_integrity_check(struct vnode *vp, off_t offset, + u_quad_t fsize, struct pefs_chunk *pc); +int pefs_sanitize_checksum_header(struct pefs_checksum *pcs); Index: pefs_kmod/sys/fs/pefs/pefs_subr.c =================================================================== --- pefs_kmod/sys/fs/pefs/pefs_subr.c (revision 235719) +++ pefs_kmod/sys/fs/pefs/pefs_subr.c (revision 240588) @@ -52,6 +52,7 @@ #include #include +#include #include typedef int pefs_node_init_fn(struct mount *mp, struct pefs_node *pn, @@ -400,6 +401,7 @@ PEFSDEBUG("pefs_node_get: creating node without key: %p\n", pn); pn->pn_vnode = vp; + pn->pn_checksum_index_entry = NULL; vp->v_type = lvp->v_type; vp->v_data = pn; vp->v_vnlock = lvp->v_vnlock; @@ -454,8 +456,24 @@ pefs_node_get_lookupkey(struct mount *mp, struct vnode *lvp, struct vnode **vpp, struct ucred *cred) { + struct pefs_mount *pm = VFS_TO_PEFS(mp); + char *encname; + int encname_len, error; + MPASS(cred != NULL); - return (pefs_node_get(mp, lvp, vpp, pefs_node_init_lookupkey, cred)); + error = pefs_node_get(mp, lvp, vpp, pefs_node_init_lookupkey, cred); + if ((error == 0) && (pm->pm_flags & PM_CHECKSUM) != 0) { + encname_len = MAXNAMLEN + 1; + encname = malloc(encname_len, M_TEMP, M_WAITOK); + + error = pefs_node_lookup_name(lvp, NULL, cred, encname, &encname_len); + if (error == 0) { + printf("lookupkey encname=%.*s\n",(int)encname_len, encname); + pefs_checksum_lookup(encname, encname_len, NULL, *vpp); + } + free(encname, M_TEMP); + } + return (error); } static __inline void Index: pefs_kmod/sys/fs/pefs/pefs_xbase64.c =================================================================== --- pefs_kmod/sys/fs/pefs/pefs_xbase64.c (revision 235719) +++ pefs_kmod/sys/fs/pefs/pefs_xbase64.c (revision 240588) @@ -48,6 +48,7 @@ #include #include #include +#include #include Index: pefs_kmod/sys/fs/pefs/pefs_vfsops.c =================================================================== --- pefs_kmod/sys/fs/pefs/pefs_vfsops.c (revision 235719) +++ pefs_kmod/sys/fs/pefs/pefs_vfsops.c (revision 240588) @@ -34,6 +34,7 @@ #include __FBSDID("$FreeBSD$"); +#include #include #include #include @@ -44,8 +45,10 @@ #include #include #include +#include #include +#include struct pefs_opt_descr { char *fs; @@ -72,10 +75,12 @@ "dircache", "nodircache", "asyncreclaim", + "checksum", NULL }; static MALLOC_DEFINE(M_PEFSMNT, "pefs_mount", "PEFS mount structure"); +static MALLOC_DEFINE(M_PEFSCSTABLE, "pefs_checksum_table", "PEFS checksum table"); static void pefs_opt_set(struct mount *mp, int opt, struct pefs_mount *pm, @@ -119,6 +124,136 @@ return (0); } +/* XXXgpf: tmp 4 dbg purposes */ +static void +pefs_dbg_vnode(struct vnode *vp, char *str) +{ + if (vp == NULL) + return; + dprintf(("%s is locked: %d\n", str, VOP_ISLOCKED(vp))); + dprintf(("%s usecount: %d\n", str, vp->v_usecount)); + dprintf(("%s holdcnt: %d\n", str, vp->v_holdcnt)); + dprintf(("%s writecount: %d\n", str, vp->v_writecount)); +} + +static int +pefs_checksum_load(struct mount *mp) +{ + char *bufp, *path; + struct nameidata nd, *ndp = &nd; + struct pefs_chunk pc; + struct pefs_mount *pm = VFS_TO_PEFS(mp); + struct pefs_checksum *pcs = &(pm->pm_checksum); + struct ucred *cred = mp->mnt_cred; + struct uio *puio; + struct vnode *checksumvp; + uint32_t buflen, pathlen; + int error; + + pcs->pcs_checksumvp = NULL; + pcs->pcs_table1 = NULL; + pcs->pcs_table2 = NULL; + + pathlen = MAXPATHLEN + 1; + path = malloc(pathlen, M_TEMP, M_WAITOK); + snprintf(path, pathlen, "%s/%s", mp->mnt_stat.f_mntfromname, + PEFS_FILE_CHECKSUM); + + /* grab a vp for our checksum file */ + NDINIT(ndp, LOOKUP, LOCKLEAF, UIO_SYSSPACE, path, curthread); + error = namei(ndp); + free(path, M_TEMP); + if (error != 0) { + dprintf(("pefs_checksum_load: namei error %d\n", error)); + return (error); + } + + checksumvp = ndp->ni_vp; + NDFREE(ndp, NDF_NO_VP_PUT); + + /* read checksum file header info */ + buflen = PEFS_CFH_SIZE; + pefs_chunk_create(&pc, NULL, buflen); + puio = pefs_chunk_uio(&pc, PEFS_SIGNATURE_MAX_LENGTH, UIO_READ); + + /* XXXgpf: gleb says I should use vn_rdwr instead of VOP_READ */ + error = VOP_READ(checksumvp, puio, IO_UNIT, cred); + if (error != 0) { + dprintf(("pefs_checksum_load: vop_read1 error %d\n", error)); + pefs_chunk_free(&pc, NULL); + vput(checksumvp); + return (error); + } + + bufp = pc.pc_base; + memcpy(&(pcs->pcs_version), bufp, sizeof(pcs->pcs_version)); + bufp+=sizeof(pcs->pcs_version); + memcpy(&(pcs->pcs_reserved), bufp, sizeof(pcs->pcs_reserved)); + bufp+=sizeof(pcs->pcs_reserved); + memcpy(&(pcs->pcs_hash_len), bufp, sizeof(pcs->pcs_hash_len)); + bufp+=sizeof(pcs->pcs_hash_len); + memcpy(&(pcs->pcs_hash_algo_name), bufp, sizeof(pcs->pcs_hash_algo_name)); + bufp+=sizeof(pcs->pcs_hash_algo_name); + memcpy(&(pcs->pcs_offset_to_hash_table), bufp, + sizeof(pcs->pcs_offset_to_hash_table)); + bufp+=sizeof(pcs->pcs_offset_to_hash_table); + memcpy(&(pcs->pcs_hash_table_size), bufp, sizeof(pcs->pcs_hash_table_size)); + pcs->pcs_hash_table_size = le32toh(pcs->pcs_hash_table_size); + + error = pefs_sanitize_checksum_header(pcs); + if (error != 0) { + dprintf(("pefs_checksum_load: sanitize error %d\n", error)); + pefs_chunk_free(&pc, NULL); + vput(checksumvp); + return (error); + } + + pefs_chunk_free(&pc, NULL); + + /* load and keep the 2 hash tables in kernel heap */ + buflen = pcs->pcs_hash_table_size * PEFS_HT_CELL_SIZE; + pefs_chunk_create(&pc, NULL, buflen); + puio = pefs_chunk_uio(&pc, PEFS_SIGNATURE_MAX_LENGTH + + pcs->pcs_offset_to_hash_table, UIO_READ); + + error = VOP_READ(checksumvp, puio, IO_UNIT, cred); + if (error != 0) { + dprintf(("pefs_checksum_load: vop_read2 error %d\n", error)); + pefs_chunk_free(&pc, NULL); + vput(checksumvp); + return (error); + } + + pcs->pcs_table1 = malloc(buflen, M_PEFSCSTABLE, M_WAITOK); + memcpy(pcs->pcs_table1, pc.pc_base, buflen); + pefs_chunk_free(&pc, NULL); + + pefs_chunk_create(&pc, NULL, buflen); + puio = pefs_chunk_uio(&pc, PEFS_SIGNATURE_MAX_LENGTH + + pcs->pcs_offset_to_hash_table + buflen, UIO_READ); + + error = VOP_READ(checksumvp, puio, IO_UNIT, cred); + if (error != 0) { + dprintf(("pefs_checksum_load: vop_read3 error %d\n", error)); + pefs_chunk_free(&pc, NULL); + free(pcs->pcs_table1, M_PEFSCSTABLE); + vput(checksumvp); + return (error); + } + + pcs->pcs_table2 = malloc(buflen, M_PEFSCSTABLE, M_WAITOK); + memcpy(pcs->pcs_table2, pc.pc_base, buflen); + pefs_chunk_free(&pc, NULL); + + pefs_dbg_vnode(checksumvp, "before VOP_UNLOCK checksumvp"); + /* keep the reference for checksumvp */ + VOP_UNLOCK(checksumvp, 0); + pefs_dbg_vnode(checksumvp, "after VOP_UNLOCK checksumvp"); + pcs->pcs_checksumvp = checksumvp; + + return (0); +} + /* * Mount null layer */ @@ -131,7 +266,7 @@ struct pefs_mount *pm; char *from, *from_free; int isvnunlocked = 0, len; - int opt_dircache, opt_asyncreclaim; + int opt_checksum, opt_dircache, opt_asyncreclaim; int error = 0; PEFSDEBUG("pefs_mount(mp = %p)\n", (void *)mp); @@ -155,6 +290,12 @@ vfs_deleteopt(mp->mnt_optnew, "asyncreclaim"); opt_asyncreclaim = 1; } + opt_checksum = -1; + if (vfs_flagopt(mp->mnt_optnew, "checksum", NULL, 0)) { + vfs_deleteopt(mp->mnt_optnew, "checksum"); + dprintf(("checksum!\n")); + opt_checksum = 1; + } if (mp->mnt_flag & MNT_UPDATE) { error = EOPNOTSUPP; @@ -170,6 +311,9 @@ PM_ASYNCRECLAIM, "asyncreclaim"); error = 0; } + /* + * XXXgpf: should we allow opt_checksum with MNT_UPDATE? + */ return (error); } @@ -262,6 +406,7 @@ pm->pm_flags |= PM_ROOT_CANRECURSE; pefs_opt_set(mp, opt_dircache, pm, PM_DIRCACHE, "dircache"); pefs_opt_set(mp, opt_asyncreclaim, pm, PM_ASYNCRECLAIM, "asyncreclaim"); + pefs_opt_set(mp, opt_checksum, pm, PM_CHECKSUM, "checksum"); /* * Save reference. Each mount also holds @@ -302,6 +447,17 @@ mp->mnt_data = pm; vfs_getnewfsid(mp); + if ((pm->pm_flags & PM_CHECKSUM) != 0) { + pefs_dbg_vnode(lowerrootvp, "BEFORE lowerrootvp"); + error = pefs_checksum_load(mp); + pefs_dbg_vnode(lowerrootvp, "AFTER lowerrootvp"); + if (error != 0) { + vrele(vp); + free(pm, M_PEFSMNT); + return (error); + } + } + PEFSDEBUG("pefs_mount: lower %s, alias at %s\n", mp->mnt_stat.f_mntfromname, mp->mnt_stat.f_mntonname); return (0); @@ -331,6 +487,16 @@ * Finally, throw away the pefs_mount structure */ pm = VFS_TO_PEFS(mp); + if ((pm->pm_flags & PM_CHECKSUM) != 0) { + /* free the hash tables from the heap */ + if (pm->pm_checksum.pcs_table1 != NULL) + free(pm->pm_checksum.pcs_table1, M_PEFSCSTABLE); + if (pm->pm_checksum.pcs_table2 != NULL) + free(pm->pm_checksum.pcs_table2, M_PEFSCSTABLE); + /* vrele the checksum file's vnode */ + if (pm->pm_checksum.pcs_checksumvp != NULL) + vrele(pm->pm_checksum.pcs_checksumvp); + } mp->mnt_data = 0; pefs_key_remove_all(pm); mtx_destroy(&pm->pm_keys_lock); Index: pefs_kmod/sys/modules/pefs/Makefile =================================================================== --- pefs_kmod/sys/modules/pefs/Makefile (revision 235719) +++ pefs_kmod/sys/modules/pefs/Makefile (revision 240588) @@ -6,7 +6,9 @@ SRCS= vnode_if.h \ pefs_subr.c pefs_vfsops.c pefs_vnops.c pefs_xbase64.c pefs_crypto.c \ pefs_dircache.c \ - pefs_xts.c vmac.c + pefs_xts.c vmac.c \ + pefs_checksum.c \ + pefs_mac.c .if ${MACHINE_CPUARCH} == "i386" || ${MACHINE_CPUARCH} == "amd64" SRCS+= pefs_aesni.c @@ -17,12 +19,9 @@ #DEBUG_FLAGS+= -DPEFS_DEBUG #DEBUG_FLAGS+= -DPEFS_DEBUG_EXTRA -CFLAGS+= -I${.CURDIR}/../../ - -# Temporally build crypto/hmac into pefs module +CFLAGS+= -I${.CURDIR}/../../# Temporally build crypto/hmac into pefs module .PATH: ${.CURDIR}/../../crypto/hmac SRCS+= hmac_sha512.c .include -