linux/drivers/nvdimm/security.c

577 lines
15 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0
/* Copyright(c) 2018 Intel Corporation. All rights reserved. */
#include <linux/module.h>
#include <linux/device.h>
#include <linux/ndctl.h>
#include <linux/slab.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/cred.h>
#include <linux/key.h>
#include <linux/key-type.h>
#include <keys/user-type.h>
#include <keys/encrypted-type.h>
#include "nd-core.h"
#include "nd.h"
#define NVDIMM_BASE_KEY 0
#define NVDIMM_NEW_KEY 1
static bool key_revalidate = true;
module_param(key_revalidate, bool, 0444);
MODULE_PARM_DESC(key_revalidate, "Require key validation at init.");
static const char zero_key[NVDIMM_PASSPHRASE_LEN];
static void *key_data(struct key *key)
{
struct encrypted_key_payload *epayload = dereference_key_locked(key);
lockdep_assert_held_read(&key->sem);
return epayload->decrypted_data;
}
static void nvdimm_put_key(struct key *key)
{
if (!key)
return;
up_read(&key->sem);
key_put(key);
}
/*
* Retrieve kernel key for DIMM and request from user space if
* necessary. Returns a key held for read and must be put by
* nvdimm_put_key() before the usage goes out of scope.
*/
static struct key *nvdimm_request_key(struct nvdimm *nvdimm)
{
struct key *key = NULL;
static const char NVDIMM_PREFIX[] = "nvdimm:";
char desc[NVDIMM_KEY_DESC_LEN + sizeof(NVDIMM_PREFIX)];
struct device *dev = &nvdimm->dev;
sprintf(desc, "%s%s", NVDIMM_PREFIX, nvdimm->dimm_id);
key = request_key(&key_type_encrypted, desc, "");
if (IS_ERR(key)) {
if (PTR_ERR(key) == -ENOKEY)
dev_dbg(dev, "request_key() found no key\n");
else
dev_dbg(dev, "request_key() upcall failed\n");
key = NULL;
} else {
struct encrypted_key_payload *epayload;
down_read(&key->sem);
epayload = dereference_key_locked(key);
if (epayload->decrypted_datalen != NVDIMM_PASSPHRASE_LEN) {
up_read(&key->sem);
key_put(key);
key = NULL;
}
}
return key;
}
static const void *nvdimm_get_key_payload(struct nvdimm *nvdimm,
struct key **key)
{
*key = nvdimm_request_key(nvdimm);
if (!*key)
return zero_key;
return key_data(*key);
}
static struct key *nvdimm_lookup_user_key(struct nvdimm *nvdimm,
key_serial_t id, int subclass)
{
key_ref_t keyref;
struct key *key;
struct encrypted_key_payload *epayload;
struct device *dev = &nvdimm->dev;
keyref = lookup_user_key(id, 0, KEY_NEED_SEARCH);
if (IS_ERR(keyref))
return NULL;
key = key_ref_to_ptr(keyref);
if (key->type != &key_type_encrypted) {
key_put(key);
return NULL;
}
dev_dbg(dev, "%s: key found: %#x\n", __func__, key_serial(key));
down_read_nested(&key->sem, subclass);
epayload = dereference_key_locked(key);
if (epayload->decrypted_datalen != NVDIMM_PASSPHRASE_LEN) {
up_read(&key->sem);
key_put(key);
key = NULL;
}
return key;
}
static const void *nvdimm_get_user_key_payload(struct nvdimm *nvdimm,
key_serial_t id, int subclass, struct key **key)
{
*key = NULL;
if (id == 0) {
if (subclass == NVDIMM_BASE_KEY)
return zero_key;
else
return NULL;
}
*key = nvdimm_lookup_user_key(nvdimm, id, subclass);
if (!*key)
return NULL;
return key_data(*key);
}
static int nvdimm_key_revalidate(struct nvdimm *nvdimm)
{
struct key *key;
int rc;
const void *data;
if (!nvdimm->sec.ops->change_key)
return -EOPNOTSUPP;
data = nvdimm_get_key_payload(nvdimm, &key);
/*
* Send the same key to the hardware as new and old key to
* verify that the key is good.
*/
rc = nvdimm->sec.ops->change_key(nvdimm, data, data, NVDIMM_USER);
if (rc < 0) {
nvdimm_put_key(key);
return rc;
}
nvdimm_put_key(key);
nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
return 0;
}
static int __nvdimm_security_unlock(struct nvdimm *nvdimm)
{
struct device *dev = &nvdimm->dev;
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
struct key *key;
const void *data;
int rc;
/* The bus lock should be held at the top level of the call stack */
lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
if (!nvdimm->sec.ops || !nvdimm->sec.ops->unlock
|| !nvdimm->sec.flags)
return -EIO;
/* cxl_test needs this to pre-populate the security state */
if (IS_ENABLED(CONFIG_NVDIMM_SECURITY_TEST))
nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
/* No need to go further if security is disabled */
if (test_bit(NVDIMM_SECURITY_DISABLED, &nvdimm->sec.flags))
return 0;
if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
dev_dbg(dev, "Security operation in progress.\n");
return -EBUSY;
}
/*
* If the pre-OS has unlocked the DIMM, attempt to send the key
* from request_key() to the hardware for verification. Failure
* to revalidate the key against the hardware results in a
* freeze of the security configuration. I.e. if the OS does not
* have the key, security is being managed pre-OS.
*/
if (test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.flags)) {
if (!key_revalidate)
return 0;
return nvdimm_key_revalidate(nvdimm);
} else
data = nvdimm_get_key_payload(nvdimm, &key);
rc = nvdimm->sec.ops->unlock(nvdimm, data);
dev_dbg(dev, "key: %d unlock: %s\n", key_serial(key),
rc == 0 ? "success" : "fail");
nvdimm_put_key(key);
nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
return rc;
}
int nvdimm_security_unlock(struct device *dev)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
int rc;
nvdimm_bus_lock(dev);
rc = __nvdimm_security_unlock(nvdimm);
nvdimm_bus_unlock(dev);
return rc;
}
static int check_security_state(struct nvdimm *nvdimm)
{
struct device *dev = &nvdimm->dev;
if (test_bit(NVDIMM_SECURITY_FROZEN, &nvdimm->sec.flags)) {
dev_dbg(dev, "Incorrect security state: %#lx\n",
nvdimm->sec.flags);
return -EIO;
}
if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
dev_dbg(dev, "Security operation in progress.\n");
return -EBUSY;
}
return 0;
}
static int security_disable(struct nvdimm *nvdimm, unsigned int keyid,
enum nvdimm_passphrase_type pass_type)
{
struct device *dev = &nvdimm->dev;
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
struct key *key;
int rc;
const void *data;
/* The bus lock should be held at the top level of the call stack */
lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
if (!nvdimm->sec.ops || !nvdimm->sec.flags)
return -EOPNOTSUPP;
if (pass_type == NVDIMM_USER && !nvdimm->sec.ops->disable)
return -EOPNOTSUPP;
if (pass_type == NVDIMM_MASTER && !nvdimm->sec.ops->disable_master)
return -EOPNOTSUPP;
rc = check_security_state(nvdimm);
if (rc)
return rc;
data = nvdimm_get_user_key_payload(nvdimm, keyid,
NVDIMM_BASE_KEY, &key);
if (!data)
return -ENOKEY;
if (pass_type == NVDIMM_MASTER) {
rc = nvdimm->sec.ops->disable_master(nvdimm, data);
dev_dbg(dev, "key: %d disable_master: %s\n", key_serial(key),
rc == 0 ? "success" : "fail");
} else {
rc = nvdimm->sec.ops->disable(nvdimm, data);
dev_dbg(dev, "key: %d disable: %s\n", key_serial(key),
rc == 0 ? "success" : "fail");
}
nvdimm_put_key(key);
if (pass_type == NVDIMM_MASTER)
nvdimm->sec.ext_flags = nvdimm_security_flags(nvdimm, NVDIMM_MASTER);
else
nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
return rc;
}
static int security_update(struct nvdimm *nvdimm, unsigned int keyid,
unsigned int new_keyid,
enum nvdimm_passphrase_type pass_type)
{
struct device *dev = &nvdimm->dev;
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
struct key *key, *newkey;
int rc;
const void *data, *newdata;
/* The bus lock should be held at the top level of the call stack */
lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
if (!nvdimm->sec.ops || !nvdimm->sec.ops->change_key
|| !nvdimm->sec.flags)
return -EOPNOTSUPP;
rc = check_security_state(nvdimm);
if (rc)
return rc;
data = nvdimm_get_user_key_payload(nvdimm, keyid,
NVDIMM_BASE_KEY, &key);
if (!data)
return -ENOKEY;
newdata = nvdimm_get_user_key_payload(nvdimm, new_keyid,
NVDIMM_NEW_KEY, &newkey);
if (!newdata) {
nvdimm_put_key(key);
return -ENOKEY;
}
rc = nvdimm->sec.ops->change_key(nvdimm, data, newdata, pass_type);
dev_dbg(dev, "key: %d %d update%s: %s\n",
key_serial(key), key_serial(newkey),
pass_type == NVDIMM_MASTER ? "(master)" : "(user)",
rc == 0 ? "success" : "fail");
nvdimm_put_key(newkey);
nvdimm_put_key(key);
if (pass_type == NVDIMM_MASTER)
nvdimm->sec.ext_flags = nvdimm_security_flags(nvdimm,
NVDIMM_MASTER);
else
nvdimm->sec.flags = nvdimm_security_flags(nvdimm,
NVDIMM_USER);
return rc;
}
static int security_erase(struct nvdimm *nvdimm, unsigned int keyid,
enum nvdimm_passphrase_type pass_type)
{
struct device *dev = &nvdimm->dev;
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
struct key *key = NULL;
int rc;
const void *data;
/* The bus lock should be held at the top level of the call stack */
lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
if (!nvdimm->sec.ops || !nvdimm->sec.ops->erase
|| !nvdimm->sec.flags)
return -EOPNOTSUPP;
rc = check_security_state(nvdimm);
if (rc)
return rc;
if (!test_bit(NVDIMM_SECURITY_UNLOCKED, &nvdimm->sec.ext_flags)
&& pass_type == NVDIMM_MASTER) {
dev_dbg(dev,
"Attempt to secure erase in wrong master state.\n");
return -EOPNOTSUPP;
}
data = nvdimm_get_user_key_payload(nvdimm, keyid,
NVDIMM_BASE_KEY, &key);
if (!data)
return -ENOKEY;
rc = nvdimm->sec.ops->erase(nvdimm, data, pass_type);
dev_dbg(dev, "key: %d erase%s: %s\n", key_serial(key),
pass_type == NVDIMM_MASTER ? "(master)" : "(user)",
rc == 0 ? "success" : "fail");
nvdimm_put_key(key);
nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
return rc;
}
static int security_overwrite(struct nvdimm *nvdimm, unsigned int keyid)
{
struct device *dev = &nvdimm->dev;
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(dev);
struct key *key = NULL;
int rc;
const void *data;
/* The bus lock should be held at the top level of the call stack */
lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
if (!nvdimm->sec.ops || !nvdimm->sec.ops->overwrite
|| !nvdimm->sec.flags)
return -EOPNOTSUPP;
rc = check_security_state(nvdimm);
if (rc)
return rc;
data = nvdimm_get_user_key_payload(nvdimm, keyid,
NVDIMM_BASE_KEY, &key);
if (!data)
return -ENOKEY;
rc = nvdimm->sec.ops->overwrite(nvdimm, data);
dev_dbg(dev, "key: %d overwrite submission: %s\n", key_serial(key),
rc == 0 ? "success" : "fail");
nvdimm_put_key(key);
if (rc == 0) {
set_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags);
set_bit(NDD_WORK_PENDING, &nvdimm->flags);
set_bit(NVDIMM_SECURITY_OVERWRITE, &nvdimm->sec.flags);
/*
* Make sure we don't lose device while doing overwrite
* query.
*/
get_device(dev);
queue_delayed_work(system_wq, &nvdimm->dwork, 0);
}
return rc;
}
static void __nvdimm_security_overwrite_query(struct nvdimm *nvdimm)
{
struct nvdimm_bus *nvdimm_bus = walk_to_nvdimm_bus(&nvdimm->dev);
int rc;
unsigned int tmo;
/* The bus lock should be held at the top level of the call stack */
lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
/*
* Abort and release device if we no longer have the overwrite
* flag set. It means the work has been canceled.
*/
if (!test_bit(NDD_WORK_PENDING, &nvdimm->flags))
return;
tmo = nvdimm->sec.overwrite_tmo;
if (!nvdimm->sec.ops || !nvdimm->sec.ops->query_overwrite
|| !nvdimm->sec.flags)
return;
rc = nvdimm->sec.ops->query_overwrite(nvdimm);
if (rc == -EBUSY) {
/* setup delayed work again */
tmo += 10;
queue_delayed_work(system_wq, &nvdimm->dwork, tmo * HZ);
nvdimm->sec.overwrite_tmo = min(15U * 60U, tmo);
return;
}
if (rc < 0)
dev_dbg(&nvdimm->dev, "overwrite failed\n");
else
dev_dbg(&nvdimm->dev, "overwrite completed\n");
/*
* Mark the overwrite work done and update dimm security flags,
* then send a sysfs event notification to wake up userspace
* poll threads to picked up the changed state.
*/
nvdimm->sec.overwrite_tmo = 0;
clear_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags);
clear_bit(NDD_WORK_PENDING, &nvdimm->flags);
nvdimm->sec.flags = nvdimm_security_flags(nvdimm, NVDIMM_USER);
nvdimm->sec.ext_flags = nvdimm_security_flags(nvdimm, NVDIMM_MASTER);
if (nvdimm->sec.overwrite_state)
sysfs_notify_dirent(nvdimm->sec.overwrite_state);
put_device(&nvdimm->dev);
}
void nvdimm_security_overwrite_query(struct work_struct *work)
{
struct nvdimm *nvdimm =
container_of(work, typeof(*nvdimm), dwork.work);
nvdimm_bus_lock(&nvdimm->dev);
__nvdimm_security_overwrite_query(nvdimm);
nvdimm_bus_unlock(&nvdimm->dev);
}
#define OPS \
C( OP_FREEZE, "freeze", 1), \
C( OP_DISABLE, "disable", 2), \
C( OP_DISABLE_MASTER, "disable_master", 2), \
C( OP_UPDATE, "update", 3), \
C( OP_ERASE, "erase", 2), \
C( OP_OVERWRITE, "overwrite", 2), \
C( OP_MASTER_UPDATE, "master_update", 3), \
C( OP_MASTER_ERASE, "master_erase", 2)
#undef C
#define C(a, b, c) a
enum nvdimmsec_op_ids { OPS };
#undef C
#define C(a, b, c) { b, c }
static struct {
const char *name;
int args;
} ops[] = { OPS };
#undef C
#define SEC_CMD_SIZE 32
#define KEY_ID_SIZE 10
ssize_t nvdimm_security_store(struct device *dev, const char *buf, size_t len)
{
struct nvdimm *nvdimm = to_nvdimm(dev);
ssize_t rc;
char cmd[SEC_CMD_SIZE+1], keystr[KEY_ID_SIZE+1],
nkeystr[KEY_ID_SIZE+1];
unsigned int key, newkey;
int i;
rc = sscanf(buf, "%"__stringify(SEC_CMD_SIZE)"s"
" %"__stringify(KEY_ID_SIZE)"s"
" %"__stringify(KEY_ID_SIZE)"s",
cmd, keystr, nkeystr);
if (rc < 1)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(ops); i++)
if (sysfs_streq(cmd, ops[i].name))
break;
if (i >= ARRAY_SIZE(ops))
return -EINVAL;
if (ops[i].args > 1)
rc = kstrtouint(keystr, 0, &key);
if (rc >= 0 && ops[i].args > 2)
rc = kstrtouint(nkeystr, 0, &newkey);
if (rc < 0)
return rc;
if (i == OP_FREEZE) {
dev_dbg(dev, "freeze\n");
rc = nvdimm_security_freeze(nvdimm);
} else if (i == OP_DISABLE) {
dev_dbg(dev, "disable %u\n", key);
rc = security_disable(nvdimm, key, NVDIMM_USER);
} else if (i == OP_DISABLE_MASTER) {
dev_dbg(dev, "disable_master %u\n", key);
rc = security_disable(nvdimm, key, NVDIMM_MASTER);
} else if (i == OP_UPDATE || i == OP_MASTER_UPDATE) {
dev_dbg(dev, "%s %u %u\n", ops[i].name, key, newkey);
rc = security_update(nvdimm, key, newkey, i == OP_UPDATE
? NVDIMM_USER : NVDIMM_MASTER);
} else if (i == OP_ERASE || i == OP_MASTER_ERASE) {
dev_dbg(dev, "%s %u\n", ops[i].name, key);
if (atomic_read(&nvdimm->busy)) {
dev_dbg(dev, "Unable to secure erase while DIMM active.\n");
return -EBUSY;
}
rc = security_erase(nvdimm, key, i == OP_ERASE
? NVDIMM_USER : NVDIMM_MASTER);
} else if (i == OP_OVERWRITE) {
dev_dbg(dev, "overwrite %u\n", key);
if (atomic_read(&nvdimm->busy)) {
dev_dbg(dev, "Unable to overwrite while DIMM active.\n");
return -EBUSY;
}
rc = security_overwrite(nvdimm, key);
} else
return -EINVAL;
if (rc == 0)
rc = len;
return rc;
}