From: Darrick J. Wong <djwong@xxxxxxxxxx> If phase 5 encounters a fsverity file, read its metadata to see if we encounter any errors. The consistency of the file data vs. the hashes in the merkle tree are checked during the media scan. Signed-off-by: Darrick J. Wong <djwong@xxxxxxxxxx> --- scrub/Makefile | 4 + scrub/inodes.h | 22 +++++++ scrub/phase5.c | 182 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 208 insertions(+) diff --git a/scrub/Makefile b/scrub/Makefile index 885b43e9948d..ad010a05249f 100644 --- a/scrub/Makefile +++ b/scrub/Makefile @@ -109,6 +109,10 @@ CFILES += unicrash.c LCFLAGS += -DHAVE_LIBICU $(LIBICU_CFLAGS) endif +ifeq ($(HAVE_FSVERITY_DESCR),yes) +LCFLAGS += -DHAVE_FSVERITY_DESCR +endif + # Automatically trigger a media scan once per month XFS_SCRUB_ALL_AUTO_MEDIA_SCAN_INTERVAL=1mo diff --git a/scrub/inodes.h b/scrub/inodes.h index 7a0b275e575e..aab2d721fe02 100644 --- a/scrub/inodes.h +++ b/scrub/inodes.h @@ -25,4 +25,26 @@ int scrub_scan_all_inodes(struct scrub_ctx *ctx, scrub_inode_iter_fn fn, int scrub_open_handle(struct xfs_handle *handle); +/* + * Might this be a file that's missing its fsverity metadata? When this is the + * case, an open() call will return ENODATA. + */ +static inline bool fsverity_meta_is_missing(int error) +{ + switch (error) { + case ENODATA: + case EMSGSIZE: + case EINVAL: + case EFSCORRUPTED: + case EFBIG: + /* + * The nonzero errno codes above are the error codes that can + * be returned from fsverity on metadata validation errors. + */ + return true; + } + + return false; +} + #endif /* XFS_SCRUB_INODES_H_ */ diff --git a/scrub/phase5.c b/scrub/phase5.c index 6fd3c6982704..6f157fa3570c 100644 --- a/scrub/phase5.c +++ b/scrub/phase5.c @@ -28,6 +28,7 @@ #include "descr.h" #include "unicrash.h" #include "repair.h" +#include "atomic.h" /* Phase 5: Full inode scans and check directory connectivity. */ @@ -359,6 +360,183 @@ check_dir_connection( return EADDRNOTAVAIL; } +#ifdef HAVE_FSVERITY_DESCR +struct fsverity_object { + const char *name; + int type; +}; + +struct fsverity_object fsverity_objects[] = { + { + .name = "descriptor", + .type = FS_VERITY_METADATA_TYPE_DESCRIPTOR, + }, + { + .name = "merkle tree", + .type = FS_VERITY_METADATA_TYPE_MERKLE_TREE, + }, + { + .name = "signature", + .type = FS_VERITY_METADATA_TYPE_SIGNATURE, + }, +}; + +static void *fsverity_buf; +#define FSVERITY_BUFSIZE (32768) + +static inline void * +get_fsverity_buf(void) +{ + static pthread_mutex_t buf_lock = PTHREAD_MUTEX_INITIALIZER; + void *new_buf; + + if (!fsverity_buf) { + new_buf = malloc(FSVERITY_BUFSIZE); + if (!new_buf) + return NULL; + + pthread_mutex_lock(&buf_lock); + if (!fsverity_buf) { + fsverity_buf = new_buf; + new_buf = NULL; + } + pthread_mutex_unlock(&buf_lock); + if (new_buf) + free(new_buf); + } + + return fsverity_buf; +} + +static int +read_fsverity_object( + struct scrub_ctx *ctx, + struct descr *dsc, + int fd, + const struct fsverity_object *verity_obj) +{ + struct fsverity_read_metadata_arg arg = { + .buf_ptr = (uintptr_t)get_fsverity_buf(), + .metadata_type = verity_obj->type, + .length = FSVERITY_BUFSIZE, + }; + int ret; + + if (!arg.buf_ptr) { + str_liberror(ctx, ENOMEM, descr_render(dsc)); + return ENOMEM; + } + + do { + ret = ioctl(fd, FS_IOC_READ_VERITY_METADATA, &arg); + if (ret < 0) { + ret = errno; + switch (ret) { + case ENODATA: + /* No fsverity metadata found. We're done. */ + return 0; + case ENOTTY: + case EOPNOTSUPP: + /* not a verity file or object doesn't exist */ + str_error(ctx, descr_render(dsc), + _("fsverity %s not supported at data offset %llu length %llu?"), + verity_obj->name, + arg.offset, arg.length); + return ret; + default: + /* some other error */ + str_error(ctx, descr_render(dsc), + _("fsverity %s read error at data offset %llu length %llu."), + verity_obj->name, + arg.offset, arg.length); + return ret; + } + } + arg.offset += ret; + } while (ret > 0); + + return 0; +} + +/* Read all the fsverity metadata. */ +static int +check_fsverity_metadata( + struct scrub_ctx *ctx, + struct descr *dsc, + int fd) +{ + unsigned int i; + int error; + + for (i = 0; i < ARRAY_SIZE(fsverity_objects); i++) { + error = read_fsverity_object(ctx, dsc, fd, + &fsverity_objects[i]); + if (error) + return error; + } + + return 0; +} + +/* Open this verity file and check its merkle tree and verity descriptor. */ +static int +check_verity_file( + struct scrub_ctx *ctx, + struct xfs_handle *handle, + struct xfs_bulkstat *bstat, + struct descr *dsc, + int *fdp) +{ + int error; + + /* Only regular files can have fsverity set. */ + if (!S_ISREG(bstat->bs_mode)) { + str_error(ctx, descr_render(dsc), + _("fsverity cannot be set on a regular file.")); + return 0; + } + + *fdp = scrub_open_handle(handle); + if (*fdp >= 0) + return check_fsverity_metadata(ctx, dsc, *fdp); + + /* Handle is stale, try again. */ + if (errno == ESTALE) + return ESTALE; + + /* + * If the fsverity metadata is missing, inform the user and move on to + * the next file. + */ + if (fsverity_meta_is_missing(errno)) { + str_error(ctx, descr_render(dsc), + _("fsverity metadata missing.")); + return 0; + } + + /* Some other runtime error. */ + error = errno; + str_errno(ctx, descr_render(dsc)); + return error; +} +#else +static int +check_verity_file( + struct scrub_ctx *ctx, + struct xfs_handle *handle, + struct xfs_bulkstat *bstat, + struct descr *dsc, + int *fdp) +{ + static atomic_t warned; + + if (!atomic_inc_return(&warned)) + str_warn(ctx, descr_render(dsc), + _("fsverity metadata checking not supported\n")); + return 0; +} +#endif /* HAVE_FSVERITY_DESCR */ + /* * Verify the connectivity of the directory tree. * We know that the kernel's open-by-handle function will try to reconnect @@ -422,6 +600,10 @@ check_inode_names( error = check_dirent_names(ctx, &dsc, &fd, bstat); if (error) goto err_fd; + } else if (bstat->bs_xflags & FS_XFLAG_VERITY) { + error = check_verity_file(ctx, handle, bstat, &dsc, &fd); + if (error) + goto err_fd; } progress_add(1);