From a21b0bc2c47adf0efc2d1b171b2b821fcf4de2a0 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Mon, 25 Nov 2024 09:46:30 +0100 Subject: [PATCH 1/3] mke2fs: modify the fallback path for copying data Right now we jump to the end as soon as we've found a method that works. This is a reasonable approach because it's the last operation in the function, but soon it won't be. Switch to a logically-equivalent alternative approach: keep trying until we find the approach that works, dropping the `goto out`. Now we can add code after this. Signed-off-by: Allison Karlitskaya --- misc/create_inode.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/misc/create_inode.c b/misc/create_inode.c index 4e292a2d2..f61d41163 100644 --- a/misc/create_inode.c +++ b/misc/create_inode.c @@ -599,18 +599,18 @@ static errcode_t copy_file(ext2_filsys fs, int fd, struct stat *statbuf, #if defined(SEEK_DATA) && defined(SEEK_HOLE) err = try_lseek_copy(fs, fd, statbuf, e2_file, buf, zerobuf); - if (err != EXT2_ET_UNIMPLEMENTED) - goto out; +#else + err = EXT2_ET_UNIMPLEMENTED; #endif #if defined(FS_IOC_FIEMAP) - err = try_fiemap_copy(fs, fd, e2_file, buf, zerobuf); - if (err != EXT2_ET_UNIMPLEMENTED) - goto out; + if (err == EXT2_ET_UNIMPLEMENTED) + err = try_fiemap_copy(fs, fd, e2_file, buf, zerobuf); #endif - err = copy_file_chunk(fs, fd, e2_file, 0, statbuf->st_size, buf, - zerobuf); + if (err == EXT2_ET_UNIMPLEMENTED) + err = copy_file_chunk(fs, fd, e2_file, 0, statbuf->st_size, buf, + zerobuf); out: ext2fs_free_mem(&zerobuf); ext2fs_free_mem(&buf); From ee0b7135c3fa4361308ddc9b46d737de028d5426 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Mon, 25 Nov 2024 09:46:30 +0100 Subject: [PATCH 2/3] mke2fs: factor out 'write_all()' functionality When writing data to an inode (with mke2fs -d) we need to do the typical loop to handle partial writes to make sure all of the data gets written. Move that code to its own function. This function also takes an offset parameter, which makes it feel a bit like pwrite() (except that it does modify the file offset). Signed-off-by: Allison Karlitskaya --- misc/create_inode.c | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/misc/create_inode.c b/misc/create_inode.c index f61d41163..15f0a2cfc 100644 --- a/misc/create_inode.c +++ b/misc/create_inode.c @@ -430,6 +430,26 @@ static ssize_t my_pread(int fd, void *buf, size_t count, off_t offset) } #endif /* !defined HAVE_PREAD64 && !defined HAVE_PREAD */ +static errcode_t write_all(ext2_file_t e2_file, ext2_off_t off, const char *buf, unsigned int n_bytes) { + errcode_t err = ext2fs_file_llseek(e2_file, off, EXT2_SEEK_SET, NULL); + if (err) + return err; + + const char *ptr = buf; + while (n_bytes) { + unsigned int written; + err = ext2fs_file_write(e2_file, ptr, n_bytes, &written); + if (err) + return err; + if (written == 0) + return EIO; + n_bytes -= written; + ptr += written; + } + + return 0; +} + static errcode_t copy_file_chunk(ext2_filsys fs, int fd, ext2_file_t e2_file, off_t start, off_t end, char *buf, char *zerobuf) @@ -460,22 +480,10 @@ static errcode_t copy_file_chunk(ext2_filsys fs, int fd, ext2_file_t e2_file, ptr += blen; continue; } - err = ext2fs_file_llseek(e2_file, off + bpos, - EXT2_SEEK_SET, NULL); + err = write_all(e2_file, off + bpos, ptr, blen); if (err) goto fail; - while (blen > 0) { - err = ext2fs_file_write(e2_file, ptr, blen, - &written); - if (err) - goto fail; - if (written == 0) { - err = EIO; - goto fail; - } - blen -= written; - ptr += written; - } + ptr += blen; } } fail: From 08a14a0bb8566a29086fda70c7fab0b319904144 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Mon, 25 Nov 2024 09:46:30 +0100 Subject: [PATCH 3/3] mke2fs: enable copying of fs-verity metadata When creating a filesystem with `mke2fs -O verity` and populating content via `-d`, check if that content is fs-verity enabled, and if it is, copy the fs-verity metadata from the host-native filesystem into the created filesystem. This currently doesn't work for reading from btrfs, which fails to implement the required ioctl(), but it will work between two ext4 filesystems. Closes: https://github.com/tytso/e2fsprogs/pull/201 Signed-off-by: Allison Karlitskaya --- configure | 6 ++ configure.ac | 1 + lib/config.h.in | 3 + misc/create_inode.c | 133 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 143 insertions(+) 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 15f0a2cfc..75399cf89 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 @@ -586,6 +591,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) { @@ -619,6 +745,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);