diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 01119ba..51c9c69 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -141,7 +141,7 @@ jobs: ./hacking/installdeps.sh meson setup build --werror meson compile -C build - meson test -C build --timeout-multiplier 10 + env CFS_TEST_ARCH_EMULATION=${{ matrix.arch }} meson test -C build --timeout-multiplier 10 - name: Upload log uses: actions/upload-artifact@v4 if: always() diff --git a/libcomposefs/lcfs-mount.c b/libcomposefs/lcfs-mount.c index 916d984..780d6d1 100644 --- a/libcomposefs/lcfs-mount.c +++ b/libcomposefs/lcfs-mount.c @@ -210,20 +210,15 @@ static errint_t lcfs_validate_mount_options(struct lcfs_mount_state_s *state) static errint_t lcfs_validate_verity_fd(struct lcfs_mount_state_s *state) { - char buf[sizeof(struct fsverity_digest) + MAX_DIGEST_SIZE]; - struct fsverity_digest *fsv = (struct fsverity_digest *)&buf; int res; if (state->expected_digest_len != 0) { - fsv->digest_size = MAX_DIGEST_SIZE; - res = ioctl(state->fd, FS_IOC_MEASURE_VERITY, fsv); - if (res == -1) { - if (errno == ENODATA || errno == EOPNOTSUPP || errno == ENOTTY) - return -ENOVERITY; - return -errno; + uint8_t found_digest[LCFS_DIGEST_SIZE]; + res = lcfs_fd_measure_fsverity(found_digest, state->fd); + if (res < 0) { + return res; } - if (fsv->digest_size != state->expected_digest_len || - memcmp(state->expected_digest, fsv->digest, fsv->digest_size) != 0) + if (memcmp(state->expected_digest, found_digest, LCFS_DIGEST_SIZE) != 0) return -EWRONGVERITY; } diff --git a/libcomposefs/lcfs-writer.c b/libcomposefs/lcfs-writer.c index f7560c6..f6e202e 100644 --- a/libcomposefs/lcfs-writer.c +++ b/libcomposefs/lcfs-writer.c @@ -564,42 +564,59 @@ int lcfs_compute_fsverity_from_fd(uint8_t *digest, int fd) return lcfs_compute_fsverity_from_content(digest, &_fd, fsverity_read_cb); } -// Given a file descriptor, first query the kernel for its fsverity digest. If -// it is not available in the kernel, perform an in-memory computation. The file -// position will always be reset to zero if needed. -int lcfs_fd_get_fsverity(uint8_t *digest, int fd) +// Given a file descriptor, query the kernel for its fsverity digest. It +// is an error if fsverity is not enabled. +int lcfs_fd_measure_fsverity(uint8_t *digest, int fd) { - char buf[sizeof(struct fsverity_digest) + MAX_DIGEST_SIZE]; - struct fsverity_digest *fsv = (struct fsverity_digest *)&buf; + union { + struct fsverity_digest fsv; + char buf[sizeof(struct fsverity_digest) + MAX_DIGEST_SIZE]; + } result; // First, ask the kernel if the file already has fsverity; if so we just return // that. - fsv->digest_size = MAX_DIGEST_SIZE; - int res = ioctl(fd, FS_IOC_MEASURE_VERITY, fsv); + result.fsv.digest_size = MAX_DIGEST_SIZE; + int res = ioctl(fd, FS_IOC_MEASURE_VERITY, &result); if (res == -1) { - // Under this condition, the file didn't have fsverity enabled or the - // kernel doesn't support it at all. We need to compute it in the current process. if (errno == ENODATA || errno == EOPNOTSUPP || errno == ENOTTY) { - // For consistency ensure we start from the beginning. We could - // avoid this by using pread() in the future. - if (lseek(fd, 0, SEEK_SET) < 0) - return -errno; - return lcfs_compute_fsverity_from_fd(digest, fd); + // Canonicalize errno + errno = ENOVERITY; } - // In this case, we found an unexpected error return -errno; } // The file has fsverity enabled, but with an unexpected different algorithm (e.g. sha512). // This is going to be a weird corner case. For now, we error out. - if (fsv->digest_size != LCFS_DIGEST_SIZE) { + if (result.fsv.digest_size != LCFS_DIGEST_SIZE) { return -EWRONGVERITY; } - memcpy(digest, buf + sizeof(struct fsverity_digest), LCFS_DIGEST_SIZE); + memcpy(digest, result.buf + sizeof(struct fsverity_digest), LCFS_DIGEST_SIZE); return 0; } +// Given a file descriptor, first query the kernel for its fsverity digest. If +// it is not available in the kernel, perform an in-memory computation. The file +// position will always be reset to zero if needed. +int lcfs_fd_get_fsverity(uint8_t *digest, int fd) +{ + int res = lcfs_fd_measure_fsverity(digest, fd); + if (res == 0) { + return 0; + } + // Under this condition, the file didn't have fsverity enabled or the + // kernel doesn't support it at all. We need to compute it in the current process. + if (errno == ENODATA || errno == EOPNOTSUPP || errno == ENOTTY) { + // For consistency ensure we start from the beginning. We could + // avoid this by using pread() in the future. + if (lseek(fd, 0, SEEK_SET) < 0) + return -errno; + return lcfs_compute_fsverity_from_fd(digest, fd); + } + // In this case, we found an unexpected error + return -errno; +} + int lcfs_compute_fsverity_from_data(uint8_t *digest, uint8_t *data, size_t data_len) { FsVerityContext *ctx; diff --git a/libcomposefs/lcfs-writer.h b/libcomposefs/lcfs-writer.h index 212164a..a68564f 100644 --- a/libcomposefs/lcfs-writer.h +++ b/libcomposefs/lcfs-writer.h @@ -182,6 +182,7 @@ LCFS_EXTERN int lcfs_compute_fsverity_from_content(uint8_t *digest, void *file, LCFS_EXTERN int lcfs_compute_fsverity_from_fd(uint8_t *digest, int fd); LCFS_EXTERN int lcfs_compute_fsverity_from_data(uint8_t *digest, uint8_t *data, size_t data_len); +LCFS_EXTERN int lcfs_fd_measure_fsverity(uint8_t *digest, int fd); LCFS_EXTERN int lcfs_fd_get_fsverity(uint8_t *digest, int fd); LCFS_EXTERN int lcfs_node_set_from_content(struct lcfs_node_s *node, int dirfd, diff --git a/tests/test-lcfs.c b/tests/test-lcfs.c index 9f1ac1d..527271d 100644 --- a/tests/test-lcfs.c +++ b/tests/test-lcfs.c @@ -2,7 +2,9 @@ #define _GNU_SOURCE #include "lcfs-writer.h" +#include "lcfs-mount.h" #include +#include #include static inline void lcfs_node_unrefp(struct lcfs_node_s **nodep) @@ -75,8 +77,26 @@ static void test_add_uninitialized_child(void) assert(errno == EINVAL); } +// Verifies that lcfs_fd_measure_fsverity fails on a fd without fsverity +static void test_no_verity(void) +{ + char buf[] = "/tmp/test-verity.XXXXXX"; + int tmpfd = mkstemp(buf); + assert(tmpfd > 0); + + uint8_t digest[LCFS_DIGEST_SIZE]; + int r = lcfs_fd_measure_fsverity(digest, tmpfd); + int errsv = errno; + assert(r != 0); + // We may get ENOSYS from qemu userspace emulation not implementing the ioctl + if (getenv("CFS_TEST_ARCH_EMULATION") == NULL) + assert(errsv == ENOVERITY); + close(tmpfd); +} + int main(int argc, char **argv) { test_basic(); + test_no_verity(); test_add_uninitialized_child(); }