Skip to content

Commit

Permalink
mke2fs: enable copying of fs-verity metadata
Browse files Browse the repository at this point in the history
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: tytso#201
Signed-off-by: Allison Karlitskaya <[email protected]>
  • Loading branch information
allisonkarlitskaya committed Nov 25, 2024
1 parent 9aaafa3 commit 4d047ec
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 0 deletions.
6 changes: 6 additions & 0 deletions configure
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions lib/config.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@
/* Define to 1 if you have the <linux/fsmap.h> header file. */
#undef HAVE_LINUX_FSMAP_H

/* Define to 1 if you have the <linux/fsverity.h> header file. */
#undef HAVE_LINUX_FSVERITY_H

/* Define to 1 if you have the <linux/loop.h> header file. */
#undef HAVE_LINUX_LOOP_H

Expand Down
133 changes: 133 additions & 0 deletions misc/create_inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <time.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <assert.h>
#include <unistd.h>
#include <limits.h> /* for PATH_MAX */
#include <dirent.h> /* for scandir() and alphasort() */
Expand All @@ -30,6 +31,10 @@
#ifdef HAVE_SYS_SYSMACROS_H
#include <sys/sysmacros.h>
#endif
#ifdef HAVE_LINUX_FSVERITY_H
#include <linux/fsverity.h>
#include <linux/fs.h>
#endif

#include <ext2fs/ext2fs.h>
#include <ext2fs/ext2_types.h>
Expand Down Expand Up @@ -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)
{
Expand Down Expand Up @@ -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);
Expand Down

0 comments on commit 4d047ec

Please sign in to comment.