On 12/11/18 4:44 PM, Dan Williams wrote:
> On Tue, Dec 11, 2018 at 12:26 PM Dave Jiang <[email protected]> wrote:
>>
>> We are adding support for the security calls of ovewrite and query
>> overwrite introduced from Intel DSM spec v1.7. This will allow triggering
>> of overwrite on Intel NVDIMMs. The overwrite operation can take tens
>> of minutes. When the overwrite DSM is issued successfully, the NVDIMMs
>> will be unaccessible. The kernel will do backoff polling to detect when
>> the overwrite process is completed. According to the DSM spec v1.7,
>> the 128G NVDIMMs can take up to 15mins to perform overwrite and larger
>> DIMMs will take longer.
>>
>> Signed-off-by: Dave Jiang <[email protected]>
>> ---
>> drivers/acpi/nfit/core.c | 5 ++
>> drivers/acpi/nfit/intel.c | 93 ++++++++++++++++++++++++++++++++++++++++++
>> drivers/acpi/nfit/nfit.h | 1
>> drivers/nvdimm/core.c | 3 +
>> drivers/nvdimm/dimm_devs.c | 35 +++++++++++++++-
>> drivers/nvdimm/nd-core.h | 8 ++++
>> drivers/nvdimm/security.c | 98
>> ++++++++++++++++++++++++++++++++++++++++++++
>> include/linux/libnvdimm.h | 4 ++
>> 8 files changed, 245 insertions(+), 2 deletions(-)
>>
>> diff --git a/drivers/acpi/nfit/core.c b/drivers/acpi/nfit/core.c
>> index 77f188cd8023..173517eb35b1 100644
>> --- a/drivers/acpi/nfit/core.c
>> +++ b/drivers/acpi/nfit/core.c
>> @@ -2043,6 +2043,11 @@ static int acpi_nfit_register_dimms(struct
>> acpi_nfit_desc *acpi_desc)
>> if (!nvdimm)
>> continue;
>>
>> + rc = nvdimm_security_setup_events(nvdimm);
>> + if (rc < 0)
>> + dev_warn(acpi_desc->dev,
>> + "security event setup failed: %d\n", rc);
>> +
>> nfit_kernfs = sysfs_get_dirent(nvdimm_kobj(nvdimm)->sd,
>> "nfit");
>> if (nfit_kernfs)
>> nfit_mem->flags_attr = sysfs_get_dirent(nfit_kernfs,
>> diff --git a/drivers/acpi/nfit/intel.c b/drivers/acpi/nfit/intel.c
>> index ce65111ea4e1..3c3f99dfc32c 100644
>> --- a/drivers/acpi/nfit/intel.c
>> +++ b/drivers/acpi/nfit/intel.c
>> @@ -254,6 +254,97 @@ static int intel_security_erase(struct nvdimm *nvdimm,
>> return 0;
>> }
>>
>> +static int intel_security_query_overwrite(struct nvdimm *nvdimm)
>> +{
>> + int rc;
>> + struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
>> + struct {
>> + struct nd_cmd_pkg pkg;
>> + struct nd_intel_query_overwrite cmd;
>> + } nd_cmd = {
>> + .pkg = {
>> + .nd_command = NVDIMM_INTEL_QUERY_OVERWRITE,
>> + .nd_family = NVDIMM_FAMILY_INTEL,
>> + .nd_size_in = 0,
>> + .nd_size_out = ND_INTEL_STATUS_SIZE,
>> + .nd_fw_size = ND_INTEL_STATUS_SIZE,
>> + },
>> + .cmd = {
>> + .status = 0,
>> + },
>> + };
>> +
>> + if (!test_bit(NVDIMM_INTEL_QUERY_OVERWRITE, &nfit_mem->dsm_mask))
>> + return -ENOTTY;
>> +
>> + rc = nvdimm_ctl(nvdimm, ND_CMD_CALL, &nd_cmd, sizeof(nd_cmd), NULL);
>> + if (rc < 0)
>> + return rc;
>> +
>> + switch (nd_cmd.cmd.status) {
>> + case 0:
>> + break;
>> + case ND_INTEL_STATUS_OQUERY_INPROGRESS:
>> + return -EBUSY;
>> + default:
>> + return -ENXIO;
>> + }
>> +
>> + /* flush all cache before we make the nvdimms available */
>> + nvdimm_invalidate_cache();
>> + nfit_mem->overwrite = false;
>> + return 0;
>> +}
>> +
>> +static int intel_security_overwrite(struct nvdimm *nvdimm,
>> + const struct nvdimm_key_data *nkey)
>> +{
>> + int rc;
>> + struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
>> + struct {
>> + struct nd_cmd_pkg pkg;
>> + struct nd_intel_overwrite cmd;
>> + } nd_cmd = {
>> + .pkg = {
>> + .nd_command = NVDIMM_INTEL_OVERWRITE,
>> + .nd_family = NVDIMM_FAMILY_INTEL,
>> + .nd_size_in = ND_INTEL_PASSPHRASE_SIZE,
>> + .nd_size_out = ND_INTEL_STATUS_SIZE,
>> + .nd_fw_size = ND_INTEL_STATUS_SIZE,
>> + },
>> + .cmd = {
>> + .status = 0,
>> + },
>> + };
>> +
>> + if (!test_bit(NVDIMM_INTEL_OVERWRITE, &nfit_mem->dsm_mask))
>> + return -ENOTTY;
>> +
>> + /* flush all cache before we erase DIMM */
>> + nvdimm_invalidate_cache();
>> + if (nkey)
>> + memcpy(nd_cmd.cmd.passphrase, nkey->data,
>> + sizeof(nd_cmd.cmd.passphrase));
>> + rc = nvdimm_ctl(nvdimm, ND_CMD_CALL, &nd_cmd, sizeof(nd_cmd), NULL);
>> + if (rc < 0)
>> + return rc;
>> +
>> + switch (nd_cmd.cmd.status) {
>> + case 0:
>> + nfit_mem->overwrite = true;
>> + break;
>> + case ND_INTEL_STATUS_OVERWRITE_UNSUPPORTED:
>> + return -ENOTSUPP;
>> + case ND_INTEL_STATUS_INVALID_PASS:
>> + return -EINVAL;
>> + case ND_INTEL_STATUS_INVALID_STATE:
>> + default:
>> + return -ENXIO;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> /*
>> * TODO: define a cross arch wbinvd equivalent when/if
>> * NVDIMM_FAMILY_INTEL command support arrives on another arch.
>> @@ -278,6 +369,8 @@ static const struct nvdimm_security_ops
>> __intel_security_ops = {
>> #ifdef CONFIG_X86
>> .unlock = intel_security_unlock,
>> .erase = intel_security_erase,
>> + .overwrite = intel_security_overwrite,
>> + .query_overwrite = intel_security_query_overwrite,
>> #endif
>> };
>>
>> diff --git a/drivers/acpi/nfit/nfit.h b/drivers/acpi/nfit/nfit.h
>> index 33691aecfcee..e0ee54049c89 100644
>> --- a/drivers/acpi/nfit/nfit.h
>> +++ b/drivers/acpi/nfit/nfit.h
>> @@ -208,6 +208,7 @@ struct nfit_mem {
>> unsigned long flags;
>> u32 dirty_shutdown;
>> int family;
>> + bool overwrite;
>
> What is this for? It's not used in this patch.
I'm missing couple lines in intel_dimm_security_state() to short circuit
things and set NVDIMM_SECURITY_OVERWRITE when this is set when I was
moving things to the squashed tree.
>
>> };
>>
>> struct acpi_nfit_desc {
>> diff --git a/drivers/nvdimm/core.c b/drivers/nvdimm/core.c
>> index acce050856a8..b2496c06178b 100644
>> --- a/drivers/nvdimm/core.c
>> +++ b/drivers/nvdimm/core.c
>> @@ -437,6 +437,9 @@ static __init int libnvdimm_init(void)
>> {
>> int rc;
>>
>> + rc = nvdimm_devs_init();
>> + if (rc)
>> + return rc;
>> rc = nvdimm_bus_init();
>> if (rc)
>> return rc;
>> diff --git a/drivers/nvdimm/dimm_devs.c b/drivers/nvdimm/dimm_devs.c
>> index bfdc4824ba11..fa42774efb15 100644
>> --- a/drivers/nvdimm/dimm_devs.c
>> +++ b/drivers/nvdimm/dimm_devs.c
>> @@ -24,6 +24,7 @@
>> #include "nd.h"
>>
>> static DEFINE_IDA(dimm_ida);
>> +struct workqueue_struct *nvdimm_wq;
>
> static?
>
> Or why not use the system workqueue? I don't think this necessarily
> needs its own workqueue.
>
>>
>> /*
>> * Retrieve bus and dimm handle and return if this bus supports
>> @@ -439,6 +440,15 @@ static ssize_t __security_store(struct device *dev,
>> const char *buf, size_t len)
>> rc = nvdimm_security_erase(nvdimm, key);
>> if (rc < 0)
>> return rc;
>> + } else if (sysfs_streq(cmd, "overwite")) {
>
> "overwrite"
>
>> + if (rc != 2)
>> + return -EINVAL;
>> + rc = kstrtouint(keystr, 0, &key);
>> + if (rc < 0)
>> + return rc;
>> + rc = nvdimm_security_overwrite(nvdimm, key);
>> + if (rc < 0)
>> + return rc;
>> } else
>> return -EINVAL;
>>
>> @@ -491,7 +501,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;
>> }
>> @@ -534,6 +545,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.
>> @@ -545,6 +558,16 @@ 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_security_freeze(struct nvdimm *nvdimm)
>> {
>> int rc;
>> @@ -839,7 +862,17 @@ int nvdimm_bus_check_dimm_count(struct nvdimm_bus
>> *nvdimm_bus, int dimm_count)
>> }
>> EXPORT_SYMBOL_GPL(nvdimm_bus_check_dimm_count);
>>
>> +int __init nvdimm_devs_init(void)
>> +{
>> + nvdimm_wq = create_singlethread_workqueue("nvdimm");
>> + if (!nvdimm_wq)
>> + return -ENOMEM;
>> +
>> + return 0;
>> +}
>> +
>> void __exit nvdimm_devs_exit(void)
>> {
>> + destroy_workqueue(nvdimm_wq);
>
> I don't see a call to cancel_delayed_work_sync(), what ensures that
> the dimm is not hot removed while overwrite is in flight? It's
> probably ok to not wait for completion because the driver will
> reacquire the state at the next driver load, but the work needs to be
> cancelled, shutoff, and flushed before the device_del() of the nvdimm
> device.
>
>
>
>> ida_destroy(&dimm_ida);
>> }
>> diff --git a/drivers/nvdimm/nd-core.h b/drivers/nvdimm/nd-core.h
>> index 353f945cda5b..2c93e2139346 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(
>> @@ -78,6 +82,9 @@ static inline void nvdimm_clear_security_busy(struct
>> nvdimm *nvdimm)
>> clear_bit(NDD_SECURITY_BUSY, &nvdimm->flags);
>> }
>>
>> +int nvdimm_security_overwrite(struct nvdimm *nvdimm, unsigned int keyid);
>> +void nvdimm_security_overwrite_query(struct work_struct *work);
>> +
>> /**
>> * struct blk_alloc_info - tracking info for BLK dpa scanning
>> * @nd_mapping: blk region mapping boundaries
>> @@ -110,6 +117,7 @@ static inline bool is_memory(struct device *dev)
>> struct nvdimm_bus *walk_to_nvdimm_bus(struct device *nd_dev);
>> int __init nvdimm_bus_init(void);
>> void nvdimm_bus_exit(void);
>> +int nvdimm_devs_init(void);
>> void nvdimm_devs_exit(void);
>> void nd_region_devs_exit(void);
>> void nd_region_probe_success(struct nvdimm_bus *nvdimm_bus, struct device
>> *dev);
>> diff --git a/drivers/nvdimm/security.c b/drivers/nvdimm/security.c
>> index 5a03dffd0056..3603c5fda1cf 100644
>> --- a/drivers/nvdimm/security.c
>> +++ b/drivers/nvdimm/security.c
>> @@ -282,7 +282,7 @@ int nvdimm_security_erase(struct nvdimm *nvdimm,
>> unsigned int keyid)
>> struct key *key;
>> int rc;
>>
>> - /* The bus lock should be held at the top level of the call stack */
>> + /* the bus lock should be held at the top level of the call stack */
>
> Unintended change? Capitalized sentences is the local nvdimm custom.
>
>> lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
>>
>> if (!nvdimm->sec.ops || !nvdimm->sec.ops->erase
>> @@ -318,3 +318,99 @@ 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 -EIO;
>> +
>> + if (atomic_read(&nvdimm->busy)) {
>> + dev_warn(dev, "Unable to overwrite while DIMM active.\n");
>> + nvdimm_clear_security_busy(nvdimm);
>> + return -EBUSY;
>> + }
>> +
>> + if (dev_get_drvdata(dev)) {
>
> Use "dev->driver != NULL" as the "is enabled" test. dev_get_drvdata()
> works today, but I've seen driver-core patches that tried to make
> assumptions about the availability of drvdata for the core to use
> while dev->driver is NULL.
>
>
>> + dev_warn(dev, "Unable to overwrite while DIMM active.\n");
>> + nvdimm_clear_security_busy(nvdimm);
>> + return -EINVAL;
>> + }
>> +
>> + if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
>> + dev_warn(dev, "Incorrect security state: %d\n",
>> + nvdimm->sec.state);
>> + return -EIO;
>> + }
>> +
>> + rc = nvdimm_security_check_busy(nvdimm);
>> + if (rc < 0) {
>> + dev_warn(dev, "Security operation in progress.\n");
>> + return rc;
>> + }
>> +
>> + 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);
>> + nvdimm->sec.state = nvdimm_security_state(nvdimm);
>> + nvdimm_set_security_busy(nvdimm);
>> + return rc;
>> +}
>> +
>> +void nvdimm_security_overwrite_query(struct work_struct *work)
>> +{
>> + struct nvdimm_bus *nvdimm_bus;
>> + struct nvdimm *nvdimm;
>> + int rc;
>> + unsigned int tmo;
>> +
>> + nvdimm = container_of(work, typeof(*nvdimm), dwork.work);
>> + nvdimm_bus = walk_to_nvdimm_bus(&nvdimm->dev);
>> + tmo = nvdimm->sec.overwrite_tmo;
>> +
>> + /* The bus lock should be held at the top level of the call stack */
>> + lockdep_assert_held(&nvdimm_bus->reconfig_mutex);
>> +
>
> The reconfig_mutex should have been dropped after the work was queued,
> so we would need to reacquire and revalidate state here.
>
> We'd also need to abort and not re-queue if the nvdimm device is going away.
>
>> + 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(nvdimm_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_info(&nvdimm->dev, "overwrite completed\n");
>
> Let's do dev_dbg(), no need to be chatty on success, and the driver is
> already notifying userspace of the completion event.
>
>> +
>> + if (nvdimm->sec.overwrite_state)
>> + sysfs_notify_dirent(nvdimm->sec.overwrite_state);
>> + nvdimm->sec.overwrite_tmo = 0;
>> + nvdimm_clear_security_busy(nvdimm);
>> + nvdimm->sec.state = nvdimm_security_state(nvdimm);
>> +}
>> diff --git a/include/linux/libnvdimm.h b/include/linux/libnvdimm.h
>> index 8507d2896ae0..b728952ccb28 100644
>> --- a/include/linux/libnvdimm.h
>> +++ b/include/linux/libnvdimm.h
>> @@ -184,6 +184,9 @@ struct nvdimm_security_ops {
>> const struct nvdimm_key_data *key_data);
>> int (*erase)(struct nvdimm *nvdimm,
>> const struct nvdimm_key_data *key_data);
>> + int (*overwrite)(struct nvdimm *nvdimm,
>> + const struct nvdimm_key_data *key_data);
>> + int (*query_overwrite)(struct nvdimm *nvdimm);
>> };
>>
>> void badrange_init(struct badrange *badrange);
>> @@ -221,6 +224,7 @@ static inline struct nvdimm *nvdimm_create(struct
>> nvdimm_bus *nvdimm_bus,
>> cmd_mask, num_flush, flush_wpq, NULL, NULL);
>> }
>>
>> +int nvdimm_security_setup_events(struct nvdimm *nvdimm);
>> const struct nd_cmd_desc *nd_cmd_dimm_desc(int cmd);
>> const struct nd_cmd_desc *nd_cmd_bus_desc(int cmd);
>> u32 nd_cmd_in_size(struct nvdimm *nvdimm, int cmd,
>>
_______________________________________________
Linux-nvdimm mailing list
[email protected]
https://lists.01.org/mailman/listinfo/linux-nvdimm