aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOndrej Kozina <okozina@redhat.com>2023-12-13 15:06:03 +0100
committerMilan Broz <gmazyland@gmail.com>2023-12-13 20:59:14 +0000
commit0ca1e680db26aedab26b879b81b9dce5cb013570 (patch)
tree5a55c9a180a32786267af2fb30adeac71747d9e6
parent2e978c8776e07917e82d3c2c2eedd079e39b7293 (diff)
downloadcryptsetup-0ca1e680db26aedab26b879b81b9dce5cb013570.tar.gz
opal: add exclusive lock to avoid race.
Activating LUKS2 device with OPAL support is multistep process. 1) read LR state 2) unlock LR 3) activate dm device 4) in case step 3) failed lock the device if in step 1) the device was locked. Otherwise, in case parallel activation happened on one device the process that failed to map dm device (device already active) could relock the LR afterwards and effectively break already active device. To avoid that we do steps 1) through 4) protected by exclusive opal lock unique per data block device configured for use with LUKS2 OPAL support.
-rw-r--r--lib/luks2/hw_opal/hw_opal.c65
-rw-r--r--lib/luks2/hw_opal/hw_opal.h6
-rw-r--r--lib/luks2/luks2_json_metadata.c24
-rw-r--r--lib/setup.c30
-rw-r--r--lib/utils_wipe.c9
5 files changed, 130 insertions, 4 deletions
diff --git a/lib/luks2/hw_opal/hw_opal.c b/lib/luks2/hw_opal/hw_opal.c
index b56cb64b..e40d138e 100644
--- a/lib/luks2/hw_opal/hw_opal.c
+++ b/lib/luks2/hw_opal/hw_opal.c
@@ -28,10 +28,15 @@
#include <assert.h>
#include <sys/ioctl.h>
#include <sys/types.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_SYSMACROS_H
+# include <sys/sysmacros.h> /* for major, minor */
+#endif
#include "internal.h"
#include "libcryptsetup.h"
#include "luks2/hw_opal/hw_opal.h"
+#include "utils_device_locking.h"
#if HAVE_HW_OPAL
@@ -396,6 +401,7 @@ static int opal_enabled(struct crypt_device *cd, struct device *dev)
return opal_query_status(cd, dev, OPAL_FL_LOCKING_ENABLED);
}
+/* requires opal lock */
int opal_setup_ranges(struct crypt_device *cd,
struct device *dev,
const struct volume_key *vk,
@@ -747,11 +753,13 @@ out:
return r;
}
+/* requires opal lock */
int opal_lock(struct crypt_device *cd, struct device *dev, uint32_t segment_number)
{
return opal_lock_unlock(cd, dev, segment_number, NULL, /* lock= */ true);
}
+/* requires opal lock */
int opal_unlock(struct crypt_device *cd,
struct device *dev,
uint32_t segment_number,
@@ -807,6 +815,7 @@ out:
return r;
}
+/* requires opal lock */
int opal_reset_segment(struct crypt_device *cd,
struct device *dev,
uint32_t segment_number,
@@ -914,6 +923,7 @@ int opal_geometry(struct crypt_device *cd,
ret_alignment_granularity_blocks, ret_lowest_lba_blocks);
}
+/* requires opal lock */
int opal_range_check_attributes_and_get_lock_state(struct crypt_device *cd,
struct device *dev,
uint32_t segment_number,
@@ -938,6 +948,52 @@ int opal_range_check_attributes_and_get_lock_state(struct crypt_device *cd,
NULL, ret_read_locked, ret_write_locked);
}
+static int opal_lock_internal(struct crypt_device *cd, struct device *opal_device, struct crypt_lock_handle **opal_lock)
+{
+ char *lock_resource;
+ int devfd, r;
+ struct stat st;
+
+ if (!crypt_metadata_locking_enabled()) {
+ *opal_lock = NULL;
+ return 0;
+ }
+
+ /*
+ * This also asserts we do not hold any metadata lock on the same device to
+ * avoid deadlock (OPAL lock must be taken first)
+ */
+ devfd = device_open(cd, opal_device, O_RDONLY);
+ if (devfd < 0)
+ return -EINVAL;
+
+ if (fstat(devfd, &st) || !S_ISBLK(st.st_mode))
+ return -EINVAL;
+
+ r = asprintf(&lock_resource, "OPAL_%d:%d", major(st.st_rdev), minor(st.st_rdev));
+ if (r < 0)
+ return -ENOMEM;
+
+ r = crypt_write_lock(cd, lock_resource, true, opal_lock);
+
+ free(lock_resource);
+
+ return r;
+}
+
+int opal_exclusive_lock(struct crypt_device *cd, struct device *opal_device, struct crypt_lock_handle **opal_lock)
+{
+ if (!cd || !opal_device || (crypt_get_type(cd) && strcmp(crypt_get_type(cd), CRYPT_LUKS2)))
+ return -EINVAL;
+
+ return opal_lock_internal(cd, opal_device, opal_lock);
+}
+
+void opal_exclusive_unlock(struct crypt_device *cd, struct crypt_lock_handle *opal_lock)
+{
+ crypt_unlock_internal(cd, opal_lock);
+}
+
#else
#pragma GCC diagnostic ignored "-Wunused-parameter"
@@ -1010,4 +1066,13 @@ int opal_range_check_attributes_and_get_lock_state(struct crypt_device *cd,
return -ENOTSUP;
}
+int opal_exclusive_lock(struct crypt_device *cd, struct device *opal_device, struct crypt_lock_handle **opal_lock)
+{
+ return -ENOTSUP;
+}
+
+void opal_exclusive_unlock(struct crypt_device *cd, struct crypt_lock_handle *opal_lock)
+{
+}
+
#endif
diff --git a/lib/luks2/hw_opal/hw_opal.h b/lib/luks2/hw_opal/hw_opal.h
index b2835dab..f1823bfe 100644
--- a/lib/luks2/hw_opal/hw_opal.h
+++ b/lib/luks2/hw_opal/hw_opal.h
@@ -24,6 +24,8 @@
#include "internal.h"
+struct crypt_lock_handle;
+
int opal_setup_ranges(struct crypt_device *cd,
struct device *dev,
const struct volume_key *vk,
@@ -61,5 +63,9 @@ int opal_range_check_attributes_and_get_lock_state(struct crypt_device *cd,
const uint64_t *check_length_sectors,
bool *ret_read_locked,
bool *ret_write_locked);
+int opal_exclusive_lock(struct crypt_device *cd,
+ struct device *opal_device,
+ struct crypt_lock_handle **opal_lock);
+void opal_exclusive_unlock(struct crypt_device *cd, struct crypt_lock_handle *opal_lock);
#endif
diff --git a/lib/luks2/luks2_json_metadata.c b/lib/luks2/luks2_json_metadata.c
index c6084f4a..c2a94c62 100644
--- a/lib/luks2/luks2_json_metadata.c
+++ b/lib/luks2/luks2_json_metadata.c
@@ -2651,6 +2651,7 @@ int LUKS2_activate(struct crypt_device *cd,
struct crypt_dm_active_device dmdi = {}, dmd = {
.uuid = crypt_get_uuid(cd)
};
+ struct crypt_lock_handle *opal_lh = NULL;
/* do not allow activation when particular requirements detected */
if ((r = LUKS2_unmet_requirements(cd, hdr, CRYPT_REQUIREMENT_OPAL, 0)))
@@ -2697,11 +2698,17 @@ int LUKS2_activate(struct crypt_device *cd,
}
range_offset_sectors = crypt_get_data_offset(cd) + crypt_dev_partition_offset(device_path(crypt_data_device(cd)));
+ r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh);
+ if (r < 0) {
+ log_err(cd, _("Failed to acquire opal lock on device %s."), device_path(crypt_data_device(cd)));
+ return -EINVAL;
+ }
+
r = opal_range_check_attributes_and_get_lock_state(cd, crypt_data_device(cd), opal_segment_number,
opal_key, &range_offset_sectors, &range_length_sectors,
&read_lock, &write_lock);
if (r < 0)
- return r;
+ goto out;
opal_lock_on_error = read_lock && write_lock;
if (!opal_lock_on_error && !(flags & CRYPT_ACTIVATE_REFRESH))
@@ -2710,7 +2717,7 @@ int LUKS2_activate(struct crypt_device *cd,
r = opal_unlock(cd, crypt_data_device(cd), opal_segment_number, opal_key);
if (r < 0)
- return r;
+ goto out;
}
if (LUKS2_segment_is_type(hdr, CRYPT_DEFAULT_SEGMENT, "crypt") ||
@@ -2779,6 +2786,8 @@ out:
if (r < 0 && opal_lock_on_error)
opal_lock(cd, crypt_data_device(cd), opal_segment_number);
+ opal_exclusive_unlock(cd, opal_lh);
+
return r;
}
@@ -2815,7 +2824,7 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr
uint32_t opal_segment_number;
char **dep, deps_uuid_prefix[40], *deps[MAX_DM_DEPS+1] = { 0 };
const char *namei = NULL;
- struct crypt_lock_handle *reencrypt_lock = NULL;
+ struct crypt_lock_handle *reencrypt_lock = NULL, *opal_lh = NULL;
if (!dmd || !dmd->uuid || strncmp(CRYPT_LUKS2, dmd->uuid, sizeof(CRYPT_LUKS2)-1))
return -EINVAL;
@@ -2940,10 +2949,19 @@ int LUKS2_deactivate(struct crypt_device *cd, const char *name, struct luks2_hdr
opal_segment_number = ret;
}
+ if (crypt_data_device(cd)) {
+ r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh);
+ if (r < 0) {
+ log_err(cd, _("Failed to acquire opal lock on device %s."), device_path(crypt_data_device(cd)));
+ goto out;
+ }
+ }
+
if (!crypt_data_device(cd) || opal_lock(cd, crypt_data_device(cd), opal_segment_number))
log_err(cd, _("Device %s was deactivated but hardware OPAL device cannot be locked."), name);
}
out:
+ opal_exclusive_unlock(cd, opal_lh);
LUKS2_reencrypt_unlock(cd, reencrypt_lock);
dep = deps;
while (*dep)
diff --git a/lib/setup.c b/lib/setup.c
index b4455fe5..bbcdf66b 100644
--- a/lib/setup.c
+++ b/lib/setup.c
@@ -2333,6 +2333,7 @@ int crypt_format_luks2_opal(struct crypt_device *cd,
required_alignment_bytes, metadata_size_bytes, keyslots_size_bytes,
provided_data_sectors;
struct volume_key *user_key = NULL;
+ struct crypt_lock_handle *opal_lh = NULL;
if (!cd || !params || !opal_params ||
!opal_params->admin_key || !opal_params->admin_key_size || !opal_params->user_key_size)
@@ -2545,6 +2546,12 @@ int crypt_format_luks2_opal(struct crypt_device *cd,
range_offset_blocks = (data_offset_bytes + partition_offset_sectors * SECTOR_SIZE) / opal_block_bytes;
+ r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh);
+ if (r < 0) {
+ log_err(cd, _("Failed to acquire opal lock on device %s."), device_path(crypt_data_device(cd)));
+ goto out;
+ }
+
r = opal_setup_ranges(cd, crypt_data_device(cd), user_key ?: cd->volume_key,
range_offset_blocks, range_size_bytes / opal_block_bytes,
opal_segment_number, opal_params->admin_key, opal_params->admin_key_size);
@@ -2636,8 +2643,10 @@ out:
if (subsystem_overridden)
params->subsystem = NULL;
- if (r >= 0)
+ if (r >= 0) {
+ opal_exclusive_unlock(cd, opal_lh);
return 0;
+ }
if (opal_range_reset &&
(opal_reset_segment(cd, crypt_data_device(cd), opal_segment_number,
@@ -2645,6 +2654,7 @@ out:
log_err(cd, _("Locking range %d reset on device %s failed."),
opal_segment_number, device_path(crypt_data_device(cd)));
+ opal_exclusive_unlock(cd, opal_lh);
LUKS2_hdr_free(cd, &cd->u.luks2.hdr);
crypt_set_null_type(cd);
@@ -3930,6 +3940,7 @@ int crypt_suspend(struct crypt_device *cd,
uint32_t opal_segment_number = 1, dmflags = DM_SUSPEND_WIPE_KEY;
struct dm_target *tgt = &dmd.segment;
char *key_desc = NULL, *iname = NULL;
+ struct crypt_lock_handle *opal_lh = NULL;
if (!cd || !name)
return -EINVAL;
@@ -4050,9 +4061,18 @@ int crypt_suspend(struct crypt_device *cd,
crypt_drop_keyring_key_by_description(cd, key_desc, cd->keyring_key_type);
+ if (dm_opal_uuid && crypt_data_device(cd)) {
+ r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh);
+ if (r < 0) {
+ log_err(cd, _("Failed to acquire opal lock on device %s."), device_path(crypt_data_device(cd)));
+ goto out;
+ }
+ }
+
if (dm_opal_uuid && (!crypt_data_device(cd) || opal_lock(cd, crypt_data_device(cd), opal_segment_number)))
log_err(cd, _("Device %s was suspended but hardware OPAL device cannot be locked."), name);
out:
+ opal_exclusive_unlock(cd, opal_lh);
free(key_desc);
free(iname);
dm_targets_free(cd, &dmd);
@@ -4140,6 +4160,7 @@ static int resume_luks2_by_volume_key(struct crypt_device *cd,
key_serial_t user_vk_kid = 0;
struct volume_key *p_crypt = vk, *p_opal = NULL, *zerokey = NULL, *crypt_key = NULL, *opal_key = NULL;
char *iname = NULL;
+ struct crypt_lock_handle *opal_lh = NULL;
assert(digest >= 0);
assert(vk && crypt_volume_key_get_id(vk) == digest);
@@ -4196,6 +4217,12 @@ static int resume_luks2_by_volume_key(struct crypt_device *cd,
}
if (p_opal) {
+ r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh);
+ if (r < 0) {
+ log_err(cd, _("Failed to acquire opal lock on device %s."), device_path(crypt_data_device(cd)));
+ goto out;
+ }
+
r = opal_unlock(cd, crypt_data_device(cd), opal_segment_number, p_opal);
if (r < 0) {
p_opal = NULL; /* do not lock on error path */
@@ -4233,6 +4260,7 @@ out:
if (r < 0 && p_opal)
opal_lock(cd, crypt_data_device(cd), opal_segment_number);
+ opal_exclusive_unlock(cd, opal_lh);
crypt_free_volume_key(zerokey);
crypt_free_volume_key(opal_key);
crypt_free_volume_key(crypt_key);
diff --git a/lib/utils_wipe.c b/lib/utils_wipe.c
index b2293804..0a167f54 100644
--- a/lib/utils_wipe.c
+++ b/lib/utils_wipe.c
@@ -321,6 +321,7 @@ int crypt_wipe_hw_opal(struct crypt_device *cd,
int r;
struct luks2_hdr *hdr;
uint32_t opal_segment_number;
+ struct crypt_lock_handle *opal_lh = NULL;
UNUSED(flags);
@@ -362,11 +363,19 @@ int crypt_wipe_hw_opal(struct crypt_device *cd,
} else
opal_segment_number = segment;
+ r = opal_exclusive_lock(cd, crypt_data_device(cd), &opal_lh);
+ if (r < 0) {
+ log_err(cd, _("Failed to acquire opal lock on device %s."), device_path(crypt_data_device(cd)));
+ return -EINVAL;
+ }
+
r = opal_reset_segment(cd,
crypt_data_device(cd),
opal_segment_number,
password,
password_size);
+
+ opal_exclusive_unlock(cd, opal_lh);
if (r < 0)
return r;