Skip to content

Commit

Permalink
PCI/CMA: Expose in sysfs a log of received signatures
Browse files Browse the repository at this point in the history
When authenticating a device with CMA-SPDM, the kernel verifies the
challenge-response received from the device, but otherwise keeps it to
itself.

However user space or a remote attestation service may want to re-verify
the challenge-response, either because it mistrusts the kernel or
because the kernel is unaware of policy constraints that user space or
the remote attestation service want to apply.

Facilitate such use cases by exposing a log in sysfs which consists of
several files for each challenge-response event.  The files are prefixed
with a monotonically increasing number, starting at 0:

/sys/devices/.../signatures/0_signature
/sys/devices/.../signatures/0_transcript
/sys/devices/.../signatures/0_requester_nonce
/sys/devices/.../signatures/0_responder_nonce
/sys/devices/.../signatures/0_hash_algorithm
/sys/devices/.../signatures/0_combined_spdm_prefix
/sys/devices/.../signatures/0_type

The signature is computed over the transcript (a concatenation of all
SPDM messages exchanged with the device).

The nonces chosen by requester and responder are exposed as separate
attributes to ease verification of their freshness.  They're already
contained in the transcript but their offsets within the transcript are
variable, so user space would otherwise have to parse the SPDM messages
in the transcript to find the nonces.

For signature verification, the transcript is hashed with hash_algorithm
(e.g. "sha384") and prefixed by combined_spdm_prefix.

The type attribute contains the event type:  Currently it is always
"responder-challenge_auth signing".  In the future it may also contain
"responder-measurements signing".

This custom log format was chosen for lack of a better alternative.
Although the TCG PFP Specification defines DEVICE_SECURITY_EVENT_DATA
structures, those structures do not store the transcript (which can be
a few kBytes or up to several MBytes in size).  They do store nonces,
hence at least allow for verification of nonce freshness.  But without
the transcript, user space cannot verify the signature.

Exposing the transcript as an attribute of its own has the benefit that
it can directly be fed into a protocol dissector for debugging purposes
(think Wireshark).

Signed-off-by: Lukas Wunner <lukas@wunner.de>
Cc: James Bottomley <James.Bottomley@HansenPartnership.com>
Cc: Jérôme Glisse <jglisse@google.com>
Cc: Jason Gunthorpe <jgg@nvidia.com>
  • Loading branch information
l1k committed Apr 12, 2024
1 parent 33696ce commit ca420b2
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 1 deletion.
14 changes: 14 additions & 0 deletions drivers/pci/cma.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@ void pci_cma_init(struct pci_dev *pdev)
spdm_authenticate(pdev->spdm_state);
}

#ifdef CONFIG_SYSFS
void pci_cma_publish(struct pci_dev *pdev)
{
if (pdev->spdm_state)
spdm_publish_log(pdev->spdm_state);
}

void pci_cma_unpublish(struct pci_dev *pdev)
{
if (pdev->spdm_state)
spdm_unpublish_log(pdev->spdm_state);
}
#endif

/**
* pci_cma_reauthenticate() - Perform CMA-SPDM authentication again
* @pdev: Device to reauthenticate
Expand Down
1 change: 1 addition & 0 deletions drivers/pci/pci-sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -1664,6 +1664,7 @@ const struct attribute_group *pci_dev_attr_groups[] = {
#endif
#ifdef CONFIG_PCI_CMA
&spdm_attr_group,
&spdm_signatures_group,
#endif
NULL,
};
4 changes: 4 additions & 0 deletions drivers/pci/pci.h
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,8 @@ static inline void pci_doe_disconnected(struct pci_dev *pdev) { }
#ifdef CONFIG_PCI_CMA
void pci_cma_init(struct pci_dev *pdev);
void pci_cma_destroy(struct pci_dev *pdev);
void pci_cma_publish(struct pci_dev *pdev);
void pci_cma_unpublish(struct pci_dev *pdev);
void pci_cma_reauthenticate(struct pci_dev *pdev);
static inline void pci_cma_disable(struct pci_dev *pdev)
{
Expand All @@ -346,6 +348,8 @@ static inline void pci_cma_disable(struct pci_dev *pdev)
#else
static inline void pci_cma_init(struct pci_dev *pdev) { }
static inline void pci_cma_destroy(struct pci_dev *pdev) { }
static inline void pci_cma_publish(struct pci_dev *pdev) { }
static inline void pci_cma_unpublish(struct pci_dev *pdev) { }
static inline void pci_cma_reauthenticate(struct pci_dev *pdev) { }
static inline void pci_cma_disable(struct pci_dev *pdev) { }
#endif
Expand Down
2 changes: 2 additions & 0 deletions drivers/pci/probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -2577,6 +2577,8 @@ void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
dev->match_driver = false;
ret = device_add(&dev->dev);
WARN_ON(ret < 0);

pci_cma_publish(dev);
}

struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
Expand Down
2 changes: 2 additions & 0 deletions drivers/pci/remove.c
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ static void pci_destroy_dev(struct pci_dev *dev)
if (!dev->dev.kobj.parent)
return;

pci_cma_unpublish(dev);

device_del(&dev->dev);

down_write(&pci_bus_sem);
Expand Down
8 changes: 8 additions & 0 deletions include/linux/spdm.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,14 @@ int spdm_authenticate(struct spdm_state *spdm_state);

void spdm_destroy(struct spdm_state *spdm_state);

#ifdef CONFIG_SYSFS
extern const struct attribute_group spdm_attr_group;
extern const struct attribute_group spdm_signatures_group;
void spdm_publish_log(struct spdm_state *spdm_state);
void spdm_unpublish_log(struct spdm_state *spdm_state);
#else
static inline void spdm_publish_log(struct spdm_state *spdm_state) { }
static inline void spdm_unpublish_log(struct spdm_state *spdm_state) { }
#endif

#endif
2 changes: 2 additions & 0 deletions lib/spdm/core.c
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ struct spdm_state *spdm_create(struct device *dev, spdm_transport *transport,
spdm_state->validate = validate;

mutex_init(&spdm_state->lock);
INIT_LIST_HEAD(&spdm_state->log);

return spdm_state;
}
Expand All @@ -360,6 +361,7 @@ EXPORT_SYMBOL_GPL(spdm_create);
*/
void spdm_destroy(struct spdm_state *spdm_state)
{
spdm_destroy_log(spdm_state);
spdm_reset(spdm_state);
mutex_destroy(&spdm_state->lock);
kfree(spdm_state);
Expand Down
10 changes: 9 additions & 1 deletion lib/spdm/req-authenticate.c
Original file line number Diff line number Diff line change
Expand Up @@ -575,13 +575,13 @@ static size_t spdm_challenge_rsp_sz(struct spdm_state *spdm_state,
static int spdm_challenge(struct spdm_state *spdm_state, u8 slot)
{
static const char *spdm_context = "responder-challenge_auth signing";
size_t req_sz, rsp_sz, rsp_sz_max, req_nonce_off, rsp_nonce_off;
struct spdm_challenge_rsp *rsp __free(kfree);
struct spdm_challenge_req req = {
.code = SPDM_CHALLENGE,
.param1 = slot,
.param2 = 0, /* No measurement summary hash */
};
size_t req_sz, rsp_sz, rsp_sz_max;
int rc, length;

get_random_bytes(&req.nonce, sizeof(req.nonce));
Expand All @@ -607,10 +607,14 @@ static int spdm_challenge(struct spdm_state *spdm_state, u8 slot)
return -EIO;
}

req_nonce_off = spdm_state->transcript_end - spdm_state->transcript +
offsetof(typeof(req), nonce);
rc = spdm_append_transcript(spdm_state, &req, req_sz);
if (rc)
return rc;

rsp_nonce_off = spdm_state->transcript_end - spdm_state->transcript +
sizeof(*rsp) + spdm_state->hash_len;
rc = spdm_append_transcript(spdm_state, rsp, rsp_sz);
if (rc)
return rc;
Expand All @@ -620,6 +624,10 @@ static int spdm_challenge(struct spdm_state *spdm_state, u8 slot)
if (rc)
dev_err(spdm_state->dev,
"Failed to verify challenge_auth signature: %d\n", rc);

spdm_create_log_entry(spdm_state, spdm_context,
req_nonce_off, rsp_nonce_off);

return rc;
}

Expand Down
234 changes: 234 additions & 0 deletions lib/spdm/req-sysfs.c
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,237 @@ const struct attribute_group spdm_attr_group = {
.attrs = spdm_attrs,
.is_visible = spdm_attrs_are_visible,
};

struct spdm_log_entry {
struct list_head list;
struct bin_attribute sig;
struct bin_attribute req_nonce;
struct bin_attribute rsp_nonce;
struct bin_attribute transcript;
struct bin_attribute combined_prefix;
struct dev_ext_attribute spdm_context;
struct dev_ext_attribute hash_alg;
char sig_name[sizeof(__stringify(UINT_MAX) "_signature")];
char req_nonce_name[sizeof(__stringify(UINT_MAX) "_requester_nonce")];
char rsp_nonce_name[sizeof(__stringify(UINT_MAX) "_responder_nonce")];
char transcript_name[sizeof(__stringify(UINT_MAX) "_transcript")];
char combined_prefix_name[sizeof(__stringify(UINT_MAX) "_combined_spdm_prefix")];
char spdm_context_name[sizeof(__stringify(UINT_MAX) "_type")];
char hash_alg_name[sizeof(__stringify(UINT_MAX) "_hash_algorithm")];
u8 version;
};

static struct attribute *spdm_signatures_attrs[] = {
NULL
};

const struct attribute_group spdm_signatures_group = {
.name = "signatures",
.attrs = spdm_signatures_attrs,
};

static void spdm_unpublish_log_entry(struct spdm_state *spdm_state,
struct spdm_log_entry *log)
{
const char *group = spdm_signatures_group.name;
struct kobject *kobj = &spdm_state->dev->kobj;

sysfs_remove_bin_file_from_group(kobj, &log->sig, group);
sysfs_remove_bin_file_from_group(kobj, &log->req_nonce, group);
sysfs_remove_bin_file_from_group(kobj, &log->rsp_nonce, group);
sysfs_remove_bin_file_from_group(kobj, &log->transcript, group);
sysfs_remove_bin_file_from_group(kobj, &log->combined_prefix, group);
sysfs_remove_file_from_group(kobj, &log->spdm_context.attr.attr, group);
sysfs_remove_file_from_group(kobj, &log->hash_alg.attr.attr, group);
}

static void spdm_publish_log_entry(struct spdm_state *spdm_state,
struct spdm_log_entry *log)
{
const char *group = spdm_signatures_group.name;
struct kobject *kobj = &spdm_state->dev->kobj;
int rc;

rc = sysfs_add_bin_file_to_group(kobj, &log->sig, group);
if (rc)
goto err;

rc = sysfs_add_bin_file_to_group(kobj, &log->req_nonce, group);
if (rc)
goto err;

rc = sysfs_add_bin_file_to_group(kobj, &log->rsp_nonce, group);
if (rc)
goto err;

rc = sysfs_add_bin_file_to_group(kobj, &log->transcript, group);
if (rc)
goto err;

rc = sysfs_add_bin_file_to_group(kobj, &log->combined_prefix, group);
if (rc)
goto err;

rc = sysfs_add_file_to_group(kobj, &log->spdm_context.attr.attr, group);
if (rc)
goto err;

rc = sysfs_add_file_to_group(kobj, &log->hash_alg.attr.attr, group);
if (rc)
goto err;

return;
err:
dev_err(spdm_state->dev,
"Failed to publish event log entry: %d\n", rc);
spdm_unpublish_log_entry(spdm_state, log);
}

static ssize_t spdm_read_combined_prefix(struct file *file,
struct kobject *kobj,
struct bin_attribute *attr,
char *buf, loff_t off, size_t count)
{
struct spdm_log_entry *log = attr->private;

/*
* SPDM 1.0 and 1.1 do not add a combined prefix to the hash
* before computing the signature, so return an empty file.
*/
if (log->version <= 0x11)
return 0;

void *tmp __free(kfree) = kmalloc(SPDM_COMBINED_PREFIX_SZ, GFP_KERNEL);
if (!tmp)
return -ENOMEM;

spdm_create_combined_prefix(log->version, log->spdm_context.var, tmp);
memcpy(buf, tmp + off, count);
return count;
}

void spdm_create_log_entry(struct spdm_state *spdm_state,
const char *spdm_context,
size_t req_nonce_off, size_t rsp_nonce_off)
{
struct spdm_log_entry *log = kmalloc(sizeof(*log), GFP_KERNEL);
if (!log)
return;

*log = (struct spdm_log_entry) {
.version = spdm_state->version,
.list = LIST_HEAD_INIT(log->list),

.sig = {
.attr.name = log->sig_name,
.attr.mode = 0444,
.read = sysfs_bin_attr_simple_read,
.private = spdm_state->transcript_end -
spdm_state->sig_len,
.size = spdm_state->sig_len },

.req_nonce = {
.attr.name = log->req_nonce_name,
.attr.mode = 0444,
.read = sysfs_bin_attr_simple_read,
.private = spdm_state->transcript + req_nonce_off,
.size = SPDM_NONCE_SZ },

.rsp_nonce = {
.attr.name = log->rsp_nonce_name,
.attr.mode = 0444,
.read = sysfs_bin_attr_simple_read,
.private = spdm_state->transcript + rsp_nonce_off,
.size = SPDM_NONCE_SZ },

.transcript = {
.attr.name = log->transcript_name,
.attr.mode = 0400,
.read = sysfs_bin_attr_simple_read,
.private = spdm_state->transcript,
.size = spdm_state->transcript_end -
spdm_state->transcript -
spdm_state->sig_len },

.combined_prefix = {
.attr.name = log->combined_prefix_name,
.attr.mode = 0444,
.read = spdm_read_combined_prefix,
.private = log,
.size = spdm_state->version <= 0x11 ? 0 :
SPDM_COMBINED_PREFIX_SZ },

.spdm_context = {
.attr.attr.name = log->spdm_context_name,
.attr.attr.mode = 0444,
.attr.show = device_show_string,
.var = (char *)spdm_context },

.hash_alg = {
.attr.attr.name = log->hash_alg_name,
.attr.attr.mode = 0444,
.attr.show = device_show_string,
.var = (char *)spdm_state->base_hash_alg_name },
};

snprintf(log->sig_name, sizeof(log->sig_name),
"%u_signature", spdm_state->log_counter);
snprintf(log->req_nonce_name, sizeof(log->req_nonce_name),
"%u_requester_nonce", spdm_state->log_counter);
snprintf(log->rsp_nonce_name, sizeof(log->rsp_nonce_name),
"%u_responder_nonce", spdm_state->log_counter);
snprintf(log->transcript_name, sizeof(log->transcript_name),
"%u_transcript", spdm_state->log_counter);
snprintf(log->combined_prefix_name, sizeof(log->combined_prefix_name),
"%u_combined_spdm_prefix", spdm_state->log_counter);
snprintf(log->spdm_context_name, sizeof(log->spdm_context_name),
"%u_type", spdm_state->log_counter);
snprintf(log->hash_alg_name, sizeof(log->hash_alg_name),
"%u_hash_algorithm", spdm_state->log_counter);

sysfs_bin_attr_init(&log->sig);
sysfs_bin_attr_init(&log->req_nonce);
sysfs_bin_attr_init(&log->rsp_nonce);
sysfs_bin_attr_init(&log->transcript);
sysfs_bin_attr_init(&log->combined_prefix);
sysfs_attr_init(&log->spdm_context.attr.attr);
sysfs_attr_init(&log->hash_alg.attr.attr);

list_add_tail(&log->list, &spdm_state->log);
spdm_state->log_counter++;

/* Steal transcript pointer ahead of spdm_free_transcript() */
spdm_state->transcript = NULL;

if (device_is_registered(spdm_state->dev))
spdm_publish_log_entry(spdm_state, log);
}

void spdm_destroy_log(struct spdm_state *spdm_state)
{
struct spdm_log_entry *log, *tmp;

list_for_each_entry_safe(log, tmp, &spdm_state->log, list) {
list_del(&log->list);
kvfree(log->transcript.private);
kfree(log);
}
}

void spdm_unpublish_log(struct spdm_state *spdm_state)
{
struct spdm_log_entry *log;

list_for_each_entry(log, &spdm_state->log, list)
spdm_unpublish_log_entry(spdm_state, log);
}
EXPORT_SYMBOL_GPL(spdm_unpublish_log);

void spdm_publish_log(struct spdm_state *spdm_state)
{
struct spdm_log_entry *log;

list_for_each_entry(log, &spdm_state->log, list)
spdm_publish_log_entry(spdm_state, log);
}
EXPORT_SYMBOL_GPL(spdm_publish_log);
Loading

0 comments on commit ca420b2

Please sign in to comment.