aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEric Biggers <ebiggers@google.com>2021-06-15 00:51:11 +0000
committerAutomerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>2021-06-15 00:51:11 +0000
commita58f0a3a9a5356ef50072414864ec97abaa9d752 (patch)
tree93cf5c5e908b291e65da8c8f3c97930c91ad0d78
parent980e8fe649dbd23741fe5123a170136a0a543a64 (diff)
parent410ed4977f4dfd3500e2230a128203171b8cb970 (diff)
downloadfsverity-utils-a58f0a3a9a5356ef50072414864ec97abaa9d752.tar.gz
Upgrade fsverity-utils to v1.4 am: 410ed4977f
Original change: https://android-review.googlesource.com/c/platform/external/fsverity-utils/+/1736357 Change-Id: I0d87f5107faaab99cb2563c9d1ffc90361488312
-rw-r--r--.gitignore2
-rw-r--r--Makefile33
-rw-r--r--NEWS.md11
-rw-r--r--README.md14
-rw-r--r--common/fsverity_uapi.h14
-rw-r--r--include/libfsverity.h48
-rw-r--r--lib/compute_digest.c130
-rw-r--r--lib/libfsverity.pc.in2
-rw-r--r--man/fsverity.1.md191
-rw-r--r--programs/cmd_digest.c7
-rw-r--r--programs/cmd_dump_metadata.c163
-rw-r--r--programs/cmd_sign.c17
-rw-r--r--programs/fsverity.c94
-rw-r--r--programs/fsverity.h10
-rw-r--r--programs/test_compute_digest.c133
-rw-r--r--programs/utils.c59
-rw-r--r--programs/utils.h3
-rwxr-xr-xscripts/do-release.sh6
-rwxr-xr-xscripts/run-tests.sh2
19 files changed, 901 insertions, 38 deletions
diff --git a/.gitignore b/.gitignore
index 04f9a6f..eba6ab6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,6 @@
+*.[1-9]
*.a
+*.exe
*.o
*.patch
*.so
diff --git a/Makefile b/Makefile
index 0354f62..c97790e 100644
--- a/Makefile
+++ b/Makefile
@@ -25,6 +25,9 @@
# Define LIBDIR to override where to install libraries, like './configure
# --libdir' in autotools-based projects (default: PREFIX/lib)
#
+# Define MANDIR to override where to install man pages, like './configure
+# --mandir' in autotools-based projects (default: PREFIX/share/man)
+#
# Define DESTDIR to override the installation destination directory
# (default: empty string)
#
@@ -61,12 +64,14 @@ QUIET_CCLD = @echo ' CCLD ' $@;
QUIET_AR = @echo ' AR ' $@;
QUIET_LN = @echo ' LN ' $@;
QUIET_GEN = @echo ' GEN ' $@;
+QUIET_PANDOC = @echo ' PANDOC ' $@;
endif
USE_SHARED_LIB ?=
PREFIX ?= /usr/local
BINDIR ?= $(PREFIX)/bin
INCDIR ?= $(PREFIX)/include
LIBDIR ?= $(PREFIX)/lib
+MANDIR ?= $(PREFIX)/share/man
DESTDIR ?=
ifneq ($(MINGW),1)
PKGCONF ?= pkg-config
@@ -92,6 +97,7 @@ FSVERITY := fsverity$(EXEEXT)
fi
DEFAULT_TARGETS :=
+EXTRA_TARGETS :=
COMMON_HEADERS := $(wildcard common/*.h)
LDLIBS := $(shell "$(PKGCONF)" libcrypto --libs 2>/dev/null || echo -lcrypto)
CFLAGS += $(shell "$(PKGCONF)" libcrypto --cflags 2>/dev/null || echo)
@@ -160,6 +166,7 @@ FSVERITY_PROG_OBJ := $(PROG_COMMON_OBJ) \
programs/fsverity.o
ifneq ($(MINGW),1)
FSVERITY_PROG_OBJ += \
+ programs/cmd_dump_metadata.o \
programs/cmd_enable.o \
programs/cmd_measure.o
endif
@@ -186,9 +193,22 @@ DEFAULT_TARGETS += $(FSVERITY)
$(TEST_PROGRAMS): %$(EXEEXT): programs/%.o $(PROG_COMMON_OBJ) libfsverity.a
$(QUIET_CCLD) $(CC) -o $@ $+ $(CFLAGS) $(LDFLAGS) $(LDLIBS)
+EXTRA_TARGETS += $(TEST_PROGRAMS)
+
##############################################################################
-SPECIAL_TARGETS := all test_programs check install uninstall help clean
+#### Manual pages
+
+man/fsverity.1:man/fsverity.1.md
+ $(QUIET_PANDOC) pandoc $+ -s -t man > $@
+
+MAN_PAGES := man/fsverity.1
+EXTRA_TARGETS += $(MAN_PAGES)
+
+##############################################################################
+
+SPECIAL_TARGETS := all test_programs check install install-man uninstall \
+ help clean
FORCE:
@@ -232,6 +252,10 @@ install:all
> $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
chmod 644 $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
+install-man:$(MAN_PAGES)
+ install -d $(DESTDIR)$(MANDIR)/man1
+ install -m644 $+ $(DESTDIR)$(MANDIR)/man1/
+
uninstall:
rm -f $(DESTDIR)$(BINDIR)/$(FSVERITY)
rm -f $(DESTDIR)$(LIBDIR)/libfsverity.a
@@ -239,15 +263,18 @@ uninstall:
rm -f $(DESTDIR)$(LIBDIR)/libfsverity.so
rm -f $(DESTDIR)$(LIBDIR)/pkgconfig/libfsverity.pc
rm -f $(DESTDIR)$(INCDIR)/libfsverity.h
+ for page in $(notdir $(MAN_PAGES)); do \
+ rm -f $(DESTDIR)$(MANDIR)/man1/$$page; \
+ done
help:
@echo "Available targets:"
@echo "------------------"
- @for target in $(DEFAULT_TARGETS) $(TEST_PROGRAMS) $(SPECIAL_TARGETS); \
+ @for target in $(DEFAULT_TARGETS) $(EXTRA_TARGETS) $(SPECIAL_TARGETS); \
do \
echo $$target; \
done
clean:
- rm -f $(DEFAULT_TARGETS) $(TEST_PROGRAMS) \
+ rm -f $(DEFAULT_TARGETS) $(EXTRA_TARGETS) \
lib/*.o programs/*.o .build-config fsverity.sig
diff --git a/NEWS.md b/NEWS.md
index 8745745..be8d12f 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,16 @@
# fsverity-utils release notes
+## Version 1.4
+
+* Added a manual page for the `fsverity` utility.
+
+* Added the `fsverity dump_metadata` subcommand.
+
+* Added the `--out-merkle-tree` and `--out-descriptor` options to
+ `fsverity digest` and `fsverity sign`.
+
+* Added metadata callbacks support to `libfsverity_compute_digest()`.
+
## Version 1.3
* Added a `fsverity digest` subcommand.
diff --git a/README.md b/README.md
index 2b63488..14c7bbe 100644
--- a/README.md
+++ b/README.md
@@ -24,20 +24,22 @@ See `libfsverity.h` for the API of this library.
## Building and installing
-fsverity-utils uses the OpenSSL library, so you first must install the
-needed development files. For example, on Debian-based systems, run:
+To build fsverity-utils, first install the needed build dependencies. For
+example, on Debian-based systems, run:
```bash
sudo apt-get install libssl-dev
+ sudo apt-get install pandoc # optional
```
-OpenSSL must be version 1.0.0 or later.
+OpenSSL must be version 1.0.0 or later. This is the only runtime dependency.
Then, to build and install fsverity-utils:
```bash
make
sudo make install
+ sudo make install-man # optional
```
By default, the following targets are built and installed: the program
@@ -45,6 +47,9 @@ By default, the following targets are built and installed: the program
`libfsverity.so`. You can also run `make check` to build and run the
tests, or `make help` to display all available build targets.
+`make install-man` installs the `fsverity.1` manual page. This step requires
+that `pandoc` be installed.
+
By default, `fsverity` is statically linked to `libfsverity`. You can
use `make USE_SHARED_LIB=1` to use dynamic linking instead.
@@ -63,6 +68,9 @@ A Windows build of OpenSSL/libcrypto needs to be available.
## Examples
+Full usage information for `fsverity` can be found in the manual page
+(`man fsverity`). Here, we just show some typical examples.
+
### Basic use
```bash
diff --git a/common/fsverity_uapi.h b/common/fsverity_uapi.h
index a739c9a..c59a897 100644
--- a/common/fsverity_uapi.h
+++ b/common/fsverity_uapi.h
@@ -85,7 +85,21 @@ struct fsverity_formatted_digest {
__u8 digest[];
};
+#define FS_VERITY_METADATA_TYPE_MERKLE_TREE 1
+#define FS_VERITY_METADATA_TYPE_DESCRIPTOR 2
+#define FS_VERITY_METADATA_TYPE_SIGNATURE 3
+
+struct fsverity_read_metadata_arg {
+ __u64 metadata_type;
+ __u64 offset;
+ __u64 length;
+ __u64 buf_ptr;
+ __u64 __reserved;
+};
+
#define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg)
#define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest)
+#define FS_IOC_READ_VERITY_METADATA \
+ _IOWR('f', 135, struct fsverity_read_metadata_arg)
#endif /* _UAPI_LINUX_FSVERITY_H */
diff --git a/include/libfsverity.h b/include/libfsverity.h
index 6c42e5e..6cefa2b 100644
--- a/include/libfsverity.h
+++ b/include/libfsverity.h
@@ -22,7 +22,7 @@ extern "C" {
#include <stdint.h>
#define FSVERITY_UTILS_MAJOR_VERSION 1
-#define FSVERITY_UTILS_MINOR_VERSION 3
+#define FSVERITY_UTILS_MINOR_VERSION 4
#define FS_VERITY_HASH_ALG_SHA256 1
#define FS_VERITY_HASH_ALG_SHA512 2
@@ -61,8 +61,18 @@ struct libfsverity_merkle_tree_params {
/** @reserved1: must be 0 */
uint64_t reserved1[8];
+ /**
+ * @metadata_callbacks: if non-NULL, this gives a set of callback
+ * functions to which libfsverity_compute_digest() will pass the Merkle
+ * tree blocks and fs-verity descriptor after they are computed.
+ * Normally this isn't useful, but this can be needed in rare cases
+ * where the metadata needs to be consumed by something other than one
+ * of the native Linux kernel implementations of fs-verity.
+ */
+ const struct libfsverity_metadata_callbacks *metadata_callbacks;
+
/** @reserved2: must be 0 */
- uintptr_t reserved2[8];
+ uintptr_t reserved2[7];
};
struct libfsverity_digest {
@@ -78,6 +88,37 @@ struct libfsverity_signature_params {
uintptr_t reserved2[8]; /* must be 0 */
};
+struct libfsverity_metadata_callbacks {
+
+ /** @ctx: context passed to the below callbacks (opaque to library) */
+ void *ctx;
+
+ /**
+ * @merkle_tree_size: if non-NULL, called with the total size of the
+ * Merkle tree in bytes, prior to any call to @merkle_tree_block. Must
+ * return 0 on success, or a negative errno value on failure.
+ */
+ int (*merkle_tree_size)(void *ctx, uint64_t size);
+
+ /**
+ * @merkle_tree_block: if non-NULL, called with each block of the
+ * Merkle tree after it is computed. The offset is the offset in bytes
+ * to the block within the Merkle tree, using the Merkle tree layout
+ * used by FS_IOC_READ_VERITY_METADATA. The offsets won't necessarily
+ * be in increasing order. Must return 0 on success, or a negative
+ * errno value on failure.
+ */
+ int (*merkle_tree_block)(void *ctx, const void *block, size_t size,
+ uint64_t offset);
+
+ /**
+ * @descriptor: if non-NULL, called with the fs-verity descriptor after
+ * it is computed. Must return 0 on success, or a negative errno value
+ * on failure.
+ */
+ int (*descriptor)(void *ctx, const void *descriptor, size_t size);
+};
+
/*
* libfsverity_read_fn_t - callback that incrementally provides a file's data
* @fd: the user-provided "file descriptor" (opaque to library)
@@ -101,7 +142,8 @@ typedef int (*libfsverity_read_fn_t)(void *fd, void *buf, size_t count);
*
* Returns:
* * 0 for success, -EINVAL for invalid input arguments, -ENOMEM if libfsverity
- * failed to allocate memory, or an error returned by @read_fn.
+ * failed to allocate memory, or an error returned by @read_fn or by one of
+ * the @params->metadata_callbacks.
* * digest_ret returns a pointer to the digest on success. The digest object
* is allocated by libfsverity and must be freed by the caller using free().
*/
diff --git a/lib/compute_digest.c b/lib/compute_digest.c
index a4f649c..c5b0100 100644
--- a/lib/compute_digest.c
+++ b/lib/compute_digest.c
@@ -24,9 +24,8 @@ struct block_buffer {
/*
* Hash a block, writing the result to the next level's pending block buffer.
- * Returns true if the next level's block became full, else false.
*/
-static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
+static void hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
u32 block_size, const u8 *salt, u32 salt_size)
{
struct block_buffer *next = cur + 1;
@@ -41,8 +40,60 @@ static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
next->filled += hash->alg->digest_size;
cur->filled = 0;
+}
+
+static bool block_is_full(const struct block_buffer *block, u32 block_size,
+ struct hash_ctx *hash)
+{
+ /* Would the next hash put us over the limit? */
+ return block->filled + hash->alg->digest_size > block_size;
+}
+
+static int report_merkle_tree_size(const struct libfsverity_metadata_callbacks *cbs,
+ u64 size)
+{
+ if (cbs && cbs->merkle_tree_size) {
+ int err = cbs->merkle_tree_size(cbs->ctx, size);
- return next->filled + hash->alg->digest_size > block_size;
+ if (err) {
+ libfsverity_error_msg("error processing Merkle tree size");
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int report_merkle_tree_block(const struct libfsverity_metadata_callbacks *cbs,
+ const struct block_buffer *block,
+ u32 block_size, u64 *level_offset)
+{
+
+ if (cbs && cbs->merkle_tree_block) {
+ int err = cbs->merkle_tree_block(cbs->ctx, block->data,
+ block_size,
+ *level_offset * block_size);
+
+ if (err) {
+ libfsverity_error_msg("error processing Merkle tree block");
+ return err;
+ }
+ (*level_offset)++;
+ }
+ return 0;
+}
+
+static int report_descriptor(const struct libfsverity_metadata_callbacks *cbs,
+ const void *descriptor, size_t size)
+{
+ if (cbs && cbs->descriptor) {
+ int err = cbs->descriptor(cbs->ctx, descriptor, size);
+
+ if (err) {
+ libfsverity_error_msg("error processing fs-verity descriptor");
+ return err;
+ }
+ }
+ return 0;
}
/*
@@ -52,6 +103,7 @@ static bool hash_one_block(struct hash_ctx *hash, struct block_buffer *cur,
static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
u64 file_size, struct hash_ctx *hash,
u32 block_size, const u8 *salt, u32 salt_size,
+ const struct libfsverity_metadata_callbacks *metadata_cbs,
u8 *root_hash)
{
const u32 hashes_per_block = block_size / hash->alg->digest_size;
@@ -60,6 +112,7 @@ static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
u64 blocks;
int num_levels = 0;
int level;
+ u64 level_offset[FS_VERITY_MAX_LEVELS];
struct block_buffer _buffers[1 + FS_VERITY_MAX_LEVELS + 1] = {};
struct block_buffer *buffers = &_buffers[1];
u64 offset;
@@ -68,7 +121,7 @@ static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
/* Root hash of empty file is all 0's */
if (file_size == 0) {
memset(root_hash, 0, hash->alg->digest_size);
- return 0;
+ return report_merkle_tree_size(metadata_cbs, 0);
}
if (salt_size != 0) {
@@ -78,15 +131,39 @@ static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
memcpy(padded_salt, salt, salt_size);
}
- /* Compute number of levels */
- for (blocks = DIV_ROUND_UP(file_size, block_size); blocks > 1;
- blocks = DIV_ROUND_UP(blocks, hashes_per_block)) {
+ /* Compute number of levels and the number of blocks in each level. */
+ blocks = DIV_ROUND_UP(file_size, block_size);
+ while (blocks > 1) {
if (WARN_ON(num_levels >= FS_VERITY_MAX_LEVELS)) {
err = -EINVAL;
goto out;
}
- num_levels++;
+ blocks = DIV_ROUND_UP(blocks, hashes_per_block);
+ /*
+ * Temporarily use level_offset[] to store the number of blocks
+ * in each level. It will be overwritten later.
+ */
+ level_offset[num_levels++] = blocks;
+ }
+
+ /*
+ * Compute the starting block of each level, using the convention where
+ * the root level is first, i.e. the convention used by
+ * FS_IOC_READ_VERITY_METADATA. At the same time, compute the total
+ * size of the Merkle tree. These values are only needed for the
+ * metadata callbacks (if they were given), as the hash computation
+ * itself doesn't prescribe an ordering of the levels and doesn't
+ * prescribe any special meaning to the total size of the Merkle tree.
+ */
+ offset = 0;
+ for (level = num_levels - 1; level >= 0; level--) {
+ blocks = level_offset[level];
+ level_offset[level] = offset;
+ offset += blocks;
}
+ err = report_merkle_tree_size(metadata_cbs, offset * block_size);
+ if (err)
+ goto out;
/*
* Allocate the block buffers. Buffer "-1" is for data blocks.
@@ -112,21 +189,33 @@ static int compute_root_hash(void *fd, libfsverity_read_fn_t read_fn,
goto out;
}
- level = -1;
- while (hash_one_block(hash, &buffers[level], block_size,
- padded_salt, padded_salt_size)) {
- level++;
- if (WARN_ON(level >= num_levels)) {
- err = -EINVAL;
+ hash_one_block(hash, &buffers[-1], block_size,
+ padded_salt, padded_salt_size);
+ for (level = 0; level < num_levels; level++) {
+ if (!block_is_full(&buffers[level], block_size, hash))
+ break;
+ hash_one_block(hash, &buffers[level], block_size,
+ padded_salt, padded_salt_size);
+ err = report_merkle_tree_block(metadata_cbs,
+ &buffers[level],
+ block_size,
+ &level_offset[level]);
+ if (err)
goto out;
- }
}
}
/* Finish all nonempty pending tree blocks */
for (level = 0; level < num_levels; level++) {
- if (buffers[level].filled != 0)
+ if (buffers[level].filled != 0) {
hash_one_block(hash, &buffers[level], block_size,
padded_salt, padded_salt_size);
+ err = report_merkle_tree_block(metadata_cbs,
+ &buffers[level],
+ block_size,
+ &level_offset[level]);
+ if (err)
+ goto out;
+ }
}
/* Root hash was filled by the last call to hash_one_block() */
@@ -217,8 +306,13 @@ libfsverity_compute_digest(void *fd, libfsverity_read_fn_t read_fn,
}
err = compute_root_hash(fd, read_fn, params->file_size, hash,
- block_size, params->salt,
- params->salt_size, desc.root_hash);
+ block_size, params->salt, params->salt_size,
+ params->metadata_callbacks, desc.root_hash);
+ if (err)
+ goto out;
+
+ err = report_descriptor(params->metadata_callbacks,
+ &desc, sizeof(desc));
if (err)
goto out;
diff --git a/lib/libfsverity.pc.in b/lib/libfsverity.pc.in
index be3ef44..03496fe 100644
--- a/lib/libfsverity.pc.in
+++ b/lib/libfsverity.pc.in
@@ -4,7 +4,7 @@ includedir=@INCDIR@
Name: libfsverity
Description: fs-verity library
-Version: 1.3
+Version: 1.4
Libs: -L${libdir} -lfsverity
Requires.private: libcrypto
Cflags: -I${includedir}
diff --git a/man/fsverity.1.md b/man/fsverity.1.md
new file mode 100644
index 0000000..e1007f5
--- /dev/null
+++ b/man/fsverity.1.md
@@ -0,0 +1,191 @@
+% FSVERITY(1) fsverity-utils v1.4 | User Commands
+%
+% June 2021
+
+# NAME
+
+fsverity - userspace utility for fs-verity
+
+# SYNOPSIS
+**fsverity digest** [*OPTION*...] *FILE*... \
+**fsverity dump_metadata** [*OPTION*...] *TYPE* *FILE* \
+**fsverity enable** [*OPTION*...] *FILE* \
+**fsverity measure** *FILE*... \
+**fsverity sign \-\-key**=*KEYFILE* [*OPTION*...] *FILE* *OUT_SIGFILE*
+
+# DESCRIPTION
+
+**fsverity** is a userspace utility for fs-verity. fs-verity is a Linux kernel
+filesystem feature that does transparent on-demand verification of the contents
+of read-only files using Merkle trees.
+
+**fsverity** can enable fs-verity on files, retrieve the digests of fs-verity
+files, and sign files for use with fs-verity (among other things).
+**fsverity**'s functionality is divided among various subcommands.
+
+This manual page focuses on documenting all **fsverity** subcommands and
+options. For examples and more information about the fs-verity kernel feature,
+see the references at the end of this page.
+
+# OPTIONS
+
+**fsverity** always accepts the following options:
+
+**\-\-help**
+: Show the help, for either one subcommand or for all subcommands.
+
+**\-\-version**
+: Show the version of fsverity-utils.
+
+# SUBCOMMANDS
+
+## **fsverity digest** [*OPTION*...] *FILE*...
+
+Compute the fs-verity digest of the given file(s). This is mainly intended to
+used in preparation for signing the digest. In some cases **fsverity sign**
+can be used instead to digest and sign the file in one step.
+
+Options accepted by **fsverity digest**:
+
+**\-\-block-size**=*BLOCK_SIZE*
+: The Merkle tree block size (in bytes) to use. This must be a power of 2 and
+ at least twice the size of the hash values. However, note that currently
+ (as of Linux kernel v5.13), the Linux kernel implementations of fs-verity
+ only support the case where the Merkle tree block size is equal to the
+ system page size, usually 4096 bytes. The default value of this option is
+ 4096.
+
+**\-\-compact**
+: When printing the file digest, only print the actual digest hex string;
+ don't print the algorithm name and filename.
+
+**\-\-for-builtin-sig**
+: Format the file digest in a way that is compatible with the Linux kernel's
+ fs-verity built-in signature verification support. This means formatting it
+ as a `struct fsverity_formatted_digest`. Use this option if you are using
+ built-in signatures but are not using **fsverity sign** to do the signing.
+
+**\-\-hash-alg**=*HASH_ALG*
+: The hash algorithm to use to build the Merkle tree. Valid options are
+ sha256 and sha512. Default is sha256.
+
+**\-\-out-merkle-tree**=*FILE*
+: Write the computed Merkle tree to the given file. The Merkle tree layout
+ will be the same as that used by the Linux kernel's
+ `FS_IOC_READ_VERITY_METADATA` ioctl.
+
+ Normally this option isn't useful, but it can be needed in cases where the
+ fs-verity metadata needs to be consumed by something other than one of the
+ native Linux kernel implementations of fs-verity. This is not needed for
+ file signing.
+
+**\-\-out-descriptor**=*FILE*
+: Write the computed fs-verity descriptor to the given file.
+
+ Normally this option isn't useful, but it can be needed in cases where the
+ fs-verity metadata needs to be consumed by something other than one of the
+ native Linux kernel implementations of fs-verity. This is not needed for
+ file signing.
+
+**\-\-salt**=*SALT*
+: The salt to use in the Merkle tree, as a hex string. The salt is a value
+ that is prepended to every hashed block; it can be used to personalize the
+ hashing for a particular file or device. The default is no salt.
+
+## **fsverity dump_metadata** [*OPTION*...] *TYPE* *FILE*
+
+Dump the fs-verity metadata of the given file. The file must have fs-verity
+enabled, and the filesystem must support the `FS_IOC_READ_VERITY_METADATA` ioctl
+(it was added in Linux v5.12). This subcommand normally isn't useful, but it
+can be useful in cases where a userspace server program is serving a verity file
+to a client which implements fs-verity compatible verification.
+
+*TYPE* may be "merkle\_tree", "descriptor", or "signature", indicating the type
+of metadata to dump. "signature" refers to the built-in signature, if present;
+userspace-managed signatures will not be included.
+
+Options accepted by **fsverity dump_metadata**:
+
+**\-\-length**=*LENGTH*
+: Length in bytes to dump from the specified metadata item. Only accepted in
+ combination with **\-\-offset**.
+
+**\-\-offset**=*offset*
+: Offset in bytes into the specified metadata item at which to start dumping.
+ Only accepted in combination with **\-\-length**.
+
+## **fsverity enable** [*OPTION*...] *FILE*
+
+Enable fs-verity on the specified file. This will only work if the filesystem
+supports fs-verity.
+
+Options accepted by **fsverity enable**:
+
+**\-\-block-size**=*BLOCK_SIZE*
+: Same as for **fsverity digest**.
+
+**\-\-hash-alg**=*HASH_ALG*
+: Same as for **fsverity digest**.
+
+**\-\-salt**=*SALT*
+: Same as for **fsverity digest**.
+
+**\-\-signature**=*SIGFILE*
+: Specifies the built-in signature to apply to the file. *SIGFILE* must be a
+ file that contains the signature in PKCS#7 DER format, e.g. as produced by
+ the **fsverity sign** command.
+
+ Note that this option is only needed if the Linux kernel's fs-verity
+ built-in signature verification support is being used. It is not needed if
+ the signatures will be verified in userspace, as in that case the signatures
+ should be stored separately.
+
+## **fsverity measure** *FILE*...
+
+Display the fs-verity digest of the given file(s). The files must have
+fs-verity enabled. The output will be the same as **fsverity digest** with
+the appropriate parameters, but **fsverity measure** will take constant time
+for each file regardless of the size of the file.
+
+**fsverity measure** does not accept any options.
+
+## **fsverity sign** **\-\-key**=*KEYFILE* [*OPTION*...] *FILE* *OUT_SIGFILE*
+
+Sign the given file for fs-verity, in a way that is compatible with the Linux
+kernel's fs-verity built-in signature verification support. The signature will
+be written to *OUT_SIGFILE* in PKCS#7 DER format.
+
+Options accepted by **fsverity sign**:
+
+**\-\-block-size**=*BLOCK_SIZE*
+: Same as for **fsverity digest**.
+
+**\-\-cert**=*CERTFILE*
+: Specifies the file that contains the certificate, in PEM format. This
+ option is required if *KEYFILE* contains only the private key and not also
+ the certificate.
+
+**\-\-hash-alg**=*HASH_ALG*
+: Same as for **fsverity digest**.
+
+**\-\-key**=*KEYFILE*
+: Specifies the file that contains the private key, in PEM format. This
+ option is required.
+
+**\-\-out-descriptor**=*FILE*
+: Same as for **fsverity digest**.
+
+**\-\-out-merkle-tree**=*FILE*
+: Same as for **fsverity digest**.
+
+**\-\-salt**=*SALT*
+: Same as for **fsverity digest**.
+
+# SEE ALSO
+
+For example commands and more information, see the
+[README file for
+fsverity-utils](https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/fsverity-utils.git/tree/README.md).
+
+Also see the [kernel documentation for
+fs-verity](https://www.kernel.org/doc/html/latest/filesystems/fsverity.html).
diff --git a/programs/cmd_digest.c b/programs/cmd_digest.c
index 1a3c769..fd9f4de 100644
--- a/programs/cmd_digest.c
+++ b/programs/cmd_digest.c
@@ -18,6 +18,8 @@ static const struct option longopts[] = {
{"hash-alg", required_argument, NULL, OPT_HASH_ALG},
{"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
{"salt", required_argument, NULL, OPT_SALT},
+ {"out-merkle-tree", required_argument, NULL, OPT_OUT_MERKLE_TREE},
+ {"out-descriptor", required_argument, NULL, OPT_OUT_DESCRIPTOR},
{"compact", no_argument, NULL, OPT_COMPACT},
{"for-builtin-sig", no_argument, NULL, OPT_FOR_BUILTIN_SIG},
{NULL, 0, NULL, 0}
@@ -40,6 +42,8 @@ int fsverity_cmd_digest(const struct fsverity_command *cmd,
case OPT_HASH_ALG:
case OPT_BLOCK_SIZE:
case OPT_SALT:
+ case OPT_OUT_MERKLE_TREE:
+ case OPT_OUT_DESCRIPTOR:
if (!parse_tree_param(c, optarg, &tree_params))
goto out_usage;
break;
@@ -114,7 +118,8 @@ int fsverity_cmd_digest(const struct fsverity_command *cmd,
}
status = 0;
out:
- destroy_tree_params(&tree_params);
+ if (!destroy_tree_params(&tree_params) && status == 0)
+ status = 1;
return status;
out_err:
diff --git a/programs/cmd_dump_metadata.c b/programs/cmd_dump_metadata.c
new file mode 100644
index 0000000..94cc8ec
--- /dev/null
+++ b/programs/cmd_dump_metadata.c
@@ -0,0 +1,163 @@
+// SPDX-License-Identifier: MIT
+/*
+ * The 'fsverity dump_metadata' command
+ *
+ * Copyright 2021 Google LLC
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+#include "fsverity.h"
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <unistd.h>
+
+static const struct option longopts[] = {
+ {"offset", required_argument, NULL, OPT_OFFSET},
+ {"length", required_argument, NULL, OPT_LENGTH},
+ {NULL, 0, NULL, 0}
+};
+
+static const struct {
+ const char *name;
+ int val;
+} metadata_types[] = {
+ {"merkle_tree", FS_VERITY_METADATA_TYPE_MERKLE_TREE},
+ {"descriptor", FS_VERITY_METADATA_TYPE_DESCRIPTOR},
+ {"signature", FS_VERITY_METADATA_TYPE_SIGNATURE},
+};
+
+static bool parse_metadata_type(const char *name, __u64 *val_ret)
+{
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(metadata_types); i++) {
+ if (strcmp(name, metadata_types[i].name) == 0) {
+ *val_ret = metadata_types[i].val;
+ return true;
+ }
+ }
+ error_msg("unknown metadata type: %s", name);
+ fputs(" Expected", stderr);
+ for (i = 0; i < ARRAY_SIZE(metadata_types); i++) {
+ if (i != 0 && ARRAY_SIZE(metadata_types) > 2)
+ putc(',', stderr);
+ putc(' ', stderr);
+ if (i != 0 && i == ARRAY_SIZE(metadata_types) - 1)
+ fputs("or ", stderr);
+ fprintf(stderr, "\"%s\"", metadata_types[i].name);
+ }
+ fprintf(stderr, "\n");
+ return false;
+}
+
+/* Dump the fs-verity metadata of the given file. */
+int fsverity_cmd_dump_metadata(const struct fsverity_command *cmd,
+ int argc, char *argv[])
+{
+ bool offset_specified = false;
+ bool length_specified = false;
+ struct filedes file = { .fd = -1 };
+ struct filedes stdout_filedes = { .fd = STDOUT_FILENO,
+ .name = "stdout" };
+ struct fsverity_read_metadata_arg arg = { .length = 32768 };
+ void *buf = NULL;
+ char *tmp;
+ int c;
+ int status;
+ int bytes_read;
+
+ while ((c = getopt_long(argc, argv, "", longopts, NULL)) != -1) {
+ switch (c) {
+ case OPT_OFFSET:
+ if (offset_specified) {
+ error_msg("--offset can only be specified once");
+ goto out_usage;
+ }
+ errno = 0;
+ arg.offset = strtoull(optarg, &tmp, 10);
+ if (errno || *tmp) {
+ error_msg("invalid value for --offset");
+ goto out_usage;
+ }
+ offset_specified = true;
+ break;
+ case OPT_LENGTH:
+ if (length_specified) {
+ error_msg("--length can only be specified once");
+ goto out_usage;
+ }
+ errno = 0;
+ arg.length = strtoull(optarg, &tmp, 10);
+ if (errno || *tmp || arg.length > SIZE_MAX) {
+ error_msg("invalid value for --length");
+ goto out_usage;
+ }
+ length_specified = true;
+ break;
+ default:
+ goto out_usage;
+ }
+ }
+
+ argv += optind;
+ argc -= optind;
+
+ if (argc != 2)
+ goto out_usage;
+
+ if (!parse_metadata_type(argv[0], &arg.metadata_type))
+ goto out_usage;
+
+ if (length_specified && !offset_specified) {
+ error_msg("--length specified without --offset");
+ goto out_usage;
+ }
+ if (offset_specified && !length_specified) {
+ error_msg("--offset specified without --length");
+ goto out_usage;
+ }
+
+ buf = xzalloc(arg.length);
+ arg.buf_ptr = (uintptr_t)buf;
+
+ if (!open_file(&file, argv[1], O_RDONLY, 0))
+ goto out_err;
+
+ /*
+ * If --offset and --length were specified, then do only the single read
+ * requested. Otherwise read until EOF.
+ */
+ do {
+ bytes_read = ioctl(file.fd, FS_IOC_READ_VERITY_METADATA, &arg);
+ if (bytes_read < 0) {
+ error_msg_errno("FS_IOC_READ_VERITY_METADATA failed on '%s'",
+ file.name);
+ goto out_err;
+ }
+ if (bytes_read == 0)
+ break;
+ if (!full_write(&stdout_filedes, buf, bytes_read))
+ goto out_err;
+ arg.offset += bytes_read;
+ } while (!length_specified);
+
+ status = 0;
+out:
+ free(buf);
+ filedes_close(&file);
+ return status;
+
+out_err:
+ status = 1;
+ goto out;
+
+out_usage:
+ usage(cmd, stderr);
+ status = 2;
+ goto out;
+}
diff --git a/programs/cmd_sign.c b/programs/cmd_sign.c
index 47ba6a2..81a4ddc 100644
--- a/programs/cmd_sign.c
+++ b/programs/cmd_sign.c
@@ -27,11 +27,13 @@ static bool write_signature(const char *filename, const u8 *sig, u32 sig_size)
}
static const struct option longopts[] = {
- {"hash-alg", required_argument, NULL, OPT_HASH_ALG},
- {"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
- {"salt", required_argument, NULL, OPT_SALT},
- {"key", required_argument, NULL, OPT_KEY},
- {"cert", required_argument, NULL, OPT_CERT},
+ {"hash-alg", required_argument, NULL, OPT_HASH_ALG},
+ {"block-size", required_argument, NULL, OPT_BLOCK_SIZE},
+ {"salt", required_argument, NULL, OPT_SALT},
+ {"out-merkle-tree", required_argument, NULL, OPT_OUT_MERKLE_TREE},
+ {"out-descriptor", required_argument, NULL, OPT_OUT_DESCRIPTOR},
+ {"key", required_argument, NULL, OPT_KEY},
+ {"cert", required_argument, NULL, OPT_CERT},
{NULL, 0, NULL, 0}
};
@@ -54,6 +56,8 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd,
case OPT_HASH_ALG:
case OPT_BLOCK_SIZE:
case OPT_SALT:
+ case OPT_OUT_MERKLE_TREE:
+ case OPT_OUT_DESCRIPTOR:
if (!parse_tree_param(c, optarg, &tree_params))
goto out_usage;
break;
@@ -117,7 +121,8 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd,
status = 0;
out:
filedes_close(&file);
- destroy_tree_params(&tree_params);
+ if (!destroy_tree_params(&tree_params) && status == 0)
+ status = 1;
free(digest);
free(sig);
return status;
diff --git a/programs/fsverity.c b/programs/fsverity.c
index b911b2e..f6aff3a 100644
--- a/programs/fsverity.c
+++ b/programs/fsverity.c
@@ -11,6 +11,7 @@
#include "fsverity.h"
+#include <fcntl.h>
#include <limits.h>
static const struct fsverity_command {
@@ -27,9 +28,16 @@ static const struct fsverity_command {
.usage_str =
" fsverity digest FILE...\n"
" [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
+" [--out-merkle-tree=FILE] [--out-descriptor=FILE]\n"
" [--compact] [--for-builtin-sig]\n"
#ifndef _WIN32
}, {
+ .name = "dump_metadata",
+ .func = fsverity_cmd_dump_metadata,
+ .short_desc = "Dump the fs-verity metadata of the given file",
+ .usage_str =
+" fsverity dump_metadata TYPE FILE [--offset=OFFSET] [--length=LENGTH]\n"
+ }, {
.name = "enable",
.func = fsverity_cmd_enable,
.short_desc = "Enable fs-verity on a file",
@@ -52,6 +60,7 @@ static const struct fsverity_command {
.usage_str =
" fsverity sign FILE OUT_SIGFILE --key=KEYFILE\n"
" [--hash-alg=HASH_ALG] [--block-size=BLOCK_SIZE] [--salt=SALT]\n"
+" [--out-merkle-tree=FILE] [--out-descriptor=FILE]\n"
" [--cert=CERTFILE]\n"
}
};
@@ -194,6 +203,74 @@ static bool parse_salt_option(const char *arg, u8 **salt_ptr,
return true;
}
+struct metadata_callback_ctx {
+ struct filedes merkle_tree_file;
+ struct filedes descriptor_file;
+ struct libfsverity_metadata_callbacks callbacks;
+};
+
+static int handle_merkle_tree_size(void *_ctx, u64 size)
+{
+ struct metadata_callback_ctx *ctx = _ctx;
+
+ if (!preallocate_file(&ctx->merkle_tree_file, size))
+ return -EIO;
+ return 0;
+}
+
+static int handle_merkle_tree_block(void *_ctx, const void *block, size_t size,
+ u64 offset)
+{
+ struct metadata_callback_ctx *ctx = _ctx;
+
+ if (!full_pwrite(&ctx->merkle_tree_file, block, size, offset))
+ return -EIO;
+ return 0;
+}
+
+static int handle_descriptor(void *_ctx, const void *descriptor, size_t size)
+{
+ struct metadata_callback_ctx *ctx = _ctx;
+
+ if (!full_write(&ctx->descriptor_file, descriptor, size))
+ return -EIO;
+ return 0;
+}
+
+static bool parse_out_metadata_option(int opt_char, const char *arg,
+ const struct libfsverity_metadata_callbacks **cbs)
+{
+ struct metadata_callback_ctx *ctx;
+ struct filedes *file;
+ const char *opt_name;
+
+ if (*cbs) {
+ ctx = (*cbs)->ctx;
+ } else {
+ ctx = xzalloc(sizeof(*ctx));
+ ctx->merkle_tree_file.fd = -1;
+ ctx->descriptor_file.fd = -1;
+ ctx->callbacks.ctx = ctx;
+ *cbs = &ctx->callbacks;
+ }
+
+ if (opt_char == OPT_OUT_MERKLE_TREE) {
+ file = &ctx->merkle_tree_file;
+ opt_name = "--out-merkle-tree";
+ ctx->callbacks.merkle_tree_size = handle_merkle_tree_size;
+ ctx->callbacks.merkle_tree_block = handle_merkle_tree_block;
+ } else {
+ file = &ctx->descriptor_file;
+ opt_name = "--out-descriptor";
+ ctx->callbacks.descriptor = handle_descriptor;
+ }
+ if (file->fd >= 0) {
+ error_msg("%s can only be specified once", opt_name);
+ return false;
+ }
+ return open_file(file, arg, O_WRONLY|O_CREAT|O_TRUNC, 0644);
+}
+
bool parse_tree_param(int opt_char, const char *arg,
struct libfsverity_merkle_tree_params *params)
{
@@ -205,15 +282,30 @@ bool parse_tree_param(int opt_char, const char *arg,
case OPT_SALT:
return parse_salt_option(arg, (u8 **)&params->salt,
&params->salt_size);
+ case OPT_OUT_MERKLE_TREE:
+ case OPT_OUT_DESCRIPTOR:
+ return parse_out_metadata_option(opt_char, arg,
+ &params->metadata_callbacks);
default:
ASSERT(0);
}
}
-void destroy_tree_params(struct libfsverity_merkle_tree_params *params)
+bool destroy_tree_params(struct libfsverity_merkle_tree_params *params)
{
+ bool ok = true;
+
free((u8 *)params->salt);
+ if (params->metadata_callbacks) {
+ struct metadata_callback_ctx *ctx =
+ params->metadata_callbacks->ctx;
+
+ ok &= filedes_close(&ctx->merkle_tree_file);
+ ok &= filedes_close(&ctx->descriptor_file);
+ free(ctx);
+ }
memset(params, 0, sizeof(*params));
+ return ok;
}
int main(int argc, char *argv[])
diff --git a/programs/fsverity.h b/programs/fsverity.h
index 45c4fe1..fe24087 100644
--- a/programs/fsverity.h
+++ b/programs/fsverity.h
@@ -27,6 +27,10 @@ enum {
OPT_FOR_BUILTIN_SIG,
OPT_HASH_ALG,
OPT_KEY,
+ OPT_LENGTH,
+ OPT_OFFSET,
+ OPT_OUT_DESCRIPTOR,
+ OPT_OUT_MERKLE_TREE,
OPT_SALT,
OPT_SIGNATURE,
};
@@ -37,6 +41,10 @@ struct fsverity_command;
int fsverity_cmd_digest(const struct fsverity_command *cmd,
int argc, char *argv[]);
+/* cmd_dump_metadata.c */
+int fsverity_cmd_dump_metadata(const struct fsverity_command *cmd,
+ int argc, char *argv[]);
+
/* cmd_enable.c */
int fsverity_cmd_enable(const struct fsverity_command *cmd,
int argc, char *argv[]);
@@ -53,6 +61,6 @@ int fsverity_cmd_sign(const struct fsverity_command *cmd,
void usage(const struct fsverity_command *cmd, FILE *fp);
bool parse_tree_param(int opt_char, const char *arg,
struct libfsverity_merkle_tree_params *params);
-void destroy_tree_params(struct libfsverity_merkle_tree_params *params);
+bool destroy_tree_params(struct libfsverity_merkle_tree_params *params);
#endif /* PROGRAMS_FSVERITY_H */
diff --git a/programs/test_compute_digest.c b/programs/test_compute_digest.c
index e7f2645..67266fa 100644
--- a/programs/test_compute_digest.c
+++ b/programs/test_compute_digest.c
@@ -13,6 +13,7 @@
#include <ctype.h>
#include <inttypes.h>
+#include <openssl/sha.h>
struct mem_file {
u8 *data;
@@ -37,6 +38,13 @@ static int error_read_fn(void *fd __attribute__((unused)),
return -EIO;
}
+static int zeroes_read_fn(void *fd __attribute__((unused)),
+ void *buf, size_t count)
+{
+ memset(buf, 0, count);
+ return 0;
+}
+
static const struct test_case {
u32 hash_algorithm;
u32 block_size;
@@ -249,6 +257,130 @@ static void test_invalid_params(void)
ASSERT(d == NULL);
}
+static struct {
+ u64 merkle_tree_size;
+ u64 merkle_tree_block;
+ u64 descriptor;
+} metadata_callback_counts;
+
+static int handle_merkle_tree_size(void *ctx, u64 size)
+{
+ metadata_callback_counts.merkle_tree_size++;
+
+ /* Test that the ctx argument is passed through correctly. */
+ ASSERT(ctx == (void *)1);
+
+ /* Test that the expected Merkle tree size is reported. */
+ ASSERT(size == 5 * 1024);
+ return 0;
+}
+
+static int handle_merkle_tree_block(void *ctx, const void *block, size_t size,
+ u64 offset)
+{
+ u8 digest[SHA256_DIGEST_LENGTH];
+ u64 count = metadata_callback_counts.merkle_tree_block++;
+ const char *expected_digest;
+
+ /* Test that ->merkle_tree_size() was called first. */
+ ASSERT(metadata_callback_counts.merkle_tree_size == 1);
+
+ /* Test that the ctx argument is passed through correctly. */
+ ASSERT(ctx == (void *)1);
+
+ /*
+ * Test that this Merkle tree block has the expected size, offset, and
+ * contents. The 4 blocks at "level 0" should be reported first, in
+ * order; then the 1 block at "level 1" should be reported last (but the
+ * level 1 block should have the smallest offset).
+ */
+ ASSERT(size == 1024);
+ SHA256(block, size, digest);
+ if (count == 4) {
+ /* 1 block at level 1 */
+ ASSERT(offset == 0);
+ expected_digest = "\x68\xc5\x38\xe1\x19\x58\xd6\x5d"
+ "\x68\xb6\xfe\x8e\x9f\xb8\xcc\xab"
+ "\xec\xfd\x92\x8b\x01\xd0\x63\x44"
+ "\xe2\x23\xed\x41\xdd\xc4\x54\x4a";
+ } else {
+ /* 4 blocks at level 0 */
+ ASSERT(offset == 1024 + (count * 1024));
+ if (count < 3) {
+ expected_digest = "\xf7\x89\xba\xab\x53\x85\x9f\xaf"
+ "\x36\xd6\xd7\x5d\x10\x42\x06\x42"
+ "\x94\x20\x2d\x6e\x13\xe7\x71\x6f"
+ "\x39\x4f\xba\x43\x4c\xcc\x49\x86";
+ } else {
+ expected_digest = "\x00\xfe\xd0\x3c\x5d\x6e\xab\x21"
+ "\x31\x43\xf3\xd9\x6a\x5c\xa3\x1c"
+ "\x2b\x89\xf5\x68\x4e\x6c\x8e\x07"
+ "\x87\x3e\x5e\x97\x65\x17\xb4\x8f";
+ }
+ }
+ ASSERT(!memcmp(digest, expected_digest, SHA256_DIGEST_LENGTH));
+ return 0;
+}
+
+static const u8 expected_file_digest[SHA256_DIGEST_LENGTH] =
+ "\x09\xcb\xba\xee\xd2\xa0\x4c\x2d\xa2\x42\xc1\x0e\x15\x68\xd9\x6f"
+ "\x35\x8a\x16\xaa\x1e\xbe\x8c\xf0\x28\x61\x20\xc1\x3c\x93\x66\xd1";
+
+static int handle_descriptor(void *ctx, const void *descriptor, size_t size)
+{
+ u8 digest[SHA256_DIGEST_LENGTH];
+
+ metadata_callback_counts.descriptor++;
+ /* Test that the ctx argument is passed through correctly. */
+ ASSERT(ctx == (void *)1);
+
+ /* Test that the fs-verity descriptor is reported correctly. */
+ ASSERT(size == 256);
+ SHA256(descriptor, size, digest);
+ ASSERT(!memcmp(digest, expected_file_digest, SHA256_DIGEST_LENGTH));
+ return 0;
+}
+
+static const struct libfsverity_metadata_callbacks metadata_callbacks = {
+ .ctx = (void *)1, /* arbitrary value for testing purposes */
+ .merkle_tree_size = handle_merkle_tree_size,
+ .merkle_tree_block = handle_merkle_tree_block,
+ .descriptor = handle_descriptor,
+};
+
+/* Test that the libfsverity_metadata_callbacks work correctly. */
+static void test_metadata_callbacks(void)
+{
+ /*
+ * For a useful test, we want a file whose Merkle tree will have at
+ * least 2 levels (this one will have exactly 2). The contents of the
+ * file aren't too important.
+ */
+ struct libfsverity_merkle_tree_params params = {
+ .version = 1,
+ .hash_algorithm = FS_VERITY_HASH_ALG_SHA256,
+ .block_size = 1024,
+ .file_size = 100000,
+ .metadata_callbacks = &metadata_callbacks,
+ };
+ struct libfsverity_digest *d;
+
+ ASSERT(libfsverity_compute_digest(NULL, zeroes_read_fn,
+ &params, &d) == 0);
+
+ /* Test that the callbacks were called the correct number of times. */
+ ASSERT(metadata_callback_counts.merkle_tree_size == 1);
+ ASSERT(metadata_callback_counts.merkle_tree_block == 5);
+ ASSERT(metadata_callback_counts.descriptor == 1);
+
+ /* Test that the computed file digest is as expected. */
+ ASSERT(d->digest_algorithm == FS_VERITY_HASH_ALG_SHA256);
+ ASSERT(d->digest_size == SHA256_DIGEST_LENGTH);
+ ASSERT(!memcmp(d->digest, expected_file_digest, SHA256_DIGEST_LENGTH));
+
+ free(d);
+}
+
int main(int argc, char *argv[])
{
const bool update = (argc == 2 && !strcmp(argv[1], "--update"));
@@ -305,6 +437,7 @@ int main(int argc, char *argv[])
}
test_invalid_params();
+ test_metadata_callbacks();
printf("test_compute_digest passed\n");
return 0;
}
diff --git a/programs/utils.c b/programs/utils.c
index ce19b57..116eb95 100644
--- a/programs/utils.c
+++ b/programs/utils.c
@@ -13,10 +13,14 @@
#include <errno.h>
#include <fcntl.h>
+#include <inttypes.h>
#include <limits.h>
#include <stdarg.h>
#include <sys/stat.h>
#include <unistd.h>
+#ifdef _WIN32
+# include <windows.h>
+#endif
/* ========== Memory allocation ========== */
@@ -126,6 +130,26 @@ bool get_file_size(struct filedes *file, u64 *size_ret)
return true;
}
+bool preallocate_file(struct filedes *file, u64 size)
+{
+ int res;
+
+ if (size == 0)
+ return true;
+#ifdef _WIN32
+ /* Not exactly the same as posix_fallocate(), but good enough... */
+ res = _chsize_s(file->fd, size);
+#else
+ res = posix_fallocate(file->fd, 0, size);
+#endif
+ if (res != 0) {
+ error_msg_errno("preallocating %" PRIu64 "-byte file '%s'",
+ size, file->name);
+ return false;
+ }
+ return true;
+}
+
bool full_read(struct filedes *file, void *buf, size_t count)
{
while (count) {
@@ -160,6 +184,41 @@ bool full_write(struct filedes *file, const void *buf, size_t count)
return true;
}
+static int raw_pwrite(int fd, const void *buf, int count, u64 offset)
+{
+#ifdef _WIN32
+ HANDLE h = (HANDLE)_get_osfhandle(fd);
+ OVERLAPPED pos = { .Offset = offset, .OffsetHigh = offset >> 32 };
+ DWORD written = 0;
+
+ /* Not exactly the same as pwrite(), but good enough... */
+ if (!WriteFile(h, buf, count, &written, &pos)) {
+ errno = EIO;
+ return -1;
+ }
+ return written;
+#else
+ return pwrite(fd, buf, count, offset);
+#endif
+}
+
+bool full_pwrite(struct filedes *file, const void *buf, size_t count,
+ u64 offset)
+{
+ while (count) {
+ int n = raw_pwrite(file->fd, buf, min(count, INT_MAX), offset);
+
+ if (n < 0) {
+ error_msg_errno("writing to '%s'", file->name);
+ return false;
+ }
+ buf += n;
+ count -= n;
+ offset += n;
+ }
+ return true;
+}
+
bool filedes_close(struct filedes *file)
{
int res;
diff --git a/programs/utils.h b/programs/utils.h
index ab5005f..9a5c97a 100644
--- a/programs/utils.h
+++ b/programs/utils.h
@@ -40,8 +40,11 @@ struct filedes {
bool open_file(struct filedes *file, const char *filename, int flags, int mode);
bool get_file_size(struct filedes *file, u64 *size_ret);
+bool preallocate_file(struct filedes *file, u64 size);
bool full_read(struct filedes *file, void *buf, size_t count);
bool full_write(struct filedes *file, const void *buf, size_t count);
+bool full_pwrite(struct filedes *file, const void *buf, size_t count,
+ u64 offset);
bool filedes_close(struct filedes *file);
int read_callback(void *file, void *buf, size_t count);
diff --git a/scripts/do-release.sh b/scripts/do-release.sh
index 352fcf1..9f6bf73 100755
--- a/scripts/do-release.sh
+++ b/scripts/do-release.sh
@@ -25,11 +25,17 @@ git clean -fdx
major=$(echo "$VERS" | cut -d. -f1)
minor=$(echo "$VERS" | cut -d. -f2)
+month=$(LC_ALL=C date +%B)
+year=$(LC_ALL=C date +%Y)
+
sed -E -i -e "/FSVERITY_UTILS_MAJOR_VERSION/s/[0-9]+/$major/" \
-e "/FSVERITY_UTILS_MINOR_VERSION/s/[0-9]+/$minor/" \
include/libfsverity.h
sed -E -i "/Version:/s/[0-9]+\.[0-9]+/$VERS/" \
lib/libfsverity.pc.in
+sed -E -i -e "/^% /s/fsverity-utils v[0-9]+(\.[0-9]+)+/fsverity-utils v$VERS/" \
+ -e "/^% /s/[a-zA-Z]+ 2[0-9]{3}/$month $year/" \
+ man/*.[1-9].md
git commit -a --signoff --message="v$VERS"
git tag --sign "v$VERS" --message="$PKG"
diff --git a/scripts/run-tests.sh b/scripts/run-tests.sh
index 530fe34..9cdc7c1 100755
--- a/scripts/run-tests.sh
+++ b/scripts/run-tests.sh
@@ -101,7 +101,7 @@ log "Check that all files have license and copyright info"
list="$TMPDIR/filelist"
filter_license_info() {
# files to exclude from license and copyright info checks
- grep -E -v '(\.gitignore|LICENSE|NEWS|README|testdata|fsverity_uapi\.h|libfsverity\.pc\.in)'
+ grep -E -v '(\.gitignore|LICENSE|.*\.md|testdata|fsverity_uapi\.h|libfsverity\.pc\.in)'
}
git grep -L 'SPDX-License-Identifier: MIT' \
| filter_license_info > "$list" || true