summaryrefslogtreecommitdiffstats
path: root/lib/luks2/hw_opal/hw_opal.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/luks2/hw_opal/hw_opal.c')
-rw-r--r--lib/luks2/hw_opal/hw_opal.c1089
1 files changed, 1089 insertions, 0 deletions
diff --git a/lib/luks2/hw_opal/hw_opal.c b/lib/luks2/hw_opal/hw_opal.c
new file mode 100644
index 0000000..31ef87e
--- /dev/null
+++ b/lib/luks2/hw_opal/hw_opal.c
@@ -0,0 +1,1089 @@
+/*
+ * OPAL utilities
+ *
+ * Copyright (C) 2022-2023 Luca Boccassi <bluca@debian.org>
+ * 2023 Ondrej Kozina <okozina@redhat.com>
+ *
+ * This file is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This file is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this file; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#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
+
+#include <linux/sed-opal.h>
+
+/* Error codes are defined in the specification:
+ * TCG_Storage_Architecture_Core_Spec_v2.01_r1.00
+ * Section 5.1.5: Method Status Codes
+ * Names and values from table 166 */
+typedef enum OpalStatus {
+ OPAL_STATUS_SUCCESS,
+ OPAL_STATUS_NOT_AUTHORIZED,
+ OPAL_STATUS_OBSOLETE0, /* Undefined but possible return values are called 'obsolete' */
+ OPAL_STATUS_SP_BUSY,
+ OPAL_STATUS_SP_FAILED,
+ OPAL_STATUS_SP_DISABLED,
+ OPAL_STATUS_SP_FROZEN,
+ OPAL_STATUS_NO_SESSIONS_AVAILABLE,
+ OPAL_STATUS_UNIQUENESS_CONFLICT,
+ OPAL_STATUS_INSUFFICIENT_SPACE,
+ OPAL_STATUS_INSUFFICIENT_ROWS,
+ OPAL_STATUS_INVALID_PARAMETER,
+ OPAL_STATUS_OBSOLETE1,
+ OPAL_STATUS_OBSOLETE2,
+ OPAL_STATUS_TPER_MALFUNCTION,
+ OPAL_STATUS_TRANSACTION_FAILURE,
+ OPAL_STATUS_RESPONSE_OVERFLOW,
+ OPAL_STATUS_AUTHORITY_LOCKED_OUT,
+ OPAL_STATUS_FAIL = 0x3F, /* As defined by specification */
+ _OPAL_STATUS_MAX,
+ _OPAL_STATUS_INVALID = -EINVAL,
+} OpalStatus;
+
+static const char* const opal_status_table[_OPAL_STATUS_MAX] = {
+ [OPAL_STATUS_SUCCESS] = "success",
+ [OPAL_STATUS_NOT_AUTHORIZED] = "not authorized",
+ [OPAL_STATUS_OBSOLETE0] = "obsolete",
+ [OPAL_STATUS_SP_BUSY] = "SP busy",
+ [OPAL_STATUS_SP_FAILED] = "SP failed",
+ [OPAL_STATUS_SP_DISABLED] = "SP disabled",
+ [OPAL_STATUS_SP_FROZEN] = "SP frozen",
+ [OPAL_STATUS_NO_SESSIONS_AVAILABLE] = "no sessions available",
+ [OPAL_STATUS_UNIQUENESS_CONFLICT] = "uniqueness conflict",
+ [OPAL_STATUS_INSUFFICIENT_SPACE] = "insufficient space",
+ [OPAL_STATUS_INSUFFICIENT_ROWS] = "insufficient rows",
+ [OPAL_STATUS_INVALID_PARAMETER] = "invalid parameter",
+ [OPAL_STATUS_OBSOLETE1] = "obsolete",
+ [OPAL_STATUS_OBSOLETE2] = "obsolete",
+ [OPAL_STATUS_TPER_MALFUNCTION] = "TPer malfunction",
+ [OPAL_STATUS_TRANSACTION_FAILURE] = "transaction failure",
+ [OPAL_STATUS_RESPONSE_OVERFLOW] = "response overflow",
+ [OPAL_STATUS_AUTHORITY_LOCKED_OUT] = "authority locked out",
+ [OPAL_STATUS_FAIL] = "unknown failure",
+};
+
+static const char *opal_status_to_string(int t)
+{
+ if (t < 0)
+ return strerror(-t);
+
+ if (t >= _OPAL_STATUS_MAX)
+ return "unknown error";
+
+ return opal_status_table[t];
+}
+
+static const char *opal_ioctl_to_string(unsigned long rq)
+{
+ switch(rq) {
+ case IOC_OPAL_GET_STATUS: return "GET_STATUS";
+ case IOC_OPAL_GET_GEOMETRY: return "GET_GEOMETRY";
+ case IOC_OPAL_GET_LR_STATUS: return "GET_LR_STATUS";
+ case IOC_OPAL_TAKE_OWNERSHIP: return "TAKE_OWNERSHIP";
+ case IOC_OPAL_ACTIVATE_USR: return "ACTIVATE_USR";
+ case IOC_OPAL_ACTIVATE_LSP: return "ACTIVATE_LSP";
+ case IOC_OPAL_ERASE_LR: return "ERASE_LR";
+ case IOC_OPAL_SECURE_ERASE_LR: return "SECURE_ERASE_LR";
+ case IOC_OPAL_ADD_USR_TO_LR: return "ADD_USR_TO_LR";
+ case IOC_OPAL_SET_PW: return "SET_PW";
+ case IOC_OPAL_LR_SETUP: return "LR_SETUP";
+ case IOC_OPAL_LOCK_UNLOCK: return "LOCK_UNLOCK";
+ case IOC_OPAL_SAVE: return "SAVE";
+ case IOC_OPAL_PSID_REVERT_TPR: return "PSID_REVERT_TPR";
+ }
+
+ assert(false && "unknown OPAL ioctl");
+ return NULL;
+}
+
+static void opal_ioctl_debug(struct crypt_device *cd,
+ unsigned long rq,
+ void *args,
+ bool post,
+ int ret)
+{
+ const char *cmd = opal_ioctl_to_string(rq);
+
+ if (ret) {
+ log_dbg(cd, "OPAL %s failed: %s", cmd, opal_status_to_string(ret));
+ return;
+ }
+
+ if (post) switch(rq) {
+ case IOC_OPAL_GET_STATUS: { /* OUT */
+ struct opal_status *st = args;
+ log_dbg(cd, "OPAL %s: flags:%" PRIu32, cmd, st->flags);
+ };
+ break;
+ case IOC_OPAL_GET_GEOMETRY: { /* OUT */
+ struct opal_geometry *geo = args;
+ log_dbg(cd, "OPAL %s: align:%" PRIu8 ", lb_size:%" PRIu32 ", gran:%" PRIu64 ", lowest_lba:%" PRIu64,
+ cmd, geo->align, geo->logical_block_size, geo->alignment_granularity, geo->lowest_aligned_lba);
+ };
+ break;
+ case IOC_OPAL_GET_LR_STATUS: { /* OUT */
+ struct opal_lr_status *lrs = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8
+ ", start:%" PRIu64 ", length:%" PRIu64 ", rle:%" PRIu32 ", rwe:%" PRIu32 ", state:%" PRIu32,
+ cmd, lrs->session.sum, lrs->session.who, lrs->session.opal_key.lr,
+ lrs->range_start, lrs->range_length, lrs->RLE, lrs->WLE, lrs->l_state);
+ };
+ break;
+ } else switch (rq) {
+ case IOC_OPAL_TAKE_OWNERSHIP: { /* IN */
+ log_dbg(cd, "OPAL %s", cmd);
+ };
+ break;
+ case IOC_OPAL_ACTIVATE_USR: { /* IN */
+ struct opal_session_info *ui = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8,
+ cmd, ui->sum, ui->who, ui->opal_key.lr);
+ };
+ break;
+ case IOC_OPAL_ACTIVATE_LSP: { /* IN */
+ struct opal_lr_act *act = args;
+ log_dbg(cd, "OPAL %s: k.lr:%" PRIu8 ", sum:%" PRIu32 ", num_lrs:%" PRIu8 ", lr:"
+ "%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8"|%"PRIu8,
+ cmd, act->key.lr, act->sum, act->num_lrs,
+ act->lr[0], act->lr[1], act->lr[2], act->lr[3], act->lr[4],
+ act->lr[5], act->lr[6], act->lr[7], act->lr[8]);
+ };
+ break;
+ case IOC_OPAL_ERASE_LR: { /* IN */
+ struct opal_session_info *ui = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8,
+ cmd, ui->sum, ui->who, ui->opal_key.lr);
+ };
+ break;
+ case IOC_OPAL_SECURE_ERASE_LR: { /* IN */
+ struct opal_session_info *ui = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8,
+ cmd, ui->sum, ui->who, ui->opal_key.lr);
+ };
+ break;
+ case IOC_OPAL_ADD_USR_TO_LR: { /* IN */
+ struct opal_lock_unlock *lu = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8
+ ", l_state:%" PRIu32 ", flags:%" PRIu16,
+ cmd, lu->session.sum, lu->session.who, lu->session.opal_key.lr,
+ lu->l_state, lu->flags);
+ };
+ break;
+ case IOC_OPAL_SET_PW: { /* IN */
+ struct opal_new_pw *pw = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8,
+ cmd, pw->session.sum, pw->session.who, pw->session.opal_key.lr);
+ };
+ break;
+ case IOC_OPAL_LR_SETUP: { /* IN */
+ struct opal_user_lr_setup *lrs = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8
+ ", start:%" PRIu64 ", length:%" PRIu64 ", rle:%" PRIu32 ", rwe:%" PRIu32,
+ cmd, lrs->session.sum, lrs->session.who, lrs->session.opal_key.lr,
+ lrs->range_start, lrs->range_length, lrs->RLE, lrs->WLE);
+ };
+ break;
+ case IOC_OPAL_LOCK_UNLOCK: { /* IN */
+ struct opal_lock_unlock *lu = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8
+ ", l_state:%" PRIu32 ", flags:%" PRIu16,
+ cmd, lu->session.sum, lu->session.who, lu->session.opal_key.lr,
+ lu->l_state, lu->flags);
+ };
+ break;
+ case IOC_OPAL_SAVE: { /* IN */
+ struct opal_lock_unlock *lu = args;
+ log_dbg(cd, "OPAL %s: sum:%" PRIu32 ", who:%" PRIu32 ", lr:%" PRIu8
+ ", l_state:%" PRIu32 ", flags:%" PRIu16,
+ cmd, lu->session.sum, lu->session.who, lu->session.opal_key.lr,
+ lu->l_state, lu->flags);
+ };
+ break;
+ case IOC_OPAL_PSID_REVERT_TPR: { /* IN */
+ struct opal_key *key = args;
+ log_dbg(cd, "OPAL %s: lr:%" PRIu8,
+ cmd, key->lr);
+ };
+ break;
+ }
+}
+
+static int opal_ioctl(struct crypt_device *cd, int fd, unsigned long rq, void *args)
+{
+ int r;
+
+ opal_ioctl_debug(cd, rq, args, false, 0);
+ r = ioctl(fd, rq, args);
+ opal_ioctl_debug(cd, rq, args, true, r);
+
+ return r;
+}
+
+static int opal_geometry_fd(struct crypt_device *cd,
+ int fd,
+ bool *ret_align,
+ uint32_t *ret_block_size,
+ uint64_t *ret_alignment_granularity_blocks,
+ uint64_t *ret_lowest_lba_blocks)
+{
+ int r;
+ struct opal_geometry geo;
+
+ assert(fd >= 0);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_GET_GEOMETRY, &geo);
+ if (r != OPAL_STATUS_SUCCESS)
+ return r;
+
+ if (ret_align)
+ *ret_align = (geo.align == 1);
+ if (ret_block_size)
+ *ret_block_size = geo.logical_block_size;
+ if (ret_alignment_granularity_blocks)
+ *ret_alignment_granularity_blocks = geo.alignment_granularity;
+ if (ret_lowest_lba_blocks)
+ *ret_lowest_lba_blocks = geo.lowest_aligned_lba;
+
+ return r;
+}
+
+static int opal_range_check_attributes_fd(struct crypt_device *cd,
+ int fd,
+ uint32_t segment_number,
+ const struct volume_key *vk,
+ const uint64_t *check_offset_sectors,
+ const uint64_t *check_length_sectors,
+ bool *check_read_locked,
+ bool *check_write_locked,
+ bool *ret_read_locked,
+ bool *ret_write_locked)
+{
+ int r;
+ struct opal_lr_status *lrs;
+ uint32_t opal_block_bytes = 0;
+ uint64_t offset, length;
+ bool read_locked, write_locked;
+
+ assert(fd >= 0);
+ assert(cd);
+ assert(vk);
+
+ if (check_offset_sectors || check_length_sectors) {
+ r = opal_geometry_fd(cd, fd, NULL, &opal_block_bytes, NULL, NULL);
+ if (r != OPAL_STATUS_SUCCESS)
+ return -EINVAL;
+ }
+
+ lrs = crypt_safe_alloc(sizeof(*lrs));
+ if (!lrs)
+ return -ENOMEM;
+
+ *lrs = (struct opal_lr_status) {
+ .session = {
+ .who = segment_number + 1,
+ .opal_key = {
+ .key_len = vk->keylength,
+ .lr = segment_number
+ }
+ }
+ };
+ memcpy(lrs->session.opal_key.key, vk->key, vk->keylength);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_GET_LR_STATUS, lrs);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to get locking range status on device '%s'.",
+ crypt_get_device_name(cd));
+ r = -EINVAL;
+ goto out;
+ }
+
+ r = 0;
+
+ if (check_offset_sectors) {
+ offset = lrs->range_start * opal_block_bytes / SECTOR_SIZE;
+ if (offset != *check_offset_sectors) {
+ log_err(cd, _("OPAL range %d offset %" PRIu64 " does not match expected values %" PRIu64 "."),
+ segment_number, offset, *check_offset_sectors);
+ r = -EINVAL;
+ }
+ }
+
+ if (check_length_sectors) {
+ length = lrs->range_length * opal_block_bytes / SECTOR_SIZE;
+ if (length != *check_length_sectors) {
+ log_err(cd, _("OPAL range %d length %" PRIu64" does not match device length %" PRIu64 "."),
+ segment_number, length, *check_length_sectors);
+ r = -EINVAL;
+ }
+ }
+
+ if (!lrs->RLE || !lrs->WLE) {
+ log_err(cd, _("OPAL range %d locking is disabled."), segment_number);
+ r = -EINVAL;
+ }
+
+ read_locked = (lrs->l_state == OPAL_LK);
+ write_locked = !!(lrs->l_state & (OPAL_RO | OPAL_LK));
+
+ if (check_read_locked && (read_locked != *check_read_locked)) {
+ log_dbg(cd, "OPAL range %d read lock is %slocked.",
+ segment_number, *check_read_locked ? "" : "not ");
+ log_err(cd, _("Unexpected OPAL range %d lock state."), segment_number);
+ r = -EINVAL;
+ }
+
+ if (check_write_locked && (write_locked != *check_write_locked)) {
+ log_dbg(cd, "OPAL range %d write lock is %slocked.",
+ segment_number, *check_write_locked ? "" : "not ");
+ log_err(cd, _("Unexpected OPAL range %d lock state."), segment_number);
+ r = -EINVAL;
+ }
+
+ if (ret_read_locked)
+ *ret_read_locked = read_locked;
+ if (ret_write_locked)
+ *ret_write_locked = write_locked;
+out:
+ crypt_safe_free(lrs);
+
+ return r;
+}
+
+static int opal_query_status(struct crypt_device *cd, struct device *dev, unsigned expected)
+{
+ struct opal_status st = { };
+ int fd, r;
+
+ assert(cd);
+ assert(dev);
+
+ fd = device_open(cd, dev, O_RDONLY);
+ if (fd < 0)
+ return -EIO;
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_GET_STATUS, &st);
+
+ return r < 0 ? -EINVAL : (st.flags & expected) ? 1 : 0;
+}
+
+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,
+ uint64_t range_start,
+ uint64_t range_length,
+ uint32_t segment_number,
+ const void *admin_key,
+ size_t admin_key_len)
+{
+ struct opal_lr_act *activate = NULL;
+ struct opal_session_info *user_session = NULL;
+ struct opal_lock_unlock *user_add_to_lr = NULL, *lock = NULL;
+ struct opal_new_pw *new_pw = NULL;
+ struct opal_user_lr_setup *setup = NULL;
+ int r, fd;
+
+ assert(cd);
+ assert(dev);
+ assert(vk);
+ assert(admin_key);
+ assert(vk->keylength <= OPAL_KEY_MAX);
+
+ if (admin_key_len > OPAL_KEY_MAX)
+ return -EINVAL;
+
+ fd = device_open(cd, dev, O_RDONLY);
+ if (fd < 0)
+ return -EIO;
+
+ r = opal_enabled(cd, dev);
+ if (r < 0)
+ return r;
+
+ /* If OPAL has never been enabled, we need to take ownership and do basic setup first */
+ if (r == 0) {
+ activate = crypt_safe_alloc(sizeof(struct opal_lr_act));
+ if (!activate) {
+ r = -ENOMEM;
+ goto out;
+ }
+ *activate = (struct opal_lr_act) {
+ .key = {
+ .key_len = admin_key_len,
+ },
+ .num_lrs = 8,
+ /* A max of 9 segments are supported, enable them all as there's no reason not to
+ * (0 is whole-volume)
+ */
+ .lr = { 1, 2, 3, 4, 5, 6, 7, 8 },
+ };
+ memcpy(activate->key.key, admin_key, admin_key_len);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_TAKE_OWNERSHIP, &activate->key);
+ if (r < 0) {
+ r = -ENOTSUP;
+ log_dbg(cd, "OPAL not supported on this kernel version, refusing.");
+ goto out;
+ }
+ if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ {
+ r = -EPERM;
+ log_dbg(cd, "Failed to take ownership of OPAL device '%s': permission denied",
+ crypt_get_device_name(cd));
+ goto out;
+ }
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to take ownership of OPAL device '%s': %s",
+ crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_ACTIVATE_LSP, activate);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to activate OPAL device '%s': %s",
+ crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+ } else {
+ /* If it is already enabled, wipe the locking range first */
+ user_session = crypt_safe_alloc(sizeof(struct opal_session_info));
+ if (!user_session) {
+ r = -ENOMEM;
+ goto out;
+ }
+ *user_session = (struct opal_session_info) {
+ .who = OPAL_ADMIN1,
+ .opal_key = {
+ .lr = segment_number,
+ .key_len = admin_key_len,
+ },
+ };
+ memcpy(user_session->opal_key.key, admin_key, admin_key_len);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_ERASE_LR, user_session);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to reset (erase) OPAL locking range %u on device '%s': %s",
+ segment_number, crypt_get_device_name(cd), opal_status_to_string(r));
+ r = opal_ioctl(cd, fd, IOC_OPAL_SECURE_ERASE_LR, user_session);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to reset (secure erase) OPAL locking range %u on device '%s': %s",
+ segment_number, crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+ }
+ }
+
+ crypt_safe_free(user_session);
+
+ user_session = crypt_safe_alloc(sizeof(struct opal_session_info));
+ if (!user_session) {
+ r = -ENOMEM;
+ goto out;
+ }
+ *user_session = (struct opal_session_info) {
+ .who = segment_number + 1,
+ .opal_key = {
+ .key_len = admin_key_len,
+ },
+ };
+ memcpy(user_session->opal_key.key, admin_key, admin_key_len);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_ACTIVATE_USR, user_session);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to activate OPAL user on device '%s': %s",
+ crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+
+ user_add_to_lr = crypt_safe_alloc(sizeof(struct opal_lock_unlock));
+ if (!user_add_to_lr) {
+ r = -ENOMEM;
+ goto out;
+ }
+ *user_add_to_lr = (struct opal_lock_unlock) {
+ .session = {
+ .who = segment_number + 1,
+ .opal_key = {
+ .lr = segment_number,
+ .key_len = admin_key_len,
+ },
+ },
+ .l_state = OPAL_RO,
+ };
+ memcpy(user_add_to_lr->session.opal_key.key, admin_key, admin_key_len);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_ADD_USR_TO_LR, user_add_to_lr);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to add OPAL user to locking range %u (RO) on device '%s': %s",
+ segment_number, crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+ user_add_to_lr->l_state = OPAL_RW;
+ r = opal_ioctl(cd, fd, IOC_OPAL_ADD_USR_TO_LR, user_add_to_lr);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to add OPAL user to locking range %u (RW) on device '%s': %s",
+ segment_number, crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+
+ new_pw = crypt_safe_alloc(sizeof(struct opal_new_pw));
+ if (!new_pw) {
+ r = -ENOMEM;
+ goto out;
+ }
+ *new_pw = (struct opal_new_pw) {
+ .session = {
+ .who = OPAL_ADMIN1,
+ .opal_key = {
+ .lr = segment_number,
+ .key_len = admin_key_len,
+ },
+ },
+ .new_user_pw = {
+ .who = segment_number + 1,
+ .opal_key = {
+ .key_len = vk->keylength,
+ .lr = segment_number,
+ },
+ },
+ };
+ memcpy(new_pw->new_user_pw.opal_key.key, vk->key, vk->keylength);
+ memcpy(new_pw->session.opal_key.key, admin_key, admin_key_len);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_SET_PW, new_pw);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to set OPAL user password on device '%s': (%d) %s",
+ crypt_get_device_name(cd), r, opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+
+ setup = crypt_safe_alloc(sizeof(struct opal_user_lr_setup));
+ if (!setup) {
+ r = -ENOMEM;
+ goto out;
+ }
+ *setup = (struct opal_user_lr_setup) {
+ .range_start = range_start,
+ .range_length = range_length,
+ /* Some drives do not enable Locking Ranges on setup. This have some
+ * interesting consequences: Lock command called later below will pass,
+ * but locking range will _not_ be locked at all.
+ */
+ .RLE = 1,
+ .WLE = 1,
+ .session = {
+ .who = OPAL_ADMIN1,
+ .opal_key = {
+ .key_len = admin_key_len,
+ .lr = segment_number,
+ },
+ },
+ };
+ memcpy(setup->session.opal_key.key, admin_key, admin_key_len);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_LR_SETUP, setup);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to setup locking range of length %llu at offset %llu on OPAL device '%s': %s",
+ setup->range_length, setup->range_start, crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+
+ /* After setup an OPAL device is unlocked, but the expectation with cryptsetup is that it needs
+ * to be activated separately, so lock it immediately. */
+ lock = crypt_safe_alloc(sizeof(struct opal_lock_unlock));
+ if (!lock) {
+ r = -ENOMEM;
+ goto out;
+ }
+ *lock = (struct opal_lock_unlock) {
+ .l_state = OPAL_LK,
+ .session = {
+ .who = segment_number + 1,
+ .opal_key = {
+ .key_len = vk->keylength,
+ .lr = segment_number,
+ },
+ }
+ };
+ memcpy(lock->session.opal_key.key, vk->key, vk->keylength);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_LOCK_UNLOCK, lock);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to lock OPAL device '%s': %s",
+ crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+
+ /* Double check the locking range is locked and the ranges are set up as configured */
+ r = opal_range_check_attributes_fd(cd, fd, segment_number, vk, &range_start,
+ &range_length, &(bool) {true}, &(bool){true},
+ NULL, NULL);
+out:
+ crypt_safe_free(activate);
+ crypt_safe_free(user_session);
+ crypt_safe_free(user_add_to_lr);
+ crypt_safe_free(new_pw);
+ crypt_safe_free(setup);
+ crypt_safe_free(lock);
+
+ return r;
+}
+
+static int opal_lock_unlock(struct crypt_device *cd,
+ struct device *dev,
+ uint32_t segment_number,
+ const struct volume_key *vk,
+ bool lock)
+{
+ struct opal_lock_unlock unlock = {
+ .l_state = lock ? OPAL_LK : OPAL_RW,
+ .session = {
+ .who = segment_number + 1,
+ .opal_key = {
+ .lr = segment_number,
+ },
+ },
+ };
+ int r, fd;
+
+ if (opal_supported(cd, dev) <= 0)
+ return -ENOTSUP;
+ if (!lock && !vk)
+ return -EINVAL;
+
+ fd = device_open(cd, dev, O_RDONLY);
+ if (fd < 0)
+ return -EIO;
+
+ if (!lock) {
+ assert(vk->keylength <= OPAL_KEY_MAX);
+
+ unlock.session.opal_key.key_len = vk->keylength;
+ memcpy(unlock.session.opal_key.key, vk->key, vk->keylength);
+ }
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_LOCK_UNLOCK, &unlock);
+ if (r < 0) {
+ r = -ENOTSUP;
+ log_dbg(cd, "OPAL not supported on this kernel version, refusing.");
+ goto out;
+ }
+ if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ {
+ r = -EPERM;
+ log_dbg(cd, "Failed to %slock OPAL device '%s': permission denied",
+ lock ? "" : "un", crypt_get_device_name(cd));
+ goto out;
+ }
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to %slock OPAL device '%s': %s",
+ lock ? "" : "un", crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+
+ /* If we are unlocking, also tell the kernel to automatically unlock when resuming
+ * from suspend, otherwise the drive will be locked and everything will go up in flames.
+ * Also set the flag to allow locking without having to pass the key again.
+ * But do not error out if this fails, as the device will already be unlocked.
+ *
+ * On a lock path we have to overwrite the cached key from kernel otherwise the locking range
+ * gets unlocked automatically after system resume even when cryptsetup previously locked it
+ * on purpose (crypt_deactivate* or crypt_suspend)
+ */
+ if (!lock)
+ unlock.flags = OPAL_SAVE_FOR_LOCK;
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_SAVE, &unlock);
+ if (r != OPAL_STATUS_SUCCESS) {
+ if (!lock)
+ log_std(cd, "Failed to prepare OPAL device '%s' for sleep resume, be aware before suspending: %s",
+ crypt_get_device_name(cd), opal_status_to_string(r));
+ else
+ log_std(cd, "Failed to erase OPAL key for device '%s' from kernel: %s",
+ crypt_get_device_name(cd), opal_status_to_string(r));
+ r = 0;
+ }
+out:
+ if (!lock)
+ crypt_safe_memzero(unlock.session.opal_key.key, unlock.session.opal_key.key_len);
+
+ 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,
+ const struct volume_key *vk)
+{
+ return opal_lock_unlock(cd, dev, segment_number, vk, /* lock= */ false);
+}
+
+/*
+ * It does not require opal lock. This completely destroys
+ * data on whole OPAL block device. Serialization does not
+ * make sense here.
+ */
+int opal_factory_reset(struct crypt_device *cd,
+ struct device *dev,
+ const char *password,
+ size_t password_len)
+{
+ struct opal_key reset = {
+ .key_len = password_len,
+ };
+ int r, fd;
+
+ assert(cd);
+ assert(dev);
+ assert(password);
+
+ if (password_len > OPAL_KEY_MAX)
+ return -EINVAL;
+
+ fd = device_open(cd, dev, O_RDONLY);
+ if (fd < 0)
+ return -EIO;
+
+ memcpy(reset.key, password, password_len);
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_PSID_REVERT_TPR, &reset);
+ if (r < 0) {
+ r = -ENOTSUP;
+ log_dbg(cd, "OPAL not supported on this kernel version, refusing.");
+ goto out;
+ }
+ if (r == OPAL_STATUS_NOT_AUTHORIZED) /* We'll try again with a different key. */ {
+ r = -EPERM;
+ log_dbg(cd, "Failed to reset OPAL device '%s', incorrect PSID?",
+ crypt_get_device_name(cd));
+ goto out;
+ }
+ if (r != OPAL_STATUS_SUCCESS) {
+ r = -EINVAL;
+ log_dbg(cd, "Failed to reset OPAL device '%s' with PSID: %s",
+ crypt_get_device_name(cd), opal_status_to_string(r));
+ goto out;
+ }
+out:
+ crypt_safe_memzero(reset.key, reset.key_len);
+
+ return r;
+}
+
+/* requires opal lock */
+int opal_reset_segment(struct crypt_device *cd,
+ struct device *dev,
+ uint32_t segment_number,
+ const char *password,
+ size_t password_len)
+{
+ struct opal_session_info *user_session = NULL;
+ struct opal_user_lr_setup *setup = NULL;
+ int r, fd;
+
+ assert(cd);
+ assert(dev);
+ assert(password);
+
+ if (password_len > OPAL_KEY_MAX)
+ return -EINVAL;
+
+ if (opal_enabled(cd, dev) <= 0)
+ return -EINVAL;
+
+ user_session = crypt_safe_alloc(sizeof(struct opal_session_info));
+ if (!user_session)
+ return -ENOMEM;
+ *user_session = (struct opal_session_info) {
+ .who = OPAL_ADMIN1,
+ .opal_key = {
+ .lr = segment_number,
+ .key_len = password_len,
+ },
+ };
+ memcpy(user_session->opal_key.key, password, password_len);
+
+ fd = device_open(cd, dev, O_RDONLY);
+ if (fd < 0) {
+ r = -EIO;
+ goto out;
+ }
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_ERASE_LR, user_session);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to reset (erase) OPAL locking range %u on device '%s': %s",
+ segment_number, crypt_get_device_name(cd), opal_status_to_string(r));
+ r = opal_ioctl(cd, fd, IOC_OPAL_SECURE_ERASE_LR, user_session);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to reset (secure erase) OPAL locking range %u on device '%s': %s",
+ segment_number, crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+
+ /* Unlike IOC_OPAL_ERASE_LR, IOC_OPAL_SECURE_ERASE_LR does not disable the locking range,
+ * we have to do that by hand.
+ */
+ setup = crypt_safe_alloc(sizeof(struct opal_user_lr_setup));
+ if (!setup) {
+ r = -ENOMEM;
+ goto out;
+ }
+ *setup = (struct opal_user_lr_setup) {
+ .range_start = 0,
+ .range_length = 0,
+ .session = {
+ .who = OPAL_ADMIN1,
+ .opal_key = user_session->opal_key,
+ },
+ };
+
+ r = opal_ioctl(cd, fd, IOC_OPAL_LR_SETUP, setup);
+ if (r != OPAL_STATUS_SUCCESS) {
+ log_dbg(cd, "Failed to disable locking range on OPAL device '%s': %s",
+ crypt_get_device_name(cd), opal_status_to_string(r));
+ r = -EINVAL;
+ goto out;
+ }
+ }
+out:
+ crypt_safe_free(user_session);
+ crypt_safe_free(setup);
+
+ return r;
+}
+
+/*
+ * Does not require opal lock (immutable).
+ */
+int opal_supported(struct crypt_device *cd, struct device *dev)
+{
+ return opal_query_status(cd, dev, OPAL_FL_SUPPORTED|OPAL_FL_LOCKING_SUPPORTED);
+}
+
+/*
+ * Does not require opal lock (immutable).
+ */
+int opal_geometry(struct crypt_device *cd,
+ struct device *dev,
+ bool *ret_align,
+ uint32_t *ret_block_size,
+ uint64_t *ret_alignment_granularity_blocks,
+ uint64_t *ret_lowest_lba_blocks)
+{
+ int fd;
+
+ assert(cd);
+ assert(dev);
+
+ fd = device_open(cd, dev, O_RDONLY);
+ if (fd < 0)
+ return -EIO;
+
+ return opal_geometry_fd(cd, fd, ret_align, ret_block_size,
+ 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,
+ const struct volume_key *vk,
+ const uint64_t *check_offset_sectors,
+ const uint64_t *check_length_sectors,
+ bool *ret_read_locked,
+ bool *ret_write_locked)
+{
+ int fd;
+
+ assert(cd);
+ assert(dev);
+ assert(vk);
+
+ fd = device_open(cd, dev, O_RDONLY);
+ if (fd < 0)
+ return -EIO;
+
+ return opal_range_check_attributes_fd(cd, fd, segment_number, vk,
+ check_offset_sectors, check_length_sectors, NULL,
+ 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"
+
+int opal_setup_ranges(struct crypt_device *cd,
+ struct device *dev,
+ const struct volume_key *vk,
+ uint64_t range_start,
+ uint64_t range_length,
+ uint32_t segment_number,
+ const void *admin_key,
+ size_t admin_key_len)
+{
+ return -ENOTSUP;
+}
+
+int opal_lock(struct crypt_device *cd, struct device *dev, uint32_t segment_number)
+{
+ return -ENOTSUP;
+}
+
+int opal_unlock(struct crypt_device *cd,
+ struct device *dev,
+ uint32_t segment_number,
+ const struct volume_key *vk)
+{
+ return -ENOTSUP;
+}
+
+int opal_supported(struct crypt_device *cd, struct device *dev)
+{
+ return -ENOTSUP;
+}
+
+int opal_factory_reset(struct crypt_device *cd,
+ struct device *dev,
+ const char *password,
+ size_t password_len)
+{
+ return -ENOTSUP;
+}
+
+int opal_reset_segment(struct crypt_device *cd,
+ struct device *dev,
+ uint32_t segment_number,
+ const char *password,
+ size_t password_len)
+{
+ return -ENOTSUP;
+}
+
+int opal_geometry(struct crypt_device *cd,
+ struct device *dev,
+ bool *ret_align,
+ uint32_t *ret_block_size,
+ uint64_t *ret_alignment_granularity_blocks,
+ uint64_t *ret_lowest_lba_blocks)
+{
+ return -ENOTSUP;
+}
+
+int opal_range_check_attributes_and_get_lock_state(struct crypt_device *cd,
+ struct device *dev,
+ uint32_t segment_number,
+ const struct volume_key *vk,
+ const uint64_t *check_offset_sectors,
+ const uint64_t *check_length_sectors,
+ bool *ret_read_locked,
+ bool *ret_write_locked)
+{
+ 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