From 1a4a34287fec4dbb01712232955225a6982a6dd7 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Wed, 8 Oct 2025 18:32:56 +0200 Subject: [PATCH 1/5] repart: add basic support for LUKS2 integrity verification Authenticated disk encryption is experimentally supported by cryptsetup since v2.0.0 and allows for automatic dm-integrity setup for LUKS devices. Add support for the mode to systemd-repart. Currently, the option can only be used in 'online' mode as libcryptsetup does not support creating integrity data without the use of in-kernel dm-integrity infrastructure. Integrity=/IntegrityAlgorithm= are added in the anticipation of other integrity protection options, e.g. enabling dm-integrity for a plain unencrypted partition. --- man/repart.d.xml | 21 +++++++ src/repart/repart.c | 114 +++++++++++++++++++++++++++++++++-- src/shared/cryptsetup-util.c | 4 +- src/shared/cryptsetup-util.h | 1 + 4 files changed, 133 insertions(+), 7 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index e83ed34f79..5d32557745 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -909,6 +909,27 @@ + + Integrity= + + Enable integrity checking for the partition. Currently, the only supported option + is Integrity=inline which enables authenticated disk encryption for LUKS2 devices. + The option requires Encrypt= setting and can only be used in online image build. + Defaults to off, i.e. integrity protection is disabled. + Note: authenticated disk encryption is considered EXPERIMENTAL by cryptsetup. + + + + + + IntegrityAlgorithm= + Specify the integrity algorithm to be used for integrity verification. For + Integrity=inline the supported values are: hmac-sha1, + hmac-sha256(default), and hmac-sha512. + + + + Compression= diff --git a/src/repart/repart.c b/src/repart/repart.c index edd4816f13..8768375d47 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -105,7 +105,7 @@ /* To do LUKS2 offline encryption, we need to keep some extra free space at the end of the partition. */ #define LUKS2_METADATA_KEEP_FREE (LUKS2_METADATA_SIZE*2ULL) -/* LUKS2 volume key size. */ +/* LUKS2 default volume key size (no integrity). */ #define VOLUME_KEY_SIZE (512ULL/8ULL) /* Use 4K as the default filesystem sector size because as long as the partitions are aligned to 4K, the @@ -266,6 +266,21 @@ typedef enum EncryptMode { _ENCRYPT_MODE_INVALID = -EINVAL, } EncryptMode; +typedef enum IntegrityMode { + INTEGRITY_OFF, + INTEGRITY_INLINE, + _INTEGRITY_MODE_MAX, + _INTEGRITY_MODE_INVALID = -EINVAL, +} IntegrityMode; + +typedef enum IntegrityAlg { + INTEGRITY_ALG_HMAC_SHA1, + INTEGRITY_ALG_HMAC_SHA256, + INTEGRITY_ALG_HMAC_SHA512, + _INTEGRITY_ALG_MAX, + _INTEGRITY_ALG_INVALID = -EINVAL, +} IntegrityAlg; + typedef enum VerityMode { VERITY_OFF, VERITY_DATA, @@ -444,6 +459,8 @@ typedef struct Partition { struct iovec key; Tpm2PCRValue *tpm2_hash_pcr_values; size_t tpm2_n_hash_pcr_values; + IntegrityMode integrity; + IntegrityAlg integrity_alg; VerityMode verity; char *verity_match_key; MinimizeMode minimize; @@ -552,6 +569,22 @@ static const char *encrypt_mode_table[_ENCRYPT_MODE_MAX] = { [ENCRYPT_KEY_FILE_TPM2] = "key-file+tpm2", }; +/* Going forward, the plan is to add two more modes: + * [INTEGRITY_DATA] = "data" (interleave data and integrity tags on the same device), + * [INTEGRITY_META] = "meta" (use a separate device for storing integrity tags). + * Also, INTEGRITY_INLINE will be using hardware sector integrity fields when used + * without encryption. */ +static const char *integrity_mode_table[_INTEGRITY_MODE_MAX] = { + [INTEGRITY_OFF] = "off", /* no integrity protection */ + [INTEGRITY_INLINE] = "inline", /* luks2 storage when encrypted */ +}; + +static const char *integrity_alg_table[_INTEGRITY_ALG_MAX] = { + [INTEGRITY_ALG_HMAC_SHA1] = "hmac-sha1", + [INTEGRITY_ALG_HMAC_SHA256] = "hmac-sha256", + [INTEGRITY_ALG_HMAC_SHA512] = "hmac-sha512", +}; + static const char *verity_mode_table[_VERITY_MODE_MAX] = { [VERITY_OFF] = "off", [VERITY_DATA] = "data", @@ -584,6 +617,8 @@ static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP(empty_mode, EmptyMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(append_mode, AppendMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(integrity_mode, IntegrityMode, INTEGRITY_INLINE); +DEFINE_PRIVATE_STRING_TABLE_LOOKUP(integrity_alg, IntegrityAlg); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(verity_mode, VerityMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(minimize_mode, MinimizeMode, MINIMIZE_BEST); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(progress_phase, ProgressPhase); @@ -2633,6 +2668,9 @@ static int config_parse_key_file( return parse_key_file(rvalue, &partition->key); } +static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity, integrity_mode, IntegrityMode, INTEGRITY_OFF); +static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_integrity_alg, integrity_alg, IntegrityAlg, INTEGRITY_ALG_HMAC_SHA256); + static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_verity, verity_mode, VerityMode, VERITY_OFF); static DEFINE_CONFIG_PARSE_ENUM_WITH_DEFAULT(config_parse_minimize, minimize_mode, MinimizeMode, MINIMIZE_OFF); @@ -2787,6 +2825,8 @@ static int partition_read_definition( { "Partition", "EncryptedVolume", config_parse_encrypted_volume, 0, p }, { "Partition", "TPM2PCRs", config_parse_tpm2_pcrs, 0, p }, { "Partition", "KeyFile", config_parse_key_file, 0, p }, + { "Partition", "Integrity", config_parse_integrity, 0, &p->integrity }, + { "Partition", "IntegrityAlgorithm", config_parse_integrity_alg, 0, &p->integrity_alg }, { "Partition", "Compression", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression }, { "Partition", "CompressionLevel", config_parse_string, CONFIG_PARSE_STRING_SAFE_AND_ASCII, &p->compression_level }, { "Partition", "SupplementFor", config_parse_string, 0, &p->supplement_for_name }, @@ -2921,6 +2961,10 @@ static int partition_read_definition( "SizeMinBytes=/SizeMaxBytes= cannot be used with Verity=%s.", verity_mode_to_string(p->verity)); + if (p->integrity == INTEGRITY_INLINE && p->encrypt == ENCRYPT_OFF) + return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), + "Integrity=inline requires Encrypt=."); + if (p->default_subvolume && !ordered_hashmap_contains(p->subvolumes, p->default_subvolume)) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "DefaultSubvolume= must be one of the paths in Subvolumes=."); @@ -4989,6 +5033,39 @@ static int partition_target_sync(Context *context, Partition *p, PartitionTarget return 0; } +/* libcryptsetup uses its own names for integrity algorithms, e.g. 'hmac(sha1)' but systemd + * prefers more standardized 'hmac-sha1', do the conversion here. Default to hmac(sha256). */ +static const char* dmcrypt_integrity_alg_name(Partition *p) { + if (p->integrity != INTEGRITY_INLINE) + return NULL; + + switch (p->integrity_alg) { + case INTEGRITY_ALG_HMAC_SHA1: + return "hmac(sha1)"; + case INTEGRITY_ALG_HMAC_SHA512: + return "hmac(sha512)"; + case INTEGRITY_ALG_HMAC_SHA256: + default: + return "hmac(sha256)"; + } +} + +/* Integrity puts specific limitations on the key size depending on the algorithm */ +static size_t dmcrypt_proper_key_size(Partition *p) { + if (p->integrity != INTEGRITY_INLINE) + return VOLUME_KEY_SIZE; + + switch (p->integrity_alg) { + case INTEGRITY_ALG_HMAC_SHA1: + return 672/8; + case INTEGRITY_ALG_HMAC_SHA512: + return 1024/8; + case INTEGRITY_ALG_HMAC_SHA256: + default: + return 768/8; + } +} + static int partition_encrypt(Context *context, Partition *p, PartitionTarget *target, bool offline) { #if HAVE_LIBCRYPTSETUP #if HAVE_TPM2 @@ -4997,6 +5074,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta _cleanup_fclose_ FILE *h = NULL; _cleanup_free_ char *hp = NULL, *vol = NULL, *dm_name = NULL; const char *passphrase = NULL; + const size_t volume_key_size = dmcrypt_proper_key_size(p); size_t passphrase_size = 0; const char *vt; int r; @@ -5021,6 +5099,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta .label = vl, .sector_size = partition_fs_sector_size(context, p), .data_device = offline ? node : NULL, + .integrity = dmcrypt_integrity_alg_name(p), }; struct crypt_params_reencrypt reencrypt_params = { .mode = CRYPT_REENCRYPT_ENCRYPT, @@ -5032,6 +5111,10 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta }; if (offline) { + /* libcryptsetup does not currently support reencryption of devices with integrity profiles.*/ + if (p->integrity == INTEGRITY_INLINE) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Integrity=inline cannot be enabled in offline mode."); + r = var_tmp_dir(&vt); if (r < 0) return log_error_errno(r, "Failed to determine temporary files directory: %m"); @@ -5084,7 +5167,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta "xts-plain64", SD_ID128_TO_UUID_STRING(p->luks_uuid), NULL, - VOLUME_KEY_SIZE, + /* volume_key_size= */ volume_key_size, &luks_params); if (r < 0) return log_error_errno(r, "Failed to LUKS2 format future partition: %m"); @@ -5097,7 +5180,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta cd, CRYPT_ANY_SLOT, NULL, - VOLUME_KEY_SIZE, + /* volume_key_size= */ volume_key_size, strempty(iovec_key->iov_base), iovec_key->iov_len); if (r < 0) @@ -5272,7 +5355,7 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta cd, CRYPT_ANY_SLOT, /* volume_key= */ NULL, - /* volume_key_size= */ VOLUME_KEY_SIZE, + /* volume_key_size= */ volume_key_size, base64_encoded, base64_encoded_size); if (keyslot < 0) @@ -5372,11 +5455,30 @@ static int partition_encrypt(Context *context, Partition *p, PartitionTarget *ta cd, dm_name, NULL, - VOLUME_KEY_SIZE, - (arg_discard ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0) | CRYPT_ACTIVATE_PRIVATE); + /* volume_key_size= */ volume_key_size, + (arg_discard && p->integrity != INTEGRITY_INLINE ? CRYPT_ACTIVATE_ALLOW_DISCARDS : 0) | CRYPT_ACTIVATE_PRIVATE); if (r < 0) return log_error_errno(r, "Failed to activate LUKS superblock: %m"); + /* crypt_wipe() the whole device to avoid integrity errors upon mkfs */ + if (p->integrity == INTEGRITY_INLINE) { + r = sym_crypt_wipe( + cd, + vol, + CRYPT_WIPE_ZERO, + /* offset= */ 0, + /* length= */ 0, + /* wipe_block_size= */ 1 * U64_MB, + /* flags= */ 0, + /* progress= */ NULL, + /* usrptr= */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to wipe LUKS device: %m"); + + log_info("%s integrity protection for future partition %" PRIu64 " initialized.", + integrity_alg_to_string(p->integrity_alg), p->partno); + } + dev_fd = open(vol, O_RDWR|O_CLOEXEC|O_NOCTTY); if (dev_fd < 0) return log_error_errno(errno, "Failed to open LUKS volume '%s': %m", vol); diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index 7f5ab38ef8..e5dc86b501 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -70,6 +70,7 @@ DLSYM_PROTOTYPE(crypt_token_set_external_path) = NULL; DLSYM_PROTOTYPE(crypt_token_status) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_get) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_keyring) = NULL; +DLSYM_PROTOTYPE(crypt_wipe) = NULL; static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) { @@ -274,7 +275,8 @@ int dlopen_cryptsetup(void) { #endif DLSYM_ARG(crypt_token_status), DLSYM_ARG(crypt_volume_key_get), - DLSYM_ARG(crypt_volume_key_keyring)); + DLSYM_ARG(crypt_volume_key_keyring), + DLSYM_ARG(crypt_wipe)); if (r <= 0) return r; diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index ce9782b7c4..8555625732 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -68,6 +68,7 @@ extern DLSYM_PROTOTYPE(crypt_token_set_external_path); extern DLSYM_PROTOTYPE(crypt_token_status); extern DLSYM_PROTOTYPE(crypt_volume_key_get); extern DLSYM_PROTOTYPE(crypt_volume_key_keyring); +extern DLSYM_PROTOTYPE(crypt_wipe); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL); From cb2bc7575a8ded46b1743954c07e8583545c50e0 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Mon, 13 Oct 2025 15:59:29 +0200 Subject: [PATCH 2/5] TEST-58-REPART: add testcase for LUKS2 integrity Create an encrypted partition with LUKS2 integrity checking enabled, verify that dm-integrity mapping appears. --- test/units/TEST-58-REPART.sh | 61 ++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/test/units/TEST-58-REPART.sh b/test/units/TEST-58-REPART.sh index de64c84c65..386462aa26 100755 --- a/test/units/TEST-58-REPART.sh +++ b/test/units/TEST-58-REPART.sh @@ -1838,6 +1838,67 @@ EOF cmp "$imgs/disk1.img" "$imgs/disk3.img" } +_test_luks2_integrity() { + local defs imgs output root + + if [[ "$OFFLINE" != "no" ]]; then + return 0 + fi + + defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" + imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" + root="$(mktemp --directory "/var/test-repart.root.XXXXXXXXXX")" + # shellcheck disable=SC2064 + trap "rm -rf '$defs' '$imgs' '$root'" RETURN + chmod 0755 "$defs" + + echo "*** testcase for LUKS2 integrity ***" + + tee "$defs/root.conf" <> "$defs/root.conf" + + systemd-repart --pretty=yes \ + --definitions "$defs" \ + --empty=create \ + --size=100M \ + --seed="$seed" \ + --dry-run=no \ + --offline=no \ + "$imgs/encint.img" + + loop="$(losetup -P --show --find "$imgs/encint.img")" + udevadm wait --timeout=60 --settle "${loop:?}p1" + + volume="test-repart-luksint-$RANDOM" + dmstatus="$imgs/dmsetup-$RANDOM" + + touch "$imgs/empty-password" + + # the expectation for hmac-sha256 is 'integrity: hmac(sha256)' + cryptsetup luksDump "${loop}p1" | grep -q "integrity: $(echo "$1" | sed -r 's/^hmac-(.*)$/hmac(\1)/')" + + cryptsetup open --type=luks2 --key-file="$imgs/empty-password" "${loop}p1" "$volume" + dmsetup status > "$dmstatus" + cryptsetup close "$volume" + losetup -d "$loop" + # Check that there's a dm-integrity entry + grep -q "$volume""_dif.* integrity " "$dmstatus" +} + +testcase_luks2_integrity() { + _test_luks2_integrity "" + _test_luks2_integrity "hmac-sha1" + _test_luks2_integrity "hmac-sha256" + _test_luks2_integrity "hmac-sha512" +} + OFFLINE="yes" run_testcases From 771085291dab707b2c7502420b81f7890aa61213 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Thu, 16 Oct 2025 14:47:22 +0200 Subject: [PATCH 3/5] dissect-image: introduce 'encryptedwithintegrity' policy LUKS2 supports built-in integrity checking which may come very handy to mitigate partial rollback attacks on the storage when only some specific parts are restored to some old encrypted state. Specific use-cases like Confidential VMs may want to mandate the usage of feature e.g. on the root volume. Introduce "encryptedwithintegrity" image policy to support that. Note, due to the current libcryptsetup limitations, checking whether the feature is enabled or not for the 'file' case (e.g. DDI image as a raw file) requires setting up a loop device. To avoid that and keep dissect fully functional when working unpriviliged, implement a minimal custom LUKS header parser. --- src/analyze/analyze-image-policy.c | 4 +- src/shared/cryptsetup-util.c | 4 +- src/shared/cryptsetup-util.h | 1 + src/shared/dissect-image.c | 131 ++++++++++++++++++++++++++++- src/shared/image-policy.c | 50 ++++++----- src/shared/image-policy.h | 41 ++++----- src/test/test-image-policy.c | 4 +- test/units/TEST-65-ANALYZE.sh | 2 +- 8 files changed, 184 insertions(+), 53 deletions(-) diff --git a/src/analyze/analyze-image-policy.c b/src/analyze/analyze-image-policy.c index 2075d1b437..aa7f744555 100644 --- a/src/analyze/analyze-image-policy.c +++ b/src/analyze/analyze-image-policy.c @@ -20,8 +20,8 @@ static int table_add_designator_line(Table *table, PartitionDesignator d, Partit return log_oom(); color = (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_IGNORE ? ansi_grey() : - ((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) == - (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() : + ((f & (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) == + (PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ABSENT)) ? ansi_highlight_yellow() : (f & _PARTITION_POLICY_USE_MASK) == PARTITION_POLICY_ABSENT ? ansi_highlight_red() : !(f & PARTITION_POLICY_UNPROTECTED) ? ansi_highlight_green() : NULL; diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c index e5dc86b501..90e98c1b83 100644 --- a/src/shared/cryptsetup-util.c +++ b/src/shared/cryptsetup-util.c @@ -71,6 +71,7 @@ DLSYM_PROTOTYPE(crypt_token_status) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_get) = NULL; DLSYM_PROTOTYPE(crypt_volume_key_keyring) = NULL; DLSYM_PROTOTYPE(crypt_wipe) = NULL; +DLSYM_PROTOTYPE(crypt_get_integrity_info) = NULL; static void cryptsetup_log_glue(int level, const char *msg, void *usrptr) { @@ -276,7 +277,8 @@ int dlopen_cryptsetup(void) { DLSYM_ARG(crypt_token_status), DLSYM_ARG(crypt_volume_key_get), DLSYM_ARG(crypt_volume_key_keyring), - DLSYM_ARG(crypt_wipe)); + DLSYM_ARG(crypt_wipe), + DLSYM_ARG(crypt_get_integrity_info)); if (r <= 0) return r; diff --git a/src/shared/cryptsetup-util.h b/src/shared/cryptsetup-util.h index 8555625732..7c52049796 100644 --- a/src/shared/cryptsetup-util.h +++ b/src/shared/cryptsetup-util.h @@ -69,6 +69,7 @@ extern DLSYM_PROTOTYPE(crypt_token_status); extern DLSYM_PROTOTYPE(crypt_volume_key_get); extern DLSYM_PROTOTYPE(crypt_volume_key_keyring); extern DLSYM_PROTOTYPE(crypt_wipe); +extern DLSYM_PROTOTYPE(crypt_get_integrity_info); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, crypt_free, NULL); DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct crypt_device *, sym_crypt_free, NULL); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 05c2514593..0fe3a5b3e0 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -397,6 +397,114 @@ static int image_policy_check_protection( return 0; } +/* internal LUKS2 header defines */ +#define LUKS2_FIXED_HDR_SIZE UINT64_C(0x1000) +#define LUKS2_MAGIC "LUKS\xba\xbe" + +/* Matches the beginning of 'struct luks2_hdr_disk' from cryptsetup */ +struct luks_header_incomplete { + char luks_magic[sizeof(LUKS2_MAGIC) - 1]; + be16_t version; + be64_t hdr_len; +}; + +/* 'integrity' information from LUKS JSON header. Currenly, only 'type' is extracted/checked. */ +struct luks_integrity_data { + char *type; +}; + +static int integrity_information(const char *name, sd_json_variant *v, sd_json_dispatch_flags_t flags, void *userdata) { + static const sd_json_dispatch_field table[] = { + { "type", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, offsetof(struct luks_integrity_data, type), SD_JSON_MANDATORY }, + {} + }; + + return sd_json_dispatch(v, table, flags, userdata); +} + +/* cryptsetup needs a loop device to work with a partition which has offset/size but + * dissect may be running unpriviliged. Implement a minimal custom LUKS header parser + * checking integrity protection information. */ +static int partition_is_luks2_integrity(int part_fd, uint64_t offset, uint64_t size) { + _cleanup_(sd_json_variant_unrefp) sd_json_variant *v = NULL; + _cleanup_free_ char *json = NULL; + sd_json_variant *w; + const char *key; + struct luks_header_incomplete header; + ssize_t sz, json_len; + int r; + + assert(part_fd >= 0); + + if (size < LUKS2_FIXED_HDR_SIZE) { + log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Partition is too small to contain a LUKS header."); + return 0; + } + + sz = pread(part_fd, &header, sizeof(header), offset); + if (sz < 0) + return log_error_errno(errno, "Failed to read LUKS header."); + if (sz != sizeof(header)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read LUKS header."); + + if (memcmp(header.luks_magic, LUKS2_MAGIC, sizeof(header.luks_magic)) != 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Partition's magic is not LUKS."); + + if (be16toh(header.version) != 2) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unsupported LUKS header version: %" PRIu16 ".", be16toh(header.version)); + + if (be64toh(header.hdr_len) > size) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "LUKS header length exceeds partition size."); + + if (be64toh(header.hdr_len) <= LUKS2_FIXED_HDR_SIZE || offset > UINT64_MAX - be64toh(header.hdr_len)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid LUKS header length: %" PRIu64 ".", be64toh(header.hdr_len)); + + json_len = be64toh(header.hdr_len) - LUKS2_FIXED_HDR_SIZE; + json = malloc(json_len + 1); + if (!json) + return -ENOMEM; + + sz = pread(part_fd, json, json_len, offset + LUKS2_FIXED_HDR_SIZE); + if (sz < 0) + return log_error_errno(errno, "Failed to read LUKS JSON header."); + if (sz != json_len) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read LUKS JSON header."); + json[sz] = '\0'; + + r = sd_json_parse(json, /* flags = */ 0, &v, /* reterr_line = */ NULL, /* reterr_column = */ NULL); + if (r < 0) + return log_error_errno(r, "Failed to parse LUKS JSON header."); + + v = sd_json_variant_by_key(v, "segments"); + if (!v || !sd_json_variant_is_object(v)) { + log_debug("LUKS JSON header lacks 'segments' information, assuming no integrity."); + return 0; + } + + /* Verify that all segments have integrity protection */ + JSON_VARIANT_OBJECT_FOREACH(key, w, v) { + struct luks_integrity_data data = {}; + + static const sd_json_dispatch_field dispatch_segment[] = { + { "integrity", SD_JSON_VARIANT_OBJECT, integrity_information, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_json_dispatch(w, dispatch_segment, SD_JSON_ALLOW_EXTENSIONS, &data); + if (r < 0) { + log_debug("Failed to get integrity information from LUKS JSON for segment %s, assuming no integrity.", key); + return 0; + } + + /* We don't require a particular integrity algorithm, everything but 'none' (which shouldn't + * be there in the first place but is theoretically possible) works. */ + if (streq(data.type, "none")) + return 0; + } + + return 1; +} + static int image_policy_check_partition_flags( const ImagePolicy *policy, PartitionDesignator designator, @@ -457,7 +565,16 @@ static int dissected_image_probe_filesystems( if (streq_ptr(p->fstype, "crypto_LUKS")) { m->encrypted = true; - found_flags = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ENCRYPTED; /* found this one, and its definitely encrypted */ + + if (p->mount_node_fd >= 0) + r = partition_is_luks2_integrity(p->mount_node_fd, /* offset = */ 0, /* size = */ UINT64_MAX); + else + r = partition_is_luks2_integrity(fd, p->offset, p->size); + if (r < 0) + return r; + + /* found this one, it's definitely encrypted + with or without integrity checking */ + found_flags = PARTITION_POLICY_UNUSED|(r > 0 ? PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY : PARTITION_POLICY_ENCRYPTED); } else /* found it, but it's definitely not encrypted, hence mask the encrypted flag, but * set all other ways that indicate "present". */ @@ -948,8 +1065,14 @@ static int dissect_image( if (verity_settings_data_covers(verity, PARTITION_ROOT)) found_flags = iovec_is_set(&verity->root_hash_sig) ? PARTITION_POLICY_SIGNED : PARTITION_POLICY_VERITY; - else - found_flags = encrypted ? PARTITION_POLICY_ENCRYPTED : PARTITION_POLICY_UNPROTECTED; + else if (encrypted) { + r = partition_is_luks2_integrity(fd, /* offset = */ 0, /* size = */ UINT64_MAX); + if (r < 0) + return r; + + found_flags = r > 0 ? PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY : PARTITION_POLICY_ENCRYPTED; + } else + found_flags = PARTITION_POLICY_UNPROTECTED; r = image_policy_check_protection(policy, PARTITION_ROOT, found_flags); if (r < 0) @@ -1732,7 +1855,7 @@ static int dissect_image( /* Determine the verity protection level for this partition. */ PartitionPolicyFlags found_flags; if (m->partitions[di].found) { - found_flags = PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED; + found_flags = PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED; PartitionDesignator vi = partition_verity_hash_of(di); if (vi >= 0 && m->partitions[vi].found) { diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 59d422dcd2..e917b03b06 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -78,7 +78,7 @@ static PartitionPolicyFlags partition_policy_normalized_flags(const PartitionPol * all needs no protection, because it *is* the protection */ if (partition_verity_hash_to_data(policy->designator) >= 0 || partition_verity_sig_to_data(policy->designator) >= 0) - flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED); + flags &= ~(PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY); /* if this designator has no verity concept, then mask off verity protection flags */ if (partition_verity_hash_of(policy->designator) < 0) @@ -187,6 +187,8 @@ static PartitionPolicyFlags policy_flag_from_string_one(const char *s) { return PARTITION_POLICY_SIGNED; if (streq(s, "encrypted")) return PARTITION_POLICY_ENCRYPTED; + if (streq(s, "encryptedwithintegrity")) + return PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY; if (streq(s, "unprotected")) return PARTITION_POLICY_UNPROTECTED; if (streq(s, "unused")) @@ -423,6 +425,8 @@ int partition_policy_flags_to_string(PartitionPolicyFlags flags, bool simplify, l[m++] = "signed"; if (flags & PARTITION_POLICY_ENCRYPTED) l[m++] = "encrypted"; + if (flags & PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY) + l[m++] = "encryptedwithintegrity"; if (flags & PARTITION_POLICY_UNPROTECTED) l[m++] = "unprotected"; if (flags & PARTITION_POLICY_UNUSED) @@ -863,8 +867,8 @@ const ImagePolicy image_policy_sysext = { * be. */ .n_policies = 2, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; @@ -884,7 +888,7 @@ const ImagePolicy image_policy_confext = { * are only interested in the /etc/ tree anyway, and that's really the only place it can be. */ .n_policies = 1, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; @@ -901,14 +905,14 @@ const ImagePolicy image_policy_container = { /* For systemd-nspawn containers we use all partitions, with the exception of swap */ .n_policies = 8, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; @@ -917,15 +921,15 @@ const ImagePolicy image_policy_host = { /* For the host policy we basically use everything */ .n_policies = 9, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, { PARTITION_ESP, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, { PARTITION_XBOOTLDR, PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_SWAP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; @@ -934,12 +938,12 @@ const ImagePolicy image_policy_service = { /* For RootImage= in services we skip ESP/XBOOTLDR and swap */ .n_policies = 6, .policies = { - { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, - { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_ROOT, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_USR, PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_HOME, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_SRV, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_TMP, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, + { PARTITION_VAR, PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY|PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_ABSENT }, }, .default_flags = PARTITION_POLICY_IGNORE, }; diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index 037900b840..30d319ed69 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -14,29 +14,30 @@ typedef enum PartitionPolicyFlags { * on. Example: a default policy of "verity+encrypted" certainly makes sense, but for /home/ * partitions this gracefully degrades to "encrypted" (as we do not have a concept of verity for * /home/), and so on. */ - PARTITION_POLICY_VERITY = 1 << 0, /* must exist, activate with verity (only applies to root/usr partitions) */ - PARTITION_POLICY_SIGNED = 1 << 1, /* must exist, activate with signed verity (only applies to root/usr partitions) */ - PARTITION_POLICY_ENCRYPTED = 1 << 2, /* must exist, activate with LUKS encryption (applies to any data partition, but not to verity/signature partitions */ - PARTITION_POLICY_UNPROTECTED = 1 << 3, /* must exist, activate without encryption/verity */ - PARTITION_POLICY_UNUSED = 1 << 4, /* must exist, don't use */ - PARTITION_POLICY_ABSENT = 1 << 5, /* must not exist */ - PARTITION_POLICY_OPEN = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED| - PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT, - PARTITION_POLICY_IGNORE = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT, - _PARTITION_POLICY_USE_MASK = PARTITION_POLICY_OPEN, + PARTITION_POLICY_VERITY = 1 << 0, /* must exist, activate with verity (only applies to root/usr partitions) */ + PARTITION_POLICY_SIGNED = 1 << 1, /* must exist, activate with signed verity (only applies to root/usr partitions) */ + PARTITION_POLICY_ENCRYPTED = 1 << 2, /* must exist, activate with LUKS encryption (applies to any data partition, but not to verity/signature partitions */ + PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY = 1 << 3, /* same as PARTITION_POLICY_ENCRYPTED but also requires integrity checking */ + PARTITION_POLICY_UNPROTECTED = 1 << 4, /* must exist, activate without encryption/verity */ + PARTITION_POLICY_UNUSED = 1 << 5, /* must exist, don't use */ + PARTITION_POLICY_ABSENT = 1 << 6, /* must not exist */ + PARTITION_POLICY_OPEN = PARTITION_POLICY_VERITY|PARTITION_POLICY_SIGNED|PARTITION_POLICY_ENCRYPTED|PARTITION_POLICY_ENCRYPTEDWITHINTEGRITY| + PARTITION_POLICY_UNPROTECTED|PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT, + PARTITION_POLICY_IGNORE = PARTITION_POLICY_UNUSED|PARTITION_POLICY_ABSENT, + _PARTITION_POLICY_USE_MASK = PARTITION_POLICY_OPEN, - PARTITION_POLICY_READ_ONLY_OFF = 1 << 6, /* State of GPT partition flag "read-only" must be on */ - PARTITION_POLICY_READ_ONLY_ON = 1 << 7, - _PARTITION_POLICY_READ_ONLY_MASK = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON, - PARTITION_POLICY_GROWFS_OFF = 1 << 8, /* State of GPT partition flag "growfs" must be on */ - PARTITION_POLICY_GROWFS_ON = 1 << 9, - _PARTITION_POLICY_GROWFS_MASK = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON, - _PARTITION_POLICY_PFLAGS_MASK = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK, + PARTITION_POLICY_READ_ONLY_OFF = 1 << 7, /* State of GPT partition flag "read-only" must be on */ + PARTITION_POLICY_READ_ONLY_ON = 1 << 8, + _PARTITION_POLICY_READ_ONLY_MASK = PARTITION_POLICY_READ_ONLY_OFF|PARTITION_POLICY_READ_ONLY_ON, + PARTITION_POLICY_GROWFS_OFF = 1 << 9, /* State of GPT partition flag "growfs" must be on */ + PARTITION_POLICY_GROWFS_ON = 1 << 10, + _PARTITION_POLICY_GROWFS_MASK = PARTITION_POLICY_GROWFS_OFF|PARTITION_POLICY_GROWFS_ON, + _PARTITION_POLICY_PFLAGS_MASK = _PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK, - _PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK, + _PARTITION_POLICY_MASK = _PARTITION_POLICY_USE_MASK|_PARTITION_POLICY_READ_ONLY_MASK|_PARTITION_POLICY_GROWFS_MASK, - _PARTITION_POLICY_FLAGS_INVALID = -EINVAL, - _PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */ + _PARTITION_POLICY_FLAGS_INVALID = -EINVAL, + _PARTITION_POLICY_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */ } PartitionPolicyFlags; assert_cc((_PARTITION_POLICY_USE_MASK | _PARTITION_POLICY_PFLAGS_MASK) >= 0); /* ensure flags don't collide with errno range */ diff --git a/src/test/test-image-policy.c b/src/test/test-image-policy.c index 6cf7dff20b..512df19078 100644 --- a/src/test/test-image-policy.c +++ b/src/test/test-image-policy.c @@ -99,8 +99,8 @@ TEST_RET(test_image_policy_to_string) { test_policy_equiv("~", image_policy_equiv_deny); test_policy_equiv("=absent", image_policy_equiv_deny); test_policy_equiv("=open", image_policy_equiv_allow); - test_policy_equiv("=verity+signed+encrypted+unprotected+unused+absent", image_policy_equiv_allow); - test_policy_equiv("=signed+verity+encrypted+unused+unprotected+absent", image_policy_equiv_allow); + test_policy_equiv("=verity+signed+encrypted+encryptedwithintegrity+unprotected+unused+absent", image_policy_equiv_allow); + test_policy_equiv("=signed+verity+encrypted+encryptedwithintegrity+unused+unprotected+absent", image_policy_equiv_allow); test_policy_equiv("=ignore", image_policy_equiv_ignore); test_policy_equiv("=absent+unused", image_policy_equiv_ignore); test_policy_equiv("=unused+absent", image_policy_equiv_ignore); diff --git a/test/units/TEST-65-ANALYZE.sh b/test/units/TEST-65-ANALYZE.sh index ed20be680a..f98ececc53 100755 --- a/test/units/TEST-65-ANALYZE.sh +++ b/test/units/TEST-65-ANALYZE.sh @@ -1083,7 +1083,7 @@ check deny no "$name" # Let's also test the "image-policy" verb -systemd-analyze image-policy '*' 2>&1 | grep -F "Long form: =verity+signed+encrypted+unprotected+unused+absent" >/dev/null +systemd-analyze image-policy '*' 2>&1 | grep -F "Long form: =verity+signed+encrypted+encryptedwithintegrity+unprotected+unused+absent" >/dev/null systemd-analyze image-policy '-' 2>&1 | grep -F "Long form: =unused+absent" >/dev/null systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -F "Long form: usr=verity:home=encrypted:=unused+absent" >/dev/null systemd-analyze image-policy 'home=encrypted:usr=verity' 2>&1 | grep -e '^home \+encrypted \+' >/dev/null From bb09cc02bdb9940c73e946116cda4f6b2dc076d6 Mon Sep 17 00:00:00 2001 From: Vitaly Kuznetsov Date: Tue, 21 Oct 2025 15:23:32 +0200 Subject: [PATCH 4/5] TEST-50-DISSECT: test 'encrypted' and 'encryptedwithintegrity' policies Test the newly introduced 'encryptedwithintegrity' image policy and verify that it's different from 'encrypted' one. --- test/units/TEST-50-DISSECT.encrypted.sh | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100755 test/units/TEST-50-DISSECT.encrypted.sh diff --git a/test/units/TEST-50-DISSECT.encrypted.sh b/test/units/TEST-50-DISSECT.encrypted.sh new file mode 100755 index 0000000000..9d067f597a --- /dev/null +++ b/test/units/TEST-50-DISSECT.encrypted.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: LGPL-2.1-or-later +set -eux +set -o pipefail + +defs="$(mktemp --directory "/tmp/test-repart.defs.XXXXXXXXXX")" +imgs="$(mktemp --directory "/var/tmp/test-repart.imgs.XXXXXXXXXX")" +# shellcheck disable=SC2064 +trap "rm -rf '$defs' '$imgs'" RETURN +chmod 0755 "$defs" + +echo "*** testcase for systemd-dissect and encrypted partitions ***" + +tee "$defs/root.conf" < Date: Thu, 23 Oct 2025 17:35:47 +0200 Subject: [PATCH 5/5] TODO: add unimplemented Integrity= systemd-repart options --- TODO | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/TODO b/TODO index b1bc038ef5..d849c75a5f 100644 --- a/TODO +++ b/TODO @@ -137,6 +137,12 @@ Deprecations and removals: Features: +* systemd-repart: implement Integrity=data/meta and Integrity=inline for non-LUKS + case. Currently, only Integrity=inline combined with Encrypt= is implemented + and uses libcryptsetup features. Add support for plain dm-integrity setups when + integrity tags are stored by the device (inline), interleaved with data (data), + and on a separate device (meta). + * networkd/machined: implement reverse name lookups in the resolved hook * networkd's resolved hook: optionally map all lease IP addresses handed out to