aboutsummaryrefslogtreecommitdiff
path: root/drivers/nvdimm
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/nvdimm')
-rw-r--r--drivers/nvdimm/bus.c21
-rw-r--r--drivers/nvdimm/dimm_devs.c32
-rw-r--r--drivers/nvdimm/nd-core.h14
-rw-r--r--drivers/nvdimm/region_devs.c5
-rw-r--r--drivers/nvdimm/security.c133
5 files changed, 200 insertions, 5 deletions
diff --git a/drivers/nvdimm/bus.c b/drivers/nvdimm/bus.c
index eae17d8ee539..adb01c1f92de 100644
--- a/drivers/nvdimm/bus.c
+++ b/drivers/nvdimm/bus.c
@@ -393,9 +393,24 @@ static int child_unregister(struct device *dev, void *data)
* i.e. remove classless children
*/
if (dev->class)
- /* pass */;
- else
- nd_device_unregister(dev, ND_SYNC);
+ return 0;
+
+ if (is_nvdimm(dev)) {
+ struct nvdimm *nvdimm = to_nvdimm(dev);
+ bool dev_put = false;
+
+ /* We are shutting down. Make state frozen artificially. */
+ nvdimm_bus_lock(dev);
+ nvdimm->sec.state = NVDIMM_SECURITY_FROZEN;
+ if (test_and_clear_bit(NDD_WORK_PENDING, &nvdimm->flags))
+ dev_put = true;
+ nvdimm_bus_unlock(dev);
+ cancel_delayed_work_sync(&nvdimm->dwork);
+ if (dev_put)
+ put_device(dev);
+ }
+ nd_device_unregister(dev, ND_SYNC);
+
return 0;
}
diff --git a/drivers/nvdimm/dimm_devs.c b/drivers/nvdimm/dimm_devs.c
index bc432b7c17b8..6affa270abd3 100644
--- a/drivers/nvdimm/dimm_devs.c
+++ b/drivers/nvdimm/dimm_devs.c
@@ -395,7 +395,8 @@ static ssize_t security_show(struct device *dev,
C( OP_FREEZE, "freeze", 1), \
C( OP_DISABLE, "disable", 2), \
C( OP_UPDATE, "update", 3), \
- C( OP_ERASE, "erase", 2)
+ C( OP_ERASE, "erase", 2), \
+ C( OP_OVERWRITE, "overwrite", 2)
#undef C
#define C(a, b, c) a
enum nvdimmsec_op_ids { OPS };
@@ -452,6 +453,9 @@ static ssize_t __security_store(struct device *dev, const char *buf, size_t len)
} else if (i == OP_ERASE) {
dev_dbg(dev, "erase %u\n", key);
rc = nvdimm_security_erase(nvdimm, key);
+ } else if (i == OP_OVERWRITE) {
+ dev_dbg(dev, "overwrite %u\n", key);
+ rc = nvdimm_security_overwrite(nvdimm, key);
} else
return -EINVAL;
@@ -503,7 +507,8 @@ static umode_t nvdimm_visible(struct kobject *kobj, struct attribute *a, int n)
/* Are there any state mutation ops? */
if (nvdimm->sec.ops->freeze || nvdimm->sec.ops->disable
|| nvdimm->sec.ops->change_key
- || nvdimm->sec.ops->erase)
+ || nvdimm->sec.ops->erase
+ || nvdimm->sec.ops->overwrite)
return a->mode;
return 0444;
}
@@ -546,6 +551,8 @@ struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus,
dev->devt = MKDEV(nvdimm_major, nvdimm->id);
dev->groups = groups;
nvdimm->sec.ops = sec_ops;
+ nvdimm->sec.overwrite_tmo = 0;
+ INIT_DELAYED_WORK(&nvdimm->dwork, nvdimm_security_overwrite_query);
/*
* Security state must be initialized before device_add() for
* attribute visibility.
@@ -557,6 +564,22 @@ struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus,
}
EXPORT_SYMBOL_GPL(__nvdimm_create);
+int nvdimm_security_setup_events(struct nvdimm *nvdimm)
+{
+ nvdimm->sec.overwrite_state = sysfs_get_dirent(nvdimm->dev.kobj.sd,
+ "security");
+ if (!nvdimm->sec.overwrite_state)
+ return -ENODEV;
+ return 0;
+}
+EXPORT_SYMBOL_GPL(nvdimm_security_setup_events);
+
+int nvdimm_in_overwrite(struct nvdimm *nvdimm)
+{
+ return test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags);
+}
+EXPORT_SYMBOL_GPL(nvdimm_in_overwrite);
+
int nvdimm_security_freeze(struct nvdimm *nvdimm)
{
int rc;
@@ -569,6 +592,11 @@ int nvdimm_security_freeze(struct nvdimm *nvdimm)
if (nvdimm->sec.state < 0)
return -EIO;
+ if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
+ dev_warn(&nvdimm->dev, "Overwrite operation in progress.\n");
+ return -EBUSY;
+ }
+
rc = nvdimm->sec.ops->freeze(nvdimm);
nvdimm->sec.state = nvdimm_security_state(nvdimm);
diff --git a/drivers/nvdimm/nd-core.h b/drivers/nvdimm/nd-core.h
index b4b633ccfbe9..952d688982d8 100644
--- a/drivers/nvdimm/nd-core.h
+++ b/drivers/nvdimm/nd-core.h
@@ -21,6 +21,7 @@
extern struct list_head nvdimm_bus_list;
extern struct mutex nvdimm_bus_list_mutex;
extern int nvdimm_major;
+extern struct workqueue_struct *nvdimm_wq;
struct nvdimm_bus {
struct nvdimm_bus_descriptor *nd_desc;
@@ -45,7 +46,10 @@ struct nvdimm {
struct {
const struct nvdimm_security_ops *ops;
enum nvdimm_security_state state;
+ unsigned int overwrite_tmo;
+ struct kernfs_node *overwrite_state;
} sec;
+ struct delayed_work dwork;
};
static inline enum nvdimm_security_state nvdimm_security_state(
@@ -62,6 +66,8 @@ int nvdimm_security_disable(struct nvdimm *nvdimm, unsigned int keyid);
int nvdimm_security_update(struct nvdimm *nvdimm, unsigned int keyid,
unsigned int new_keyid);
int nvdimm_security_erase(struct nvdimm *nvdimm, unsigned int keyid);
+int nvdimm_security_overwrite(struct nvdimm *nvdimm, unsigned int keyid);
+void nvdimm_security_overwrite_query(struct work_struct *work);
#else
static inline int nvdimm_security_disable(struct nvdimm *nvdimm,
unsigned int keyid)
@@ -77,6 +83,14 @@ static inline int nvdimm_security_erase(struct nvdimm *nvdimm, unsigned int keyi
{
return -EOPNOTSUPP;
}
+static inline int nvdimm_security_overwrite(struct nvdimm *nvdimm,
+ unsigned int keyid)
+{
+ return -EOPNOTSUPP;
+}
+static inline void nvdimm_security_overwrite_query(struct work_struct *work)
+{
+}
#endif
/**
diff --git a/drivers/nvdimm/region_devs.c b/drivers/nvdimm/region_devs.c
index 174a418cb171..b4d8e4ed3020 100644
--- a/drivers/nvdimm/region_devs.c
+++ b/drivers/nvdimm/region_devs.c
@@ -79,6 +79,11 @@ int nd_region_activate(struct nd_region *nd_region)
struct nd_mapping *nd_mapping = &nd_region->mapping[i];
struct nvdimm *nvdimm = nd_mapping->nvdimm;
+ if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
+ nvdimm_bus_unlock(&nd_region->dev);
+ return -EBUSY;
+ }
+
/* at least one null hint slot per-dimm for the "no-hint" case */
flush_data_size += sizeof(void *);
num_flush = min_not_zero(num_flush, nvdimm->num_flush);
diff --git a/drivers/nvdimm/security.c b/drivers/nvdimm/security.c
index 05677be3c0dd..5055979f89c4 100644
--- a/drivers/nvdimm/security.c
+++ b/drivers/nvdimm/security.c
@@ -143,6 +143,11 @@ static int __nvdimm_security_unlock(struct nvdimm *nvdimm)
|| nvdimm->sec.state < 0)
return -EIO;
+ if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
+ dev_warn(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
@@ -203,6 +208,11 @@ int nvdimm_security_disable(struct nvdimm *nvdimm, unsigned int keyid)
return -EIO;
}
+ if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
+ dev_warn(dev, "Security operation in progress.\n");
+ return -EBUSY;
+ }
+
key = nvdimm_lookup_user_key(nvdimm, keyid, NVDIMM_BASE_KEY);
if (!key)
return -ENOKEY;
@@ -288,6 +298,11 @@ int nvdimm_security_erase(struct nvdimm *nvdimm, unsigned int keyid)
return -EIO;
}
+ if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
+ dev_warn(dev, "Security operation in progress.\n");
+ return -EBUSY;
+ }
+
key = nvdimm_lookup_user_key(nvdimm, keyid, NVDIMM_BASE_KEY);
if (!key)
return -ENOKEY;
@@ -300,3 +315,121 @@ int nvdimm_security_erase(struct nvdimm *nvdimm, unsigned int keyid)
nvdimm->sec.state = nvdimm_security_state(nvdimm);
return rc;
}
+
+int nvdimm_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;
+ 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->overwrite
+ || nvdimm->sec.state < 0)
+ return -EOPNOTSUPP;
+
+ if (atomic_read(&nvdimm->busy)) {
+ dev_warn(dev, "Unable to overwrite while DIMM active.\n");
+ return -EBUSY;
+ }
+
+ if (dev->driver == NULL) {
+ dev_warn(dev, "Unable to overwrite while DIMM active.\n");
+ return -EINVAL;
+ }
+
+ if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
+ dev_warn(dev, "Incorrect security state: %d\n",
+ nvdimm->sec.state);
+ return -EIO;
+ }
+
+ if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
+ dev_warn(dev, "Security operation in progress.\n");
+ return -EBUSY;
+ }
+
+ if (keyid == 0)
+ key = NULL;
+ else {
+ key = nvdimm_lookup_user_key(nvdimm, keyid, NVDIMM_BASE_KEY);
+ if (!key)
+ return -ENOKEY;
+ }
+
+ rc = nvdimm->sec.ops->overwrite(nvdimm, key ? key_data(key) : NULL);
+ 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);
+ nvdimm->sec.state = NVDIMM_SECURITY_OVERWRITE;
+ /*
+ * Make sure we don't lose device while doing overwrite
+ * query.
+ */
+ get_device(dev);
+ queue_delayed_work(system_wq, &nvdimm->dwork, 0);
+ }
+ return rc;
+}
+
+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.state < 0)
+ 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_warn(&nvdimm->dev, "overwrite failed\n");
+ else
+ dev_dbg(&nvdimm->dev, "overwrite completed\n");
+
+ if (nvdimm->sec.overwrite_state)
+ sysfs_notify_dirent(nvdimm->sec.overwrite_state);
+ nvdimm->sec.overwrite_tmo = 0;
+ clear_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags);
+ clear_bit(NDD_WORK_PENDING, &nvdimm->flags);
+ put_device(&nvdimm->dev);
+ nvdimm->sec.state = nvdimm_security_state(nvdimm);
+}
+
+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);
+}