diff --git a/lib/internal.h b/lib/internal.h index ec864bdd..4648d6ed 100644 --- a/lib/internal.h +++ b/lib/internal.h @@ -120,6 +120,7 @@ int device_check_size(struct crypt_device *cd, struct device *device, uint64_t req_offset, int falloc); void device_set_block_size(struct device *device, size_t size); +size_t device_optimal_encryption_sector_size(struct crypt_device *cd, struct device *device); int device_open_locked(struct crypt_device *cd, struct device *device, int flags); int device_read_lock(struct crypt_device *cd, struct device *device); diff --git a/lib/libcryptsetup.h b/lib/libcryptsetup.h index c1b3d5c5..3574f508 100644 --- a/lib/libcryptsetup.h +++ b/lib/libcryptsetup.h @@ -599,7 +599,7 @@ struct crypt_params_luks2 { const struct crypt_params_integrity *integrity_params; /**< Data integrity parameters or @e NULL*/ size_t data_alignment; /**< data area alignment in 512B sectors, data offset is multiple of this */ const char *data_device; /**< detached encrypted data device or @e NULL */ - uint32_t sector_size; /**< encryption sector size */ + uint32_t sector_size; /**< encryption sector size, 0 triggers auto-detection for optimal encryption sector size */ const char *label; /**< header label or @e NULL*/ const char *subsystem; /**< header subsystem label or @e NULL*/ }; diff --git a/lib/libcryptsetup.sym b/lib/libcryptsetup.sym index f0668e36..8ce29701 100644 --- a/lib/libcryptsetup.sym +++ b/lib/libcryptsetup.sym @@ -7,6 +7,7 @@ CRYPTSETUP_2.4 { crypt_activate_by_token_type; crypt_activate_by_token_pin; crypt_dump_json; + crypt_format; }; CRYPTSETUP_2.0 { diff --git a/lib/setup.c b/lib/setup.c index 2c4b17d3..d38ab8c4 100644 --- a/lib/setup.c +++ b/lib/setup.c @@ -1706,12 +1706,13 @@ static int _crypt_format_luks2(struct crypt_device *cd, const char *uuid, const char *volume_key, size_t volume_key_size, - struct crypt_params_luks2 *params) + struct crypt_params_luks2 *params, + bool sector_size_autodetect) { int r, integrity_key_size = 0; unsigned long required_alignment = DEFAULT_DISK_ALIGNMENT; unsigned long alignment_offset = 0; - unsigned int sector_size = params ? params->sector_size : SECTOR_SIZE; + unsigned int sector_size; const char *integrity = params ? params->integrity : NULL; uint64_t dev_size; uint32_t dmc_flags; @@ -1733,15 +1734,30 @@ static int _crypt_format_luks2(struct crypt_device *cd, return -EINVAL; } + if (params && params->sector_size) + sector_size_autodetect = false; + + if (sector_size_autodetect) { + sector_size = device_optimal_encryption_sector_size(cd, crypt_data_device(cd)); + log_dbg(cd, "Auto-detected optimal encryption sector size for device %s is %d bytes.", + device_path(crypt_data_device(cd)), sector_size); + } else + sector_size = params ? params->sector_size : SECTOR_SIZE; + if (sector_size < SECTOR_SIZE || sector_size > MAX_SECTOR_SIZE || NOTPOW2(sector_size)) { log_err(cd, _("Unsupported encryption sector size.")); return -EINVAL; } if (sector_size != SECTOR_SIZE && !dm_flags(cd, DM_CRYPT, &dmc_flags) && - !(dmc_flags & DM_SECTOR_SIZE_SUPPORTED)) - log_std(cd, _("WARNING: The device activation will fail, dm-crypt is missing " - "support for requested encryption sector size.\n")); + !(dmc_flags & DM_SECTOR_SIZE_SUPPORTED)) { + if (sector_size_autodetect) { + log_dbg(cd, "dm-crypt does not support encryption sector size option. Reverting to 512 bytes."); + sector_size = SECTOR_SIZE; + } else + log_std(cd, _("WARNING: The device activation will fail, dm-crypt is missing " + "support for requested encryption sector size.\n")); + } if (integrity) { if (params->integrity_params) { @@ -1813,6 +1829,21 @@ static int _crypt_format_luks2(struct crypt_device *cd, &required_alignment, &alignment_offset, DEFAULT_DISK_ALIGNMENT); + r = device_size(crypt_data_device(cd), &dev_size); + if (r < 0) + goto out; + + if (sector_size_autodetect) { + if (cd->data_offset && MISALIGNED(cd->data_offset, sector_size)) { + log_dbg(cd, "Data offset not alligned to sector size. Reverting to 512 bytes."); + sector_size = SECTOR_SIZE; + } else if (MISALIGNED(dev_size - (uint64_t)required_alignment - (uint64_t)alignment_offset, sector_size)) { + /* underflow does not affect misalignment checks */ + log_err(cd, "Device size is not aligned to sector size. Reverting to 512 bytes."); + sector_size = SECTOR_SIZE; + } + } + /* FIXME: allow this later also for normal ciphers (check AF_ALG availability. */ if (integrity && !integrity_key_size) { r = crypt_cipher_check_kernel(cipher, cipher_mode, integrity, volume_key_size); @@ -1842,10 +1873,6 @@ static int _crypt_format_luks2(struct crypt_device *cd, if (r < 0) goto out; - r = device_size(crypt_data_device(cd), &dev_size); - if (r < 0) - goto out; - if (dev_size < (crypt_get_data_offset(cd) * SECTOR_SIZE)) log_std(cd, _("WARNING: Data offset is outside of currently available data device.\n")); @@ -2241,14 +2268,15 @@ out: return r; } -int crypt_format(struct crypt_device *cd, +static int _crypt_format(struct crypt_device *cd, const char *type, const char *cipher, const char *cipher_mode, const char *uuid, const char *volume_key, size_t volume_key_size, - void *params) + void *params, + bool sector_size_autodetect) { int r; @@ -2276,7 +2304,7 @@ int crypt_format(struct crypt_device *cd, uuid, volume_key, volume_key_size, params); else if (isLUKS2(type)) r = _crypt_format_luks2(cd, cipher, cipher_mode, - uuid, volume_key, volume_key_size, params); + uuid, volume_key, volume_key_size, params, sector_size_autodetect); else if (isLOOPAES(type)) r = _crypt_format_loopaes(cd, cipher, uuid, volume_key_size, params); else if (isVERITY(type)) @@ -2297,6 +2325,34 @@ int crypt_format(struct crypt_device *cd, return r; } +int crypt_format(struct crypt_device *cd, + const char *type, + const char *cipher, + const char *cipher_mode, + const char *uuid, + const char *volume_key, + size_t volume_key_size, + void *params) +{ + return _crypt_format(cd, type, cipher, cipher_mode, uuid, volume_key, volume_key_size, params, true); +} + +#if defined(__GNUC__) +#define CRYPT_EXPORT_SYMBOL(func, maj, min) \ + __asm__(".symver " #func "_v" #maj "_" #min ", " #func "@CRYPTSETUP_" #maj "." #min) +int crypt_format_v2_0(struct crypt_device *cd, const char *type, const char *cipher, + const char *cipher_mode,const char *uuid, const char *volume_key, + size_t volume_key_size, void *params); + +int crypt_format_v2_0(struct crypt_device *cd, const char *type, const char *cipher, + const char *cipher_mode,const char *uuid, const char *volume_key, + size_t volume_key_size, void *params) +{ + return _crypt_format(cd, type, cipher, cipher_mode, uuid, volume_key, volume_key_size, params, false); +} +CRYPT_EXPORT_SYMBOL(crypt_format, 2, 0); +#endif + int crypt_repair(struct crypt_device *cd, const char *requested_type, void *params __attribute__((unused))) diff --git a/lib/utils_device.c b/lib/utils_device.c index c34acf0f..a80cbd26 100644 --- a/lib/utils_device.c +++ b/lib/utils_device.c @@ -112,6 +112,23 @@ static size_t device_block_size_fd(int fd, size_t *min_size) return bsize; } +static size_t device_block_phys_size_fd(int fd) +{ + struct stat st; + int arg; + size_t bsize = SECTOR_SIZE; + + if (fstat(fd, &st) < 0) + return bsize; + + if (S_ISREG(st.st_mode)) + bsize = MAX_SECTOR_SIZE; + else if (ioctl(fd, BLKPBSZGET, &arg) >= 0) + bsize = (size_t)arg; + + return bsize; +} + static size_t device_alignment_fd(int devfd) { long alignment = DEFAULT_MEM_ALIGNMENT; @@ -570,6 +587,42 @@ size_t device_block_size(struct crypt_device *cd, struct device *device) return device->block_size; } +size_t device_optimal_encryption_sector_size(struct crypt_device *cd, struct device *device) +{ + int fd; + size_t phys_block_size; + + if (!device) + return SECTOR_SIZE; + + fd = open(device->file_path ?: device->path, O_RDONLY); + if (fd < 0) { + log_dbg(cd, "Cannot get optimal encryption sector size for device %s.", device_path(device)); + return SECTOR_SIZE; + } + + /* cache device block size */ + device->block_size = device_block_size_fd(fd, NULL); + if (!device->block_size) { + close(fd); + log_dbg(cd, "Cannot get block size for device %s.", device_path(device)); + return SECTOR_SIZE; + } + + if (device->block_size >= MAX_SECTOR_SIZE) { + close(fd); + return MISALIGNED(device->block_size, MAX_SECTOR_SIZE) ? SECTOR_SIZE : MAX_SECTOR_SIZE; + } + + phys_block_size = device_block_phys_size_fd(fd); + close(fd); + + if (device->block_size >= phys_block_size || phys_block_size <= SECTOR_SIZE || phys_block_size > MAX_SECTOR_SIZE || MISALIGNED(phys_block_size, device->block_size)) + return device->block_size; + + return phys_block_size; +} + int device_read_ahead(struct device *device, uint32_t *read_ahead) { int fd, r = 0; diff --git a/man/cryptsetup.8 b/man/cryptsetup.8 index 3b249643..05ee1b7a 100644 --- a/man/cryptsetup.8 +++ b/man/cryptsetup.8 @@ -1328,8 +1328,15 @@ action asks for passphrase to proceed further. .TP .B "\-\-sector\-size " Set sector size for use with disk encryption. It must be power of two -and in range 512 - 4096 bytes. The default is 512 bytes sectors. -This option is available only in the LUKS2 mode. +and in range 512 - 4096 bytes. This option is available only in the LUKS2 +or plain modes. + +The default for plain mode is 512 bytes. For LUKS2 devices it's established +during luksFormat operation based on parameters provided by underlying data device. +For native 4K block devices it's 4096 bytes. For 4K/512e (4K physical sector size +with 512 bytes emulation) it's 4096 bytes. For drives reporting only 512 bytes +block size it remains 512 bytes. If data device is regular file put in filesystem +it's 4096 bytes. Note that if sector size is higher than underlying device hardware sector and there is not integrity protection that uses data journal, using diff --git a/src/cryptsetup.c b/src/cryptsetup.c index ff45f8f0..a7dfcf03 100644 --- a/src/cryptsetup.c +++ b/src/cryptsetup.c @@ -1288,7 +1288,7 @@ static int _luksFormat(struct crypt_device **r_cd, char **r_password, size_t *r_ struct crypt_params_luks2 params2 = { .data_alignment = params1.data_alignment, .data_device = params1.data_device, - .sector_size = ARG_UINT32(OPT_SECTOR_SIZE_ID) ?: SECTOR_SIZE, + .sector_size = ARG_UINT32(OPT_SECTOR_SIZE_ID), .label = ARG_STR(OPT_LABEL_ID), .subsystem = ARG_STR(OPT_SUBSYSTEM_ID) }; diff --git a/tests/compat-test2 b/tests/compat-test2 index e4c51895..2e6516d8 100755 --- a/tests/compat-test2 +++ b/tests/compat-test2 @@ -711,7 +711,7 @@ echo $PWD1 | $CRYPTSETUP luksFormat $FAST_PBKDF_OPT --type luks2 $LOOPDEV --head echo $PWD1 | $CRYPTSETUP -q luksFormat $FAST_PBKDF_OPT --type luks2 $LOOPDEV --header $HEADER_IMG --align-payload 8192 || fail echo $PWD1 | $CRYPTSETUP -q luksFormat $FAST_PBKDF_OPT --type luks2 $LOOPDEV --header $HEADER_IMG --align-payload 4096 >/dev/null || fail $CRYPTSETUP luksDump $HEADER_IMG | grep -e "0: crypt" -A1 | grep -qe $((4096*512)) || fail -echo $PWD1 | $CRYPTSETUP -q luksFormat $FAST_PBKDF_OPT --type luks2 $LOOPDEV --header $HEADER_IMG --align-payload 0 || fail +echo $PWD1 | $CRYPTSETUP -q luksFormat $FAST_PBKDF_OPT --type luks2 $LOOPDEV --header $HEADER_IMG --align-payload 0 --sector-size 512 || fail echo $PWD1 | $CRYPTSETUP luksOpen $LOOPDEV-missing --header $HEADER_IMG $DEV_NAME 2>/dev/null && fail echo $PWD1 | $CRYPTSETUP luksOpen $LOOPDEV --header $HEADER_IMG $DEV_NAME || fail echo $PWD1 | $CRYPTSETUP -q resize $DEV_NAME --size 100 --header $HEADER_IMG || fail diff --git a/tests/luks2-reencryption-test b/tests/luks2-reencryption-test index 0e56669c..904ee6ae 100755 --- a/tests/luks2-reencryption-test +++ b/tests/luks2-reencryption-test @@ -917,7 +917,7 @@ $CRYPTSETUP close $DEV_NAME || fail $CRYPTSETUP close $DEV_NAME2 || fail echo "[5] Decryption with detached header" -echo $PWD1 | $CRYPTSETUP luksFormat --type luks2 -c aes-cbc-essiv:sha256 -s 128 --header $IMG_HDR -q $FAST_PBKDF_ARGON $DEV || fail +echo $PWD1 | $CRYPTSETUP luksFormat --type luks2 --sector-size 512 -c aes-cbc-essiv:sha256 -s 128 --header $IMG_HDR -q $FAST_PBKDF_ARGON $DEV || fail wipe $PWD1 $IMG_HDR echo $PWD1 | $CRYPTSETUP reencrypt -q --decrypt --header $IMG_HDR $DEV || fail check_hash_dev $DEV $HASH3 @@ -935,7 +935,7 @@ echo $PWD1 | $CRYPTSETUP reencrypt -q --decrypt --resilience checksum --header $ check_hash_dev $DEV $HASH3 # check deferred remove works as expected after decryption -echo $PWD1 | $CRYPTSETUP luksFormat --type luks2 -c serpent-xts-plain --header $IMG_HDR -q $FAST_PBKDF_ARGON $DEV || fail +echo $PWD1 | $CRYPTSETUP luksFormat --type luks2 --sector-size 512 -c serpent-xts-plain --header $IMG_HDR -q $FAST_PBKDF_ARGON $DEV || fail open_crypt $PWD1 $IMG_HDR dmsetup create $DEV_NAME2 --table "0 1 linear /dev/mapper/$DEV_NAME 0" || fail echo $PWD1 | $CRYPTSETUP reencrypt -q --decrypt --resilience checksum --header $IMG_HDR --active-name $DEV_NAME || fail diff --git a/tests/reencryption-compat-test2 b/tests/reencryption-compat-test2 index 692ad992..ac2c6ce2 100755 --- a/tests/reencryption-compat-test2 +++ b/tests/reencryption-compat-test2 @@ -462,7 +462,7 @@ check_hash $PWD1 $HASH1 $CRYPTSETUP -q convert --type luks2 $IMG || fail echo $PWD1 | $REENC $IMG -q $FAST_PBKDF_PBKDF2 || fail check_hash $PWD1 $HASH1 -echo $PWD1 | $CRYPTSETUP -q luksFormat --type luks2 $FAST_PBKDF_PBKDF2 $IMG --offset 8192 || fail +echo $PWD1 | $CRYPTSETUP -q luksFormat --sector-size 512 --type luks2 $FAST_PBKDF_PBKDF2 $IMG --offset 8192 || fail wipe $PWD1 check_hash $PWD1 $HASH5 $CRYPTSETUP -q convert --type luks1 $IMG || fail