diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 7204779041..9da7778655 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include +#include #include #include @@ -591,4 +593,120 @@ TEST(copy_verify_linked) { assert_se(faccessat(tfd, "to_2", F_OK, AT_SYMLINK_NOFOLLOW) < 0 && errno == ENOENT); } +static bool enable_fsverity(int dirfd, const char *name, int algo, const char *salt, size_t salt_size) { + _cleanup_close_ int fd = -EBADF; + ASSERT_OK_ERRNO(fd = openat(dirfd, name, O_RDONLY | O_CLOEXEC | O_NOCTTY)); + + struct fsverity_enable_arg enable_arg = { + .version = 1, + .hash_algorithm = algo, + .block_size = 4096, + .salt_size = salt_size, + .salt_ptr = (uintptr_t) salt + }; + + if (ioctl(fd, FS_IOC_ENABLE_VERITY, &enable_arg) == 0) + return true; + + /* We might get this if we're on a filesystem without support. */ + ASSERT_TRUE(IN_SET(errno, EOPNOTSUPP, ENOTTY)); + return false; +} + +static struct fsverity_digest* alloc_fsverity_digest(size_t digest_size) { + struct fsverity_digest *digest = malloc(sizeof (struct fsverity_digest) + digest_size); + ASSERT_NOT_NULL(digest); + digest->digest_size = digest_size; + return digest; +} + +static bool measure_fsverity(int dirfd, const char *name, struct fsverity_digest *digest) { + _cleanup_close_ int fd = -EBADF; + ASSERT_OK_ERRNO(fd = openat(dirfd, name, O_RDONLY | O_CLOEXEC | O_NOCTTY)); + if (ioctl(fd, FS_IOC_MEASURE_VERITY, digest) == 0) + return true; + + /* Make sure that the error is caused by the file simply not having fs-verity. */ + ASSERT_TRUE(errno == ENODATA); + return false; +} + +static void assert_no_fsverity(int dirfd, const char *name) { + _cleanup_free_ struct fsverity_digest *digest = alloc_fsverity_digest(0); + ASSERT_FALSE(measure_fsverity(dirfd, name, digest)); +} + +static void assert_fsverity_eq(int dirfd_a, int dirfd_b, const char *name) { + _cleanup_free_ struct fsverity_digest *digest_a = alloc_fsverity_digest(64); + _cleanup_free_ struct fsverity_digest *digest_b = alloc_fsverity_digest(64); + + bool enabled_a = measure_fsverity(dirfd_a, name, digest_a); + bool enabled_b = measure_fsverity(dirfd_b, name, digest_b); + ASSERT_EQ(enabled_a, enabled_b); + if (enabled_a) { + ASSERT_EQ(digest_a->digest_algorithm, digest_b->digest_algorithm); + ASSERT_EQ(digest_a->digest_size, digest_b->digest_size); + ASSERT_EQ(memcmp(digest_a->digest, digest_b->digest, digest_a->digest_size), 0); + } +} + +TEST_RET(copy_with_verity) { + _cleanup_(rm_rf_physical_and_freep) char *srcp = NULL, *dstp = NULL, *badsrcp = NULL, *baddstp = NULL; + const char *files[] = { "disabled", "simple", "bigsha", "salty" }; + _cleanup_close_ int src = -EBADF, dst = -EBADF, badsrc = -EBADF, baddst = -EBADF; + + /* We're more likely to hit a filesystem with fs-verity enabled on /var/tmp than on /tmp (tmpfs) */ + ASSERT_OK(src = mkdtemp_open("/var/tmp/test-copy_file-src.XXXXXX", 0, &srcp)); + ASSERT_OK(dst = mkdtemp_open("/var/tmp/test-copy_file-dst.XXXXXX", 0, &dstp)); + + /* Populate some data to differentiate the files. */ + FOREACH_ELEMENT(file, files) + ASSERT_OK(write_string_file_at(src, *file, "src file", WRITE_STRING_FILE_CREATE)); + + /* Enable on some file using a range of options */ + if (!enable_fsverity(src, "simple", FS_VERITY_HASH_ALG_SHA256, NULL, 0)) + return log_tests_skipped_errno(errno, "/var/tmp: fs-verity is not supported here"); + ASSERT_TRUE(enable_fsverity(src, "bigsha", FS_VERITY_HASH_ALG_SHA512, NULL, 0)); + ASSERT_TRUE(enable_fsverity(src, "salty", FS_VERITY_HASH_ALG_SHA512, "edamame", 8)); + + /* Copy without fs-verity enabled and make sure nothing is set on the destination */ + ASSERT_OK(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REPLACE|COPY_MERGE, NULL, NULL)); + FOREACH_ELEMENT(file, files) + assert_no_fsverity(dst, *file); + + /* Copy *with* fs-verity enabled and make sure it works properly */ + int r = copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL); + if (r == -ENOTTY) + /* This can happen on some versions of btrfs, for example */ + return log_tests_skipped_errno(errno, "/var/tmp: fs-verity supported, but not reading metadata"); + ASSERT_OK(r); + FOREACH_ELEMENT(file, files) + assert_fsverity_eq(src, dst, *file); + + /* Now try to create files where we know fs-verity doesn't work: tmpfs */ + ASSERT_OK(badsrc = mkdtemp_open("/tmp/test-copy_file-src.XXXXXX", 0, &badsrcp)); + ASSERT_OK(baddst = mkdtemp_open("/tmp/test-copy_file-src.XXXXXX", 0, &baddstp)); + + /* Populate the source, same as before */ + FOREACH_ELEMENT(file, files) + ASSERT_OK(write_string_file_at(badsrc, *file, "src file", WRITE_STRING_FILE_CREATE)); + + /* Ensure the attempting to enable fs-verity here will fail */ + if (enable_fsverity(badsrc, "simple", FS_VERITY_HASH_ALG_SHA256, NULL, 0)) + return log_tests_skipped_errno(errno, "/tmp: fs-verity *is* unexpectedly supported here"); + + /* Copy from our non-verity filesystem into dst, requesting verity and making sure we notice that + * we failed to read verity from the source. */ + ASSERT_ERROR(copy_tree_at(badsrc, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY); + + /* Copy from our verity filesystem into our baddst, requesting verity and making sure we notice that + * we failed to set verity on the destination. */ + ASSERT_ERROR(copy_tree_at(src, ".", baddst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY); + + /* Of course this should fail too... */ + ASSERT_ERROR(copy_tree_at(badsrc, ".", baddst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY); + + return 0; +} + DEFINE_TEST_MAIN(LOG_DEBUG);