libnvdimm for 4.21
* Add support for the security features of nvdimm devices that implement a security model similar to ATA hard drive security. The security model supports locking access to the media at device-power-loss, to be unlocked with a passphrase, and secure-erase (crypto-scramble). Unlike the ATA security case where the kernel expects device security to be managed in a pre-OS environment, the libnvdimm security implementation allows key provisioning and key-operations at OS runtime. Keys are managed with the kernel's encrypted-keys facility to provide data-at-rest security for the libnvdimm key material. The usage model mirrors fscrypt key management, but is driven via libnvdimm sysfs. * Miscellaneous updates for api usage and comment fixes. -----BEGIN PGP SIGNATURE----- iQIcBAABAgAGBQJcJalaAAoJEB7SkWpmfYgCmpkP/35Isou2xxbYdICt9HR4WjiE E4r66Ck+ivZW4oEAub3RQw/SwcZePjuHArO5MgVGFoM1dBMABE+JvKiZm1xybCil iuWT/V7ozlNf5LuvacvjUXe/suUIoqji9yzLEEx9mDaISehrYvvy579DFxkE8Aww 8Vcx8E1DQkXiWY4d9nmDmAjt8GQPNmsdHfTwMcJg7XSywTkURF64vW7/94aN8QtV rH5ZKKOra8Lqn0S05RoH4XegZcE6TZxgE23ZXsobBJrWdbGVFmgbz6AaewJc/+QU ZW3dx/1NAe7Op25xewJOjdG/Kl0gzWtqCrHCu8LeaSjwtHsgMQ/FBhL9g/7GRtHt hcQy12Iu9YTn3FOl8yfNRKb7lE5/1nJ2MtW8Z55WKHONHzN9cgrZLOwmpTYgrR/r SmIzwKNFavLwwz9bj+paHhmTngLDnNbvR6FwAKwSbWinotbLEyfMhU1jJ9RmHxSV M+jqS1BJ9IvH+WVN5bVa7oCNpVeq6yMw0Ow2vWXMPbiwh17ShACI59paqZKRiR06 WH8SgdTPOZuMndtQV+m8zFBiJtmgAtGSiN8c1A/1QZJHDTpXz2MdWhtx5McvFsZ9 Jot4Vd8mFWI7oVLdb+PF/AGZnKNAnD8PdsAlFws0k7+rM6lf6gf5n/CkjjMuSapG Q/kf0cOpPq8wdsrnOzyc =7KYd -----END PGP SIGNATURE----- Merge tag 'libnvdimm-for-4.21' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm Pull libnvdimm updates from Dan Williams: "The vast bulk of this update is the new support for the security capabilities of some nvdimms. The userspace tooling for this capability is still a work in progress, but the changes survive the existing libnvdimm unit tests. The changes also pass manual checkout on hardware and the new nfit_test emulation of the security capability. The touches of the security/keys/ files have received the necessary acks from Mimi and David. Those changes were necessary to allow for a new generic encrypted-key type, and allow the nvdimm sub-system to lookup key material referenced by the libnvdimm-sysfs interface. Summary: - Add support for the security features of nvdimm devices that implement a security model similar to ATA hard drive security. The security model supports locking access to the media at device-power-loss, to be unlocked with a passphrase, and secure-erase (crypto-scramble). Unlike the ATA security case where the kernel expects device security to be managed in a pre-OS environment, the libnvdimm security implementation allows key provisioning and key-operations at OS runtime. Keys are managed with the kernel's encrypted-keys facility to provide data-at-rest security for the libnvdimm key material. The usage model mirrors fscrypt key management, but is driven via libnvdimm sysfs. - Miscellaneous updates for api usage and comment fixes" * tag 'libnvdimm-for-4.21' of git://git.kernel.org/pub/scm/linux/kernel/git/nvdimm/nvdimm: (21 commits) libnvdimm/security: Quiet security operations libnvdimm/security: Add documentation for nvdimm security support tools/testing/nvdimm: add Intel DSM 1.8 support for nfit_test tools/testing/nvdimm: Add overwrite support for nfit_test tools/testing/nvdimm: Add test support for Intel nvdimm security DSMs acpi/nfit, libnvdimm/security: add Intel DSM 1.8 master passphrase support acpi/nfit, libnvdimm/security: Add security DSM overwrite support acpi/nfit, libnvdimm: Add support for issue secure erase DSM to Intel nvdimm acpi/nfit, libnvdimm: Add enable/update passphrase support for Intel nvdimms acpi/nfit, libnvdimm: Add disable passphrase support to Intel nvdimm. acpi/nfit, libnvdimm: Add unlock of nvdimm support for Intel DIMMs acpi/nfit, libnvdimm: Add freeze security support to Intel nvdimm acpi/nfit, libnvdimm: Introduce nvdimm_security_ops keys-encrypted: add nvdimm key format type to encrypted keys keys: Export lookup_user_key to external users acpi/nfit, libnvdimm: Store dimm id as a member to struct nvdimm libnvdimm, namespace: Replace kmemdup() with kstrndup() libnvdimm, label: Switch to bitmap_zalloc() ACPI/nfit: Adjust annotation for why return 0 if fail to find NFIT at start libnvdimm, bus: Check id immediately following ida_simple_get ...
This commit is contained in:
commit
75f95da078
|
@ -0,0 +1,141 @@
|
|||
NVDIMM SECURITY
|
||||
===============
|
||||
|
||||
1. Introduction
|
||||
---------------
|
||||
|
||||
With the introduction of Intel Device Specific Methods (DSM) v1.8
|
||||
specification [1], security DSMs are introduced. The spec added the following
|
||||
security DSMs: "get security state", "set passphrase", "disable passphrase",
|
||||
"unlock unit", "freeze lock", "secure erase", and "overwrite". A security_ops
|
||||
data structure has been added to struct dimm in order to support the security
|
||||
operations and generic APIs are exposed to allow vendor neutral operations.
|
||||
|
||||
2. Sysfs Interface
|
||||
------------------
|
||||
The "security" sysfs attribute is provided in the nvdimm sysfs directory. For
|
||||
example:
|
||||
/sys/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0012:00/ndbus0/nmem0/security
|
||||
|
||||
The "show" attribute of that attribute will display the security state for
|
||||
that DIMM. The following states are available: disabled, unlocked, locked,
|
||||
frozen, and overwrite. If security is not supported, the sysfs attribute
|
||||
will not be visible.
|
||||
|
||||
The "store" attribute takes several commands when it is being written to
|
||||
in order to support some of the security functionalities:
|
||||
update <old_keyid> <new_keyid> - enable or update passphrase.
|
||||
disable <keyid> - disable enabled security and remove key.
|
||||
freeze - freeze changing of security states.
|
||||
erase <keyid> - delete existing user encryption key.
|
||||
overwrite <keyid> - wipe the entire nvdimm.
|
||||
master_update <keyid> <new_keyid> - enable or update master passphrase.
|
||||
master_erase <keyid> - delete existing user encryption key.
|
||||
|
||||
3. Key Management
|
||||
-----------------
|
||||
|
||||
The key is associated to the payload by the DIMM id. For example:
|
||||
# cat /sys/devices/LNXSYSTM:00/LNXSYBUS:00/ACPI0012:00/ndbus0/nmem0/nfit/id
|
||||
8089-a2-1740-00000133
|
||||
The DIMM id would be provided along with the key payload (passphrase) to
|
||||
the kernel.
|
||||
|
||||
The security keys are managed on the basis of a single key per DIMM. The
|
||||
key "passphrase" is expected to be 32bytes long. This is similar to the ATA
|
||||
security specification [2]. A key is initially acquired via the request_key()
|
||||
kernel API call during nvdimm unlock. It is up to the user to make sure that
|
||||
all the keys are in the kernel user keyring for unlock.
|
||||
|
||||
A nvdimm encrypted-key of format enc32 has the description format of:
|
||||
nvdimm:<bus-provider-specific-unique-id>
|
||||
|
||||
See file ``Documentation/security/keys/trusted-encrypted.rst`` for creating
|
||||
encrypted-keys of enc32 format. TPM usage with a master trusted key is
|
||||
preferred for sealing the encrypted-keys.
|
||||
|
||||
4. Unlocking
|
||||
------------
|
||||
When the DIMMs are being enumerated by the kernel, the kernel will attempt to
|
||||
retrieve the key from the kernel user keyring. This is the only time
|
||||
a locked DIMM can be unlocked. Once unlocked, the DIMM will remain unlocked
|
||||
until reboot. Typically an entity (i.e. shell script) will inject all the
|
||||
relevant encrypted-keys into the kernel user keyring during the initramfs phase.
|
||||
This provides the unlock function access to all the related keys that contain
|
||||
the passphrase for the respective nvdimms. It is also recommended that the
|
||||
keys are injected before libnvdimm is loaded by modprobe.
|
||||
|
||||
5. Update
|
||||
---------
|
||||
When doing an update, it is expected that the existing key is removed from
|
||||
the kernel user keyring and reinjected as different (old) key. It's irrelevant
|
||||
what the key description is for the old key since we are only interested in the
|
||||
keyid when doing the update operation. It is also expected that the new key
|
||||
is injected with the description format described from earlier in this
|
||||
document. The update command written to the sysfs attribute will be with
|
||||
the format:
|
||||
update <old keyid> <new keyid>
|
||||
|
||||
If there is no old keyid due to a security enabling, then a 0 should be
|
||||
passed in.
|
||||
|
||||
6. Freeze
|
||||
---------
|
||||
The freeze operation does not require any keys. The security config can be
|
||||
frozen by a user with root privelege.
|
||||
|
||||
7. Disable
|
||||
----------
|
||||
The security disable command format is:
|
||||
disable <keyid>
|
||||
|
||||
An key with the current passphrase payload that is tied to the nvdimm should be
|
||||
in the kernel user keyring.
|
||||
|
||||
8. Secure Erase
|
||||
---------------
|
||||
The command format for doing a secure erase is:
|
||||
erase <keyid>
|
||||
|
||||
An key with the current passphrase payload that is tied to the nvdimm should be
|
||||
in the kernel user keyring.
|
||||
|
||||
9. Overwrite
|
||||
------------
|
||||
The command format for doing an overwrite is:
|
||||
overwrite <keyid>
|
||||
|
||||
Overwrite can be done without a key if security is not enabled. A key serial
|
||||
of 0 can be passed in to indicate no key.
|
||||
|
||||
The sysfs attribute "security" can be polled to wait on overwrite completion.
|
||||
Overwrite can last tens of minutes or more depending on nvdimm size.
|
||||
|
||||
An encrypted-key with the current user passphrase that is tied to the nvdimm
|
||||
should be injected and its keyid should be passed in via sysfs.
|
||||
|
||||
10. Master Update
|
||||
-----------------
|
||||
The command format for doing a master update is:
|
||||
update <old keyid> <new keyid>
|
||||
|
||||
The operating mechanism for master update is identical to update except the
|
||||
master passphrase key is passed to the kernel. The master passphrase key
|
||||
is just another encrypted-key.
|
||||
|
||||
This command is only available when security is disabled.
|
||||
|
||||
11. Master Erase
|
||||
----------------
|
||||
The command format for doing a master erase is:
|
||||
master_erase <current keyid>
|
||||
|
||||
This command has the same operating mechanism as erase except the master
|
||||
passphrase key is passed to the kernel. The master passphrase key is just
|
||||
another encrypted-key.
|
||||
|
||||
This command is only available when the master security is enabled, indicated
|
||||
by the extended security status.
|
||||
|
||||
[1]: http://pmem.io/documents/NVDIMM_DSM_Interface-V1.8.pdf
|
||||
[2]: http://www.t13.org/documents/UploadedDocuments/docs2006/e05179r4-ACS-SecurityClarifications.pdf
|
|
@ -76,7 +76,7 @@ Usage::
|
|||
|
||||
Where::
|
||||
|
||||
format:= 'default | ecryptfs'
|
||||
format:= 'default | ecryptfs | enc32'
|
||||
key-type:= 'trusted' | 'user'
|
||||
|
||||
|
||||
|
@ -173,3 +173,7 @@ are anticipated. In particular the new format 'ecryptfs' has been defined in
|
|||
in order to use encrypted keys to mount an eCryptfs filesystem. More details
|
||||
about the usage can be found in the file
|
||||
``Documentation/security/keys/ecryptfs.rst``.
|
||||
|
||||
Another new format 'enc32' has been defined in order to support encrypted keys
|
||||
with payload size of 32 bytes. This will initially be used for nvdimm security
|
||||
but may expand to other usages that require 32 bytes payload.
|
||||
|
|
|
@ -13,3 +13,14 @@ config ACPI_NFIT
|
|||
|
||||
To compile this driver as a module, choose M here:
|
||||
the module will be called nfit.
|
||||
|
||||
config NFIT_SECURITY_DEBUG
|
||||
bool "Enable debug for NVDIMM security commands"
|
||||
depends on ACPI_NFIT
|
||||
help
|
||||
Some NVDIMM devices and controllers support encryption and
|
||||
other security features. The payloads for the commands that
|
||||
enable those features may contain sensitive clear-text
|
||||
security material. Disable debug of those command payloads
|
||||
by default. If you are a kernel developer actively working
|
||||
on NVDIMM security enabling say Y, otherwise say N.
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
obj-$(CONFIG_ACPI_NFIT) := nfit.o
|
||||
nfit-y := core.o
|
||||
nfit-y += intel.o
|
||||
nfit-$(CONFIG_X86_MCE) += mce.o
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <linux/nd.h>
|
||||
#include <asm/cacheflush.h>
|
||||
#include <acpi/nfit.h>
|
||||
#include "intel.h"
|
||||
#include "nfit.h"
|
||||
#include "intel.h"
|
||||
|
||||
|
@ -380,6 +381,16 @@ static u8 nfit_dsm_revid(unsigned family, unsigned func)
|
|||
[NVDIMM_INTEL_QUERY_FWUPDATE] = 2,
|
||||
[NVDIMM_INTEL_SET_THRESHOLD] = 2,
|
||||
[NVDIMM_INTEL_INJECT_ERROR] = 2,
|
||||
[NVDIMM_INTEL_GET_SECURITY_STATE] = 2,
|
||||
[NVDIMM_INTEL_SET_PASSPHRASE] = 2,
|
||||
[NVDIMM_INTEL_DISABLE_PASSPHRASE] = 2,
|
||||
[NVDIMM_INTEL_UNLOCK_UNIT] = 2,
|
||||
[NVDIMM_INTEL_FREEZE_LOCK] = 2,
|
||||
[NVDIMM_INTEL_SECURE_ERASE] = 2,
|
||||
[NVDIMM_INTEL_OVERWRITE] = 2,
|
||||
[NVDIMM_INTEL_QUERY_OVERWRITE] = 2,
|
||||
[NVDIMM_INTEL_SET_MASTER_PASSPHRASE] = 2,
|
||||
[NVDIMM_INTEL_MASTER_SECURE_ERASE] = 2,
|
||||
},
|
||||
};
|
||||
u8 id;
|
||||
|
@ -394,6 +405,17 @@ static u8 nfit_dsm_revid(unsigned family, unsigned func)
|
|||
return id;
|
||||
}
|
||||
|
||||
static bool payload_dumpable(struct nvdimm *nvdimm, unsigned int func)
|
||||
{
|
||||
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
||||
|
||||
if (nfit_mem && nfit_mem->family == NVDIMM_FAMILY_INTEL
|
||||
&& func >= NVDIMM_INTEL_GET_SECURITY_STATE
|
||||
&& func <= NVDIMM_INTEL_MASTER_SECURE_ERASE)
|
||||
return IS_ENABLED(CONFIG_NFIT_SECURITY_DEBUG);
|
||||
return true;
|
||||
}
|
||||
|
||||
int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
|
||||
unsigned int cmd, void *buf, unsigned int buf_len, int *cmd_rc)
|
||||
{
|
||||
|
@ -478,9 +500,10 @@ int acpi_nfit_ctl(struct nvdimm_bus_descriptor *nd_desc, struct nvdimm *nvdimm,
|
|||
|
||||
dev_dbg(dev, "%s cmd: %d: func: %d input length: %d\n",
|
||||
dimm_name, cmd, func, in_buf.buffer.length);
|
||||
print_hex_dump_debug("nvdimm in ", DUMP_PREFIX_OFFSET, 4, 4,
|
||||
in_buf.buffer.pointer,
|
||||
min_t(u32, 256, in_buf.buffer.length), true);
|
||||
if (payload_dumpable(nvdimm, func))
|
||||
print_hex_dump_debug("nvdimm in ", DUMP_PREFIX_OFFSET, 4, 4,
|
||||
in_buf.buffer.pointer,
|
||||
min_t(u32, 256, in_buf.buffer.length), true);
|
||||
|
||||
/* call the BIOS, prefer the named methods over _DSM if available */
|
||||
if (nvdimm && cmd == ND_CMD_GET_CONFIG_SIZE
|
||||
|
@ -1573,18 +1596,10 @@ static DEVICE_ATTR_RO(flags);
|
|||
static ssize_t id_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct acpi_nfit_control_region *dcr = to_nfit_dcr(dev);
|
||||
struct nvdimm *nvdimm = to_nvdimm(dev);
|
||||
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
||||
|
||||
if (dcr->valid_fields & ACPI_NFIT_CONTROL_MFG_INFO_VALID)
|
||||
return sprintf(buf, "%04x-%02x-%04x-%08x\n",
|
||||
be16_to_cpu(dcr->vendor_id),
|
||||
dcr->manufacturing_location,
|
||||
be16_to_cpu(dcr->manufacturing_date),
|
||||
be32_to_cpu(dcr->serial_number));
|
||||
else
|
||||
return sprintf(buf, "%04x-%08x\n",
|
||||
be16_to_cpu(dcr->vendor_id),
|
||||
be32_to_cpu(dcr->serial_number));
|
||||
return sprintf(buf, "%s\n", nfit_mem->id);
|
||||
}
|
||||
static DEVICE_ATTR_RO(id);
|
||||
|
||||
|
@ -1780,10 +1795,23 @@ static int acpi_nfit_add_dimm(struct acpi_nfit_desc *acpi_desc,
|
|||
const guid_t *guid;
|
||||
int i;
|
||||
int family = -1;
|
||||
struct acpi_nfit_control_region *dcr = nfit_mem->dcr;
|
||||
|
||||
/* nfit test assumes 1:1 relationship between commands and dsms */
|
||||
nfit_mem->dsm_mask = acpi_desc->dimm_cmd_force_en;
|
||||
nfit_mem->family = NVDIMM_FAMILY_INTEL;
|
||||
|
||||
if (dcr->valid_fields & ACPI_NFIT_CONTROL_MFG_INFO_VALID)
|
||||
sprintf(nfit_mem->id, "%04x-%02x-%04x-%08x",
|
||||
be16_to_cpu(dcr->vendor_id),
|
||||
dcr->manufacturing_location,
|
||||
be16_to_cpu(dcr->manufacturing_date),
|
||||
be32_to_cpu(dcr->serial_number));
|
||||
else
|
||||
sprintf(nfit_mem->id, "%04x-%08x",
|
||||
be16_to_cpu(dcr->vendor_id),
|
||||
be32_to_cpu(dcr->serial_number));
|
||||
|
||||
adev = to_acpi_dev(acpi_desc);
|
||||
if (!adev) {
|
||||
/* unit test case */
|
||||
|
@ -1904,6 +1932,16 @@ static void shutdown_dimm_notify(void *data)
|
|||
mutex_unlock(&acpi_desc->init_mutex);
|
||||
}
|
||||
|
||||
static const struct nvdimm_security_ops *acpi_nfit_get_security_ops(int family)
|
||||
{
|
||||
switch (family) {
|
||||
case NVDIMM_FAMILY_INTEL:
|
||||
return intel_security_ops;
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)
|
||||
{
|
||||
struct nfit_mem *nfit_mem;
|
||||
|
@ -1970,10 +2008,11 @@ static int acpi_nfit_register_dimms(struct acpi_nfit_desc *acpi_desc)
|
|||
|
||||
flush = nfit_mem->nfit_flush ? nfit_mem->nfit_flush->flush
|
||||
: NULL;
|
||||
nvdimm = nvdimm_create(acpi_desc->nvdimm_bus, nfit_mem,
|
||||
nvdimm = __nvdimm_create(acpi_desc->nvdimm_bus, nfit_mem,
|
||||
acpi_nfit_dimm_attribute_groups,
|
||||
flags, cmd_mask, flush ? flush->hint_count : 0,
|
||||
nfit_mem->flush_wpq);
|
||||
nfit_mem->flush_wpq, &nfit_mem->id[0],
|
||||
acpi_nfit_get_security_ops(nfit_mem->family));
|
||||
if (!nvdimm)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -2008,6 +2047,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,
|
||||
|
@ -3337,7 +3381,7 @@ static int acpi_nfit_flush_probe(struct nvdimm_bus_descriptor *nd_desc)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int acpi_nfit_clear_to_send(struct nvdimm_bus_descriptor *nd_desc,
|
||||
static int __acpi_nfit_clear_to_send(struct nvdimm_bus_descriptor *nd_desc,
|
||||
struct nvdimm *nvdimm, unsigned int cmd)
|
||||
{
|
||||
struct acpi_nfit_desc *acpi_desc = to_acpi_nfit_desc(nd_desc);
|
||||
|
@ -3359,6 +3403,23 @@ static int acpi_nfit_clear_to_send(struct nvdimm_bus_descriptor *nd_desc,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* prevent security commands from being issued via ioctl */
|
||||
static int acpi_nfit_clear_to_send(struct nvdimm_bus_descriptor *nd_desc,
|
||||
struct nvdimm *nvdimm, unsigned int cmd, void *buf)
|
||||
{
|
||||
struct nd_cmd_pkg *call_pkg = buf;
|
||||
unsigned int func;
|
||||
|
||||
if (nvdimm && cmd == ND_CMD_CALL &&
|
||||
call_pkg->nd_family == NVDIMM_FAMILY_INTEL) {
|
||||
func = call_pkg->nd_command;
|
||||
if ((1 << func) & NVDIMM_INTEL_SECURITY_CMDMASK)
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
return __acpi_nfit_clear_to_send(nd_desc, nvdimm, cmd);
|
||||
}
|
||||
|
||||
int acpi_nfit_ars_rescan(struct acpi_nfit_desc *acpi_desc,
|
||||
enum nfit_ars_state req_type)
|
||||
{
|
||||
|
@ -3474,7 +3535,13 @@ static int acpi_nfit_add(struct acpi_device *adev)
|
|||
|
||||
status = acpi_get_table(ACPI_SIG_NFIT, 0, &tbl);
|
||||
if (ACPI_FAILURE(status)) {
|
||||
/* This is ok, we could have an nvdimm hotplugged later */
|
||||
/* The NVDIMM root device allows OS to trigger enumeration of
|
||||
* NVDIMMs through NFIT at boot time and re-enumeration at
|
||||
* root level via the _FIT method during runtime.
|
||||
* This is ok to return 0 here, we could have an nvdimm
|
||||
* hotplugged later and evaluate _FIT method which returns
|
||||
* data in the format of a series of NFIT Structures.
|
||||
*/
|
||||
dev_dbg(dev, "failed to find NFIT at startup\n");
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,388 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright(c) 2018 Intel Corporation. All rights reserved. */
|
||||
#include <linux/libnvdimm.h>
|
||||
#include <linux/ndctl.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <asm/smp.h>
|
||||
#include "intel.h"
|
||||
#include "nfit.h"
|
||||
|
||||
static enum nvdimm_security_state intel_security_state(struct nvdimm *nvdimm,
|
||||
enum nvdimm_passphrase_type ptype)
|
||||
{
|
||||
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
||||
struct {
|
||||
struct nd_cmd_pkg pkg;
|
||||
struct nd_intel_get_security_state cmd;
|
||||
} nd_cmd = {
|
||||
.pkg = {
|
||||
.nd_command = NVDIMM_INTEL_GET_SECURITY_STATE,
|
||||
.nd_family = NVDIMM_FAMILY_INTEL,
|
||||
.nd_size_out =
|
||||
sizeof(struct nd_intel_get_security_state),
|
||||
.nd_fw_size =
|
||||
sizeof(struct nd_intel_get_security_state),
|
||||
},
|
||||
};
|
||||
int rc;
|
||||
|
||||
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) && ptype == NVDIMM_USER)
|
||||
return NVDIMM_SECURITY_OVERWRITE;
|
||||
|
||||
rc = nvdimm_ctl(nvdimm, ND_CMD_CALL, &nd_cmd, sizeof(nd_cmd), NULL);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (nd_cmd.cmd.status)
|
||||
return -EIO;
|
||||
|
||||
/* check and see if security is enabled and locked */
|
||||
if (ptype == NVDIMM_MASTER) {
|
||||
if (nd_cmd.cmd.extended_state & ND_INTEL_SEC_ESTATE_ENABLED)
|
||||
return NVDIMM_SECURITY_UNLOCKED;
|
||||
else if (nd_cmd.cmd.extended_state &
|
||||
ND_INTEL_SEC_ESTATE_PLIMIT)
|
||||
return NVDIMM_SECURITY_FROZEN;
|
||||
} else {
|
||||
if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_UNSUPPORTED)
|
||||
return -ENXIO;
|
||||
else if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_ENABLED) {
|
||||
if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_LOCKED)
|
||||
return NVDIMM_SECURITY_LOCKED;
|
||||
else if (nd_cmd.cmd.state & ND_INTEL_SEC_STATE_FROZEN
|
||||
|| nd_cmd.cmd.state &
|
||||
ND_INTEL_SEC_STATE_PLIMIT)
|
||||
return NVDIMM_SECURITY_FROZEN;
|
||||
else
|
||||
return NVDIMM_SECURITY_UNLOCKED;
|
||||
}
|
||||
}
|
||||
|
||||
/* this should cover master security disabled as well */
|
||||
return NVDIMM_SECURITY_DISABLED;
|
||||
}
|
||||
|
||||
static int intel_security_freeze(struct nvdimm *nvdimm)
|
||||
{
|
||||
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
||||
struct {
|
||||
struct nd_cmd_pkg pkg;
|
||||
struct nd_intel_freeze_lock cmd;
|
||||
} nd_cmd = {
|
||||
.pkg = {
|
||||
.nd_command = NVDIMM_INTEL_FREEZE_LOCK,
|
||||
.nd_family = NVDIMM_FAMILY_INTEL,
|
||||
.nd_size_out = ND_INTEL_STATUS_SIZE,
|
||||
.nd_fw_size = ND_INTEL_STATUS_SIZE,
|
||||
},
|
||||
};
|
||||
int rc;
|
||||
|
||||
if (!test_bit(NVDIMM_INTEL_FREEZE_LOCK, &nfit_mem->dsm_mask))
|
||||
return -ENOTTY;
|
||||
|
||||
rc = nvdimm_ctl(nvdimm, ND_CMD_CALL, &nd_cmd, sizeof(nd_cmd), NULL);
|
||||
if (rc < 0)
|
||||
return rc;
|
||||
if (nd_cmd.cmd.status)
|
||||
return -EIO;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_security_change_key(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *old_data,
|
||||
const struct nvdimm_key_data *new_data,
|
||||
enum nvdimm_passphrase_type ptype)
|
||||
{
|
||||
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
||||
unsigned int cmd = ptype == NVDIMM_MASTER ?
|
||||
NVDIMM_INTEL_SET_MASTER_PASSPHRASE :
|
||||
NVDIMM_INTEL_SET_PASSPHRASE;
|
||||
struct {
|
||||
struct nd_cmd_pkg pkg;
|
||||
struct nd_intel_set_passphrase cmd;
|
||||
} nd_cmd = {
|
||||
.pkg = {
|
||||
.nd_family = NVDIMM_FAMILY_INTEL,
|
||||
.nd_size_in = ND_INTEL_PASSPHRASE_SIZE * 2,
|
||||
.nd_size_out = ND_INTEL_STATUS_SIZE,
|
||||
.nd_fw_size = ND_INTEL_STATUS_SIZE,
|
||||
.nd_command = cmd,
|
||||
},
|
||||
};
|
||||
int rc;
|
||||
|
||||
if (!test_bit(cmd, &nfit_mem->dsm_mask))
|
||||
return -ENOTTY;
|
||||
|
||||
if (old_data)
|
||||
memcpy(nd_cmd.cmd.old_pass, old_data->data,
|
||||
sizeof(nd_cmd.cmd.old_pass));
|
||||
memcpy(nd_cmd.cmd.new_pass, new_data->data,
|
||||
sizeof(nd_cmd.cmd.new_pass));
|
||||
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_INVALID_PASS:
|
||||
return -EINVAL;
|
||||
case ND_INTEL_STATUS_NOT_SUPPORTED:
|
||||
return -EOPNOTSUPP;
|
||||
case ND_INTEL_STATUS_INVALID_STATE:
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
}
|
||||
|
||||
static void nvdimm_invalidate_cache(void);
|
||||
|
||||
static int intel_security_unlock(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *key_data)
|
||||
{
|
||||
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
||||
struct {
|
||||
struct nd_cmd_pkg pkg;
|
||||
struct nd_intel_unlock_unit cmd;
|
||||
} nd_cmd = {
|
||||
.pkg = {
|
||||
.nd_command = NVDIMM_INTEL_UNLOCK_UNIT,
|
||||
.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,
|
||||
},
|
||||
};
|
||||
int rc;
|
||||
|
||||
if (!test_bit(NVDIMM_INTEL_UNLOCK_UNIT, &nfit_mem->dsm_mask))
|
||||
return -ENOTTY;
|
||||
|
||||
memcpy(nd_cmd.cmd.passphrase, key_data->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:
|
||||
break;
|
||||
case ND_INTEL_STATUS_INVALID_PASS:
|
||||
return -EINVAL;
|
||||
default:
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
/* DIMM unlocked, invalidate all CPU caches before we read it */
|
||||
nvdimm_invalidate_cache();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_security_disable(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *key_data)
|
||||
{
|
||||
int rc;
|
||||
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
||||
struct {
|
||||
struct nd_cmd_pkg pkg;
|
||||
struct nd_intel_disable_passphrase cmd;
|
||||
} nd_cmd = {
|
||||
.pkg = {
|
||||
.nd_command = NVDIMM_INTEL_DISABLE_PASSPHRASE,
|
||||
.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_DISABLE_PASSPHRASE, &nfit_mem->dsm_mask))
|
||||
return -ENOTTY;
|
||||
|
||||
memcpy(nd_cmd.cmd.passphrase, key_data->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:
|
||||
break;
|
||||
case ND_INTEL_STATUS_INVALID_PASS:
|
||||
return -EINVAL;
|
||||
case ND_INTEL_STATUS_INVALID_STATE:
|
||||
default:
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int intel_security_erase(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *key,
|
||||
enum nvdimm_passphrase_type ptype)
|
||||
{
|
||||
int rc;
|
||||
struct nfit_mem *nfit_mem = nvdimm_provider_data(nvdimm);
|
||||
unsigned int cmd = ptype == NVDIMM_MASTER ?
|
||||
NVDIMM_INTEL_MASTER_SECURE_ERASE : NVDIMM_INTEL_SECURE_ERASE;
|
||||
struct {
|
||||
struct nd_cmd_pkg pkg;
|
||||
struct nd_intel_secure_erase cmd;
|
||||
} nd_cmd = {
|
||||
.pkg = {
|
||||
.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,
|
||||
.nd_command = cmd,
|
||||
},
|
||||
};
|
||||
|
||||
if (!test_bit(cmd, &nfit_mem->dsm_mask))
|
||||
return -ENOTTY;
|
||||
|
||||
/* flush all cache before we erase DIMM */
|
||||
nvdimm_invalidate_cache();
|
||||
memcpy(nd_cmd.cmd.passphrase, key->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:
|
||||
break;
|
||||
case ND_INTEL_STATUS_NOT_SUPPORTED:
|
||||
return -EOPNOTSUPP;
|
||||
case ND_INTEL_STATUS_INVALID_PASS:
|
||||
return -EINVAL;
|
||||
case ND_INTEL_STATUS_INVALID_STATE:
|
||||
default:
|
||||
return -ENXIO;
|
||||
}
|
||||
|
||||
/* DIMM erased, invalidate all CPU caches before we read it */
|
||||
nvdimm_invalidate_cache();
|
||||
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.
|
||||
*/
|
||||
#ifdef CONFIG_X86
|
||||
static void nvdimm_invalidate_cache(void)
|
||||
{
|
||||
wbinvd_on_all_cpus();
|
||||
}
|
||||
#else
|
||||
static void nvdimm_invalidate_cache(void)
|
||||
{
|
||||
WARN_ON_ONCE("cache invalidation required after unlock\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
static const struct nvdimm_security_ops __intel_security_ops = {
|
||||
.state = intel_security_state,
|
||||
.freeze = intel_security_freeze,
|
||||
.change_key = intel_security_change_key,
|
||||
.disable = intel_security_disable,
|
||||
#ifdef CONFIG_X86
|
||||
.unlock = intel_security_unlock,
|
||||
.erase = intel_security_erase,
|
||||
.overwrite = intel_security_overwrite,
|
||||
.query_overwrite = intel_security_query_overwrite,
|
||||
#endif
|
||||
};
|
||||
|
||||
const struct nvdimm_security_ops *intel_security_ops = &__intel_security_ops;
|
|
@ -35,4 +35,80 @@ struct nd_intel_smart {
|
|||
};
|
||||
} __packed;
|
||||
|
||||
extern const struct nvdimm_security_ops *intel_security_ops;
|
||||
|
||||
#define ND_INTEL_STATUS_SIZE 4
|
||||
#define ND_INTEL_PASSPHRASE_SIZE 32
|
||||
|
||||
#define ND_INTEL_STATUS_NOT_SUPPORTED 1
|
||||
#define ND_INTEL_STATUS_RETRY 5
|
||||
#define ND_INTEL_STATUS_NOT_READY 9
|
||||
#define ND_INTEL_STATUS_INVALID_STATE 10
|
||||
#define ND_INTEL_STATUS_INVALID_PASS 11
|
||||
#define ND_INTEL_STATUS_OVERWRITE_UNSUPPORTED 0x10007
|
||||
#define ND_INTEL_STATUS_OQUERY_INPROGRESS 0x10007
|
||||
#define ND_INTEL_STATUS_OQUERY_SEQUENCE_ERR 0x20007
|
||||
|
||||
#define ND_INTEL_SEC_STATE_ENABLED 0x02
|
||||
#define ND_INTEL_SEC_STATE_LOCKED 0x04
|
||||
#define ND_INTEL_SEC_STATE_FROZEN 0x08
|
||||
#define ND_INTEL_SEC_STATE_PLIMIT 0x10
|
||||
#define ND_INTEL_SEC_STATE_UNSUPPORTED 0x20
|
||||
#define ND_INTEL_SEC_STATE_OVERWRITE 0x40
|
||||
|
||||
#define ND_INTEL_SEC_ESTATE_ENABLED 0x01
|
||||
#define ND_INTEL_SEC_ESTATE_PLIMIT 0x02
|
||||
|
||||
struct nd_intel_get_security_state {
|
||||
u32 status;
|
||||
u8 extended_state;
|
||||
u8 reserved[3];
|
||||
u8 state;
|
||||
u8 reserved1[3];
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_set_passphrase {
|
||||
u8 old_pass[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u8 new_pass[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u32 status;
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_unlock_unit {
|
||||
u8 passphrase[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u32 status;
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_disable_passphrase {
|
||||
u8 passphrase[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u32 status;
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_freeze_lock {
|
||||
u32 status;
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_secure_erase {
|
||||
u8 passphrase[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u32 status;
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_overwrite {
|
||||
u8 passphrase[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u32 status;
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_query_overwrite {
|
||||
u32 status;
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_set_master_passphrase {
|
||||
u8 old_pass[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u8 new_pass[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u32 status;
|
||||
} __packed;
|
||||
|
||||
struct nd_intel_master_secure_erase {
|
||||
u8 passphrase[ND_INTEL_PASSPHRASE_SIZE];
|
||||
u32 status;
|
||||
} __packed;
|
||||
#endif
|
||||
|
|
|
@ -60,14 +60,33 @@ enum nvdimm_family_cmds {
|
|||
NVDIMM_INTEL_QUERY_FWUPDATE = 16,
|
||||
NVDIMM_INTEL_SET_THRESHOLD = 17,
|
||||
NVDIMM_INTEL_INJECT_ERROR = 18,
|
||||
NVDIMM_INTEL_GET_SECURITY_STATE = 19,
|
||||
NVDIMM_INTEL_SET_PASSPHRASE = 20,
|
||||
NVDIMM_INTEL_DISABLE_PASSPHRASE = 21,
|
||||
NVDIMM_INTEL_UNLOCK_UNIT = 22,
|
||||
NVDIMM_INTEL_FREEZE_LOCK = 23,
|
||||
NVDIMM_INTEL_SECURE_ERASE = 24,
|
||||
NVDIMM_INTEL_OVERWRITE = 25,
|
||||
NVDIMM_INTEL_QUERY_OVERWRITE = 26,
|
||||
NVDIMM_INTEL_SET_MASTER_PASSPHRASE = 27,
|
||||
NVDIMM_INTEL_MASTER_SECURE_ERASE = 28,
|
||||
};
|
||||
|
||||
#define NVDIMM_INTEL_SECURITY_CMDMASK \
|
||||
(1 << NVDIMM_INTEL_GET_SECURITY_STATE | 1 << NVDIMM_INTEL_SET_PASSPHRASE \
|
||||
| 1 << NVDIMM_INTEL_DISABLE_PASSPHRASE | 1 << NVDIMM_INTEL_UNLOCK_UNIT \
|
||||
| 1 << NVDIMM_INTEL_FREEZE_LOCK | 1 << NVDIMM_INTEL_SECURE_ERASE \
|
||||
| 1 << NVDIMM_INTEL_OVERWRITE | 1 << NVDIMM_INTEL_QUERY_OVERWRITE \
|
||||
| 1 << NVDIMM_INTEL_SET_MASTER_PASSPHRASE \
|
||||
| 1 << NVDIMM_INTEL_MASTER_SECURE_ERASE)
|
||||
|
||||
#define NVDIMM_INTEL_CMDMASK \
|
||||
(NVDIMM_STANDARD_CMDMASK | 1 << NVDIMM_INTEL_GET_MODES \
|
||||
| 1 << NVDIMM_INTEL_GET_FWINFO | 1 << NVDIMM_INTEL_START_FWUPDATE \
|
||||
| 1 << NVDIMM_INTEL_SEND_FWUPDATE | 1 << NVDIMM_INTEL_FINISH_FWUPDATE \
|
||||
| 1 << NVDIMM_INTEL_QUERY_FWUPDATE | 1 << NVDIMM_INTEL_SET_THRESHOLD \
|
||||
| 1 << NVDIMM_INTEL_INJECT_ERROR | 1 << NVDIMM_INTEL_LATCH_SHUTDOWN)
|
||||
| 1 << NVDIMM_INTEL_INJECT_ERROR | 1 << NVDIMM_INTEL_LATCH_SHUTDOWN \
|
||||
| NVDIMM_INTEL_SECURITY_CMDMASK)
|
||||
|
||||
enum nfit_uuids {
|
||||
/* for simplicity alias the uuid index with the family id */
|
||||
|
@ -164,6 +183,8 @@ enum nfit_mem_flags {
|
|||
NFIT_MEM_DIRTY_COUNT,
|
||||
};
|
||||
|
||||
#define NFIT_DIMM_ID_LEN 22
|
||||
|
||||
/* assembled tables for a given dimm/memory-device */
|
||||
struct nfit_mem {
|
||||
struct nvdimm *nvdimm;
|
||||
|
@ -181,6 +202,7 @@ struct nfit_mem {
|
|||
struct list_head list;
|
||||
struct acpi_device *adev;
|
||||
struct acpi_nfit_desc *acpi_desc;
|
||||
char id[NFIT_DIMM_ID_LEN+1];
|
||||
struct resource *flush_wpq;
|
||||
unsigned long dsm_mask;
|
||||
unsigned long flags;
|
||||
|
|
|
@ -112,4 +112,9 @@ config OF_PMEM
|
|||
|
||||
Select Y if unsure.
|
||||
|
||||
config NVDIMM_KEYS
|
||||
def_bool y
|
||||
depends on ENCRYPTED_KEYS
|
||||
depends on (LIBNVDIMM=ENCRYPTED_KEYS) || LIBNVDIMM=m
|
||||
|
||||
endif
|
||||
|
|
|
@ -27,3 +27,4 @@ libnvdimm-$(CONFIG_ND_CLAIM) += claim.o
|
|||
libnvdimm-$(CONFIG_BTT) += btt_devs.o
|
||||
libnvdimm-$(CONFIG_NVDIMM_PFN) += pfn_devs.o
|
||||
libnvdimm-$(CONFIG_NVDIMM_DAX) += dax_devs.o
|
||||
libnvdimm-$(CONFIG_NVDIMM_KEYS) += security.o
|
||||
|
|
|
@ -331,6 +331,12 @@ struct nvdimm_bus *to_nvdimm_bus(struct device *dev)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(to_nvdimm_bus);
|
||||
|
||||
struct nvdimm_bus *nvdimm_to_bus(struct nvdimm *nvdimm)
|
||||
{
|
||||
return to_nvdimm_bus(nvdimm->dev.parent);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nvdimm_to_bus);
|
||||
|
||||
struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
|
||||
struct nvdimm_bus_descriptor *nd_desc)
|
||||
{
|
||||
|
@ -344,12 +350,12 @@ struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
|
|||
INIT_LIST_HEAD(&nvdimm_bus->mapping_list);
|
||||
init_waitqueue_head(&nvdimm_bus->probe_wait);
|
||||
nvdimm_bus->id = ida_simple_get(&nd_ida, 0, 0, GFP_KERNEL);
|
||||
mutex_init(&nvdimm_bus->reconfig_mutex);
|
||||
badrange_init(&nvdimm_bus->badrange);
|
||||
if (nvdimm_bus->id < 0) {
|
||||
kfree(nvdimm_bus);
|
||||
return NULL;
|
||||
}
|
||||
mutex_init(&nvdimm_bus->reconfig_mutex);
|
||||
badrange_init(&nvdimm_bus->badrange);
|
||||
nvdimm_bus->nd_desc = nd_desc;
|
||||
nvdimm_bus->dev.parent = parent;
|
||||
nvdimm_bus->dev.release = nvdimm_bus_release;
|
||||
|
@ -387,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;
|
||||
}
|
||||
|
||||
|
@ -902,7 +923,7 @@ static int nd_cmd_clear_to_send(struct nvdimm_bus *nvdimm_bus,
|
|||
|
||||
/* ask the bus provider if it would like to block this request */
|
||||
if (nd_desc->clear_to_send) {
|
||||
int rc = nd_desc->clear_to_send(nd_desc, nvdimm, cmd);
|
||||
int rc = nd_desc->clear_to_send(nd_desc, nvdimm, cmd, data);
|
||||
|
||||
if (rc)
|
||||
return rc;
|
||||
|
|
|
@ -34,7 +34,11 @@ static int nvdimm_probe(struct device *dev)
|
|||
return rc;
|
||||
}
|
||||
|
||||
/* reset locked, to be validated below... */
|
||||
/*
|
||||
* The locked status bit reflects explicit status codes from the
|
||||
* label reading commands, revalidate it each time the driver is
|
||||
* activated and re-reads the label area.
|
||||
*/
|
||||
nvdimm_clear_locked(dev);
|
||||
|
||||
ndd = kzalloc(sizeof(*ndd), GFP_KERNEL);
|
||||
|
@ -51,6 +55,16 @@ static int nvdimm_probe(struct device *dev)
|
|||
get_device(dev);
|
||||
kref_init(&ndd->kref);
|
||||
|
||||
/*
|
||||
* Attempt to unlock, if the DIMM supports security commands,
|
||||
* otherwise the locked indication is determined by explicit
|
||||
* status codes from the label reading commands.
|
||||
*/
|
||||
rc = nvdimm_security_unlock(dev);
|
||||
if (rc < 0)
|
||||
dev_dbg(dev, "failed to unlock dimm: %d\n", rc);
|
||||
|
||||
|
||||
/*
|
||||
* EACCES failures reading the namespace label-area-properties
|
||||
* are interpreted as the DIMM capacity being locked but the
|
||||
|
|
|
@ -370,23 +370,172 @@ static ssize_t available_slots_show(struct device *dev,
|
|||
}
|
||||
static DEVICE_ATTR_RO(available_slots);
|
||||
|
||||
__weak ssize_t security_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct nvdimm *nvdimm = to_nvdimm(dev);
|
||||
|
||||
switch (nvdimm->sec.state) {
|
||||
case NVDIMM_SECURITY_DISABLED:
|
||||
return sprintf(buf, "disabled\n");
|
||||
case NVDIMM_SECURITY_UNLOCKED:
|
||||
return sprintf(buf, "unlocked\n");
|
||||
case NVDIMM_SECURITY_LOCKED:
|
||||
return sprintf(buf, "locked\n");
|
||||
case NVDIMM_SECURITY_FROZEN:
|
||||
return sprintf(buf, "frozen\n");
|
||||
case NVDIMM_SECURITY_OVERWRITE:
|
||||
return sprintf(buf, "overwrite\n");
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
#define OPS \
|
||||
C( OP_FREEZE, "freeze", 1), \
|
||||
C( OP_DISABLE, "disable", 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
|
||||
|
||||
static ssize_t __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;
|
||||
|
||||
if (atomic_read(&nvdimm->busy))
|
||||
return -EBUSY;
|
||||
|
||||
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 = nvdimm_security_disable(nvdimm, key);
|
||||
} else if (i == OP_UPDATE) {
|
||||
dev_dbg(dev, "update %u %u\n", key, newkey);
|
||||
rc = nvdimm_security_update(nvdimm, key, newkey, NVDIMM_USER);
|
||||
} else if (i == OP_ERASE) {
|
||||
dev_dbg(dev, "erase %u\n", key);
|
||||
rc = nvdimm_security_erase(nvdimm, key, NVDIMM_USER);
|
||||
} else if (i == OP_OVERWRITE) {
|
||||
dev_dbg(dev, "overwrite %u\n", key);
|
||||
rc = nvdimm_security_overwrite(nvdimm, key);
|
||||
} else if (i == OP_MASTER_UPDATE) {
|
||||
dev_dbg(dev, "master_update %u %u\n", key, newkey);
|
||||
rc = nvdimm_security_update(nvdimm, key, newkey,
|
||||
NVDIMM_MASTER);
|
||||
} else if (i == OP_MASTER_ERASE) {
|
||||
dev_dbg(dev, "master_erase %u\n", key);
|
||||
rc = nvdimm_security_erase(nvdimm, key,
|
||||
NVDIMM_MASTER);
|
||||
} else
|
||||
return -EINVAL;
|
||||
|
||||
if (rc == 0)
|
||||
rc = len;
|
||||
return rc;
|
||||
}
|
||||
|
||||
static ssize_t security_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t len)
|
||||
|
||||
{
|
||||
ssize_t rc;
|
||||
|
||||
/*
|
||||
* Require all userspace triggered security management to be
|
||||
* done while probing is idle and the DIMM is not in active use
|
||||
* in any region.
|
||||
*/
|
||||
device_lock(dev);
|
||||
nvdimm_bus_lock(dev);
|
||||
wait_nvdimm_bus_probe_idle(dev);
|
||||
rc = __security_store(dev, buf, len);
|
||||
nvdimm_bus_unlock(dev);
|
||||
device_unlock(dev);
|
||||
|
||||
return rc;
|
||||
}
|
||||
static DEVICE_ATTR_RW(security);
|
||||
|
||||
static struct attribute *nvdimm_attributes[] = {
|
||||
&dev_attr_state.attr,
|
||||
&dev_attr_flags.attr,
|
||||
&dev_attr_commands.attr,
|
||||
&dev_attr_available_slots.attr,
|
||||
&dev_attr_security.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t nvdimm_visible(struct kobject *kobj, struct attribute *a, int n)
|
||||
{
|
||||
struct device *dev = container_of(kobj, typeof(*dev), kobj);
|
||||
struct nvdimm *nvdimm = to_nvdimm(dev);
|
||||
|
||||
if (a != &dev_attr_security.attr)
|
||||
return a->mode;
|
||||
if (nvdimm->sec.state < 0)
|
||||
return 0;
|
||||
/* 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->overwrite)
|
||||
return a->mode;
|
||||
return 0444;
|
||||
}
|
||||
|
||||
struct attribute_group nvdimm_attribute_group = {
|
||||
.attrs = nvdimm_attributes,
|
||||
.is_visible = nvdimm_visible,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(nvdimm_attribute_group);
|
||||
|
||||
struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
|
||||
const struct attribute_group **groups, unsigned long flags,
|
||||
unsigned long cmd_mask, int num_flush,
|
||||
struct resource *flush_wpq)
|
||||
struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus,
|
||||
void *provider_data, const struct attribute_group **groups,
|
||||
unsigned long flags, unsigned long cmd_mask, int num_flush,
|
||||
struct resource *flush_wpq, const char *dimm_id,
|
||||
const struct nvdimm_security_ops *sec_ops)
|
||||
{
|
||||
struct nvdimm *nvdimm = kzalloc(sizeof(*nvdimm), GFP_KERNEL);
|
||||
struct device *dev;
|
||||
|
@ -399,6 +548,8 @@ struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
|
|||
kfree(nvdimm);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
nvdimm->dimm_id = dimm_id;
|
||||
nvdimm->provider_data = provider_data;
|
||||
nvdimm->flags = flags;
|
||||
nvdimm->cmd_mask = cmd_mask;
|
||||
|
@ -411,11 +562,60 @@ struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
|
|||
dev->type = &nvdimm_device_type;
|
||||
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.
|
||||
*/
|
||||
/* get security state and extended (master) state */
|
||||
nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
|
||||
nvdimm->sec.ext_state = nvdimm_security_state(nvdimm, NVDIMM_MASTER);
|
||||
nd_device_register(dev);
|
||||
|
||||
return nvdimm;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(nvdimm_create);
|
||||
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;
|
||||
|
||||
WARN_ON_ONCE(!is_nvdimm_bus_locked(&nvdimm->dev));
|
||||
|
||||
if (!nvdimm->sec.ops || !nvdimm->sec.ops->freeze)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
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, NVDIMM_USER);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
int alias_dpa_busy(struct device *dev, void *data)
|
||||
{
|
||||
|
|
|
@ -944,8 +944,7 @@ static int __blk_label_update(struct nd_region *nd_region,
|
|||
victims = 0;
|
||||
if (old_num_resources) {
|
||||
/* convert old local-label-map to dimm-slot victim-map */
|
||||
victim_map = kcalloc(BITS_TO_LONGS(nslot), sizeof(long),
|
||||
GFP_KERNEL);
|
||||
victim_map = bitmap_zalloc(nslot, GFP_KERNEL);
|
||||
if (!victim_map)
|
||||
return -ENOMEM;
|
||||
|
||||
|
@ -968,7 +967,7 @@ static int __blk_label_update(struct nd_region *nd_region,
|
|||
/* don't allow updates that consume the last label */
|
||||
if (nfree - alloc < 0 || nfree - alloc + victims < 1) {
|
||||
dev_info(&nsblk->common.dev, "insufficient label space\n");
|
||||
kfree(victim_map);
|
||||
bitmap_free(victim_map);
|
||||
return -ENOSPC;
|
||||
}
|
||||
/* from here on we need to abort on error */
|
||||
|
@ -1140,7 +1139,7 @@ static int __blk_label_update(struct nd_region *nd_region,
|
|||
|
||||
out:
|
||||
kfree(old_res_list);
|
||||
kfree(victim_map);
|
||||
bitmap_free(victim_map);
|
||||
return rc;
|
||||
|
||||
abort:
|
||||
|
|
|
@ -270,11 +270,10 @@ static ssize_t __alt_name_store(struct device *dev, const char *buf,
|
|||
if (dev->driver || to_ndns(dev)->claim)
|
||||
return -EBUSY;
|
||||
|
||||
input = kmemdup(buf, len + 1, GFP_KERNEL);
|
||||
input = kstrndup(buf, len, GFP_KERNEL);
|
||||
if (!input)
|
||||
return -ENOMEM;
|
||||
|
||||
input[len] = '\0';
|
||||
pos = strim(input);
|
||||
if (strlen(pos) + 1 > NSLABEL_NAME_LEN) {
|
||||
rc = -EINVAL;
|
||||
|
|
|
@ -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;
|
||||
|
@ -41,8 +42,64 @@ struct nvdimm {
|
|||
atomic_t busy;
|
||||
int id, num_flush;
|
||||
struct resource *flush_wpq;
|
||||
const char *dimm_id;
|
||||
struct {
|
||||
const struct nvdimm_security_ops *ops;
|
||||
enum nvdimm_security_state state;
|
||||
enum nvdimm_security_state ext_state;
|
||||
unsigned int overwrite_tmo;
|
||||
struct kernfs_node *overwrite_state;
|
||||
} sec;
|
||||
struct delayed_work dwork;
|
||||
};
|
||||
|
||||
static inline enum nvdimm_security_state nvdimm_security_state(
|
||||
struct nvdimm *nvdimm, bool master)
|
||||
{
|
||||
if (!nvdimm->sec.ops)
|
||||
return -ENXIO;
|
||||
|
||||
return nvdimm->sec.ops->state(nvdimm, master);
|
||||
}
|
||||
int nvdimm_security_freeze(struct nvdimm *nvdimm);
|
||||
#if IS_ENABLED(CONFIG_NVDIMM_KEYS)
|
||||
int nvdimm_security_disable(struct nvdimm *nvdimm, unsigned int keyid);
|
||||
int nvdimm_security_update(struct nvdimm *nvdimm, unsigned int keyid,
|
||||
unsigned int new_keyid,
|
||||
enum nvdimm_passphrase_type pass_type);
|
||||
int nvdimm_security_erase(struct nvdimm *nvdimm, unsigned int keyid,
|
||||
enum nvdimm_passphrase_type pass_type);
|
||||
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)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
static inline int nvdimm_security_update(struct nvdimm *nvdimm,
|
||||
unsigned int keyid,
|
||||
unsigned int new_keyid,
|
||||
enum nvdimm_passphrase_type pass_type)
|
||||
{
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
static inline int nvdimm_security_erase(struct nvdimm *nvdimm,
|
||||
unsigned int keyid,
|
||||
enum nvdimm_passphrase_type pass_type)
|
||||
{
|
||||
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
|
||||
|
||||
/**
|
||||
* struct blk_alloc_info - tracking info for BLK dpa scanning
|
||||
* @nd_mapping: blk region mapping boundaries
|
||||
|
|
|
@ -250,6 +250,14 @@ long nvdimm_clear_poison(struct device *dev, phys_addr_t phys,
|
|||
void nvdimm_set_aliasing(struct device *dev);
|
||||
void nvdimm_set_locked(struct device *dev);
|
||||
void nvdimm_clear_locked(struct device *dev);
|
||||
#if IS_ENABLED(CONFIG_NVDIMM_KEYS)
|
||||
int nvdimm_security_unlock(struct device *dev);
|
||||
#else
|
||||
static inline int nvdimm_security_unlock(struct device *dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
struct nd_btt *to_nd_btt(struct device *dev);
|
||||
|
||||
struct nd_gen_sb {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -0,0 +1,454 @@
|
|||
// 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 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 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, 0);
|
||||
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 struct key *nvdimm_key_revalidate(struct nvdimm *nvdimm)
|
||||
{
|
||||
struct key *key;
|
||||
int rc;
|
||||
|
||||
if (!nvdimm->sec.ops->change_key)
|
||||
return NULL;
|
||||
|
||||
key = nvdimm_request_key(nvdimm);
|
||||
if (!key)
|
||||
return NULL;
|
||||
|
||||
/*
|
||||
* 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, key_data(key),
|
||||
key_data(key), NVDIMM_USER);
|
||||
if (rc < 0) {
|
||||
nvdimm_put_key(key);
|
||||
key = NULL;
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
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 = NULL;
|
||||
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.state < 0)
|
||||
return -EIO;
|
||||
|
||||
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 (nvdimm->sec.state == NVDIMM_SECURITY_UNLOCKED) {
|
||||
if (!key_revalidate)
|
||||
return 0;
|
||||
|
||||
key = nvdimm_key_revalidate(nvdimm);
|
||||
if (!key)
|
||||
return nvdimm_security_freeze(nvdimm);
|
||||
} else
|
||||
key = nvdimm_request_key(nvdimm);
|
||||
|
||||
if (!key)
|
||||
return -ENOKEY;
|
||||
|
||||
rc = nvdimm->sec.ops->unlock(nvdimm, key_data(key));
|
||||
dev_dbg(dev, "key: %d unlock: %s\n", key_serial(key),
|
||||
rc == 0 ? "success" : "fail");
|
||||
|
||||
nvdimm_put_key(key);
|
||||
nvdimm->sec.state = nvdimm_security_state(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;
|
||||
}
|
||||
|
||||
int nvdimm_security_disable(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->disable
|
||||
|| nvdimm->sec.state < 0)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
|
||||
dev_dbg(dev, "Incorrect security state: %d\n",
|
||||
nvdimm->sec.state);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
|
||||
dev_dbg(dev, "Security operation in progress.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
key = nvdimm_lookup_user_key(nvdimm, keyid, NVDIMM_BASE_KEY);
|
||||
if (!key)
|
||||
return -ENOKEY;
|
||||
|
||||
rc = nvdimm->sec.ops->disable(nvdimm, key_data(key));
|
||||
dev_dbg(dev, "key: %d disable: %s\n", key_serial(key),
|
||||
rc == 0 ? "success" : "fail");
|
||||
|
||||
nvdimm_put_key(key);
|
||||
nvdimm->sec.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int nvdimm_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;
|
||||
|
||||
/* 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.state < 0)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
|
||||
dev_dbg(dev, "Incorrect security state: %d\n",
|
||||
nvdimm->sec.state);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (keyid == 0)
|
||||
key = NULL;
|
||||
else {
|
||||
key = nvdimm_lookup_user_key(nvdimm, keyid, NVDIMM_BASE_KEY);
|
||||
if (!key)
|
||||
return -ENOKEY;
|
||||
}
|
||||
|
||||
newkey = nvdimm_lookup_user_key(nvdimm, new_keyid, NVDIMM_NEW_KEY);
|
||||
if (!newkey) {
|
||||
nvdimm_put_key(key);
|
||||
return -ENOKEY;
|
||||
}
|
||||
|
||||
rc = nvdimm->sec.ops->change_key(nvdimm, key ? key_data(key) : NULL,
|
||||
key_data(newkey), 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_state = nvdimm_security_state(nvdimm,
|
||||
NVDIMM_MASTER);
|
||||
else
|
||||
nvdimm->sec.state = nvdimm_security_state(nvdimm,
|
||||
NVDIMM_USER);
|
||||
return rc;
|
||||
}
|
||||
|
||||
int nvdimm_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;
|
||||
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->erase
|
||||
|| nvdimm->sec.state < 0)
|
||||
return -EOPNOTSUPP;
|
||||
|
||||
if (atomic_read(&nvdimm->busy)) {
|
||||
dev_dbg(dev, "Unable to secure erase while DIMM active.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
|
||||
dev_dbg(dev, "Incorrect security state: %d\n",
|
||||
nvdimm->sec.state);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
|
||||
dev_dbg(dev, "Security operation in progress.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (nvdimm->sec.ext_state != NVDIMM_SECURITY_UNLOCKED
|
||||
&& pass_type == NVDIMM_MASTER) {
|
||||
dev_dbg(dev,
|
||||
"Attempt to secure erase in wrong master state.\n");
|
||||
return -EOPNOTSUPP;
|
||||
}
|
||||
|
||||
key = nvdimm_lookup_user_key(nvdimm, keyid, NVDIMM_BASE_KEY);
|
||||
if (!key)
|
||||
return -ENOKEY;
|
||||
|
||||
rc = nvdimm->sec.ops->erase(nvdimm, key_data(key), 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.state = nvdimm_security_state(nvdimm, NVDIMM_USER);
|
||||
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_dbg(dev, "Unable to overwrite while DIMM active.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
if (dev->driver == NULL) {
|
||||
dev_dbg(dev, "Unable to overwrite while DIMM active.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (nvdimm->sec.state >= NVDIMM_SECURITY_FROZEN) {
|
||||
dev_dbg(dev, "Incorrect security state: %d\n",
|
||||
nvdimm->sec.state);
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
if (test_bit(NDD_SECURITY_OVERWRITE, &nvdimm->flags)) {
|
||||
dev_dbg(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_dbg(&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, NVDIMM_USER);
|
||||
nvdimm->sec.ext_state = nvdimm_security_state(nvdimm, NVDIMM_MASTER);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
|
@ -346,6 +346,9 @@ static inline key_serial_t key_serial(const struct key *key)
|
|||
|
||||
extern void key_set_timeout(struct key *, unsigned);
|
||||
|
||||
extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
|
||||
key_perm_t perm);
|
||||
|
||||
/*
|
||||
* The permissions required on a key that we're looking up.
|
||||
*/
|
||||
|
|
|
@ -38,6 +38,10 @@ enum {
|
|||
NDD_UNARMED = 1,
|
||||
/* locked memory devices should not be accessed */
|
||||
NDD_LOCKED = 2,
|
||||
/* memory under security wipes should not be accessed */
|
||||
NDD_SECURITY_OVERWRITE = 3,
|
||||
/* tracking whether or not there is a pending device reference */
|
||||
NDD_WORK_PENDING = 4,
|
||||
|
||||
/* need to set a limit somewhere, but yes, this is likely overkill */
|
||||
ND_IOCTL_MAX_BUFLEN = SZ_4M,
|
||||
|
@ -87,7 +91,7 @@ struct nvdimm_bus_descriptor {
|
|||
ndctl_fn ndctl;
|
||||
int (*flush_probe)(struct nvdimm_bus_descriptor *nd_desc);
|
||||
int (*clear_to_send)(struct nvdimm_bus_descriptor *nd_desc,
|
||||
struct nvdimm *nvdimm, unsigned int cmd);
|
||||
struct nvdimm *nvdimm, unsigned int cmd, void *data);
|
||||
};
|
||||
|
||||
struct nd_cmd_desc {
|
||||
|
@ -155,6 +159,46 @@ static inline struct nd_blk_region_desc *to_blk_region_desc(
|
|||
|
||||
}
|
||||
|
||||
enum nvdimm_security_state {
|
||||
NVDIMM_SECURITY_DISABLED,
|
||||
NVDIMM_SECURITY_UNLOCKED,
|
||||
NVDIMM_SECURITY_LOCKED,
|
||||
NVDIMM_SECURITY_FROZEN,
|
||||
NVDIMM_SECURITY_OVERWRITE,
|
||||
};
|
||||
|
||||
#define NVDIMM_PASSPHRASE_LEN 32
|
||||
#define NVDIMM_KEY_DESC_LEN 22
|
||||
|
||||
struct nvdimm_key_data {
|
||||
u8 data[NVDIMM_PASSPHRASE_LEN];
|
||||
};
|
||||
|
||||
enum nvdimm_passphrase_type {
|
||||
NVDIMM_USER,
|
||||
NVDIMM_MASTER,
|
||||
};
|
||||
|
||||
struct nvdimm_security_ops {
|
||||
enum nvdimm_security_state (*state)(struct nvdimm *nvdimm,
|
||||
enum nvdimm_passphrase_type pass_type);
|
||||
int (*freeze)(struct nvdimm *nvdimm);
|
||||
int (*change_key)(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *old_data,
|
||||
const struct nvdimm_key_data *new_data,
|
||||
enum nvdimm_passphrase_type pass_type);
|
||||
int (*unlock)(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *key_data);
|
||||
int (*disable)(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *key_data);
|
||||
int (*erase)(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *key_data,
|
||||
enum nvdimm_passphrase_type pass_type);
|
||||
int (*overwrite)(struct nvdimm *nvdimm,
|
||||
const struct nvdimm_key_data *key_data);
|
||||
int (*query_overwrite)(struct nvdimm *nvdimm);
|
||||
};
|
||||
|
||||
void badrange_init(struct badrange *badrange);
|
||||
int badrange_add(struct badrange *badrange, u64 addr, u64 length);
|
||||
void badrange_forget(struct badrange *badrange, phys_addr_t start,
|
||||
|
@ -165,6 +209,7 @@ struct nvdimm_bus *nvdimm_bus_register(struct device *parent,
|
|||
struct nvdimm_bus_descriptor *nfit_desc);
|
||||
void nvdimm_bus_unregister(struct nvdimm_bus *nvdimm_bus);
|
||||
struct nvdimm_bus *to_nvdimm_bus(struct device *dev);
|
||||
struct nvdimm_bus *nvdimm_to_bus(struct nvdimm *nvdimm);
|
||||
struct nvdimm *to_nvdimm(struct device *dev);
|
||||
struct nd_region *to_nd_region(struct device *dev);
|
||||
struct device *nd_region_dev(struct nd_region *nd_region);
|
||||
|
@ -175,10 +220,21 @@ const char *nvdimm_name(struct nvdimm *nvdimm);
|
|||
struct kobject *nvdimm_kobj(struct nvdimm *nvdimm);
|
||||
unsigned long nvdimm_cmd_mask(struct nvdimm *nvdimm);
|
||||
void *nvdimm_provider_data(struct nvdimm *nvdimm);
|
||||
struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus, void *provider_data,
|
||||
const struct attribute_group **groups, unsigned long flags,
|
||||
unsigned long cmd_mask, int num_flush,
|
||||
struct resource *flush_wpq);
|
||||
struct nvdimm *__nvdimm_create(struct nvdimm_bus *nvdimm_bus,
|
||||
void *provider_data, const struct attribute_group **groups,
|
||||
unsigned long flags, unsigned long cmd_mask, int num_flush,
|
||||
struct resource *flush_wpq, const char *dimm_id,
|
||||
const struct nvdimm_security_ops *sec_ops);
|
||||
static inline struct nvdimm *nvdimm_create(struct nvdimm_bus *nvdimm_bus,
|
||||
void *provider_data, const struct attribute_group **groups,
|
||||
unsigned long flags, unsigned long cmd_mask, int num_flush,
|
||||
struct resource *flush_wpq)
|
||||
{
|
||||
return __nvdimm_create(nvdimm_bus, provider_data, groups, flags,
|
||||
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,
|
||||
|
@ -204,6 +260,16 @@ u64 nd_fletcher64(void *addr, size_t len, bool le);
|
|||
void nvdimm_flush(struct nd_region *nd_region);
|
||||
int nvdimm_has_flush(struct nd_region *nd_region);
|
||||
int nvdimm_has_cache(struct nd_region *nd_region);
|
||||
int nvdimm_in_overwrite(struct nvdimm *nvdimm);
|
||||
|
||||
static inline int nvdimm_ctl(struct nvdimm *nvdimm, unsigned int cmd, void *buf,
|
||||
unsigned int buf_len, int *cmd_rc)
|
||||
{
|
||||
struct nvdimm_bus *nvdimm_bus = nvdimm_to_bus(nvdimm);
|
||||
struct nvdimm_bus_descriptor *nd_desc = to_nd_desc(nvdimm_bus);
|
||||
|
||||
return nd_desc->ndctl(nd_desc, nvdimm, cmd, buf, buf_len, cmd_rc);
|
||||
}
|
||||
|
||||
#ifdef CONFIG_ARCH_HAS_PMEM_API
|
||||
#define ARCH_MEMREMAP_PMEM MEMREMAP_WB
|
||||
|
|
|
@ -45,6 +45,7 @@ static const char hmac_alg[] = "hmac(sha256)";
|
|||
static const char blkcipher_alg[] = "cbc(aes)";
|
||||
static const char key_format_default[] = "default";
|
||||
static const char key_format_ecryptfs[] = "ecryptfs";
|
||||
static const char key_format_enc32[] = "enc32";
|
||||
static unsigned int ivsize;
|
||||
static int blksize;
|
||||
|
||||
|
@ -54,6 +55,7 @@ static int blksize;
|
|||
#define HASH_SIZE SHA256_DIGEST_SIZE
|
||||
#define MAX_DATA_SIZE 4096
|
||||
#define MIN_DATA_SIZE 20
|
||||
#define KEY_ENC32_PAYLOAD_LEN 32
|
||||
|
||||
static struct crypto_shash *hash_tfm;
|
||||
|
||||
|
@ -62,12 +64,13 @@ enum {
|
|||
};
|
||||
|
||||
enum {
|
||||
Opt_error = -1, Opt_default, Opt_ecryptfs
|
||||
Opt_error = -1, Opt_default, Opt_ecryptfs, Opt_enc32
|
||||
};
|
||||
|
||||
static const match_table_t key_format_tokens = {
|
||||
{Opt_default, "default"},
|
||||
{Opt_ecryptfs, "ecryptfs"},
|
||||
{Opt_enc32, "enc32"},
|
||||
{Opt_error, NULL}
|
||||
};
|
||||
|
||||
|
@ -195,6 +198,7 @@ static int datablob_parse(char *datablob, const char **format,
|
|||
key_format = match_token(p, key_format_tokens, args);
|
||||
switch (key_format) {
|
||||
case Opt_ecryptfs:
|
||||
case Opt_enc32:
|
||||
case Opt_default:
|
||||
*format = p;
|
||||
*master_desc = strsep(&datablob, " \t");
|
||||
|
@ -625,15 +629,22 @@ static struct encrypted_key_payload *encrypted_key_alloc(struct key *key,
|
|||
format_len = (!format) ? strlen(key_format_default) : strlen(format);
|
||||
decrypted_datalen = dlen;
|
||||
payload_datalen = decrypted_datalen;
|
||||
if (format && !strcmp(format, key_format_ecryptfs)) {
|
||||
if (dlen != ECRYPTFS_MAX_KEY_BYTES) {
|
||||
pr_err("encrypted_key: keylen for the ecryptfs format "
|
||||
"must be equal to %d bytes\n",
|
||||
ECRYPTFS_MAX_KEY_BYTES);
|
||||
return ERR_PTR(-EINVAL);
|
||||
if (format) {
|
||||
if (!strcmp(format, key_format_ecryptfs)) {
|
||||
if (dlen != ECRYPTFS_MAX_KEY_BYTES) {
|
||||
pr_err("encrypted_key: keylen for the ecryptfs format must be equal to %d bytes\n",
|
||||
ECRYPTFS_MAX_KEY_BYTES);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
decrypted_datalen = ECRYPTFS_MAX_KEY_BYTES;
|
||||
payload_datalen = sizeof(struct ecryptfs_auth_tok);
|
||||
} else if (!strcmp(format, key_format_enc32)) {
|
||||
if (decrypted_datalen != KEY_ENC32_PAYLOAD_LEN) {
|
||||
pr_err("encrypted_key: enc32 key payload incorrect length: %d\n",
|
||||
decrypted_datalen);
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
}
|
||||
decrypted_datalen = ECRYPTFS_MAX_KEY_BYTES;
|
||||
payload_datalen = sizeof(struct ecryptfs_auth_tok);
|
||||
}
|
||||
|
||||
encrypted_datalen = roundup(decrypted_datalen, blksize);
|
||||
|
|
|
@ -158,8 +158,6 @@ extern struct key *request_key_and_link(struct key_type *type,
|
|||
|
||||
extern bool lookup_user_key_possessed(const struct key *key,
|
||||
const struct key_match_data *match_data);
|
||||
extern key_ref_t lookup_user_key(key_serial_t id, unsigned long flags,
|
||||
key_perm_t perm);
|
||||
#define KEY_LOOKUP_CREATE 0x01
|
||||
#define KEY_LOOKUP_PARTIAL 0x02
|
||||
#define KEY_LOOKUP_FOR_UNLINK 0x04
|
||||
|
|
|
@ -754,6 +754,7 @@ reget_creds:
|
|||
put_cred(ctx.cred);
|
||||
goto try_again;
|
||||
}
|
||||
EXPORT_SYMBOL(lookup_user_key);
|
||||
|
||||
/*
|
||||
* Join the named keyring as the session keyring if possible else attempt to
|
||||
|
|
|
@ -37,6 +37,7 @@ obj-$(CONFIG_DEV_DAX) += device_dax.o
|
|||
obj-$(CONFIG_DEV_DAX_PMEM) += dax_pmem.o
|
||||
|
||||
nfit-y := $(ACPI_SRC)/core.o
|
||||
nfit-y += $(ACPI_SRC)/intel.o
|
||||
nfit-$(CONFIG_X86_MCE) += $(ACPI_SRC)/mce.o
|
||||
nfit-y += acpi_nfit_test.o
|
||||
nfit-y += config_check.o
|
||||
|
@ -79,6 +80,8 @@ libnvdimm-$(CONFIG_ND_CLAIM) += $(NVDIMM_SRC)/claim.o
|
|||
libnvdimm-$(CONFIG_BTT) += $(NVDIMM_SRC)/btt_devs.o
|
||||
libnvdimm-$(CONFIG_NVDIMM_PFN) += $(NVDIMM_SRC)/pfn_devs.o
|
||||
libnvdimm-$(CONFIG_NVDIMM_DAX) += $(NVDIMM_SRC)/dax_devs.o
|
||||
libnvdimm-$(CONFIG_NVDIMM_KEYS) += $(NVDIMM_SRC)/security.o
|
||||
libnvdimm-y += dimm_devs.o
|
||||
libnvdimm-y += libnvdimm_test.o
|
||||
libnvdimm-y += config_check.o
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/* Copyright Intel Corp. 2018 */
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/nd.h>
|
||||
#include "pmem.h"
|
||||
#include "pfn.h"
|
||||
#include "nd.h"
|
||||
#include "nd-core.h"
|
||||
|
||||
ssize_t security_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct nvdimm *nvdimm = to_nvdimm(dev);
|
||||
|
||||
/*
|
||||
* For the test version we need to poll the "hardware" in order
|
||||
* to get the updated status for unlock testing.
|
||||
*/
|
||||
nvdimm->sec.state = nvdimm_security_state(nvdimm, false);
|
||||
nvdimm->sec.ext_state = nvdimm_security_state(nvdimm, true);
|
||||
|
||||
switch (nvdimm->sec.state) {
|
||||
case NVDIMM_SECURITY_DISABLED:
|
||||
return sprintf(buf, "disabled\n");
|
||||
case NVDIMM_SECURITY_UNLOCKED:
|
||||
return sprintf(buf, "unlocked\n");
|
||||
case NVDIMM_SECURITY_LOCKED:
|
||||
return sprintf(buf, "locked\n");
|
||||
case NVDIMM_SECURITY_FROZEN:
|
||||
return sprintf(buf, "frozen\n");
|
||||
case NVDIMM_SECURITY_OVERWRITE:
|
||||
return sprintf(buf, "overwrite\n");
|
||||
default:
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
||||
return -ENOTTY;
|
||||
}
|
||||
|
|
@ -143,6 +143,13 @@ static u32 handle[] = {
|
|||
|
||||
static unsigned long dimm_fail_cmd_flags[ARRAY_SIZE(handle)];
|
||||
static int dimm_fail_cmd_code[ARRAY_SIZE(handle)];
|
||||
struct nfit_test_sec {
|
||||
u8 state;
|
||||
u8 ext_state;
|
||||
u8 passphrase[32];
|
||||
u8 master_passphrase[32];
|
||||
u64 overwrite_end_time;
|
||||
} dimm_sec_info[NUM_DCR];
|
||||
|
||||
static const struct nd_intel_smart smart_def = {
|
||||
.flags = ND_INTEL_SMART_HEALTH_VALID
|
||||
|
@ -936,6 +943,242 @@ static int override_return_code(int dimm, unsigned int func, int rc)
|
|||
return rc;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_security_status(struct nfit_test *t,
|
||||
struct nd_intel_get_security_state *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
nd_cmd->status = 0;
|
||||
nd_cmd->state = sec->state;
|
||||
nd_cmd->extended_state = sec->ext_state;
|
||||
dev_dbg(dev, "security state (%#x) returned\n", nd_cmd->state);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_unlock_unit(struct nfit_test *t,
|
||||
struct nd_intel_unlock_unit *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if (!(sec->state & ND_INTEL_SEC_STATE_LOCKED) ||
|
||||
(sec->state & ND_INTEL_SEC_STATE_FROZEN)) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_STATE;
|
||||
dev_dbg(dev, "unlock unit: invalid state: %#x\n",
|
||||
sec->state);
|
||||
} else if (memcmp(nd_cmd->passphrase, sec->passphrase,
|
||||
ND_INTEL_PASSPHRASE_SIZE) != 0) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_PASS;
|
||||
dev_dbg(dev, "unlock unit: invalid passphrase\n");
|
||||
} else {
|
||||
nd_cmd->status = 0;
|
||||
sec->state = ND_INTEL_SEC_STATE_ENABLED;
|
||||
dev_dbg(dev, "Unit unlocked\n");
|
||||
}
|
||||
|
||||
dev_dbg(dev, "unlocking status returned: %#x\n", nd_cmd->status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_set_pass(struct nfit_test *t,
|
||||
struct nd_intel_set_passphrase *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if (sec->state & ND_INTEL_SEC_STATE_FROZEN) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_STATE;
|
||||
dev_dbg(dev, "set passphrase: wrong security state\n");
|
||||
} else if (memcmp(nd_cmd->old_pass, sec->passphrase,
|
||||
ND_INTEL_PASSPHRASE_SIZE) != 0) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_PASS;
|
||||
dev_dbg(dev, "set passphrase: wrong passphrase\n");
|
||||
} else {
|
||||
memcpy(sec->passphrase, nd_cmd->new_pass,
|
||||
ND_INTEL_PASSPHRASE_SIZE);
|
||||
sec->state |= ND_INTEL_SEC_STATE_ENABLED;
|
||||
nd_cmd->status = 0;
|
||||
dev_dbg(dev, "passphrase updated\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_freeze_lock(struct nfit_test *t,
|
||||
struct nd_intel_freeze_lock *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if (!(sec->state & ND_INTEL_SEC_STATE_ENABLED)) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_STATE;
|
||||
dev_dbg(dev, "freeze lock: wrong security state\n");
|
||||
} else {
|
||||
sec->state |= ND_INTEL_SEC_STATE_FROZEN;
|
||||
nd_cmd->status = 0;
|
||||
dev_dbg(dev, "security frozen\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_disable_pass(struct nfit_test *t,
|
||||
struct nd_intel_disable_passphrase *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if (!(sec->state & ND_INTEL_SEC_STATE_ENABLED) ||
|
||||
(sec->state & ND_INTEL_SEC_STATE_FROZEN)) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_STATE;
|
||||
dev_dbg(dev, "disable passphrase: wrong security state\n");
|
||||
} else if (memcmp(nd_cmd->passphrase, sec->passphrase,
|
||||
ND_INTEL_PASSPHRASE_SIZE) != 0) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_PASS;
|
||||
dev_dbg(dev, "disable passphrase: wrong passphrase\n");
|
||||
} else {
|
||||
memset(sec->passphrase, 0, ND_INTEL_PASSPHRASE_SIZE);
|
||||
sec->state = 0;
|
||||
dev_dbg(dev, "disable passphrase: done\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_secure_erase(struct nfit_test *t,
|
||||
struct nd_intel_secure_erase *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if (!(sec->state & ND_INTEL_SEC_STATE_ENABLED) ||
|
||||
(sec->state & ND_INTEL_SEC_STATE_FROZEN)) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_STATE;
|
||||
dev_dbg(dev, "secure erase: wrong security state\n");
|
||||
} else if (memcmp(nd_cmd->passphrase, sec->passphrase,
|
||||
ND_INTEL_PASSPHRASE_SIZE) != 0) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_PASS;
|
||||
dev_dbg(dev, "secure erase: wrong passphrase\n");
|
||||
} else {
|
||||
memset(sec->passphrase, 0, ND_INTEL_PASSPHRASE_SIZE);
|
||||
memset(sec->master_passphrase, 0, ND_INTEL_PASSPHRASE_SIZE);
|
||||
sec->state = 0;
|
||||
sec->ext_state = ND_INTEL_SEC_ESTATE_ENABLED;
|
||||
dev_dbg(dev, "secure erase: done\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_overwrite(struct nfit_test *t,
|
||||
struct nd_intel_overwrite *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if ((sec->state & ND_INTEL_SEC_STATE_ENABLED) &&
|
||||
memcmp(nd_cmd->passphrase, sec->passphrase,
|
||||
ND_INTEL_PASSPHRASE_SIZE) != 0) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_PASS;
|
||||
dev_dbg(dev, "overwrite: wrong passphrase\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
memset(sec->passphrase, 0, ND_INTEL_PASSPHRASE_SIZE);
|
||||
sec->state = ND_INTEL_SEC_STATE_OVERWRITE;
|
||||
dev_dbg(dev, "overwrite progressing.\n");
|
||||
sec->overwrite_end_time = get_jiffies_64() + 5 * HZ;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_query_overwrite(struct nfit_test *t,
|
||||
struct nd_intel_query_overwrite *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if (!(sec->state & ND_INTEL_SEC_STATE_OVERWRITE)) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_OQUERY_SEQUENCE_ERR;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (time_is_before_jiffies64(sec->overwrite_end_time)) {
|
||||
sec->overwrite_end_time = 0;
|
||||
sec->state = 0;
|
||||
sec->ext_state = ND_INTEL_SEC_ESTATE_ENABLED;
|
||||
dev_dbg(dev, "overwrite is complete\n");
|
||||
} else
|
||||
nd_cmd->status = ND_INTEL_STATUS_OQUERY_INPROGRESS;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_master_set_pass(struct nfit_test *t,
|
||||
struct nd_intel_set_master_passphrase *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if (!(sec->ext_state & ND_INTEL_SEC_ESTATE_ENABLED)) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_NOT_SUPPORTED;
|
||||
dev_dbg(dev, "master set passphrase: in wrong state\n");
|
||||
} else if (sec->ext_state & ND_INTEL_SEC_ESTATE_PLIMIT) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_STATE;
|
||||
dev_dbg(dev, "master set passphrase: in wrong security state\n");
|
||||
} else if (memcmp(nd_cmd->old_pass, sec->master_passphrase,
|
||||
ND_INTEL_PASSPHRASE_SIZE) != 0) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_PASS;
|
||||
dev_dbg(dev, "master set passphrase: wrong passphrase\n");
|
||||
} else {
|
||||
memcpy(sec->master_passphrase, nd_cmd->new_pass,
|
||||
ND_INTEL_PASSPHRASE_SIZE);
|
||||
sec->ext_state = ND_INTEL_SEC_ESTATE_ENABLED;
|
||||
dev_dbg(dev, "master passphrase: updated\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int nd_intel_test_cmd_master_secure_erase(struct nfit_test *t,
|
||||
struct nd_intel_master_secure_erase *nd_cmd,
|
||||
unsigned int buf_len, int dimm)
|
||||
{
|
||||
struct device *dev = &t->pdev.dev;
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
if (!(sec->ext_state & ND_INTEL_SEC_ESTATE_ENABLED)) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_NOT_SUPPORTED;
|
||||
dev_dbg(dev, "master secure erase: in wrong state\n");
|
||||
} else if (sec->ext_state & ND_INTEL_SEC_ESTATE_PLIMIT) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_STATE;
|
||||
dev_dbg(dev, "master secure erase: in wrong security state\n");
|
||||
} else if (memcmp(nd_cmd->passphrase, sec->master_passphrase,
|
||||
ND_INTEL_PASSPHRASE_SIZE) != 0) {
|
||||
nd_cmd->status = ND_INTEL_STATUS_INVALID_PASS;
|
||||
dev_dbg(dev, "master secure erase: wrong passphrase\n");
|
||||
} else {
|
||||
/* we do not erase master state passphrase ever */
|
||||
sec->ext_state = ND_INTEL_SEC_ESTATE_ENABLED;
|
||||
memset(sec->passphrase, 0, ND_INTEL_PASSPHRASE_SIZE);
|
||||
sec->state = 0;
|
||||
dev_dbg(dev, "master secure erase: done\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
static int get_dimm(struct nfit_mem *nfit_mem, unsigned int func)
|
||||
{
|
||||
int i;
|
||||
|
@ -983,6 +1226,46 @@ static int nfit_test_ctl(struct nvdimm_bus_descriptor *nd_desc,
|
|||
return i;
|
||||
|
||||
switch (func) {
|
||||
case NVDIMM_INTEL_GET_SECURITY_STATE:
|
||||
rc = nd_intel_test_cmd_security_status(t,
|
||||
buf, buf_len, i);
|
||||
break;
|
||||
case NVDIMM_INTEL_UNLOCK_UNIT:
|
||||
rc = nd_intel_test_cmd_unlock_unit(t,
|
||||
buf, buf_len, i);
|
||||
break;
|
||||
case NVDIMM_INTEL_SET_PASSPHRASE:
|
||||
rc = nd_intel_test_cmd_set_pass(t,
|
||||
buf, buf_len, i);
|
||||
break;
|
||||
case NVDIMM_INTEL_DISABLE_PASSPHRASE:
|
||||
rc = nd_intel_test_cmd_disable_pass(t,
|
||||
buf, buf_len, i);
|
||||
break;
|
||||
case NVDIMM_INTEL_FREEZE_LOCK:
|
||||
rc = nd_intel_test_cmd_freeze_lock(t,
|
||||
buf, buf_len, i);
|
||||
break;
|
||||
case NVDIMM_INTEL_SECURE_ERASE:
|
||||
rc = nd_intel_test_cmd_secure_erase(t,
|
||||
buf, buf_len, i);
|
||||
break;
|
||||
case NVDIMM_INTEL_OVERWRITE:
|
||||
rc = nd_intel_test_cmd_overwrite(t,
|
||||
buf, buf_len, i - t->dcr_idx);
|
||||
break;
|
||||
case NVDIMM_INTEL_QUERY_OVERWRITE:
|
||||
rc = nd_intel_test_cmd_query_overwrite(t,
|
||||
buf, buf_len, i - t->dcr_idx);
|
||||
break;
|
||||
case NVDIMM_INTEL_SET_MASTER_PASSPHRASE:
|
||||
rc = nd_intel_test_cmd_master_set_pass(t,
|
||||
buf, buf_len, i);
|
||||
break;
|
||||
case NVDIMM_INTEL_MASTER_SECURE_ERASE:
|
||||
rc = nd_intel_test_cmd_master_secure_erase(t,
|
||||
buf, buf_len, i);
|
||||
break;
|
||||
case ND_INTEL_ENABLE_LSS_STATUS:
|
||||
rc = nd_intel_test_cmd_set_lss_status(t,
|
||||
buf, buf_len);
|
||||
|
@ -1328,10 +1611,22 @@ static ssize_t fail_cmd_code_store(struct device *dev, struct device_attribute *
|
|||
}
|
||||
static DEVICE_ATTR_RW(fail_cmd_code);
|
||||
|
||||
static ssize_t lock_dimm_store(struct device *dev,
|
||||
struct device_attribute *attr, const char *buf, size_t size)
|
||||
{
|
||||
int dimm = dimm_name_to_id(dev);
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[dimm];
|
||||
|
||||
sec->state = ND_INTEL_SEC_STATE_ENABLED | ND_INTEL_SEC_STATE_LOCKED;
|
||||
return size;
|
||||
}
|
||||
static DEVICE_ATTR_WO(lock_dimm);
|
||||
|
||||
static struct attribute *nfit_test_dimm_attributes[] = {
|
||||
&dev_attr_fail_cmd.attr,
|
||||
&dev_attr_fail_cmd_code.attr,
|
||||
&dev_attr_handle.attr,
|
||||
&dev_attr_lock_dimm.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
|
@ -1361,6 +1656,17 @@ static int nfit_test_dimm_init(struct nfit_test *t)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void security_init(struct nfit_test *t)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < t->num_dcr; i++) {
|
||||
struct nfit_test_sec *sec = &dimm_sec_info[i];
|
||||
|
||||
sec->ext_state = ND_INTEL_SEC_ESTATE_ENABLED;
|
||||
}
|
||||
}
|
||||
|
||||
static void smart_init(struct nfit_test *t)
|
||||
{
|
||||
int i;
|
||||
|
@ -1439,6 +1745,7 @@ static int nfit_test0_alloc(struct nfit_test *t)
|
|||
if (nfit_test_dimm_init(t))
|
||||
return -ENOMEM;
|
||||
smart_init(t);
|
||||
security_init(t);
|
||||
return ars_state_init(&t->pdev.dev, &t->ars_state);
|
||||
}
|
||||
|
||||
|
@ -2210,6 +2517,20 @@ static void nfit_test0_setup(struct nfit_test *t)
|
|||
set_bit(ND_INTEL_FW_FINISH_UPDATE, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(ND_INTEL_FW_FINISH_QUERY, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(ND_INTEL_ENABLE_LSS_STATUS, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_GET_SECURITY_STATE,
|
||||
&acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_SET_PASSPHRASE, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_DISABLE_PASSPHRASE,
|
||||
&acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_UNLOCK_UNIT, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_FREEZE_LOCK, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_SECURE_ERASE, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_OVERWRITE, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_QUERY_OVERWRITE, &acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_SET_MASTER_PASSPHRASE,
|
||||
&acpi_desc->dimm_cmd_force_en);
|
||||
set_bit(NVDIMM_INTEL_MASTER_SECURE_ERASE,
|
||||
&acpi_desc->dimm_cmd_force_en);
|
||||
}
|
||||
|
||||
static void nfit_test1_setup(struct nfit_test *t)
|
||||
|
|
Loading…
Reference in New Issue