diff --git a/configure b/configure index e299be028..2f4b28082 100755 --- a/configure +++ b/configure @@ -11976,6 +11976,12 @@ if test "x$ac_cv_header_linux_fsmap_h" = xyes then : printf "%s\n" "#define HAVE_LINUX_FSMAP_H 1" >>confdefs.h +fi +ac_fn_c_check_header_compile "$LINENO" "linux/fsverity.h" "ac_cv_header_linux_fsverity_h" "$ac_includes_default" +if test "x$ac_cv_header_linux_fsverity_h" = xyes +then : + printf "%s\n" "#define HAVE_LINUX_FSVERITY_H 1" >>confdefs.h + fi ac_fn_c_check_header_compile "$LINENO" "linux/major.h" "ac_cv_header_linux_major_h" "$ac_includes_default" if test "x$ac_cv_header_linux_major_h" = xyes diff --git a/configure.ac b/configure.ac index 9a3dff1cf..b0d1965fc 100644 --- a/configure.ac +++ b/configure.ac @@ -1009,6 +1009,7 @@ AC_CHECK_HEADERS(m4_flatten([ linux/falloc.h linux/fd.h linux/fsmap.h + linux/fsverity.h linux/major.h linux/loop.h linux/types.h diff --git a/lib/config.h.in b/lib/config.h.in index 04cec72b8..f2e59d7f2 100644 --- a/lib/config.h.in +++ b/lib/config.h.in @@ -196,6 +196,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_FSMAP_H +/* Define to 1 if you have the header file. */ +#undef HAVE_LINUX_FSVERITY_H + /* Define to 1 if you have the header file. */ #undef HAVE_LINUX_LOOP_H diff --git a/misc/create_inode.c b/misc/create_inode.c index 2162b9789..4cd2baae6 100644 --- a/misc/create_inode.c +++ b/misc/create_inode.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include /* for PATH_MAX */ #include /* for scandir() and alphasort() */ @@ -30,6 +31,10 @@ #ifdef HAVE_SYS_SYSMACROS_H #include #endif +#ifdef HAVE_LINUX_FSVERITY_H +#include +#include +#endif #include #include @@ -585,6 +590,127 @@ static errcode_t try_fiemap_copy(ext2_filsys fs, int fd, ext2_file_t e2_file, } #endif /* FS_IOC_FIEMAP */ +#ifdef HAVE_LINUX_FSVERITY_H + +// add to n until (n == bias) mod blksz. blksz is power of 2. +static inline off_t round_up(off_t n, off_t blksz, off_t bias) +{ + return ((n - bias + (blksz - 1)) & ~(blksz - 1)) + bias; +} + +static errcode_t copy_fs_verity_data(ext2_file_t e2_file, ext2_off_t e2_offset, + int fd, uint64_t metadata_type, ext2_off_t *written) +{ + char buf[COPY_FILE_BUFLEN]; + int size; // return type of ioctl() + + *written = 0; + + do { + struct fsverity_read_metadata_arg arg = { + .metadata_type = metadata_type, + .buf_ptr = (uint64_t) buf, + .length = sizeof buf, + .offset = *written, + }; + + size = ioctl(fd, FS_IOC_READ_VERITY_METADATA, &arg); + if (size == -1 && errno == EINTR) + continue; + else if (size == -1) + return errno; + + errcode_t err = write_all(e2_file, e2_offset, buf, size); + if (err) + return err; + + e2_offset += size; + *written += size; + } while (size != 0); + + return 0; +} + +static errcode_t copy_fs_verity(ext2_filsys fs, int fd, + ext2_file_t e2_file, off_t st_size) +{ + /* We read the existing fs-verity data out of the host + * filesystem and write it verbatim into the file we're + * creating, as blocks following the end of the file. + * + * https://docs.kernel.org/filesystems/fsverity.html#fs-ioc-read-verity-metadata + * https://docs.kernel.org/filesystems/ext4/overview.html#verity-files + * + * 1. Zero-pad to the next 64KiB boundary (sparsely) + */ + off_t offset = round_up(st_size, 65536, 0); + + /* 2. Merkel tree data: might be empty (for empty files) */ + ext2_off_t written; + errcode_t err = copy_fs_verity_data(e2_file, offset, + fd, FS_VERITY_METADATA_TYPE_MERKLE_TREE, &written); + if (err) { + /* These three errors are if the file/filesystem/kernel + * doesn't have fs-verity. If those happened before we + * wrote anything, then we already did the right thing. + */ + if ((err == ENODATA || err == ENOTTY || err == ENOTSUP) && !written) { + err = 0; + } + return err; + } + offset += written; + + /* 3. Zero-padding to the next filesystem block boundary. */ + offset = round_up(offset, fs->blocksize, 0); + + /* 4. The verity descriptor (we don't handle signature blobs). */ + err = copy_fs_verity_data(e2_file, offset, + fd, FS_VERITY_METADATA_TYPE_DESCRIPTOR, &written); + offset += written; + + /* 5. Zero-padding to the next offset that is 4 bytes before a + * filesystem block boundary. */ + offset = round_up(offset, fs->blocksize, -4); + + /* 6. The size of the verity descriptor in bytes, as a 4-byte + * little endian integer. */ + uint32_t dsize = written; + uint8_t size_le[4] = { dsize, dsize >> 8, dsize >> 16, dsize >> 24 }; + err = write_all(e2_file, offset, (const char *) size_le, sizeof size_le); + if (err) + return err; + + /* Verity inodes have EXT4_VERITY_FL set, and they must use + * extents, i.e. EXT4_EXTENTS_FL must be set and + * EXT4_INLINE_DATA_FL must be clear. + */ + + // Patch up the inode + // TODO: This is so janky... + ext2_ino_t ino = ext2fs_file_get_inode_num(e2_file); + struct ext2_inode inode; + err = ext2fs_read_inode(fs, ino, &inode); + if (err) + return err; + assert(!(inode.i_flags & EXT4_INLINE_DATA_FL)); + assert(inode.i_flags & EXT4_EXTENTS_FL); + + // The st_size of the inode should be the original file size + ext2fs_inode_size_set(fs, &inode, st_size); + + /* ...after hours of headscratching and staring at the output of + * `debugfs -R inode_dump`... + * + * TODO: please document this somewhere + */ + inode.osd1.linux1.l_i_version = 2; // lolwat + + inode.i_flags |= EXT4_VERITY_FL; + return ext2fs_write_inode(fs, ino, &inode); +} +#endif + static errcode_t copy_file(ext2_filsys fs, int fd, struct stat *statbuf, ext2_ino_t ino) { @@ -618,6 +744,13 @@ static errcode_t copy_file(ext2_filsys fs, int fd, struct stat *statbuf, if (err == EXT2_ET_UNIMPLEMENTED) err = copy_file_chunk(fs, fd, e2_file, 0, statbuf->st_size, buf, zerobuf); + +#ifdef HAVE_LINUX_FSVERITY_H + if (!err && ext2fs_has_feature_verity(fs->super)) { + err = copy_fs_verity(fs, fd, e2_file, statbuf->st_size); + } +#endif + out: ext2fs_free_mem(&zerobuf); ext2fs_free_mem(&buf);