/* * OPAL utilities * * Copyright (C) 2022-2023 Luca Boccassi * 2023 Ondrej Kozina * * 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 #include #include #include #include #include #include #include #include #include #ifdef HAVE_SYS_SYSMACROS_H # include /* 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 /* 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