#include "conf-parser.h"
#include "cryptsetup-util.h"
#include "def.h"
+#include "dirent-util.h"
#include "efivars.h"
#include "errno-util.h"
#include "fd-util.h"
#include "mkdir.h"
#include "mkfs-util.h"
#include "mount-util.h"
+#include "mountpoint-util.h"
#include "parse-argument.h"
#include "parse-util.h"
#include "path-util.h"
FreeArea *allocated_to_area;
char *copy_blocks_path;
+ bool copy_blocks_auto;
int copy_blocks_fd;
uint64_t copy_blocks_size;
return 0;
}
+static int config_parse_copy_blocks(
+ const char *unit,
+ const char *filename,
+ unsigned line,
+ const char *section,
+ unsigned section_line,
+ const char *lvalue,
+ int ltype,
+ const char *rvalue,
+ void *data,
+ void *userdata) {
+
+ _cleanup_free_ char *d = NULL;
+ Partition *partition = data;
+ int r;
+
+ assert(rvalue);
+ assert(partition);
+
+ if (isempty(rvalue)) {
+ partition->copy_blocks_path = mfree(partition->copy_blocks_path);
+ partition->copy_blocks_auto = false;
+ return 0;
+ }
+
+ if (streq(rvalue, "auto")) {
+ partition->copy_blocks_path = mfree(partition->copy_blocks_path);
+ partition->copy_blocks_auto = true;
+ return 0;
+ }
+
+ r = specifier_printf(rvalue, specifier_table, NULL, &d);
+ if (r < 0) {
+ log_syntax(unit, LOG_WARNING, filename, line, r,
+ "Failed to expand specifiers in CopyBlocks= source path, ignoring: %s", rvalue);
+ return 0;
+ }
+
+ r = path_simplify_and_warn(d, PATH_CHECK_ABSOLUTE, unit, filename, line, lvalue);
+ if (r < 0)
+ return 0;
+
+ free_and_replace(partition->copy_blocks_path, d);
+ partition->copy_blocks_auto = false;
+ return 0;
+}
+
static int config_parse_make_dirs(
const char *unit,
const char *filename,
static int partition_read_definition(Partition *p, const char *path) {
ConfigTableItem table[] = {
- { "Partition", "Type", config_parse_type, 0, &p->type_uuid },
- { "Partition", "Label", config_parse_label, 0, &p->new_label },
- { "Partition", "UUID", config_parse_id128, 0, &p->new_uuid },
- { "Partition", "Priority", config_parse_int32, 0, &p->priority },
- { "Partition", "Weight", config_parse_weight, 0, &p->weight },
- { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight },
- { "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min },
- { "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max },
- { "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min },
- { "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max },
- { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset },
- { "Partition", "CopyBlocks", config_parse_path, 0, &p->copy_blocks_path },
- { "Partition", "Format", config_parse_fstype, 0, &p->format },
- { "Partition", "CopyFiles", config_parse_copy_files, 0, p },
- { "Partition", "MakeDirectories", config_parse_make_dirs, 0, p },
- { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
+ { "Partition", "Type", config_parse_type, 0, &p->type_uuid },
+ { "Partition", "Label", config_parse_label, 0, &p->new_label },
+ { "Partition", "UUID", config_parse_id128, 0, &p->new_uuid },
+ { "Partition", "Priority", config_parse_int32, 0, &p->priority },
+ { "Partition", "Weight", config_parse_weight, 0, &p->weight },
+ { "Partition", "PaddingWeight", config_parse_weight, 0, &p->padding_weight },
+ { "Partition", "SizeMinBytes", config_parse_size4096, 1, &p->size_min },
+ { "Partition", "SizeMaxBytes", config_parse_size4096, -1, &p->size_max },
+ { "Partition", "PaddingMinBytes", config_parse_size4096, 1, &p->padding_min },
+ { "Partition", "PaddingMaxBytes", config_parse_size4096, -1, &p->padding_max },
+ { "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 },
+ { "Partition", "MakeDirectories", config_parse_make_dirs, 0, p },
+ { "Partition", "Encrypt", config_parse_encrypt, 0, &p->encrypt },
{}
};
int r;
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Type= not defined, refusing.");
- if (p->copy_blocks_path && (p->format || !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)))
+ if ((p->copy_blocks_path || p->copy_blocks_auto) &&
+ (p->format || !strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
- "Format= and CopyBlocks= cannot be combined, refusing.");
+ "Format=/CopyFiles=/MakeDirectories= and CopyBlocks= cannot be combined, refusing.");
if ((!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories)) && streq_ptr(p->format, "swap"))
return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL),
"Format=swap and CopyFiles= cannot be combined, refusing.");
- if (!p->format && (!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || (p->encrypt != ENCRYPT_OFF && !p->copy_blocks_path))) {
+ if (!p->format && (!strv_isempty(p->copy_files) || !strv_isempty(p->make_directories) || (p->encrypt != ENCRYPT_OFF && !(p->copy_blocks_path || p->copy_blocks_auto)))) {
/* Pick "ext4" as file system if we are configured to copy files or encrypt the device */
p->format = strdup("ext4");
if (!p->format)
return false;
}
-static int context_open_copy_block_paths(Context *context) {
+static int resolve_copy_blocks_auto_candidate(
+ dev_t partition_devno,
+ sd_id128_t partition_type_uuid,
+ dev_t restrict_devno,
+ sd_id128_t *ret_uuid) {
+
+ _cleanup_(blkid_free_probep) blkid_probe b = NULL;
+ _cleanup_free_ char *p = NULL;
+ _cleanup_close_ int fd = -1;
+ const char *pttype, *t;
+ sd_id128_t pt_parsed, u;
+ blkid_partition pp;
+ dev_t whole_devno;
+ blkid_partlist pl;
+ struct stat st;
+ int r;
+
+ /* Checks if the specified partition has the specified GPT type UUID, and is located on the specified
+ * 'restrict_devno' device. The type check is particularly relevant if we have Verity volume which is
+ * backed by two separate partitions: the data and the hash partitions, and we need to find the right
+ * one of the two. */
+
+ r = block_get_whole_disk(partition_devno, &whole_devno);
+ if (r < 0)
+ return log_error_errno(
+ r,
+ "Unable to determine containing block device of partition %u:%u: %m",
+ major(partition_devno), minor(partition_devno));
+
+ if (restrict_devno != (dev_t) -1 &&
+ restrict_devno != whole_devno)
+ return log_error_errno(
+ SYNTHETIC_ERRNO(EPERM),
+ "Partition %u:%u is located outside of block device %u:%u, refusing.",
+ major(partition_devno), minor(partition_devno),
+ major(restrict_devno), minor(restrict_devno));
+
+ r = device_path_make_major_minor(S_IFBLK, whole_devno, &p);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert block device to device node path: %m");
+
+ fd = open(p, O_RDONLY|O_CLOEXEC|O_NONBLOCK);
+ if (fd < 0)
+ return log_error_errno(r, "Failed to open '%s': %m", p);
+
+ if (fstat(fd, &st) < 0)
+ return log_error_errno(r, "Failed to stat '%s': %m", p);
+
+ if (!S_ISBLK(st.st_mode) || st.st_rdev != whole_devno)
+ return log_error_errno(
+ SYNTHETIC_ERRNO(EPERM),
+ "Opened and determined block device don't match, refusing.");
+
+ b = blkid_new_probe();
+ if (!b)
+ return log_oom();
+
+ errno = 0;
+ r = blkid_probe_set_device(b, fd, 0, 0);
+ if (r != 0)
+ return log_error_errno(errno_or_else(ENOMEM), "Failed to open block device '%s': %m", p);
+
+ (void) blkid_probe_enable_partitions(b, 1);
+ (void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS);
+
+ errno = 0;
+ r = blkid_do_safeprobe(b);
+ if (IN_SET(r, -2, 1)) { /* nothing found or ambiguous result */
+ log_debug("Didn't find partition table on block device '%s'.", p);
+ return false;
+ }
+ if (r != 0)
+ return log_error_errno(errno_or_else(EIO), "Unable to probe for partition table of '%s': %m", p);
+
+ (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL);
+ if (!streq_ptr(pttype, "gpt")) {
+ log_debug("Didn't find a GPT partition table on '%s'.", p);
+ return false;
+ }
+
+ errno = 0;
+ pl = blkid_probe_get_partitions(b);
+ if (!pl)
+ return log_error_errno(errno_or_else(EIO), "Unable read partition table of '%s': %m", p);
+ errno = 0;
+
+ pp = blkid_partlist_devno_to_partition(pl, partition_devno);
+ if (!pp) {
+ log_debug("Partition %u:%u has no matching partition table entry on '%s'.",
+ major(partition_devno), minor(partition_devno), p);
+ return false;
+ }
+
+ t = blkid_partition_get_type_string(pp);
+ if (isempty(t)) {
+ log_debug("Partition %u:%u has no type on '%s'.",
+ major(partition_devno), minor(partition_devno), p);
+ return false;
+ }
+
+ r = sd_id128_from_string(t, &pt_parsed);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse partition type \"%s\": %m", t);
+ return false;
+ }
+
+ if (!sd_id128_equal(pt_parsed, partition_type_uuid)) {
+ log_debug("Partition %u:%u has non-matching partition type " SD_ID128_FORMAT_STR " (needed: " SD_ID128_FORMAT_STR "), ignoring.",
+ major(partition_devno), minor(partition_devno),
+ SD_ID128_FORMAT_VAL(pt_parsed), SD_ID128_FORMAT_VAL(partition_type_uuid));
+ return false;
+ }
+
+ t = blkid_partition_get_uuid(pp);
+ if (isempty(t)) {
+ log_debug("Partition %u:%u has no UUID.",
+ major(partition_devno), minor(partition_devno));
+ return false;
+ }
+
+ r = sd_id128_from_string(t, &u);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse partition UUID \"%s\": %m", t);
+ return false;
+ }
+
+ log_debug("Automatically found partition %u:%u of right type " SD_ID128_FORMAT_STR ".",
+ major(partition_devno), minor(partition_devno),
+ SD_ID128_FORMAT_VAL(pt_parsed));
+
+ if (ret_uuid)
+ *ret_uuid = u;
+
+ return true;
+}
+
+static int find_backing_devno(
+ const char *path,
+ const char *root,
+ dev_t *ret) {
+
+ _cleanup_free_ char *resolved = NULL;
+ int r;
+
+ assert(path);
+
+ r = chase_symlinks(path, root, CHASE_PREFIX_ROOT, &resolved, NULL);
+ if (r < 0)
+ return r;
+
+ r = path_is_mount_point(resolved, NULL, 0);
+ if (r < 0)
+ return r;
+ if (r == 0) /* Not a mount point, then it's not a partition of its own, let's not automatically use it. */
+ return -ENOENT;
+
+ r = get_block_device(resolved, ret);
+ if (r < 0)
+ return r;
+ if (r == 0) /* Not backed by physical file system, we can't use this */
+ return -ENOENT;
+
+ return 0;
+}
+
+static int resolve_copy_blocks_auto(
+ sd_id128_t type_uuid,
+ const char *root,
+ dev_t restrict_devno,
+ char **ret_path,
+ sd_id128_t *ret_uuid) {
+
+ const char *try1 = NULL, *try2 = NULL;
+ char p[SYS_BLOCK_PATH_MAX("/slaves")];
+ _cleanup_(closedirp) DIR *d = NULL;
+ sd_id128_t found_uuid = SD_ID128_NULL;
+ dev_t devno, found = 0;
+ int r;
+
+ assert(ret_path);
+
+ /* Enforce some security restrictions: CopyBlocks=auto should not be an avenue to get outside of the
+ * --root=/--image= confinement. Specifically, refuse CopyBlocks= in combination with --root= at all,
+ * and restrict block device references in the --image= case to loopback block device we set up.
+ *
+ * restrict_devno contain the dev_t of the loop back device we operate on in case of --image=, and
+ * thus declares which device (and its partition subdevices) we shall limit access to. If
+ * restrict_devno is zero no device probing access shall be allowed at all (used for --root=) and if
+ * it is (dev_t) -1 then free access shall be allowed (if neither switch is used). */
+
+ if (restrict_devno == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+ "Automatic discovery of backing block devices not permitted in --root= mode, refusing.");
+
+ /* Handles CopyBlocks=auto, and finds the right source partition to copy from. We look for matching
+ * partitions in the host, using the appropriate directory as key and ensuring that the partition
+ * type matches. */
+
+ if (gpt_partition_type_is_root(type_uuid))
+ try1 = "/";
+ else if (gpt_partition_type_is_usr(type_uuid))
+ try1 = "/usr/";
+ else if (gpt_partition_type_is_root_verity(type_uuid))
+ try1 = "/";
+ else if (gpt_partition_type_is_usr_verity(type_uuid))
+ try1 = "/usr/";
+ else if (sd_id128_equal(type_uuid, GPT_ESP)) {
+ try1 = "/efi/";
+ try2 = "/boot/";
+ } else if (sd_id128_equal(type_uuid, GPT_XBOOTLDR))
+ try1 = "/boot/";
+ else
+ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
+ "Partition type " SD_ID128_FORMAT_STR " not supported from automatic source block device discovery.",
+ SD_ID128_FORMAT_VAL(type_uuid));
+
+ r = find_backing_devno(try1, root, &devno);
+ if (r == -ENOENT && try2)
+ r = find_backing_devno(try2, root, &devno);
+ if (r < 0)
+ return log_error_errno(r, "Failed to resolve automatic CopyBlocks= path for partition type " SD_ID128_FORMAT_STR ", sorry: %m",
+ SD_ID128_FORMAT_VAL(type_uuid));
+
+ xsprintf_sys_block_path(p, "/slaves", devno);
+ d = opendir(p);
+ if (d) {
+ struct dirent *de;
+
+ for (;;) {
+ _cleanup_free_ char *q = NULL, *t = NULL;
+ sd_id128_t u;
+ dev_t sl;
+
+ errno = 0;
+ de = readdir_no_dot(d);
+ if (!de) {
+ if (errno != 0)
+ return log_error_errno(errno, "Failed to read directory '%s': %m", p);
+
+ break;
+ }
+
+ if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN))
+ continue;
+
+ q = path_join(p, de->d_name, "/dev");
+ if (!q)
+ return log_oom();
+
+ r = read_one_line_file(q, &t);
+ if (r < 0)
+ return log_error_errno(r, "Failed to read %s: %m", q);
+
+ r = parse_dev(t, &sl);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to parse %s, ignoring: %m", q);
+ continue;
+ }
+ if (major(sl) == 0) {
+ log_debug_errno(r, "Device backing %s is special, ignoring: %m", q);
+ continue;
+ }
+
+ r = resolve_copy_blocks_auto_candidate(sl, type_uuid, restrict_devno, &u);
+ if (r < 0)
+ return r;
+ if (r > 0) {
+ /* We found a matching one! */
+ if (found != 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ),
+ "Multiple matching partitions found, refusing.");
+
+ found = sl;
+ found_uuid = u;
+ }
+ }
+ } else if (errno != ENOENT)
+ return log_error_errno(errno, "Failed open %s: %m", p);
+ else {
+ r = resolve_copy_blocks_auto_candidate(devno, type_uuid, restrict_devno, &found_uuid);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ found = devno;
+ }
+
+ if (found == 0)
+ return log_error_errno(SYNTHETIC_ERRNO(ENXIO),
+ "Unable to automatically discover suitable partition to copy blocks from.");
+
+ r = device_path_make_major_minor(S_IFBLK, found, ret_path);
+ if (r < 0)
+ return log_error_errno(r, "Failed to convert dev_t to device node path: %m");
+
+ if (ret_uuid)
+ *ret_uuid = found_uuid;
+
+ return 0;
+}
+
+static int context_open_copy_block_paths(
+ Context *context,
+ const char *root,
+ dev_t restrict_devno) {
+
Partition *p;
int r;
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_close_ int source_fd = -1;
+ _cleanup_free_ char *opened = NULL;
+ sd_id128_t uuid = SD_ID128_NULL;
uint64_t size;
struct stat st;
if (PARTITION_EXISTS(p)) /* Never copy over partitions that already exist! */
continue;
- if (!p->copy_blocks_path)
- continue;
+ if (p->copy_blocks_path) {
- source_fd = open(p->copy_blocks_path, O_RDONLY|O_CLOEXEC|O_NOCTTY);
- if (source_fd < 0)
- return log_error_errno(errno, "Failed to open block copy file '%s': %m", p->copy_blocks_path);
+ source_fd = chase_symlinks_and_open(p->copy_blocks_path, root, CHASE_PREFIX_ROOT, O_RDONLY|O_CLOEXEC|O_NONBLOCK, &opened);
+ if (source_fd < 0)
+ return log_error_errno(source_fd, "Failed to open '%s': %m", p->copy_blocks_path);
- if (fstat(source_fd, &st) < 0)
- return log_error_errno(errno, "Failed to stat block copy file '%s': %m", p->copy_blocks_path);
+ if (fstat(source_fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat block copy file '%s': %m", opened);
+
+ if (!S_ISREG(st.st_mode) && restrict_devno != (dev_t) -1)
+ return log_error_errno(SYNTHETIC_ERRNO(EPERM),
+ "Copying from block device node is not permitted in --image=/--root= mode, refusing.");
+
+ } else if (p->copy_blocks_auto) {
+
+ r = resolve_copy_blocks_auto(p->type_uuid, root, restrict_devno, &opened, &uuid);
+ if (r < 0)
+ return r;
+
+ source_fd = open(opened, O_RDONLY|O_CLOEXEC|O_NOCTTY);
+ if (source_fd < 0)
+ return log_error_errno(errno, "Failed to open automatically determined source block copy device '%s': %m", opened);
+
+ if (fstat(source_fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat block copy file '%s': %m", opened);
+
+ /* If we found it automatically, it must be a block device, let's enforce that */
+ if (!S_ISBLK(st.st_mode))
+ return log_error_errno(SYNTHETIC_ERRNO(EBADF),
+ "Automatically detected source block copy device '%s' is not a block device, refusing: %m", opened);
+ } else
+ continue;
if (S_ISDIR(st.st_mode)) {
_cleanup_free_ char *bdev = NULL;
r = btrfs_get_block_device_fd(source_fd, &devt);
if (r == -EUCLEAN)
- return btrfs_log_dev_root(LOG_ERR, r, p->copy_blocks_path);
+ return btrfs_log_dev_root(LOG_ERR, r, opened);
if (r < 0)
- return log_error_errno(r, "Unable to determine backing block device of '%s': %m", p->copy_blocks_path);
+ return log_error_errno(r, "Unable to determine backing block device of '%s': %m", opened);
r = device_path_make_major_minor(S_IFBLK, devt, &bdev);
}
if (r < 0)
- return log_error_errno(r, "Failed to determine block device path for block device backing '%s': %m", p->copy_blocks_path);
+ return log_error_errno(r, "Failed to determine block device path for block device backing '%s': %m", opened);
safe_close(source_fd);
if (ioctl(source_fd, BLKGETSIZE64, &size) != 0)
return log_error_errno(errno, "Failed to determine size of block device to copy from: %m");
} else
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path to copy blocks from '%s' is not a regular file, block device or directory, refusing: %m", p->copy_blocks_path);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Specified path to copy blocks from '%s' is not a regular file, block device or directory, refusing: %m", opened);
if (size <= 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has zero size, refusing.", p->copy_blocks_path);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has zero size, refusing.", opened);
if (size % 512 != 0)
- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has size that is not multiple of 512, refusing.", p->copy_blocks_path);
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "File to copy bytes from '%s' has size that is not multiple of 512, refusing.", opened);
p->copy_blocks_fd = TAKE_FD(source_fd);
p->copy_blocks_size = size;
+
+ free_and_replace(p->copy_blocks_path, opened);
+
+ /* When copying from an existing partition copy that partitions UUID if none is configured explicitly */
+ if (sd_id128_is_null(p->new_uuid) && !sd_id128_is_null(uuid))
+ p->new_uuid = uuid;
}
return 0;
if (S_ISBLK(st.st_mode)) {
/* Refuse referencing explicit block devices if a root dir is specified, after all we should
- * be able to leave the image the root path constraints us to. */
+ * not be able to leave the image the root path constrains us to. */
if (root)
return -EPERM;
return r;
/* Open all files to copy blocks from now, since we want to take their size into consideration */
- r = context_open_copy_block_paths(context);
+ r = context_open_copy_block_paths(
+ context,
+ arg_root,
+ loop_device ? loop_device->devno : /* if --image= is specified, only allow partitions on the loopback device*/
+ arg_root && !arg_image ? 0 : /* if --root= is specified, don't accept any block device */
+ (dev_t) -1); /* if neither is specified, make no restrictions */
if (r < 0)
return r;