diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index 9147b3dfcf..9f7af06a4e 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -66,16 +66,16 @@ #include "user-record.h" #include "user-util.h" -/* Round down to the nearest 4K size. Given that newer hardware generally prefers 4K sectors, let's align our - * partitions to that too. In the worst case we'll waste 3.5K per partition that way, but I think I can live +/* Round down to the nearest 1 MiB size. Given that most tools generally align partitions to 1 MiB boundaries, let's align our + * partitions to that too. In the worst case we'll waste 1 MiB per partition that way, but I think I can live * with that. */ -#define DISK_SIZE_ROUND_DOWN(x) ((x) & ~UINT64_C(4095)) +#define DISK_SIZE_ROUND_DOWN(x) ((x) & ~(U64_MB - 1)) -/* Rounds up to the nearest 4K boundary. Returns UINT64_MAX on overflow */ +/* Rounds up to the nearest 1 MiB boundary. Returns UINT64_MAX on overflow */ #define DISK_SIZE_ROUND_UP(x) \ ({ \ uint64_t _x = (x); \ - _x > UINT64_MAX - 4095U ? UINT64_MAX : (_x + 4095U) & ~UINT64_C(4095); \ + _x > UINT64_MAX - (U64_MB - 1) ? UINT64_MAX : (DISK_SIZE_ROUND_DOWN(_x + U64_MB - 1)); \ }) /* How much larger will the image on disk be than the fs inside it, i.e. the space we pay for the GPT and @@ -666,6 +666,7 @@ static int luks_validate( int fd, const char *label, sd_id128_t partition_uuid, + uint64_t sector_size, sd_id128_t *ret_partition_uuid, uint64_t *ret_offset, uint64_t *ret_size) { @@ -683,6 +684,7 @@ static int luks_validate( assert(label); assert(ret_offset); assert(ret_size); + assert(sector_size > 0); r = dlopen_libblkid(); if (r < 0) @@ -697,6 +699,12 @@ static int luks_validate( if (r != 0) return errno_or_else(ENOMEM); + /* Set probing sector size if explicitly specified */ + if (sector_size != UINT32_MAX) { + r = sym_blkid_probe_set_sectorsize(b, sector_size); + if (r != 0) + return errno_or_else(EINVAL); + } (void) sym_blkid_probe_enable_superblocks(b, 1); (void) sym_blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); (void) sym_blkid_probe_enable_partitions(b, 1); @@ -778,6 +786,7 @@ static int luks_validate( if ((uint64_t) size > UINT64_MAX / 512U) return -EINVAL; + /* libblkid returns partitions sizes in count of 512-sectors. This does not necessarily need to match the device sector size */ *ret_offset = offset * 512U; *ret_size = size * 512U; *ret_partition_uuid = found_partition_uuid; @@ -1401,7 +1410,15 @@ int home_setup_luks( if (!subdir) return log_oom(); - r = luks_validate(setup->image_fd, user_record_user_name_and_realm(h), h->partition_uuid, &found_partition_uuid, &offset, &size); + r = luks_validate( + setup->image_fd, + user_record_user_name_and_realm(h), + h->partition_uuid, + /* if sector size is not specified, select UINT32_MAX, i.e. auto-probe */ + h->luks_sector_size == UINT64_MAX ? UINT32_MAX : user_record_luks_sector_size(h), + &found_partition_uuid, + &offset, + &size); if (r < 0) return log_error_errno(r, "Failed to validate disk label: %m"); @@ -1416,6 +1433,11 @@ int home_setup_luks( return r; } + /* Before we make the loop device, make sure offset is zero & we are using the full partition + * If our offset is not zero, loop_device_make will create a loop device on top of the block device */ + if (S_ISBLK(st.st_mode)) + assert(offset == 0 && size == UINT64_MAX); + r = loop_device_make( setup->image_fd, O_RDWR, @@ -1755,6 +1777,7 @@ static int luks_format( const PasswordCache *cache, char **effective_passwords, bool discard, + uint64_t sector_size, UserRecord *hr, struct crypt_device **ret) { @@ -1809,7 +1832,7 @@ static int luks_format( &(struct crypt_params_luks2) { .label = label, .subsystem = "systemd-home", - .sector_size = user_record_luks_sector_size(hr), + .sector_size = sector_size, /* sector-size of 0 is auto for libcryptsetup */ .pbkdf = &good_pbkdf, }); if (r < 0) @@ -1887,7 +1910,7 @@ static int make_partition_table( _cleanup_(fdisk_unref_parttypep) struct fdisk_parttype *t = NULL; _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; _cleanup_free_ char *disk_uuid_as_string = NULL; - uint64_t offset, size, first_lba, start, last_lba, end; + uint64_t offset, size, first_lba, start, last_lba, end, fdisk_sector_size; sd_id128_t disk_uuid; int r; @@ -1924,9 +1947,13 @@ static int make_partition_table( if (r < 0) return log_error_errno(r, "Failed to place partition at first free partition index: %m"); + /* Use same sector size as the fdisk context when converting to bytes */ + fdisk_sector_size = fdisk_get_sector_size(c); + assert(fdisk_sector_size > 0); + first_lba = fdisk_get_first_lba(c); /* Boundary where usable space starts */ - assert(first_lba <= UINT64_MAX/512); - start = DISK_SIZE_ROUND_UP(first_lba * 512); /* Round up to multiple of 4K */ + assert(first_lba <= UINT64_MAX / fdisk_sector_size); + start = DISK_SIZE_ROUND_UP(first_lba * fdisk_sector_size); log_debug("Starting partition at offset %" PRIu64, start); @@ -1934,17 +1961,17 @@ static int make_partition_table( return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Overflow while rounding up start LBA."); last_lba = fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ - assert(last_lba < UINT64_MAX/512); - end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * 512); /* Round down to multiple of 4K */ + assert(last_lba < UINT64_MAX / fdisk_sector_size); + end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * fdisk_sector_size); if (end <= start) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Resulting partition size zero or negative."); - r = fdisk_partition_set_start(p, start / 512); + r = fdisk_partition_set_start(p, start / fdisk_sector_size); if (r < 0) return log_error_errno(r, "Failed to place partition at offset %" PRIu64 ": %m", start); - r = fdisk_partition_set_size(p, (end - start) / 512); + r = fdisk_partition_set_size(p, (end - start) / fdisk_sector_size); if (r < 0) return log_error_errno(r, "Failed to end partition at offset %" PRIu64 ": %m", end); @@ -1978,16 +2005,16 @@ static int make_partition_table( assert(fdisk_partition_has_start(q)); offset = fdisk_partition_get_start(q); - if (offset > UINT64_MAX / 512U) + if (offset > UINT64_MAX / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition offset too large."); assert(fdisk_partition_has_size(q)); size = fdisk_partition_get_size(q); - if (size > UINT64_MAX / 512U) + if (size > UINT64_MAX / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(ERANGE), "Partition size too large."); - *ret_offset = offset * 512U; - *ret_size = size * 512U; + *ret_offset = offset * fdisk_sector_size; + *ret_size = size * fdisk_sector_size; *ret_disk_uuid = disk_uuid; return 0; @@ -2162,13 +2189,14 @@ int home_create_luks( UserRecord **ret_home) { _cleanup_free_ char *subdir = NULL, *disk_uuid_path = NULL; - uint64_t encrypted_size, + uint64_t encrypted_size, image_sector_size, luks_sector_size, host_size = 0, partition_offset = 0, partition_size = 0; /* Unnecessary initialization to appease gcc */ _cleanup_(user_record_unrefp) UserRecord *new_home = NULL; sd_id128_t partition_uuid, fs_uuid, luks_uuid, disk_uuid; _cleanup_close_ int mount_fd = -EBADF; const char *fstype, *ip; struct statfs sfs; + struct stat st; int r; _cleanup_strv_free_ char **extra_mkfs_options = NULL; @@ -2241,7 +2269,6 @@ int home_create_luks( if (path_startswith(ip, "/dev/")) { _cleanup_free_ char *sysfs = NULL; uint64_t block_device_size; - struct stat st; /* Let's place the home directory on a real device, i.e. a USB stick or such */ @@ -2333,9 +2360,23 @@ int home_create_luks( log_info("Allocating image file completed."); } + if (h->luks_sector_size == UINT64_MAX) { + /* If sector size is not specified, select UINT32_MAX, i.e. auto-probe */ + image_sector_size = UINT32_MAX; + /* Let cryptsetup decide if the sector size is not specified in home record */ + luks_sector_size = 0; + } else { + if (S_ISBLK(st.st_mode)) { + /* For physical block devices always use the actual device logical + * sector size. Else the partition will not be discoverable by kernel. */ + image_sector_size = UINT32_MAX; + luks_sector_size = user_record_luks_sector_size(h); + } else + image_sector_size = luks_sector_size = user_record_luks_sector_size(h); + } r = make_partition_table( setup->image_fd, - user_record_luks_sector_size(h), + image_sector_size, user_record_user_name_and_realm(h), partition_uuid, &partition_offset, @@ -2346,21 +2387,48 @@ int home_create_luks( log_info("Writing of partition table completed."); - r = loop_device_make( - setup->image_fd, - O_RDWR, - partition_offset, - partition_size, - user_record_luks_sector_size(h), - 0, - LOCK_EX, - &setup->loop); - if (r == -ENOENT) /* this means /dev/loop-control doesn't exist, i.e. we are in a container - * or similar and loopback bock devices are not available, return a - * recognizable error in this case. */ - return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Loopback block device support is not available on this system."); - if (r < 0) - return log_error_errno(r, "Failed to set up loopback device for %s: %m", setup->temporary_image_path); + if (fstat(setup->image_fd, &st) < 0) + return log_error_errno(errno, "Failed to fstat home image: %m"); + + /* Ensure we don't create a loop device over block device as it leads to huge overhead for discard operations + * if the device does not support discard_zeroes_data */ + if (S_ISBLK(st.st_mode)) { + _cleanup_free_ char *partition_path = NULL; + assert(!sd_id128_is_null(partition_uuid)); + if (asprintf(&partition_path, "/dev/disk/by-partuuid/" SD_ID128_UUID_FORMAT_STR, SD_ID128_FORMAT_VAL(partition_uuid)) < 0) + return log_oom(); + + /* Release the lock, so that udev can find the partition */ + setup->image_fd = safe_close(setup->image_fd); + (void) wait_for_devlink(partition_path); + setup->image_fd = open_image_file(h, ip, &st); + if (setup->image_fd < 0) + return setup->image_fd; + + r = loop_device_open_from_path( + partition_path, + O_RDWR, + LOCK_EX, + &setup->loop); + if (r < 0) + return log_error_errno(r, "Failed to open newly written partition device: %s", partition_path); + } else { + r = loop_device_make( + setup->image_fd, + O_RDWR, + partition_offset, + partition_size, + image_sector_size, + 0, + LOCK_EX, + &setup->loop); + if (r == -ENOENT) /* this means /dev/loop-control doesn't exist, i.e. we are in a container + * or similar and loopback bock devices are not available, return a + * recognizable error in this case. */ + return log_error_errno(SYNTHETIC_ERRNO(ENOLINK), "Loopback block device support is not available on this system."); + if (r < 0) + return log_error_errno(r, "Failed to set up loopback device for %s: %m", setup->temporary_image_path); + } log_info("Setting up loopback device %s completed.", setup->loop->node ?: ip); @@ -2371,6 +2439,7 @@ int home_create_luks( cache, effective_passwords, user_record_luks_discard(h) || user_record_luks_offline_discard(h), + luks_sector_size, h, &setup->crypt_device); if (r < 0) @@ -2753,6 +2822,7 @@ static int prepare_resize_partition( n_partitions = fdisk_table_get_nents(t); for (size_t i = 0; i < n_partitions; i++) { struct fdisk_partition *p; + uint64_t fdisk_sector_size; p = fdisk_table_get_partition(t, i); if (!p) @@ -2763,14 +2833,16 @@ static int prepare_resize_partition( if (fdisk_partition_has_start(p) <= 0 || fdisk_partition_has_size(p) <= 0 || fdisk_partition_has_end(p) <= 0) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found partition without a size."); - if (fdisk_partition_get_start(p) == partition_offset / 512U && - fdisk_partition_get_size(p) == old_partition_size / 512U) { + fdisk_sector_size = fdisk_get_sector_size(c); + assert(fdisk_sector_size > 0); + if (fdisk_partition_get_start(p) == partition_offset / fdisk_sector_size && + fdisk_partition_get_size(p) == old_partition_size / fdisk_sector_size) { if (found) return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), "Partition found twice, refusing."); found = p; - } else if (fdisk_partition_get_end(p) > partition_offset / 512U) + } else if (fdisk_partition_get_end(p) > partition_offset / fdisk_sector_size) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Can't extend, not last partition in image."); } @@ -2790,7 +2862,7 @@ static int get_maximum_partition_size( uint64_t *ret_maximum_partition_size) { _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; - uint64_t start_lba, start, last_lba, end; + uint64_t start_lba, start, last_lba, end, fdisk_sector_size; int r; assert(fd >= 0); @@ -2801,13 +2873,15 @@ static int get_maximum_partition_size( if (r < 0) return log_error_errno(r, "Failed to create fdisk context: %m"); + /* Get the probed sector size by fdisk */ + fdisk_sector_size = fdisk_get_sector_size(c); start_lba = fdisk_partition_get_start(p); - assert(start_lba <= UINT64_MAX/512); - start = start_lba * 512; + assert(start_lba <= UINT64_MAX / fdisk_sector_size); + start = start_lba * fdisk_sector_size; last_lba = fdisk_get_last_lba(c); /* One sector before boundary where usable space ends */ - assert(last_lba < UINT64_MAX/512); - end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * 512); /* Round down to multiple of 4K */ + assert(last_lba < UINT64_MAX / fdisk_sector_size); + end = DISK_SIZE_ROUND_DOWN((last_lba + 1) * fdisk_sector_size); if (start > end) return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Last LBA is before partition start."); @@ -2847,9 +2921,7 @@ static int apply_resize_partition( size_t new_partition_size) { _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL; - _cleanup_free_ void *two_zero_lbas = NULL; uint32_t ssz; - ssize_t n; int r; assert(fd >= 0); @@ -2860,34 +2932,23 @@ static int apply_resize_partition( assert(p); + r = probe_sector_size(fd, &ssz); + if (r < 0) + return log_error_errno(r, "Failed to determine current sector size: %m"); + + r = fdisk_new_context_at(fd, /* path= */ NULL, /* read_only= */ false, ssz, &c); + if (r < 0) + return log_error_errno(r, "Failed to open device: %m"); + /* Before writing our partition patch the final size in */ r = fdisk_partition_size_explicit(p, 1); if (r < 0) return log_error_errno(r, "Failed to enable explicit partition size: %m"); - r = fdisk_partition_set_size(p, new_partition_size / 512U); + r = fdisk_partition_set_size(p, new_partition_size / ssz); if (r < 0) return log_error_errno(r, "Failed to change partition size: %m"); - r = probe_sector_size(fd, &ssz); - if (r < 0) - return log_error_errno(r, "Failed to determine current sector size: %m"); - - two_zero_lbas = malloc0(ssz * 2); - if (!two_zero_lbas) - return log_oom(); - - /* libfdisk appears to get confused by the existing PMBR. Let's explicitly flush it out. */ - n = pwrite(fd, two_zero_lbas, ssz * 2, 0); - if (n < 0) - return log_error_errno(errno, "Failed to wipe partition table: %m"); - if ((size_t) n != ssz * 2) - return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while wiping partition table."); - - r = fdisk_new_context_at(fd, /* path= */ NULL, /* read_only= */ false, ssz, &c); - if (r < 0) - return log_error_errno(r, "Failed to open device: %m"); - r = fdisk_create_disklabel(c, "gpt"); if (r < 0) return log_error_errno(r, "Failed to create GPT disk label: %m"); @@ -3472,6 +3533,7 @@ int home_resize_luks( (void) reread_partition_table_fd(image_fd, /* flags= */ 0); /* Tell LUKS about the new bigger size too */ + /* libcrypsetup uses units of 512B sectors for size */ r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512U); if (r < 0) return log_error_errno(r, "Failed to grow LUKS device: %m"); @@ -3536,7 +3598,8 @@ int home_resize_luks( if (new_fs_size < old_fs_size) { /* → Shrink */ /* Shrink the LUKS device now, matching the new file system size */ - r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512); + /* libcrypsetup uses units of 512B sectors for size */ + r = sym_crypt_resize(setup->crypt_device, setup->dm_name, new_fs_size / 512U); if (r < 0) return log_error_errno(r, "Failed to shrink LUKS device: %m");