diff options
author | Michael Halcrow <mhalcrow@google.com> | 2018-03-08 13:08:01 -0800 |
---|---|---|
committer | Michael Halcrow <mhalcrow@google.com> | 2018-03-08 13:08:01 -0800 |
commit | 880d42668421e8fb810505f796d9e730d89389fa (patch) | |
tree | 7746f3817bb50b953a7e3448f234e6d7a5ab66eb | |
download | fsverity-880d42668421e8fb810505f796d9e730d89389fa.tar.gz |
fs-verity: Some userspace tooling that can be helpful for development
Signed-off-by: Michael Halcrow <mhalcrow@google.com>
-rw-r--r-- | f2fs-verity-bit.diff | 41 | ||||
-rwxr-xr-x | fsverity.py | 250 | ||||
-rwxr-xr-x | full-run-fsverity.sh | 15 | ||||
-rw-r--r-- | ioctl-fs-verity-measure.c | 44 | ||||
-rw-r--r-- | ioctl-fs-verity-set.c | 36 | ||||
-rwxr-xr-x | mkfsverity.sh | 48 |
6 files changed, 434 insertions, 0 deletions
diff --git a/f2fs-verity-bit.diff b/f2fs-verity-bit.diff new file mode 100644 index 0000000..cb7e87a --- /dev/null +++ b/f2fs-verity-bit.diff @@ -0,0 +1,41 @@ +diff --git a/configure.ac b/configure.ac +index 73c830d..7733af4 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -52,8 +52,6 @@ AC_PATH_PROG([LDCONFIG], [ldconfig], + [$PATH:/sbin]) + + # Checks for libraries. +-PKG_CHECK_MODULES([libuuid], [uuid]) +- + AS_IF([test "x$with_selinux" != "xno"], + [PKG_CHECK_MODULES([libselinux], [libselinux], + [have_selinux=yes], [have_selinux=no])], +diff --git a/include/f2fs_fs.h b/include/f2fs_fs.h +index 782c936..7184419 100644 +--- a/include/f2fs_fs.h ++++ b/include/f2fs_fs.h +@@ -550,6 +550,7 @@ enum { + #define F2FS_FEATURE_INODE_CHKSUM 0x0020 + #define F2FS_FEATURE_FLEXIBLE_INLINE_XATTR 0x0040 + #define F2FS_FEATURE_QUOTA_INO 0x0080 ++#define F2FS_FEATURE_VERITY 0x0200 + + #define MAX_VOLUME_NAME 512 + +diff --git a/mkfs/f2fs_format_main.c b/mkfs/f2fs_format_main.c +index e41bcb1..15de5fe 100644 +--- a/mkfs/f2fs_format_main.c ++++ b/mkfs/f2fs_format_main.c +@@ -80,7 +80,10 @@ static void parse_feature(const char *features) + { + while (*features == ' ') + features++; +- if (!strcmp(features, "encrypt")) { ++ if (!strcmp(features, "verity")) { ++ MSG(0, "Info: Setting verity\n"); ++ c.feature |= cpu_to_le32(F2FS_FEATURE_VERITY); ++ } else if (!strcmp(features, "encrypt")) { + c.feature |= cpu_to_le32(F2FS_FEATURE_ENCRYPT); + } else if (!strcmp(features, "extra_attr")) { + c.feature |= cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR); diff --git a/fsverity.py b/fsverity.py new file mode 100755 index 0000000..6289ba3 --- /dev/null +++ b/fsverity.py @@ -0,0 +1,250 @@ +#!/usr/bin/python + +import argparse +import binascii +import os +import shutil +import subprocess +import sys +import tempfile +from ctypes import * + +class FSVHeader(Structure): + _fields_ = [('magic', c_uint8 * 8), + ('maj_version', c_uint8), + ('min_version', c_uint8), + ('log_blocksize', c_uint8), + ('log_arity', c_uint8), + ('meta_algorithm', c_uint16), + ('data_algorithm', c_uint16), + ('reserved1', c_uint32), + ('size', c_uint64), + ('auth_blk_offset', c_uint8), + ('extension_count', c_uint8), + ('salt', c_char * 8), + ('reserved2', c_char * 22)] + +HEADER_SIZE = 64 + +class FSVExt(Structure): + _fields_ = [('length', c_uint16), + ('type', c_uint8), + ('reserved', c_char * 5)] + +class PatchExt(Structure): + _fields_ = [('offset', c_uint64), + ('length', c_uint8), + ('reserved', c_char * 7)] +# Append databytes at the end of the buffer this gets serialized into + +class ElideExt(Structure): + _fields_ = [('offset', c_uint64), + ('length', c_uint64)] + +def parse_args(): + parser = argparse.ArgumentParser(description='Build file-based integrity metadata') + parser.add_argument('--salt', metavar='<hex_string>', type=binascii.unhexlify, + help='Hex string, e.g. 01ab') + parser.add_argument('--tree-file', metavar='<filename>', type=str, + help='Filename for tree file (optional)') + parser.add_argument('input_file', metavar='<file>', type=str, + help='Original content input file') + parser.add_argument('output_file', metavar='<file>', type=str, + help='Output file formatted for fs-verity') + parser.add_argument('--patch_file', metavar='<file>', type=str, + help='File containing patch content') + parser.add_argument('--patch_offset', metavar='<offset>', type=str, + help='Offset to which to apply patch') + parser.add_argument('--elide_offset', metavar='<offset>', type=str, + help='Offset of segment to elide') + parser.add_argument('--elide_length', metavar='<length>', type=str, + help='Length of segment to elide') + return parser.parse_args() + +def generate_merkle_tree(args, elided_file): + if args.tree_file is not None: + tree_file_name = args.tree_file + else: + tree_file = tempfile.NamedTemporaryFile() + tree_file_name = tree_file.name + tree_file.close() + if elided_file is not None: + file_to_verity = elided_file.name + else: + file_to_verity = args.output_file + cmd = ['veritysetup', 'format', file_to_verity, tree_file_name, '-s', binascii.hexlify(args.salt), '--no-superblock'] + print ' '.join(cmd) + output = subprocess.check_output(cmd) + root_hash = '' + for line in output.split('\n'): + if line.startswith('Root hash'): + root_hash = line.split(':')[1].strip() + break + else: + sys.exit('FATAL: root hash is not found') + with file(tree_file_name, 'r') as tree_file: + tree_file.seek(0, os.SEEK_SET) + merkle_tree = tree_file.read() + return root_hash, merkle_tree + +def copy_src_to_dst(args): + with file(args.output_file, 'w') as dst: + with file (args.input_file, 'r') as src: + shutil.copyfileobj(src, dst) + +def pad_dst(args): + with file (args.output_file, 'a') as dst: + dst.write('\0' * ((4096 - dst.tell()) % 4096)) + +def append_merkle_tree_to_dst(args, tree): + with file (args.output_file, 'a') as dst: + dst.write(tree) + +def append_header_to_dst(args, header): + with file (args.output_file, 'a') as dst: + dst.write(string_at(pointer(header), sizeof(header))) + +class HeaderOffset(Structure): + _fields_ = [('hdr_offset', c_uint32)] + +def append_header_reverse_offset_to_dst(args, extensions): + hdr_offset = HeaderOffset() + hdr_offset.hdr_offset = HEADER_SIZE + len(extensions) + sizeof(hdr_offset) + with file (args.output_file, 'a') as dst: + dst.write(string_at(pointer(hdr_offset), sizeof(hdr_offset))) + +def append_extensions_to_dst(args, extensions): + with file (args.output_file, 'a') as dst: + dst.write(extensions) + +def fill_header_struct(args): + statinfo = os.stat(args.input_file) + header = FSVHeader() + assert sizeof(header) == HEADER_SIZE + memset(addressof(header), 0, sizeof(header)) + memmove(addressof(header) + FSVHeader.magic.offset, b'TrueBrew', 8) + header.maj_version = 1 + header.min_version = 0 + header.log_blocksize = 12 + header.log_arity = 7 + header.meta_algorithm = 1 # sha256 + header.data_algorithm = 1 # sha256 + header.reserved1 = 0 + header.size = statinfo.st_size + header.auth_blk_offset = 0 + header.extension_count = 0 + if args.patch_file is not None and args.patch_offset is not None: + header.extension_count += 1 + header.salt = args.salt + return header + +def apply_patch(args): + if args.patch_file is not None and args.patch_offset is not None: + statinfo = os.stat(args.patch_file) + patch_file_size = statinfo.st_size + if patch_file_size > 256: + print "Invalid patch file size; must be <= 256 bytes: [", patch_file_size, "]" + return None + statinfo = os.stat(args.output_file) + if statinfo.st_size < (int(args.patch_offset) + patch_file_size): + print "Invalid output file size for patch offset and size" + return None + with file (args.patch_file, 'r') as patch_file: + patch_buf = "" + original_content = "" + with file (args.output_file, 'r') as dst: + dst.seek(int(args.patch_offset), os.SEEK_SET) + original_content = dst.read(patch_file_size) + dst.seek(int(args.patch_offset), os.SEEK_SET) + patch_buf = patch_file.read(patch_file_size) + dst.close() + with file (args.output_file, 'w') as dst: + dst.seek(int(args.patch_offset), os.SEEK_SET) + dst.write(patch_buf) + dst.close() + return original_content + else: + return None + +def serialize_extensions(args): + patch_ext_buf = None + elide_ext_buf = None + if args.patch_file is not None and args.patch_offset is not None: + statinfo = os.stat(args.patch_file) + patch_file_size = statinfo.st_size + exthdr = FSVExt() + memset(addressof(exthdr), 0, sizeof(exthdr)) + patch_ext = PatchExt() + memset(addressof(patch_ext), 0, sizeof(patch_ext)) + aligned_patch_size = ((int(patch_file_size) + int(8 - 1)) / int(8)) * int(8) + exthdr.length = sizeof(exthdr) + sizeof(patch_ext) + aligned_patch_size; + exthdr.type = 1 # 1 == patch extension + patch_ext.offset = int(args.patch_offset) + print "Patch offset: ", patch_ext.offset + patch_ext.length = patch_file_size + print "Patch length: ", patch_ext.length + patch_ext_buf = create_string_buffer(exthdr.length) + memset(addressof(patch_ext_buf), 0, sizeof(patch_ext_buf)) # Includes the zero-pad + memmove(addressof(patch_ext_buf), addressof(exthdr), sizeof(exthdr)) + memmove(addressof(patch_ext_buf) + sizeof(exthdr), addressof(patch_ext), sizeof(patch_ext)) + with file (args.patch_file, 'r') as patch_file: + memmove(addressof(patch_ext_buf) + sizeof(exthdr) + sizeof(patch_ext), patch_file.read(patch_file_size), patch_file_size) + if args.elide_offset is not None and args.elide_length is not None: + exthdr = FSVExt() + memset(addressof(exthdr), 0, sizeof(exthdr)) + elide_ext = ElideExt() + memset(addressof(elide_ext), 0, sizeof(elide_ext)) + exthdr.length = sizeof(exthdr) + sizeof(elide_ext) + exthdr.type = 0 # 0 == elide extension + elide_ext.offset = int(args.elide_offset) + print "Elide offset: ", elide_ext.offset + elide_ext.length = int(args.elide_length) + print "Elide length: ", elide_ext.length + elide_ext_buf = create_string_buffer(exthdr.length) + memset(addressof(elide_ext_buf), 0, sizeof(elide_ext_buf)) + memmove(addressof(elide_ext_buf), addressof(exthdr), sizeof(exthdr)) + memmove(addressof(elide_ext_buf) + sizeof(exthdr), addressof(elide_ext), sizeof(elide_ext)) + return (string_at(patch_ext_buf) if (patch_ext_buf is not None) else "") + (string_at(elide_ext_buf) if (elide_ext_buf is not None) else "") + +def restore_patched_content(args, original_content): + if original_content is not None: + with file (args.output_file, 'w') as dst: + dst.seek(int(args.patch_offset), os.SEEK_SET) + dst.write(original_content) + +def elide_dst(args): + if args.elide_offset is not None and args.elide_length is not None: + statinfo = os.stat(args.output_file) + dst_size = statinfo.st_size + if dst_size < (int(args.elide_offset) + elide_length): + print "dst_size >= elide region offet+length" + return None + elided_file = tempfile.NamedTemporaryFile() + with file (args.output_file, 'r') as dst: + elided_file.write(dst.read(int(args.elide_offset))) + end_of_elided_segment = int(args.elide_offset) + int(args.elide_length) + dst.seek(end_of_elided_segment, os.SEEK_SET) + elided_file.write(dst.read(dst_size - end_of_elided_segment)) + return elided_file + else: + return None + +def main(): + args = parse_args() + + copy_src_to_dst(args) + pad_dst(args) + original_content = apply_patch(args) + elided_file = elide_dst(args) + root_hash, merkle_tree = generate_merkle_tree(args, elided_file) + append_merkle_tree_to_dst(args, merkle_tree) + header = fill_header_struct(args) + append_header_to_dst(args, header) + extensions = serialize_extensions(args) + append_extensions_to_dst(args, extensions) + restore_patched_content(args, original_content) + append_header_reverse_offset_to_dst(args, extensions) + print 'Merkle root hash: [', root_hash, "]" + +if __name__ == '__main__': + main() diff --git a/full-run-fsverity.sh b/full-run-fsverity.sh new file mode 100755 index 0000000..b1737b5 --- /dev/null +++ b/full-run-fsverity.sh @@ -0,0 +1,15 @@ +#!/bin/sh + +umount /mnt/f2fs +rm -f /root/f2fs.img +dd if=/dev/zero of=/root/f2fs.img seek=$(($1/128)) bs=512 count=1 +/root/f2fs-tools/mkfs/mkfs.f2fs -O verity /root/f2fs.img +mount -o loop /root/f2fs.img /mnt/f2fs +cp /root/output-$1.apk /mnt/f2fs/output-$1.apk +gcc -o ioctl-fs-verity-set ioctl-fs-verity-set.c +./ioctl-fs-verity-set /mnt/f2fs/output-$1.apk $1 +gcc -o ioctl-fs-verity-measure ioctl-fs-verity-measure.c +./ioctl-fs-verity-measure /mnt/f2fs/output-$1.apk $2 +sync +echo 3 > /proc/sys/vm/drop_caches +dd if=/mnt/f2fs/output-$1.apk of=byte0-$1 count=1 bs=1 diff --git a/ioctl-fs-verity-measure.c b/ioctl-fs-verity-measure.c new file mode 100644 index 0000000..e4bf09e --- /dev/null +++ b/ioctl-fs-verity-measure.c @@ -0,0 +1,44 @@ +#include <linux/fs.h> +#include <stdio.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> + +#include <string.h> + +int main(int args, char *argv[]) +{ + int res, fd, i; + struct fsverity_root_hash root_hash; + + if (args != 3 || strlen(argv[2]) != 64) { + printf("Usage:\n ioctl-fs-verity [filepath] [root hash in hex; 64 characters]\n"); + return -EINVAL; + } + + fd = open(argv[1], O_RDONLY); + + if (fd == -1) { + printf("Could not open [%s]\n", argv[1]); + return -EINVAL; + } + memset((void*)&root_hash, 0, sizeof(struct fsverity_root_hash)); + for (i = 0; i < 32; i++) { + char hdigit[3] = {0, 0, 0}; + + memcpy(hdigit, &argv[2][i*2], 2); + res = sscanf(hdigit, "%x", &root_hash.root_hash[i]); + if (res != 1) + return -EINVAL; + } + res = ioctl(fd, FS_IOC_MEASURE_FSVERITY, &root_hash); + if (res) { + printf("ioctl() returned [%d]\n", res); + return 1; + } + close(fd); + return 0; +} diff --git a/ioctl-fs-verity-set.c b/ioctl-fs-verity-set.c new file mode 100644 index 0000000..ffc3a9c --- /dev/null +++ b/ioctl-fs-verity-set.c @@ -0,0 +1,36 @@ +#include <linux/fs.h> +#include <stdio.h> +#include <errno.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <stdlib.h> + +int main(int args, char *argv[]) +{ + int res, fd; + struct fsverity_set fsverity_set; + char *endptr; + + if (args != 3) { + printf("Usage:\n ioctl-fs-verity-set [filepath] [offset of fs-verity header]\n"); + return -EINVAL; + } + fsverity_set.offset = strtol(argv[2], &endptr, 10); + printf("Parsed offset: [%llu]\n", fsverity_set.offset); + fsverity_set.flags = 0; + fd = open(argv[1], O_RDWR); + if (fd == -1) { + printf("Could not open [%s]\n", argv[1]); + return -EINVAL; + } + res = ioctl(fd, FS_IOC_SET_FSVERITY, &fsverity_set); + if (res) { + printf("ioctl() returned [%d]\n", res); + return 1; + } + close(fd); + return 0; +} diff --git a/mkfsverity.sh b/mkfsverity.sh new file mode 100755 index 0000000..bb88865 --- /dev/null +++ b/mkfsverity.sh @@ -0,0 +1,48 @@ +#!/bin/sh + +set -x + +OPTIND=1 +patch=0 +patch_offset=28672 +patch_length=128 +elide=0 +elide_offset=12288 +elid_length=8192 +size=36864 +keep_input=0 +while getopts "poeskf:" opt; do + case "$opt" in + p) patch=1 + ;; + o) patch_offset=$OPTARG + ;; + e) elide=1 + ;; + f) elide_offset=$OPTARG + ;; + s) size=$OPTARG + ;; + k) keep_input=1 + ;; + esac +done +shift $((OPTIND-1)) +[ "$1" = "--" ] && shift +filename="input-$size.apk" +backup_filename="input-$size-backup.apk" +patch_filename="output-$size-patch" +echo "size=$size, filename='$filename', patch_filename='$patch_filename', patch=$patch, patch_offset=$patch_offset, elide=$elide, unparsed: $@" +num_blks=$(($size / 4096)) +blk_aligned_sz=$(($num_blks*4096)) +echo "Number of blocks: $num_blks" +if [ $keep_input -eq 0 ]; then + remainder=$(($size % 4096)) + echo "Remainder: $remainder" + dd if=/dev/urandom of=$filename bs=4096 count=$num_blks + dd if=/dev/urandom of=$filename bs=1 count=$remainder seek=$blk_aligned_sz +fi +dd if=/dev/urandom of=$patch_filename bs=1 count=$patch_length +if [ $elide -eq 1 ]; then ELIDE_ARGS=" --elide_offset=${elide_offset} --elide_length=${elide_length}"; fi +if [ $patch -eq 1 ]; then PATCH_ARGS=" --patch_offset=${patch_offset} --patch_file=${patch_filename}"; fi +./fsverity.py $filename "output-$size.apk" --salt=deadbeef00000000 ${PATCH_ARGS} ${ELIDE_ARGS} |