Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] mke2fs: enable copying of fs-verity metadata #203

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
183 changes: 162 additions & 21 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 @@ -430,6 +435,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)
Expand Down Expand Up @@ -460,22 +485,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:
Expand Down Expand Up @@ -578,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);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't understand the loop here. I think we only need retry around the ioctl, since write_all handles the data loop.
We could just use glibc's TEMP_FAILURE_RETRY around the ioctl (any usage of that elsewhere in this code?)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This loop is indeed about repeating the ioctl(). It's a pread-style interface and we might have to call it more than once to get all of the data. For large files, the merkle tree data, in particular, can easily be larger than our read size.


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 All @@ -599,18 +733,25 @@ 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

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

err = copy_file_chunk(fs, fd, e2_file, 0, statbuf->st_size, buf,
zerobuf);
out:
ext2fs_free_mem(&zerobuf);
ext2fs_free_mem(&buf);
Expand Down