test: add a testcase for copy_tree() and fs-verity

This commit is contained in:
Allison Karlitskaya
2025-05-27 09:25:57 +02:00
parent 56a279a7f7
commit fdc5f9e844
+118
View File
@@ -1,6 +1,8 @@
/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <linux/fsverity.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <sys/xattr.h>
#include <unistd.h>
@@ -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);