mirror of
https://github.com/morgan9e/systemd
synced 2026-04-14 00:14:32 +09:00
repart: Copy fs-verity status for CopyFiles= (#35401)
When populating a filesytem with CopyFiles=, we first copy the files to a temporary directory. Make sure we use the (new) COPY_FS_VERITY flag when doing that copy so that the `mkfs` that we invoke can see the files with fs-verity enabled. Closes #35352
This commit is contained in:
@@ -426,18 +426,40 @@
|
||||
<varlistentry>
|
||||
<term><varname>CopyFiles=</varname></term>
|
||||
|
||||
<listitem><para>Takes a pair of colon separated absolute file system paths. The first path refers to
|
||||
a source file or directory on the host, the second path refers to a target in the file system of the
|
||||
newly created partition and formatted file system. This setting may be used to copy files or
|
||||
directories from the host into the file system that is created due to the <varname>Format=</varname>
|
||||
option. If <varname>CopyFiles=</varname> is used without <varname>Format=</varname> specified
|
||||
explicitly, <literal>Format=</literal> with a suitable default is implied (currently
|
||||
<literal>vfat</literal> for <literal>ESP</literal> and <literal>XBOOTLDR</literal> partitions, and
|
||||
<literal>ext4</literal> otherwise, but this may change in the future). This option may be used
|
||||
multiple times to copy multiple files or directories from host into the newly formatted file system.
|
||||
The colon and second path may be omitted in which case the source path is also used as the target
|
||||
path (relative to the root of the newly created file system). If the source path refers to a
|
||||
directory it is copied recursively.</para>
|
||||
<listitem><para>Takes a colon-separated triplet in the form
|
||||
<literal><varname>source</varname>[:<varname>target</varname>[:<varname>options</varname>]]</literal>.
|
||||
<varname>source</varname> is an absolute path which refers to a source file or directory on the host.
|
||||
<varname>target</varname> is an absolute path in the file system of the newly created partition and
|
||||
formatted file system. <varname>options</varname> is a comma-separated list of options where each
|
||||
option is in the form <literal><varname>key</varname>[=<varname>value</varname>]</literal>.</para>
|
||||
|
||||
<para>This setting may be used to copy files or directories from the host into the file system that
|
||||
is created due to the <varname>Format=</varname> option. If <varname>CopyFiles=</varname> is used
|
||||
without <varname>Format=</varname> specified explicitly, <literal>Format=</literal> with a suitable
|
||||
default is implied (currently <literal>vfat</literal> for <literal>ESP</literal> and
|
||||
<literal>XBOOTLDR</literal> partitions, and <literal>ext4</literal> otherwise, but this may change in
|
||||
the future). This option may be used multiple times to copy multiple files or directories from host
|
||||
into the newly formatted file system.</para>
|
||||
|
||||
<para>The <varname>target</varname> path may be omitted in which case the <varname>source</varname>
|
||||
path is also used as the target path (relative to the root of the newly created file system). If
|
||||
the source path refers to a directory it is copied recursively.</para>
|
||||
|
||||
<para>The <varname>options</varname> may contain the following values:</para>
|
||||
|
||||
<variablelist>
|
||||
<varlistentry>
|
||||
<term><varname>fsverity=</varname></term>
|
||||
<listitem><para>May be set to the value <literal>off</literal> (the default if the option is not
|
||||
present) or <literal>copy</literal>. If set to <literal>off</literal> then no files copied into
|
||||
the filesystem from this source will have fs-verity enabled. If set to <literal>copy</literal>
|
||||
then the fs-verity information for each file will be copied from the corresponding source
|
||||
file.</para>
|
||||
|
||||
<xi:include href="version-info.xml" xpointer="v258"/></listitem>
|
||||
|
||||
</varlistentry>
|
||||
</variablelist>
|
||||
|
||||
<para>This option has no effect if the partition already exists: it cannot be used to copy additional
|
||||
files into an existing partition, it may only be used to populate a file system created anew.</para>
|
||||
|
||||
96
src/basic/include/linux/fsverity.h
Normal file
96
src/basic/include/linux/fsverity.h
Normal file
@@ -0,0 +1,96 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
|
||||
/*
|
||||
* fs-verity user API
|
||||
*
|
||||
* These ioctls can be used on filesystems that support fs-verity. See the
|
||||
* "User API" section of Documentation/filesystems/fsverity.rst.
|
||||
*
|
||||
* Copyright 2019 Google LLC
|
||||
*/
|
||||
#ifndef _LINUX_FSVERITY_H
|
||||
#define _LINUX_FSVERITY_H
|
||||
|
||||
#include <linux/ioctl.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
#define FS_VERITY_HASH_ALG_SHA256 1
|
||||
#define FS_VERITY_HASH_ALG_SHA512 2
|
||||
|
||||
struct fsverity_enable_arg {
|
||||
__u32 version;
|
||||
__u32 hash_algorithm;
|
||||
__u32 block_size;
|
||||
__u32 salt_size;
|
||||
__u64 salt_ptr;
|
||||
__u32 sig_size;
|
||||
__u32 __reserved1;
|
||||
__u64 sig_ptr;
|
||||
__u64 __reserved2[11];
|
||||
};
|
||||
|
||||
struct fsverity_digest {
|
||||
__u16 digest_algorithm;
|
||||
__u16 digest_size; /* input/output */
|
||||
__u8 digest[];
|
||||
};
|
||||
|
||||
/*
|
||||
* Struct containing a file's Merkle tree properties. The fs-verity file digest
|
||||
* is the hash of this struct. A userspace program needs this struct only if it
|
||||
* needs to compute fs-verity file digests itself, e.g. in order to sign files.
|
||||
* It isn't needed just to enable fs-verity on a file.
|
||||
*
|
||||
* Note: when computing the file digest, 'sig_size' and 'signature' must be left
|
||||
* zero and empty, respectively. These fields are present only because some
|
||||
* filesystems reuse this struct as part of their on-disk format.
|
||||
*/
|
||||
struct fsverity_descriptor {
|
||||
__u8 version; /* must be 1 */
|
||||
__u8 hash_algorithm; /* Merkle tree hash algorithm */
|
||||
__u8 log_blocksize; /* log2 of size of data and tree blocks */
|
||||
__u8 salt_size; /* size of salt in bytes; 0 if none */
|
||||
__le32 __reserved_0x04; /* must be 0 */
|
||||
__le64 data_size; /* size of file the Merkle tree is built over */
|
||||
__u8 root_hash[64]; /* Merkle tree root hash */
|
||||
__u8 salt[32]; /* salt prepended to each hashed block */
|
||||
__u8 __reserved[144]; /* must be 0's */
|
||||
};
|
||||
|
||||
/*
|
||||
* Format in which fs-verity file digests are signed in built-in signatures.
|
||||
* This is the same as 'struct fsverity_digest', except here some magic bytes
|
||||
* are prepended to provide some context about what is being signed in case the
|
||||
* same key is used for non-fsverity purposes, and here the fields have fixed
|
||||
* endianness.
|
||||
*
|
||||
* This struct is specific to the built-in signature verification support, which
|
||||
* is optional. fs-verity users may also verify signatures in userspace, in
|
||||
* which case userspace is responsible for deciding on what bytes are signed.
|
||||
* This struct may still be used, but it doesn't have to be. For example,
|
||||
* userspace could instead use a string like "sha256:$digest_as_hex_string".
|
||||
*/
|
||||
struct fsverity_formatted_digest {
|
||||
char magic[8]; /* must be "FSVerity" */
|
||||
__le16 digest_algorithm;
|
||||
__le16 digest_size;
|
||||
__u8 digest[];
|
||||
};
|
||||
|
||||
#define FS_VERITY_METADATA_TYPE_MERKLE_TREE 1
|
||||
#define FS_VERITY_METADATA_TYPE_DESCRIPTOR 2
|
||||
#define FS_VERITY_METADATA_TYPE_SIGNATURE 3
|
||||
|
||||
struct fsverity_read_metadata_arg {
|
||||
__u64 metadata_type;
|
||||
__u64 offset;
|
||||
__u64 length;
|
||||
__u64 buf_ptr;
|
||||
__u64 __reserved;
|
||||
};
|
||||
|
||||
#define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg)
|
||||
#define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest)
|
||||
#define FS_IOC_READ_VERITY_METADATA \
|
||||
_IOWR('f', 135, struct fsverity_read_metadata_arg)
|
||||
|
||||
#endif /* _LINUX_FSVERITY_H */
|
||||
@@ -70,8 +70,7 @@ static int run(int argc, char *argv[]) {
|
||||
label,
|
||||
/* root = */ NULL,
|
||||
uuid,
|
||||
/* discard = */ true,
|
||||
/* quiet = */ true,
|
||||
MKFS_DISCARD | MKFS_QUIET,
|
||||
/* sector_size = */ 0,
|
||||
/* compression = */ NULL,
|
||||
/* compression_level = */ NULL,
|
||||
|
||||
@@ -2382,8 +2382,7 @@ int home_create_luks(
|
||||
user_record_user_name_and_realm(h),
|
||||
/* root = */ NULL,
|
||||
fs_uuid,
|
||||
user_record_luks_discard(h),
|
||||
/* quiet = */ true,
|
||||
(user_record_luks_discard(h) ? MKFS_DISCARD : 0) | MKFS_QUIET,
|
||||
/* sector_size = */ 0,
|
||||
/* compression = */ NULL,
|
||||
/* compression_level= */ NULL,
|
||||
|
||||
@@ -277,6 +277,23 @@ static PartitionEncryptedVolume* partition_encrypted_volume_free(PartitionEncryp
|
||||
return mfree(c);
|
||||
}
|
||||
|
||||
typedef struct CopyFiles {
|
||||
char *source;
|
||||
char *target;
|
||||
CopyFlags flags;
|
||||
} CopyFiles;
|
||||
|
||||
static void copy_files_free_many(CopyFiles *f, size_t n) {
|
||||
assert(f || n == 0);
|
||||
|
||||
FOREACH_ARRAY(i, f, n) {
|
||||
free(i->source);
|
||||
free(i->target);
|
||||
}
|
||||
|
||||
free(f);
|
||||
}
|
||||
|
||||
typedef enum SubvolumeFlags {
|
||||
SUBVOLUME_RO = 1 << 0,
|
||||
_SUBVOLUME_FLAGS_MASK = SUBVOLUME_RO,
|
||||
@@ -377,7 +394,6 @@ typedef struct Partition {
|
||||
uint64_t copy_blocks_done;
|
||||
|
||||
char *format;
|
||||
char **copy_files;
|
||||
char **exclude_files_source;
|
||||
char **exclude_files_target;
|
||||
char **make_directories;
|
||||
@@ -394,6 +410,8 @@ typedef struct Partition {
|
||||
char *compression_level;
|
||||
|
||||
int add_validatefs;
|
||||
CopyFiles *copy_files;
|
||||
size_t n_copy_files;
|
||||
|
||||
uint64_t gpt_flags;
|
||||
int no_auto;
|
||||
@@ -633,7 +651,6 @@ static Partition* partition_free(Partition *p) {
|
||||
safe_close(p->copy_blocks_fd);
|
||||
|
||||
free(p->format);
|
||||
strv_free(p->copy_files);
|
||||
strv_free(p->exclude_files_source);
|
||||
strv_free(p->exclude_files_target);
|
||||
strv_free(p->make_directories);
|
||||
@@ -644,11 +661,12 @@ static Partition* partition_free(Partition *p) {
|
||||
free(p->compression);
|
||||
free(p->compression_level);
|
||||
|
||||
copy_files_free_many(p->copy_files, p->n_copy_files);
|
||||
|
||||
iovec_done(&p->roothash);
|
||||
|
||||
free(p->split_name_format);
|
||||
unlink_and_free(p->split_path);
|
||||
|
||||
partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints);
|
||||
p->mountpoints = NULL;
|
||||
p->n_mountpoints = 0;
|
||||
@@ -674,7 +692,6 @@ static void partition_foreignize(Partition *p) {
|
||||
p->copy_blocks_root = NULL;
|
||||
|
||||
p->format = mfree(p->format);
|
||||
p->copy_files = strv_free(p->copy_files);
|
||||
p->exclude_files_source = strv_free(p->exclude_files_source);
|
||||
p->exclude_files_target = strv_free(p->exclude_files_target);
|
||||
p->make_directories = strv_free(p->make_directories);
|
||||
@@ -685,6 +702,10 @@ static void partition_foreignize(Partition *p) {
|
||||
p->compression = mfree(p->compression);
|
||||
p->compression_level = mfree(p->compression_level);
|
||||
|
||||
copy_files_free_many(p->copy_files, p->n_copy_files);
|
||||
p->copy_files = NULL;
|
||||
p->n_copy_files = 0;
|
||||
|
||||
p->priority = 0;
|
||||
p->weight = 1000;
|
||||
p->padding_weight = 0;
|
||||
@@ -1749,9 +1770,9 @@ static int config_parse_copy_files(
|
||||
void *data,
|
||||
void *userdata) {
|
||||
|
||||
_cleanup_free_ char *source = NULL, *buffer = NULL, *resolved_source = NULL, *resolved_target = NULL;
|
||||
_cleanup_free_ char *source = NULL, *buffer = NULL, *resolved_source = NULL, *resolved_target = NULL, *options = NULL;
|
||||
Partition *partition = ASSERT_PTR(data);
|
||||
const char *p = rvalue, *target;
|
||||
char ***copy_files = ASSERT_PTR(data);
|
||||
int r;
|
||||
|
||||
assert(rvalue);
|
||||
@@ -1772,9 +1793,38 @@ static int config_parse_copy_files(
|
||||
else
|
||||
target = buffer;
|
||||
|
||||
r = extract_first_word(&p, &options, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract options: %s", rvalue);
|
||||
|
||||
if (!isempty(p))
|
||||
return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Too many arguments: %s", rvalue);
|
||||
|
||||
CopyFlags flags = COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE|COPY_RESTORE_DIRECTORY_TIMESTAMPS;
|
||||
for (const char *opts = options;;) {
|
||||
_cleanup_free_ char *word = NULL;
|
||||
const char *val;
|
||||
|
||||
r = extract_first_word(&opts, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS);
|
||||
if (r < 0)
|
||||
return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CopyFile options: %s", options);
|
||||
if (r == 0)
|
||||
break;
|
||||
|
||||
if (isempty(word))
|
||||
continue;
|
||||
|
||||
if ((val = startswith(word, "fsverity="))) {
|
||||
if (streq(val, "copy"))
|
||||
flags |= COPY_PRESERVE_FS_VERITY;
|
||||
else if (streq(val, "off"))
|
||||
flags &= ~COPY_PRESERVE_FS_VERITY;
|
||||
else
|
||||
log_syntax(unit, LOG_WARNING, filename, line, 0, "fsverity= expects either 'off' or 'copy'.");
|
||||
} else
|
||||
log_syntax(unit, LOG_WARNING, filename, line, 0, "Encountered unknown option '%s', ignoring.", word);
|
||||
}
|
||||
|
||||
r = specifier_printf(source, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_source);
|
||||
if (r < 0) {
|
||||
log_syntax(unit, LOG_WARNING, filename, line, r,
|
||||
@@ -1797,10 +1847,15 @@ static int config_parse_copy_files(
|
||||
if (r < 0)
|
||||
return 0;
|
||||
|
||||
r = strv_consume_pair(copy_files, TAKE_PTR(resolved_source), TAKE_PTR(resolved_target));
|
||||
if (r < 0)
|
||||
if (!GREEDY_REALLOC(partition->copy_files, partition->n_copy_files + 1))
|
||||
return log_oom();
|
||||
|
||||
partition->copy_files[partition->n_copy_files++] = (CopyFiles) {
|
||||
.source = TAKE_PTR(resolved_source),
|
||||
.target = TAKE_PTR(resolved_target),
|
||||
.flags = flags,
|
||||
};
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -2358,13 +2413,31 @@ static bool partition_needs_populate(const Partition *p) {
|
||||
assert(p);
|
||||
assert(!p->supplement_for || !p->suppressing); /* Avoid infinite recursion */
|
||||
|
||||
return !strv_isempty(p->copy_files) ||
|
||||
return p->n_copy_files > 0 ||
|
||||
!strv_isempty(p->make_directories) ||
|
||||
!strv_isempty(p->make_symlinks) ||
|
||||
partition_add_validatefs(p) ||
|
||||
(p->suppressing && partition_needs_populate(p->suppressing));
|
||||
}
|
||||
|
||||
static MakeFileSystemFlags partition_mkfs_flags(const Partition *p) {
|
||||
MakeFileSystemFlags flags = 0;
|
||||
|
||||
if (arg_discard)
|
||||
flags |= MKFS_DISCARD;
|
||||
|
||||
if (streq(p->format, "erofs") && !DEBUG_LOGGING)
|
||||
flags |= MKFS_QUIET;
|
||||
|
||||
FOREACH_ARRAY(cf, p->copy_files, p->n_copy_files)
|
||||
if (cf->flags & COPY_PRESERVE_FS_VERITY) {
|
||||
flags |= MKFS_FS_VERITY;
|
||||
break;
|
||||
}
|
||||
|
||||
return flags;
|
||||
}
|
||||
|
||||
static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) {
|
||||
|
||||
ConfigTableItem table[] = {
|
||||
@@ -2381,7 +2454,7 @@ static int partition_read_definition(Partition *p, const char *path, const char
|
||||
{ "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset },
|
||||
{ "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p },
|
||||
{ "Partition", "Format", config_parse_fstype, 0, &p->format },
|
||||
{ "Partition", "CopyFiles", config_parse_copy_files, 0, &p->copy_files },
|
||||
{ "Partition", "CopyFiles", config_parse_copy_files, 0, p },
|
||||
{ "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source },
|
||||
{ "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target },
|
||||
{ "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories },
|
||||
@@ -5643,7 +5716,6 @@ static int file_is_denylisted(const char *source, Hashmap *denylist) {
|
||||
|
||||
static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
_cleanup_strv_free_ char **subvolumes = NULL;
|
||||
_cleanup_free_ char **override_copy_files = NULL;
|
||||
int r;
|
||||
|
||||
assert(p);
|
||||
@@ -5653,31 +5725,36 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
_cleanup_free_ CopyFiles *copy_files = newdup(CopyFiles, p->copy_files, p->n_copy_files);
|
||||
if (!copy_files)
|
||||
return log_oom();
|
||||
|
||||
size_t n_copy_files = p->n_copy_files;
|
||||
if (p->suppressing) {
|
||||
r = shallow_join_strv(&override_copy_files, p->copy_files, p->suppressing->copy_files);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (!GREEDY_REALLOC_APPEND(copy_files, n_copy_files,
|
||||
p->suppressing->copy_files, p->suppressing->n_copy_files))
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
/* copy_tree_at() automatically copies the permissions of source directories to target directories if
|
||||
* it created them. However, the root directory is created by us, so we have to manually take care
|
||||
* that it is initialized. We use the first source directory targeting "/" as the metadata source for
|
||||
* the root directory. */
|
||||
STRV_FOREACH_PAIR(source, target, override_copy_files ?: p->copy_files) {
|
||||
FOREACH_ARRAY(line, copy_files, n_copy_files) {
|
||||
_cleanup_close_ int rfd = -EBADF, sfd = -EBADF;
|
||||
|
||||
if (!path_equal(*target, "/"))
|
||||
if (!path_equal(line->target, "/"))
|
||||
continue;
|
||||
|
||||
rfd = open(root, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW);
|
||||
if (rfd < 0)
|
||||
return -errno;
|
||||
|
||||
sfd = chase_and_open(*source, arg_copy_source, CHASE_PREFIX_ROOT, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL);
|
||||
sfd = chase_and_open(line->source, arg_copy_source, CHASE_PREFIX_ROOT, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL);
|
||||
if (sfd == -ENOTDIR)
|
||||
continue;
|
||||
if (sfd < 0)
|
||||
return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), *source);
|
||||
return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), line->source);
|
||||
|
||||
(void) copy_xattr(sfd, NULL, rfd, NULL, COPY_ALL_XATTRS);
|
||||
(void) copy_access(sfd, rfd);
|
||||
@@ -5686,50 +5763,50 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
break;
|
||||
}
|
||||
|
||||
STRV_FOREACH_PAIR(source, target, override_copy_files ?: p->copy_files) {
|
||||
FOREACH_ARRAY(line, copy_files, n_copy_files) {
|
||||
_cleanup_hashmap_free_ Hashmap *denylist = NULL;
|
||||
_cleanup_set_free_ Set *subvolumes_by_source_inode = NULL;
|
||||
_cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF;
|
||||
usec_t ts = epoch_or_infinity();
|
||||
|
||||
r = make_copy_files_denylist(context, p, *source, *target, &denylist);
|
||||
r = make_copy_files_denylist(context, p, line->source, line->target, &denylist);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0)
|
||||
continue;
|
||||
|
||||
r = make_subvolumes_set(p, *source, *target, &subvolumes_by_source_inode);
|
||||
r = make_subvolumes_set(p, line->source, line->target, &subvolumes_by_source_inode);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
sfd = chase_and_open(*source, arg_copy_source, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL);
|
||||
sfd = chase_and_open(line->source, arg_copy_source, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL);
|
||||
if (sfd == -ENOENT) {
|
||||
log_notice_errno(sfd, "Failed to open source file '%s%s', skipping: %m", strempty(arg_copy_source), *source);
|
||||
log_notice_errno(sfd, "Failed to open source file '%s%s', skipping: %m", strempty(arg_copy_source), line->source);
|
||||
continue;
|
||||
}
|
||||
if (sfd < 0)
|
||||
return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), *source);
|
||||
return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), line->source);
|
||||
|
||||
r = fd_verify_regular(sfd);
|
||||
if (r < 0) {
|
||||
if (r != -EISDIR)
|
||||
return log_error_errno(r, "Failed to check type of source file '%s': %m", *source);
|
||||
return log_error_errno(r, "Failed to check type of source file '%s': %m", line->source);
|
||||
|
||||
/* We are looking at a directory */
|
||||
tfd = chase_and_open(*target, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
|
||||
tfd = chase_and_open(line->target, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL);
|
||||
if (tfd < 0) {
|
||||
_cleanup_free_ char *dn = NULL, *fn = NULL;
|
||||
|
||||
if (tfd != -ENOENT)
|
||||
return log_error_errno(tfd, "Failed to open target directory '%s': %m", *target);
|
||||
return log_error_errno(tfd, "Failed to open target directory '%s': %m", line->target);
|
||||
|
||||
r = path_extract_filename(*target, &fn);
|
||||
r = path_extract_filename(line->target, &fn);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract filename from '%s': %m", *target);
|
||||
return log_error_errno(r, "Failed to extract filename from '%s': %m", line->target);
|
||||
|
||||
r = path_extract_directory(*target, &dn);
|
||||
r = path_extract_directory(line->target, &dn);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract directory from '%s': %m", *target);
|
||||
return log_error_errno(r, "Failed to extract directory from '%s': %m", line->target);
|
||||
|
||||
r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, subvolumes);
|
||||
if (r < 0)
|
||||
@@ -5743,41 +5820,41 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
sfd, ".",
|
||||
pfd, fn,
|
||||
UID_INVALID, GID_INVALID,
|
||||
COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE|COPY_RESTORE_DIRECTORY_TIMESTAMPS,
|
||||
line->flags,
|
||||
denylist, subvolumes_by_source_inode);
|
||||
} else
|
||||
r = copy_tree_at(
|
||||
sfd, ".",
|
||||
tfd, ".",
|
||||
UID_INVALID, GID_INVALID,
|
||||
COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE|COPY_RESTORE_DIRECTORY_TIMESTAMPS,
|
||||
line->flags,
|
||||
denylist, subvolumes_by_source_inode);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m",
|
||||
strempty(arg_copy_source), *source, strempty(root), *target);
|
||||
strempty(arg_copy_source), line->source, strempty(root), line->target);
|
||||
} else {
|
||||
_cleanup_free_ char *dn = NULL, *fn = NULL;
|
||||
|
||||
/* We are looking at a regular file */
|
||||
|
||||
r = file_is_denylisted(*source, denylist);
|
||||
r = file_is_denylisted(line->source, denylist);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (r > 0) {
|
||||
log_debug("%s is in the denylist, ignoring", *source);
|
||||
log_debug("%s is in the denylist, ignoring", line->source);
|
||||
continue;
|
||||
}
|
||||
|
||||
r = path_extract_filename(*target, &fn);
|
||||
r = path_extract_filename(line->target, &fn);
|
||||
if (r == -EADDRNOTAVAIL || r == O_DIRECTORY)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EISDIR),
|
||||
"Target path '%s' refers to a directory, but source path '%s' refers to regular file, can't copy.", *target, *source);
|
||||
"Target path '%s' refers to a directory, but source path '%s' refers to regular file, can't copy.", line->target, line->source);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract filename from '%s': %m", *target);
|
||||
return log_error_errno(r, "Failed to extract filename from '%s': %m", line->target);
|
||||
|
||||
r = path_extract_directory(*target, &dn);
|
||||
r = path_extract_directory(line->target, &dn);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to extract directory from '%s': %m", *target);
|
||||
return log_error_errno(r, "Failed to extract directory from '%s': %m", line->target);
|
||||
|
||||
r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, subvolumes);
|
||||
if (r < 0)
|
||||
@@ -5789,11 +5866,11 @@ static int do_copy_files(Context *context, Partition *p, const char *root) {
|
||||
|
||||
tfd = openat(pfd, fn, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0700);
|
||||
if (tfd < 0)
|
||||
return log_error_errno(errno, "Failed to create target file '%s': %m", *target);
|
||||
return log_error_errno(errno, "Failed to create target file '%s': %m", line->target);
|
||||
|
||||
r = copy_bytes(sfd, tfd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_SIGINT|COPY_TRUNCATE);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", *source, strempty(arg_copy_source), *target);
|
||||
return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", line->source, strempty(arg_copy_source), line->target);
|
||||
|
||||
(void) copy_xattr(sfd, NULL, tfd, NULL, COPY_ALL_XATTRS);
|
||||
(void) copy_access(sfd, tfd);
|
||||
@@ -6254,8 +6331,7 @@ static int context_mkfs(Context *context) {
|
||||
return r;
|
||||
|
||||
r = make_filesystem(partition_target_path(t), p->format, strempty(p->new_label), root,
|
||||
p->fs_uuid, arg_discard,
|
||||
/* quiet = */ streq(p->format, "erofs") && !DEBUG_LOGGING,
|
||||
p->fs_uuid, partition_mkfs_flags(p),
|
||||
context->fs_sector_size, p->compression, p->compression_level,
|
||||
extra_mkfs_options);
|
||||
if (r < 0)
|
||||
@@ -7849,8 +7925,7 @@ static int context_minimize(Context *context) {
|
||||
strempty(p->new_label),
|
||||
root,
|
||||
fs_uuid,
|
||||
arg_discard,
|
||||
/* quiet = */ streq(p->format, "erofs") && !DEBUG_LOGGING,
|
||||
partition_mkfs_flags(p),
|
||||
context->fs_sector_size,
|
||||
p->compression,
|
||||
p->compression_level,
|
||||
@@ -7941,8 +8016,7 @@ static int context_minimize(Context *context) {
|
||||
strempty(p->new_label),
|
||||
root,
|
||||
p->fs_uuid,
|
||||
arg_discard,
|
||||
/* quiet = */ streq(p->format, "erofs") && !DEBUG_LOGGING,
|
||||
partition_mkfs_flags(p),
|
||||
context->fs_sector_size,
|
||||
p->compression,
|
||||
p->compression_level,
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <linux/btrfs.h>
|
||||
#include <linux/fsverity.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
@@ -790,6 +792,64 @@ static int prepare_nocow(int fdf, const char *from, int fdt, unsigned *chattr_ma
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Copies fs-verity status. May re-open fdt to do its job. */
|
||||
static int copy_fs_verity(int fdf, int *fdt) {
|
||||
int r;
|
||||
|
||||
assert(fdf >= 0);
|
||||
assert(fdt);
|
||||
assert(*fdt >= 0);
|
||||
|
||||
r = fd_verify_regular(fdf);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
struct fsverity_descriptor desc = {};
|
||||
struct fsverity_read_metadata_arg read_arg = {
|
||||
.metadata_type = FS_VERITY_METADATA_TYPE_DESCRIPTOR,
|
||||
.buf_ptr = (uintptr_t) &desc,
|
||||
.length = sizeof(desc),
|
||||
};
|
||||
|
||||
r = ioctl(fdf, FS_IOC_READ_VERITY_METADATA, &read_arg);
|
||||
if (r < 0) {
|
||||
/* ENODATA means that the file doesn't have fs-verity,
|
||||
* so the correct thing to do is to do nothing at all. */
|
||||
if (errno == ENODATA)
|
||||
return 0;
|
||||
return log_error_errno(errno, "Failed to read fs-verity metadata from source file: %m");
|
||||
}
|
||||
|
||||
/* Make sure that the descriptor is completely initialized */
|
||||
assert(r == (int) sizeof desc);
|
||||
|
||||
r = fd_verify_regular(*fdt);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
/* Okay. We're doing this now. We need to re-open fdt as read-only because
|
||||
* we can't enable fs-verity while writable file descriptors are outstanding. */
|
||||
_cleanup_close_ int reopened_fd = -EBADF;
|
||||
r = fd_reopen_condition(*fdt, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_ACCMODE_STRICT|O_PATH, &reopened_fd);
|
||||
if (r < 0)
|
||||
return r;
|
||||
if (reopened_fd >= 0)
|
||||
close_and_replace(*fdt, reopened_fd);
|
||||
|
||||
struct fsverity_enable_arg enable_arg = {
|
||||
.version = desc.version,
|
||||
.hash_algorithm = desc.hash_algorithm,
|
||||
.block_size = UINT32_C(1) << desc.log_blocksize,
|
||||
.salt_size = desc.salt_size,
|
||||
.salt_ptr = (uintptr_t) &desc.salt,
|
||||
};
|
||||
|
||||
if (ioctl(*fdt, FS_IOC_ENABLE_VERITY, &enable_arg) < 0)
|
||||
return log_error_errno(errno, "Failed to set fs-verity metadata: %m");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fd_copy_tree_generic(
|
||||
int df,
|
||||
const char *from,
|
||||
@@ -874,6 +934,15 @@ static int fd_copy_regular(
|
||||
return r;
|
||||
}
|
||||
|
||||
/* NB: fs-verity cannot be enabled when a writable file descriptor is outstanding.
|
||||
* copy_fs_verity() may well re-open 'fdt' as O_RDONLY. All code below this point
|
||||
* needs to be able to work with a read-only file descriptor. */
|
||||
if (FLAGS_SET(copy_flags, COPY_PRESERVE_FS_VERITY)) {
|
||||
r = copy_fs_verity(fdf, &fdt);
|
||||
if (r < 0)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (copy_flags & COPY_FSYNC) {
|
||||
if (fsync(fdt) < 0) {
|
||||
r = -errno;
|
||||
|
||||
@@ -32,6 +32,7 @@ typedef enum CopyFlags {
|
||||
* copy because reflinking from COW to NOCOW files is not supported.
|
||||
*/
|
||||
COPY_NOCOW_AFTER = 1 << 20,
|
||||
COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */
|
||||
} CopyFlags;
|
||||
|
||||
typedef enum DenyType {
|
||||
|
||||
@@ -322,8 +322,7 @@ int make_filesystem(
|
||||
const char *label,
|
||||
const char *root,
|
||||
sd_id128_t uuid,
|
||||
bool discard,
|
||||
bool quiet,
|
||||
MakeFileSystemFlags flags,
|
||||
uint64_t sector_size,
|
||||
char *compression,
|
||||
char *compression_level,
|
||||
@@ -335,7 +334,7 @@ int make_filesystem(
|
||||
_cleanup_(unlink_and_freep) char *protofile = NULL;
|
||||
char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {};
|
||||
int stdio_fds[3] = { -EBADF, STDERR_FILENO, STDERR_FILENO};
|
||||
ForkFlags flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|
|
||||
ForkFlags fork_flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT|
|
||||
FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG;
|
||||
int r;
|
||||
|
||||
@@ -424,7 +423,7 @@ int make_filesystem(
|
||||
"-U", vol_id,
|
||||
"-I", "256",
|
||||
"-m", "0",
|
||||
"-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
|
||||
"-E", FLAGS_SET(flags, MKFS_DISCARD) ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1",
|
||||
"-b", "4096",
|
||||
"-T", "default");
|
||||
if (!argv)
|
||||
@@ -433,7 +432,10 @@ int make_filesystem(
|
||||
if (root && strv_extend_many(&argv, "-d", root) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (quiet && strv_extend(&argv, "-q") < 0)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (FLAGS_SET(flags, MKFS_FS_VERITY) && strv_extend_many(&argv, "-O", "verity") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extend(&argv, node) < 0)
|
||||
@@ -454,13 +456,13 @@ int make_filesystem(
|
||||
if (!argv)
|
||||
return log_oom();
|
||||
|
||||
if (!discard && strv_extend(&argv, "--nodiscard") < 0)
|
||||
if (!FLAGS_SET(flags, MKFS_DISCARD) && strv_extend(&argv, "--nodiscard") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (root && strv_extend_many(&argv, "-r", root) < 0)
|
||||
return log_oom();
|
||||
|
||||
if (quiet && strv_extend(&argv, "-q") < 0)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (compression) {
|
||||
@@ -479,7 +481,7 @@ int make_filesystem(
|
||||
|
||||
/* mkfs.btrfs unconditionally warns about several settings changing from v5.15 onwards which
|
||||
* isn't silenced by "-q", so let's redirect stdout to /dev/null as well. */
|
||||
if (quiet)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET))
|
||||
stdio_fds[1] = -EBADF;
|
||||
|
||||
/* mkfs.btrfs expects a sector size of at least 4k bytes. */
|
||||
@@ -495,11 +497,14 @@ int make_filesystem(
|
||||
"-f", /* force override, without this it doesn't seem to want to write to an empty partition */
|
||||
"-l", label,
|
||||
"-U", vol_id,
|
||||
"-t", one_zero(discard));
|
||||
"-t", one_zero(FLAGS_SET(flags, MKFS_DISCARD)));
|
||||
if (!argv)
|
||||
return log_oom();
|
||||
|
||||
if (quiet && strv_extend(&argv, "-q") < 0)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (FLAGS_SET(flags, MKFS_FS_VERITY) && strv_extend_many(&argv, "-O", "verity") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (sector_size > 0) {
|
||||
@@ -525,7 +530,7 @@ int make_filesystem(
|
||||
if (!argv)
|
||||
return log_oom();
|
||||
|
||||
if (!discard && strv_extend(&argv, "-K") < 0)
|
||||
if (!FLAGS_SET(flags, MKFS_DISCARD) && strv_extend(&argv, "-K") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (root) {
|
||||
@@ -557,7 +562,7 @@ int make_filesystem(
|
||||
return log_oom();
|
||||
}
|
||||
|
||||
if (quiet && strv_extend(&argv, "-q") < 0)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (strv_extend(&argv, node) < 0)
|
||||
@@ -584,7 +589,7 @@ int make_filesystem(
|
||||
return log_oom();
|
||||
|
||||
/* mkfs.vfat does not have a --quiet option so let's redirect stdout to /dev/null instead. */
|
||||
if (quiet)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET))
|
||||
stdio_fds[1] = -EBADF;
|
||||
|
||||
} else if (streq(fstype, "swap")) {
|
||||
@@ -597,7 +602,7 @@ int make_filesystem(
|
||||
if (!argv)
|
||||
return log_oom();
|
||||
|
||||
if (quiet)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET))
|
||||
stdio_fds[1] = -EBADF;
|
||||
|
||||
} else if (streq(fstype, "squashfs")) {
|
||||
@@ -617,7 +622,7 @@ int make_filesystem(
|
||||
}
|
||||
|
||||
/* mksquashfs -quiet option is pretty new so let's redirect stdout to /dev/null instead. */
|
||||
if (quiet)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET))
|
||||
stdio_fds[1] = -EBADF;
|
||||
|
||||
} else if (streq(fstype, "erofs")) {
|
||||
@@ -626,7 +631,7 @@ int make_filesystem(
|
||||
if (!argv)
|
||||
return log_oom();
|
||||
|
||||
if (quiet && strv_extend(&argv, "--quiet") < 0)
|
||||
if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "--quiet") < 0)
|
||||
return log_oom();
|
||||
|
||||
if (compression) {
|
||||
@@ -663,7 +668,7 @@ int make_filesystem(
|
||||
return log_error_errno(r, "Failed to stat '%s': %m", node);
|
||||
|
||||
if (S_ISBLK(st.st_mode))
|
||||
flags |= FORK_NEW_MOUNTNS;
|
||||
fork_flags |= FORK_NEW_MOUNTNS;
|
||||
}
|
||||
|
||||
if (DEBUG_LOGGING) {
|
||||
@@ -678,7 +683,7 @@ int make_filesystem(
|
||||
stdio_fds,
|
||||
/*except_fds=*/ NULL,
|
||||
/*n_except_fds=*/ 0,
|
||||
flags,
|
||||
fork_flags,
|
||||
/*ret_pid=*/ NULL);
|
||||
if (r < 0)
|
||||
return r;
|
||||
@@ -695,7 +700,7 @@ int make_filesystem(
|
||||
* on unformatted free space, so let's trick it and other mkfs tools into thinking no
|
||||
* partitions are mounted. See https://github.com/kdave/btrfs-progs/issues/640 for more
|
||||
° information. */
|
||||
if (flags & FORK_NEW_MOUNTNS)
|
||||
if (fork_flags & FORK_NEW_MOUNTNS)
|
||||
(void) mount_nofollow_verbose(LOG_DEBUG, "/dev/null", "/proc/self/mounts", NULL, MS_BIND, NULL);
|
||||
|
||||
execvp(mkfs, argv);
|
||||
|
||||
@@ -5,6 +5,12 @@
|
||||
|
||||
#include "forward.h"
|
||||
|
||||
typedef enum MakeFilesystemFlags {
|
||||
MKFS_QUIET = 1 << 0, /* Suppress mkfs command output */
|
||||
MKFS_DISCARD = 1 << 1, /* Enable 'discard' mode on the filesystem */
|
||||
MKFS_FS_VERITY = 1 << 2, /* Enable fs-verity support on the filesystem */
|
||||
} MakeFileSystemFlags;
|
||||
|
||||
int mkfs_exists(const char *fstype);
|
||||
|
||||
int mkfs_supports_root_option(const char *fstype);
|
||||
@@ -15,8 +21,7 @@ int make_filesystem(
|
||||
const char *label,
|
||||
const char *root,
|
||||
sd_id128_t uuid,
|
||||
bool discard,
|
||||
bool quiet,
|
||||
MakeFileSystemFlags flags,
|
||||
uint64_t sector_size,
|
||||
char *compression,
|
||||
char *compression_level,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <linux/fsverity.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/xattr.h>
|
||||
#include <unistd.h>
|
||||
|
||||
@@ -591,4 +593,120 @@ TEST(copy_verify_linked) {
|
||||
assert_se(faccessat(tfd, "to_2", F_OK, AT_SYMLINK_NOFOLLOW) < 0 && errno == ENOENT);
|
||||
}
|
||||
|
||||
static bool enable_fsverity(int dirfd, const char *name, int algo, const char *salt, size_t salt_size) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
ASSERT_OK_ERRNO(fd = openat(dirfd, name, O_RDONLY | O_CLOEXEC | O_NOCTTY));
|
||||
|
||||
struct fsverity_enable_arg enable_arg = {
|
||||
.version = 1,
|
||||
.hash_algorithm = algo,
|
||||
.block_size = 4096,
|
||||
.salt_size = salt_size,
|
||||
.salt_ptr = (uintptr_t) salt
|
||||
};
|
||||
|
||||
if (ioctl(fd, FS_IOC_ENABLE_VERITY, &enable_arg) == 0)
|
||||
return true;
|
||||
|
||||
/* We might get this if we're on a filesystem without support. */
|
||||
ASSERT_TRUE(IN_SET(errno, EOPNOTSUPP, ENOTTY));
|
||||
return false;
|
||||
}
|
||||
|
||||
static struct fsverity_digest* alloc_fsverity_digest(size_t digest_size) {
|
||||
struct fsverity_digest *digest = malloc(sizeof (struct fsverity_digest) + digest_size);
|
||||
ASSERT_NOT_NULL(digest);
|
||||
digest->digest_size = digest_size;
|
||||
return digest;
|
||||
}
|
||||
|
||||
static bool measure_fsverity(int dirfd, const char *name, struct fsverity_digest *digest) {
|
||||
_cleanup_close_ int fd = -EBADF;
|
||||
ASSERT_OK_ERRNO(fd = openat(dirfd, name, O_RDONLY | O_CLOEXEC | O_NOCTTY));
|
||||
if (ioctl(fd, FS_IOC_MEASURE_VERITY, digest) == 0)
|
||||
return true;
|
||||
|
||||
/* Make sure that the error is caused by the file simply not having fs-verity. */
|
||||
ASSERT_TRUE(errno == ENODATA);
|
||||
return false;
|
||||
}
|
||||
|
||||
static void assert_no_fsverity(int dirfd, const char *name) {
|
||||
_cleanup_free_ struct fsverity_digest *digest = alloc_fsverity_digest(0);
|
||||
ASSERT_FALSE(measure_fsverity(dirfd, name, digest));
|
||||
}
|
||||
|
||||
static void assert_fsverity_eq(int dirfd_a, int dirfd_b, const char *name) {
|
||||
_cleanup_free_ struct fsverity_digest *digest_a = alloc_fsverity_digest(64);
|
||||
_cleanup_free_ struct fsverity_digest *digest_b = alloc_fsverity_digest(64);
|
||||
|
||||
bool enabled_a = measure_fsverity(dirfd_a, name, digest_a);
|
||||
bool enabled_b = measure_fsverity(dirfd_b, name, digest_b);
|
||||
ASSERT_EQ(enabled_a, enabled_b);
|
||||
if (enabled_a) {
|
||||
ASSERT_EQ(digest_a->digest_algorithm, digest_b->digest_algorithm);
|
||||
ASSERT_EQ(digest_a->digest_size, digest_b->digest_size);
|
||||
ASSERT_EQ(memcmp(digest_a->digest, digest_b->digest, digest_a->digest_size), 0);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_RET(copy_with_verity) {
|
||||
_cleanup_(rm_rf_physical_and_freep) char *srcp = NULL, *dstp = NULL, *badsrcp = NULL, *baddstp = NULL;
|
||||
const char *files[] = { "disabled", "simple", "bigsha", "salty" };
|
||||
_cleanup_close_ int src = -EBADF, dst = -EBADF, badsrc = -EBADF, baddst = -EBADF;
|
||||
|
||||
/* We're more likely to hit a filesystem with fs-verity enabled on /var/tmp than on /tmp (tmpfs) */
|
||||
ASSERT_OK(src = mkdtemp_open("/var/tmp/test-copy_file-src.XXXXXX", 0, &srcp));
|
||||
ASSERT_OK(dst = mkdtemp_open("/var/tmp/test-copy_file-dst.XXXXXX", 0, &dstp));
|
||||
|
||||
/* Populate some data to differentiate the files. */
|
||||
FOREACH_ELEMENT(file, files)
|
||||
ASSERT_OK(write_string_file_at(src, *file, "src file", WRITE_STRING_FILE_CREATE));
|
||||
|
||||
/* Enable on some file using a range of options */
|
||||
if (!enable_fsverity(src, "simple", FS_VERITY_HASH_ALG_SHA256, NULL, 0))
|
||||
return log_tests_skipped_errno(errno, "/var/tmp: fs-verity is not supported here");
|
||||
ASSERT_TRUE(enable_fsverity(src, "bigsha", FS_VERITY_HASH_ALG_SHA512, NULL, 0));
|
||||
ASSERT_TRUE(enable_fsverity(src, "salty", FS_VERITY_HASH_ALG_SHA512, "edamame", 8));
|
||||
|
||||
/* Copy without fs-verity enabled and make sure nothing is set on the destination */
|
||||
ASSERT_OK(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REPLACE|COPY_MERGE, NULL, NULL));
|
||||
FOREACH_ELEMENT(file, files)
|
||||
assert_no_fsverity(dst, *file);
|
||||
|
||||
/* Copy *with* fs-verity enabled and make sure it works properly */
|
||||
int r = copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL);
|
||||
if (r == -ENOTTY)
|
||||
/* This can happen on some versions of btrfs, for example */
|
||||
return log_tests_skipped_errno(errno, "/var/tmp: fs-verity supported, but not reading metadata");
|
||||
ASSERT_OK(r);
|
||||
FOREACH_ELEMENT(file, files)
|
||||
assert_fsverity_eq(src, dst, *file);
|
||||
|
||||
/* Now try to create files where we know fs-verity doesn't work: tmpfs */
|
||||
ASSERT_OK(badsrc = mkdtemp_open("/tmp/test-copy_file-src.XXXXXX", 0, &badsrcp));
|
||||
ASSERT_OK(baddst = mkdtemp_open("/tmp/test-copy_file-src.XXXXXX", 0, &baddstp));
|
||||
|
||||
/* Populate the source, same as before */
|
||||
FOREACH_ELEMENT(file, files)
|
||||
ASSERT_OK(write_string_file_at(badsrc, *file, "src file", WRITE_STRING_FILE_CREATE));
|
||||
|
||||
/* Ensure the attempting to enable fs-verity here will fail */
|
||||
if (enable_fsverity(badsrc, "simple", FS_VERITY_HASH_ALG_SHA256, NULL, 0))
|
||||
return log_tests_skipped_errno(errno, "/tmp: fs-verity *is* unexpectedly supported here");
|
||||
|
||||
/* Copy from our non-verity filesystem into dst, requesting verity and making sure we notice that
|
||||
* we failed to read verity from the source. */
|
||||
ASSERT_ERROR(copy_tree_at(badsrc, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY);
|
||||
|
||||
/* Copy from our verity filesystem into our baddst, requesting verity and making sure we notice that
|
||||
* we failed to set verity on the destination. */
|
||||
ASSERT_ERROR(copy_tree_at(src, ".", baddst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY);
|
||||
|
||||
/* Of course this should fail too... */
|
||||
ASSERT_ERROR(copy_tree_at(badsrc, ".", baddst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_DEBUG);
|
||||
|
||||
@@ -266,16 +266,16 @@ static int run(int argc, char *argv[]) {
|
||||
assert_se(r >= 0);
|
||||
|
||||
assert_se(sd_id128_randomize(&id) >= 0);
|
||||
assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, true, false, 0, NULL, NULL, NULL) >= 0);
|
||||
assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0);
|
||||
|
||||
assert_se(sd_id128_randomize(&id) >= 0);
|
||||
assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, true, false, 0, NULL, NULL, NULL) >= 0);
|
||||
assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0);
|
||||
|
||||
assert_se(sd_id128_randomize(&id) >= 0);
|
||||
assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, true, false, 0, NULL, NULL, NULL) >= 0);
|
||||
assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0);
|
||||
|
||||
assert_se(sd_id128_randomize(&id) >= 0);
|
||||
assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, true, false, 0, NULL, NULL, NULL) >= 0);
|
||||
assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0);
|
||||
|
||||
dissected = dissected_image_unref(dissected);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user