summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Halcrow <mhalcrow@google.com>2018-03-08 13:08:01 -0800
committerMichael Halcrow <mhalcrow@google.com>2018-03-08 13:08:01 -0800
commit880d42668421e8fb810505f796d9e730d89389fa (patch)
tree7746f3817bb50b953a7e3448f234e6d7a5ab66eb
downloadfsverity-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.diff41
-rwxr-xr-xfsverity.py250
-rwxr-xr-xfull-run-fsverity.sh15
-rw-r--r--ioctl-fs-verity-measure.c44
-rw-r--r--ioctl-fs-verity-set.c36
-rwxr-xr-xmkfsverity.sh48
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}