Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7d988097 authored by Dave Jiang's avatar Dave Jiang Committed by Dan Williams
Browse files

acpi/nfit, libnvdimm/security: Add security DSM overwrite support



Add support for the NVDIMM_FAMILY_INTEL "ovewrite" capability as
described by the 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.

Given that overwrite puts the DIMM in an indeterminate state until it
completes introduce the NDD_SECURITY_OVERWRITE flag to prevent other
operations from executing when overwrite is happening. The
NDD_WORK_PENDING flag is added to denote that there is a device reference
on the nvdimm device for an async workqueue thread context.

Signed-off-by: default avatarDave Jiang <dave.jiang@intel.com>
Signed-off-by: default avatarDan Williams <dan.j.williams@intel.com>
parent 64e77c8c
Loading
Loading
Loading
Loading
+5 −0
Original line number Diff line number Diff line
@@ -2045,6 +2045,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,
+90 −0
Original line number Diff line number Diff line
@@ -28,6 +28,14 @@ static enum nvdimm_security_state intel_security_state(struct nvdimm *nvdimm)
	if (!test_bit(NVDIMM_INTEL_GET_SECURITY_STATE, &nfit_mem->dsm_mask))
		return -ENXIO;

	/*
	 * Short circuit the state retrieval while we are doing overwrite.
	 * The DSM spec states that the security state is indeterminate
	 * until the overwrite DSM completes.
	 */
	if (nvdimm_in_overwrite(nvdimm))
		return NVDIMM_SECURITY_OVERWRITE;

	rc = nvdimm_ctl(nvdimm, ND_CMD_CALL, &nd_cmd, sizeof(nd_cmd), NULL);
	if (rc < 0)
		return rc;
@@ -249,6 +257,86 @@ 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_out = ND_INTEL_STATUS_SIZE,
			.nd_fw_size = ND_INTEL_STATUS_SIZE,
		},
	};

	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();
	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,
		},
	};

	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:
		return 0;
	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;
	}
}

/*
 * TODO: define a cross arch wbinvd equivalent when/if
 * NVDIMM_FAMILY_INTEL command support arrives on another arch.
@@ -273,6 +361,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
};

+18 −3
Original line number Diff line number Diff line
@@ -393,9 +393,24 @@ static int child_unregister(struct device *dev, void *data)
	 * i.e. remove classless children
	 */
	if (dev->class)
		/* pass */;
	else
		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;
}

+30 −2
Original line number Diff line number Diff line
@@ -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);

+14 −0
Original line number Diff line number Diff line
@@ -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

/**
Loading