Skip to contentSkip to conversation

PCI/TSM: Add CTLV attestation

RFC PATCH In review Aneesh Kumar K.V (Arm) 8 participants 88 messages Oct 28 – Nov 11

Subsystems (11)
lore
Share & tools
lore

Aneesh Kumar K.V (Arm)

author maintainer
#0 lore raw

cover PCI/TSM: Add CTLV attestation

This series connects PCI/TSM to the common CTLV attestation interface. Userspace can discover a device's evidence objects, retrieve them, and accept the evidence before the kernel establishes a secure link or moves a guest device into TDISP RUN. It replaces the PCI/TSM netlink evidence interface while retaining connect, disconnect, lock and unlock in sysfs.

This series builds on the CTLV attestation series [1] and Arm CCA device-assignment support. It covers PCI/TSM attestation through certificate slot-zero.

The development tree is available at:

https://gitlab.arm.com/linux-arm/linux-cca.git cca/topics/cca-da-attestation

That branch also includes the follow-up Rust SPDM certificate-slot discovery work, generic attestation fixes, and CTLV tooling fixes.

[1] https://lore.kernel.org/all/[email protected]

The host flow separates identity collection from session and IDE setup:

sysfs connect

    -> collect and cache the certificate chain
  DEVICE_GET / EVIDENCE_GET
    -> discover available objects and retrieve the chain for verification
  EVIDENCE_ACCEPT
    -> establish the secure session and IDE streams
  sysfs disconnect
    -> release the identity context and any established streams

Both AMD SEV-TIO and Arm CCA host drivers implement this flow using their existing firmware interfaces. AMD pauses the firmware SPDM exchange after collecting the certificate chain, before sending authentication or session establishment requests. CCA creates the PDEV and collects its certificate chain during connect, then completes session and stream setup on acceptance. After both conversions, every link driver must supply an evidence_accept callback; connect collects identity evidence without establishing streams.

This is the slot-zero baseline: the AMD and CCA host drivers expose their slot-zero certificate chains. A follow-up series adds shared SPDM identity discovery and selection among discovered certificate slots. The common interface supports any certificate slot advertised by the driver, and the same evidence-get and evidence-accept commands apply to both series.

For a guest device, sysfs lock collects the initial evidence and creates the CTLV instance. Userspace can optionally refresh supported objects, retrieve the cached evidence, verify it, and issue EVIDENCE_ACCEPT to enter RUN. The CCA guest driver supports refreshing measurements with a 32-byte challenge or an interface report without a challenge. It checks the new bytes against trusted digests and device state before replacing the selected cached object. Refresh is disabled after the device enters RUN.

EVIDENCE_GET reads the cached snapshot, so its challenge attribute becomes optional. EVIDENCE_REFRESH requests new evidence and invalidates previous evidence generations and acceptance. A failed backend refresh also invalidates them and requires recovery.

EVIDENCE_ACCEPT replaces the separate DEVSEC_RUN operation. Clearing acceptance leaves the hardware state unchanged: a guest device can remain in RUN, and host IDE streams remain established. DEVSEC_STATE_GET therefore continues to report acceptance and device state separately. Disconnect and unlock perform teardown.

The common attestation changes let each instance expose the operations and evidence objects its device supports. This allows host links and guest devices to share the PCI attestation implementation while advertising different capabilities. Additional locking and generation helpers let PCI/TSM coordinate state changes, evidence invalidation and notifications with CTLV operations.

The PCI child family adds STATE_GET and EVIDENCE_REFRESH to the inherited attestation operations. An instance appears only after a successful connect or lock and is removed on disconnect or unlock. DEVICE_GET includes the PCI name so userspace can associate an instance with its device.

The first five patches prepare the common attestation interface. The next three define and implement the PCI integration, including its locking helpers. The final three convert AMD and CCA host link setup and add CCA guest evidence refresh. The series builds on the existing CTLV attestation and PCI/TSM device-assignment support.

Notes:
* I used Codex to help implement, review and organize the series.

Cc Jiri Pirko <[email protected]>
Cc Kevin Tian <[email protected]>
Cc Alexey Kardashevskiy <[email protected]>
Cc Xu Yilun <[email protected]>
Cc Catalin Marinas <[email protected]>
Cc Suzuki K Poulose <[email protected]>
Cc Steven Price <[email protected]>
Cc Kiryl Shutsemau <[email protected]>
Cc Rick Edgecombe <[email protected]>
Cc Dave Hansen <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
Cc Bjorn Helgaas <[email protected]>
Cc Ashish Kalra <[email protected]>
Cc Tom Lendacky <[email protected]>
Cc John Allen <[email protected]>
Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>

Aneesh Kumar K.V (Arm) (11):
attestation: Restrict inherited operations per instance
attestation: Filter evidence objects per instance
attestation: Enter secure operation when evidence is accepted
attestation: Bind object selection to accepted evidence snapshots
attestation: Make the evidence-get challenge attribute optional PCI/TSM: Describe the PCI attestation control family
attestation: Add attestation helpers for PCI/TSM integration PCI/TSM: Bind CTLV attestation to connected and locked contexts crypto/ccp: Defer SEV-TIO link setup until evidence acceptance
virt: coco: arm-cca-host: Publish slot zero and establish streams
after acceptance
virt: coco: arm-cca-guest: Refresh selected evidence against trusted
digests

 Documentation/ABI/testing/sysfs-bus-pci       |  108 +-
 Documentation/ABI/testing/sysfs-class-tsm     |    9 +-
 .../ctlv/abi/ctlv-attestation-pci.yaml        |   43 +
 Documentation/ctlv/abi/ctlv-attestation.yaml  |   59 +-
 Documentation/ctlv/abi/families.registry.yaml |    1 +
 Documentation/ctlv/families.yaml              |    1 +
 Documentation/ctlv/generated-files.yaml       |   11 +
 .../ctlv/specs/ctlv-attestation-pci.yaml      |   79 +
 .../ctlv/specs/ctlv-attestation.yaml          |   40 +-
 Documentation/driver-api/attestation.rst      |   56 +
 Documentation/driver-api/pci/tsm.rst          |  160 +-
 Documentation/netlink/specs/pci-tsm.yaml      |  151 --
 drivers/attestation/core/core.c               |  123 +-
 drivers/attestation/core/ctlv.c               |  156 +-
 drivers/attestation/core/internal.h           |    3 +
 .../generated/ctlv_attestation_gen.c          |   45 +-
 .../generated/ctlv_attestation_gen.h          |   76 +-
 drivers/attestation/providers/sim.c           |   40 +-
 drivers/attestation/tests/core_kunit.c        |   72 +-
 drivers/crypto/ccp/sev-dev-tio.c              |   17 +-
 drivers/crypto/ccp/sev-dev-tio.h              |    5 +
 drivers/crypto/ccp/sev-dev-tsm.c              |  188 +-
 drivers/pci/Kconfig                           |    3 +
 drivers/pci/tsm/Makefile                      |    6 +-
 drivers/pci/tsm/attestation.c                 |  525 +++++
 drivers/pci/tsm/core.c                        |  431 +++--
 drivers/pci/tsm/ctlv_attestation_pci_gen.c    |  111 ++
 drivers/pci/tsm/ctlv_attestation_pci_gen.h    |  100 +
 drivers/pci/tsm/evidence.c                    |  274 ---
 drivers/pci/tsm/internal.h                    |   26 +
 drivers/pci/tsm/netlink.c                     |   43 -
 drivers/pci/tsm/netlink.h                     |   23 -
 drivers/virt/coco/arm-cca-guest/main.c        |  168 +-
 drivers/virt/coco/arm-cca-guest/rsi-da.c      |   16 +-
 drivers/virt/coco/arm-cca-host/main.c         |  143 +-
 drivers/virt/coco/arm-cca-host/rmi-da.c       |    7 +-
 drivers/virt/coco/arm-cca-host/rmi-da.h       |    2 +
 include/linux/attestation.h                   |   93 +-
 include/linux/pci-tsm.h                       |   86 +-
 include/linux/pci.h                           |    2 +
 include/uapi/linux/ctlv_attestation_gen.h     |   25 +-
 include/uapi/linux/ctlv_attestation_pci_gen.h |   74 +
 include/uapi/linux/ctlv_families_gen.h        |    1 +
 include/uapi/linux/pci-tsm-netlink.h          |  101 -
 samples/devsec/common.c                       |    1 -
 samples/devsec/link_tsm.c                     |   17 +-
 samples/devsec/tsm.c                          |   32 +-
 tools/ctlv/tests/support.py                   |    3 +-
 tools/ctlv/tests/test_outputs.py              |    6 +-
 tools/ctlv/tests/test_semantics.py            |   38 +
 tools/ctlv/tests/test_spec.py                 |    1 +
 .../selftests/attestation/attestation_test.c  |   32 +-
 .../ctlv_attestation_pci_user_gen.c           | 1697 +++++++++++++++++
 .../ctlv_attestation_pci_user_gen.h           |  230 +++
 .../ctlv_attestation_sim_user_gen.c           |  161 +-
 .../ctlv_attestation_tdx_user_gen.c           |  161 +-
 .../attestation/ctlv_attestation_user_gen.c   |  161 +-
 .../attestation/ctlv_attestation_user_gen.h   |   70 +-
 .../attestation/tdx_attestation_test.c        |    9 +-
 59 files changed, 4678 insertions(+), 1644 deletions(-)
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation-pci.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation-pci.yaml
 delete mode 100644 Documentation/netlink/specs/pci-tsm.yaml
 create mode 100644 drivers/pci/tsm/attestation.c
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.c
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.h
 delete mode 100644 drivers/pci/tsm/evidence.c
 create mode 100644 drivers/pci/tsm/internal.h
 delete mode 100644 drivers/pci/tsm/netlink.c
 delete mode 100644 drivers/pci/tsm/netlink.h
 create mode 100644 include/uapi/linux/ctlv_attestation_pci_gen.h
 delete mode 100644 include/uapi/linux/pci-tsm-netlink.h
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h
Signature · 1 line
2.43.0
author maintainer
#1 lore raw

RFC PATCH 1/11 attestation: Restrict inherited operations per instance

Allow an attestation instance to enable only the operations supported by its device. Update the operations and DEVICE_GET capabilities together under the instance mutex.

The following PCI/TSM patch uses one attestation implementation for host links and guest devices. Each instance must expose only the operations supported by that device:

  Instance                Evidence  Evidence acceptance  TDISP state
  ----------------------  --------  -------------------  -----------
  Host link, PF0          Yes       Yes                  No
  Host link, non-PF0      Yes       No                   No

Guest device Yes If supported Yes

For a guest device, evidence acceptance requires the TSM driver's accept callback. A device error also requires evidence retrieval and acceptance to be disabled for that instance.

Currently, DEVSEC_STATE_GET calls the TDISP callback whenever that callback is defined, even for a host link. The callback returns -EOPNOTSUPP for the host link, causing the whole query to fail and hiding its evidence acceptance verdict.

Track the enabled features separately for each instance. Use them both to enable operations and to choose which state callbacks DEVSEC_STATE_GET calls. A host link can then report its acceptance verdict without trying to read TDISP state. Keep DEVICE_GET capabilities consistent with the enabled operations.

Cc Jiri Pirko <[email protected]>
Cc Kiryl Shutsemau <[email protected]>
Cc Rick Edgecombe <[email protected]>
Cc Dave Hansen <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  |  7 +-
 .../ctlv/specs/ctlv-attestation.yaml          |  3 +
 Documentation/driver-api/attestation.rst      | 14 ++++
 drivers/attestation/core/core.c               | 79 +++++++++++++++++-
 drivers/attestation/core/ctlv.c               | 10 +--
 drivers/attestation/core/internal.h           |  1 +
 .../generated/ctlv_attestation_gen.c          |  6 +-
 .../generated/ctlv_attestation_gen.h          |  6 ++
 drivers/attestation/tests/core_kunit.c        | 83 ++++++++++++++++++-
 include/linux/attestation.h                   | 33 ++++++++
 .../ctlv_attestation_sim_user_gen.c           |  6 +-
 .../ctlv_attestation_tdx_user_gen.c           |  6 +-
 .../attestation/ctlv_attestation_user_gen.c   |  6 +-
 13 files changed, 237 insertions(+), 23 deletions(-)
Documentation/ctlv/abi/ctlv-attestation.yaml +4 -3
index 48f7454ae6c0..31dbffa59416 100644
@@ -245,12 +245,13 @@ ops:
                   attrs: [log.template-name, log.template-data, log.violation]
               - record.item: {attrs: [record.format, record.data]}
   evidence-accept:
-    abi: {id: 9, type: action, optional: true}
+    abi: {id: 9, type: action, availability: dynamic, optional: true}
     request: {attrs: [device.generation, evidence.generation]}
   evidence-accept-clear:
-    abi: {id: 10, type: action, optional: true}
+    abi: {id: 10, type: action, availability: dynamic, optional: true}
   devsec-state-get:
-    abi: {id: 11, type: query, fd-access: read, optional: true}
+    abi: {id: 11, type: query, fd-access: read, availability: dynamic,
+          optional: true}
     reply:
       attrs: [device.generation]
       optional-attrs: [evidence.generation, evidence.accepted-generation,
Documentation/ctlv/specs/ctlv-attestation.yaml +3 -0
index 5a8da657cc8b..570a97b7538f 100644
@@ -551,6 +551,7 @@ ops:
   - name: evidence-accept
     type: action
     fd-access: read-write
+    availability: dynamic
     optional: true
     request:
       attrs: evidence-generation-request
@@ -558,11 +559,13 @@ ops:
   - name: evidence-accept-clear
     type: action
     fd-access: read-write
+    availability: dynamic
     optional: true
 
   - name: devsec-state-get
     type: query
     fd-access: read
+    availability: dynamic
     optional: true
     reply:
       attrs: devsec-state-reply
Documentation/driver-api/attestation.rst +14 -0
index baf188aafe54..06af8df23a46 100644
@@ -29,6 +29,20 @@ dynamic topology retains handles returned by ``attestation_mr_create()`` and
 passes each handle exactly once to ``attestation_mr_destroy()``.  Property
 storage remains valid for the handle's lifetime.
 
+Per-instance features
+=====================
+
+A provider descriptor describes the maximum capabilities of its instances.
+A child family may restrict evidence retrieval, verifier verdicts, TDISP or
+trust-state reporting and RUN through ``attestation_features_set_locked()``.
+The features must be a subset of the descriptor. Verdicts require evidence;
+RUN additionally requires verdicts and at least one native state reporter.
+Providers that do not restrict their features keep the descriptor's defaults.
+
+The setter updates dynamic CTLV operation availability and the capabilities
+reported by DEVICE_GET under the instance mutex. DEVSEC_STATE_GET omits native
+state reporters that the instance has disabled.
+
 State changes
 =============
 
drivers/attestation/core/core.c +78 -1

104 lines not on this page · raw message

drivers/attestation/core/ctlv.c +5 -5
index b08356c3c027..8f4fa48ffdc5 100644
@@ -60,7 +60,7 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 
 	if (!ctlv_attestation_device_caps_start(reply, &caps_nest))
 		return -EMSGSIZE;
-	if (provider->evidence_caps) {
+	if (att->features & ATTESTATION_FEATURE_EVIDENCE) {
 		const struct attestation_evidence_caps *caps = provider->evidence_caps;
 
 		if (!ctlv_attestation_cap_evidence_start(&caps_nest, &group))
@@ -72,7 +72,7 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 		ok &= ctlv_attestation_cap_max_total_output_length_put(&group,
 								       caps->max_total_output_length);
 		flags = 0;
-		if (provider->evidence_accept)
+		if (att->features & ATTESTATION_FEATURE_EVIDENCE_ACCEPT)
 			flags |= CTLV_ATTESTATION_EVIDENCE_CAPS_EVIDENCE_ACCEPT;
 		if (caps->supports_mr_selection)
 			flags |= CTLV_ATTESTATION_EVIDENCE_CAPS_MR_SELECTION;
@@ -119,7 +119,7 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 		ok &= ctlv_attestation_cap_mr_flags_put(&group, flags);
 		ctlv_nest_end(&group);
 	}
-	if (provider->devsec_run) {
+	if (att->features & ATTESTATION_FEATURE_DEVSEC_RUN) {
 		if (!ctlv_attestation_cap_devsec_start(&caps_nest, &group))
 			return -EMSGSIZE;
 		ok &= ctlv_attestation_cap_devsec_flags_put(&group,
@@ -935,13 +935,13 @@ int ctlv_attestation_devsec_state_get_query(void *priv, struct ctlv_op_ctx *ctx,
 	if (att->verdict_recorded)
 		ok &= ctlv_attestation_evidence_accepted_generation_put(reply,
 									att->accepted_evidence_generation);
-	if (att->provider->tdisp_state_get) {
+	if (att->features & ATTESTATION_FEATURE_TDISP_STATE) {
 		ret = att->provider->tdisp_state_get(att, &state);
 		if (ret)
 			return ret;
 		ok &= ctlv_attestation_devsec_tdisp_state_put(reply, state);
 	}
-	if (att->provider->device_trust_get) {
+	if (att->features & ATTESTATION_FEATURE_DEVICE_TRUST) {
 		ret = att->provider->device_trust_get(att, &trust);
 		if (ret)
 			return ret;
drivers/attestation/core/internal.h +1 -0
index bd84efb5abb8..9e531e8483f7 100644
@@ -70,6 +70,7 @@ struct attestation_mr_log {
 struct attestation {
 	struct ctlv_device *ctlvdev;
 	const struct attestation_provider *provider;
+	unsigned int features;
 	unsigned int index;
 
 	/* Mutable instance, MR, verdict, and journal state. */
drivers/attestation/generated/ctlv_attestation_gen.c +3 -3
index 911b32255dd5..5882dd2c93a7 100644
@@ -300,15 +300,15 @@ const struct ctlv_op_schema ctlv_attestation_op_schemas[] = {
 	CTLV_OP_SCHEMA_EVENT(CTLV_ATTESTATION_OP_MR_LOG_ENTRY_APPENDED, 73416,
 			     CTLV_SCHEMA_TREE(mr_log_entry_appended_reply)),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT, CTLV_OP_FD_ACCESS_READ_WRITE,
-			      CTLV_OP_AVAILABILITY_FIXED, 64, 0,
+			      CTLV_OP_AVAILABILITY_DYNAMIC, 64, 0,
 			      CTLV_SCHEMA_TREE(evidence_accept_request), CTLV_SCHEMA_TREE_NONE(),
 			      .optional = true),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
-			      CTLV_OP_FD_ACCESS_READ_WRITE, CTLV_OP_AVAILABILITY_FIXED, 16, 0,
+			      CTLV_OP_FD_ACCESS_READ_WRITE, CTLV_OP_AVAILABILITY_DYNAMIC, 16, 0,
 			      CTLV_SCHEMA_TREE(evidence_accept_clear_request),
 			      CTLV_SCHEMA_TREE_NONE(), .optional = true),
 	CTLV_OP_SCHEMA_QUERY(CTLV_ATTESTATION_OP_DEVSEC_STATE_GET, CTLV_OP_FD_ACCESS_READ,
-			     CTLV_OP_AVAILABILITY_FIXED, 16, 40, 1024,
+			     CTLV_OP_AVAILABILITY_DYNAMIC, 16, 40, 1024,
 			     CTLV_SCHEMA_TREE(devsec_state_get_request),
 			     CTLV_SCHEMA_TREE(devsec_state_get_reply), .optional = true),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_DEVSEC_RUN, CTLV_OP_FD_ACCESS_READ_WRITE,
drivers/attestation/generated/ctlv_attestation_gen.h +6 -0
index 028026a82e8a..a1f125690005 100644
@@ -73,8 +73,14 @@ CTLV_OP_PROVIDE(ctlv_attestation_mr_dump, CTLV_ATTESTATION_OP_MR_DUMP)
 CTLV_OP_PROVIDE(ctlv_attestation_mr_read, CTLV_ATTESTATION_OP_MR_READ)
 CTLV_OP_PROVIDE(ctlv_attestation_mr_extend, CTLV_ATTESTATION_OP_MR_EXTEND)
 CTLV_OP_PROVIDE(ctlv_attestation_evidence_accept, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT)
+CTLV_OP_ENABLE(ctlv_attestation_evidence_accept, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT)
+CTLV_OP_DISABLE(ctlv_attestation_evidence_accept, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT)
 CTLV_OP_PROVIDE(ctlv_attestation_evidence_accept_clear, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR)
+CTLV_OP_ENABLE(ctlv_attestation_evidence_accept_clear, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR)
+CTLV_OP_DISABLE(ctlv_attestation_evidence_accept_clear, CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR)
 CTLV_OP_PROVIDE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
+CTLV_OP_ENABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
+CTLV_OP_DISABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
 CTLV_OP_PROVIDE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
 CTLV_OP_ENABLE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
 CTLV_OP_DISABLE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
drivers/attestation/tests/core_kunit.c +81 -2

110 lines not on this page · raw message

include/linux/attestation.h +33 -0
index ae6ca4024b2a..dede1b53f67a 100644
@@ -14,6 +14,15 @@ struct attestation_mr;
 struct ctlv_blob;
 struct ctlv_cursor;
 
+/* Per-instance subsets of the immutable provider's capabilities. */
+enum attestation_feature {
+	ATTESTATION_FEATURE_EVIDENCE = 1U << 0,
+	ATTESTATION_FEATURE_EVIDENCE_ACCEPT = 1U << 1,
+	ATTESTATION_FEATURE_TDISP_STATE = 1U << 2,
+	ATTESTATION_FEATURE_DEVICE_TRUST = 1U << 3,
+	ATTESTATION_FEATURE_DEVSEC_RUN = 1U << 4,
+};
+
 enum attestation_evidence_representation {
 	ATTESTATION_EVIDENCE_REPRESENTATION_RAW =
 		CTLV_ATTESTATION_EVIDENCE_REPRESENTATION_RAW,
@@ -285,4 +294,28 @@ void attestation_mr_destroy(struct attestation_mr *mr);
  */
 void attestation_state_changed(struct attestation *att);
 
+/**
+ * attestation_features_set_locked - Set enabled features with the mutex held.
+ * @att: attestation instance
+ * @features: features to enable
+ *
+ * The caller must hold the instance mutex.
+ * Update operation availability and DEVICE_GET capabilities together.
+ *
+ * Return: 0 on success, or -EINVAL if the requested features are unsupported
+ * or their dependencies are not satisfied.
+ */
+int attestation_features_set_locked(struct attestation *att, unsigned int features);
+
+/**
+ * attestation_features_set - Set enabled features.
+ * @att: attestation instance
+ * @features: features to enable
+ *
+ * Takes the instance mutex. Follow the same lock order as the CTLV pre-hook.
+ *
+ * Return: 0 on success, or -EINVAL for unsupported features or unmet dependencies.
+ */
+int attestation_features_set(struct attestation *att, unsigned int features);
+
 #endif /* _LINUX_ATTESTATION_H */
tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c +3 -3
index dd558431324e..bccac87ea4fc 100644
@@ -1552,7 +1552,7 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 64,
 		.request = &evidence_accept_request_node_schema,
@@ -1561,7 +1561,7 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.request = &evidence_accept_clear_request_node_schema,
@@ -1570,7 +1570,7 @@ static const struct ctlv_op_spec ctlv_attestation_sim_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
 		.type = CTLV_OP_TYPE_QUERY,
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.query_reply_min_len = 40,
tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c +3 -3
index 50bd181d0d15..3395c0ffc3e5 100644
@@ -1568,7 +1568,7 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 64,
 		.request = &evidence_accept_request_node_schema,
@@ -1577,7 +1577,7 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.request = &evidence_accept_clear_request_node_schema,
@@ -1586,7 +1586,7 @@ static const struct ctlv_op_spec ctlv_attestation_tdx_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
 		.type = CTLV_OP_TYPE_QUERY,
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.query_reply_min_len = 40,
tools/testing/selftests/attestation/ctlv_attestation_user_gen.c +3 -3
index d62affc3a491..6a363b29f80b 100644
@@ -1538,7 +1538,7 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 64,
 		.request = &evidence_accept_request_node_schema,
@@ -1547,7 +1547,7 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
 		.type = CTLV_OP_TYPE_ACTION,
 		.fd_access = CTLV_OP_FD_ACCESS_READ_WRITE,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.request = &evidence_accept_clear_request_node_schema,
@@ -1556,7 +1556,7 @@ static const struct ctlv_op_spec ctlv_attestation_op_specs[] = {
 		.op_id = CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
 		.type = CTLV_OP_TYPE_QUERY,
 		.fd_access = CTLV_OP_FD_ACCESS_READ,
-		.availability = CTLV_OP_AVAILABILITY_FIXED,
+		.availability = CTLV_OP_AVAILABILITY_DYNAMIC,
 		.optional = true,
 		.request_max_len = 16,
 		.query_reply_min_len = 40,
Signature · 1 line
2.43.0
#2 lore raw

Re: [RFC PATCH v1 01/11] attestation: Restrict inherited operations per instance

Wed, Oct 28, 2026 at 04:26:08PM +0100, [email protected] wrote:

[...]

Documentation/driver-api/attestation.rst dynamic topology retains handles returned by ``attestation_mr_create()`` and · line 40

Quoted 9 lines
Documentation/driver-api/attestation.rst +9 -0
+Per-instance features
+=====================
+
+A provider descriptor describes the maximum capabilities of its instances.
+A child family may restrict evidence retrieval, verifier verdicts, TDISP or
+trust-state reporting and RUN through ``attestation_features_set_locked()``.
+The features must be a subset of the descriptor. Verdicts require evidence;
+RUN additionally requires verdicts and at least one native state reporter.
+Providers that do not restrict their features keep the descriptor's defaults.

This is already covered by CTLV op enable/disable helpers. Why exactly do you need to duplicate this in features? I believe they should be fixed.

[..]

author maintainer
#3 lore raw

RFC PATCH 2/11 attestation: Filter evidence objects per instance

In reply to Aneesh Kumar K.V (Arm)

Allow an attestation instance to expose only the evidence objects its device supports. Add an optional evidence_object_supported() callback and use it both when building DEVICE_GET capabilities and when checking EVIDENCE_GET requests.

The following PCI/TSM patch uses one attestation implementation for devices that support different evidence objects. Its list includes certificate slots 0-7, SPDM VCA, measurements, and a TDISP interface report. Each device must expose only the objects it supports, for example:

Instance Supported evidence objects
------------------------ ------------------------------------------ Host link using slot zero Certificate slot 0
CCA guest device Certificate slot 0, VCA, measurements,
TDISP interface report
Host PF0 after discovery Certificate slots reported populated by SPDM

PCI/TSM checks the TSM driver's evidence_types bitmap or, after SPDM discovery on a host PF0, the populated-slot bitmap. The preceding patch controls whether EVIDENCE_GET is enabled; this patch controls which objects that operation can retrieve.

Cc Jiri Pirko <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 drivers/attestation/core/core.c |  8 ++++++--
 drivers/attestation/core/ctlv.c | 13 +++++++++++--
 include/linux/attestation.h     |  4 ++++
 3 files changed, 21 insertions(+), 4 deletions(-)
drivers/attestation/core/core.c +6 -2
index 41fe1084c989..37d5d3233cf0 100644
@@ -54,8 +54,12 @@ static int attestation_evidence_caps_validate(const struct attestation_provider
 	const struct attestation_evidence_caps *caps = provider->evidence_caps;
 	size_t object_index;
 
-	if (!caps)
-		return provider->evidence_get ? -EINVAL : 0;
+	if (!caps) {
+		if (provider->evidence_get || provider->evidence_object_supported)
+			return -EINVAL;
+		return 0;
+	}
+
 	if (!provider->evidence_get || !caps->objects || !caps->objects_count ||
 	    caps->objects_count > 16 || caps->min_challenge_length > caps->max_challenge_length ||
 	    caps->max_challenge_length > 4096 || !caps->max_total_output_length)
drivers/attestation/core/ctlv.c +11 -2
index 8f4fa48ffdc5..baf9c39f2658 100644
@@ -33,15 +33,20 @@ attestation_representation_in(const struct attestation_evidence_supported_object
 }
 
 static const struct attestation_evidence_supported_object *
-attestation_evidence_object_find(const struct attestation_evidence_caps *caps,
+attestation_evidence_object_find(struct attestation *att,
 				 enum attestation_evidence_blob_class blob_class, bool has_instance,
 				 unsigned int instance, const char *format)
 {
+	const struct attestation_evidence_caps *caps = att->provider->evidence_caps;
 	size_t index;
 
 	for (index = 0; index < caps->objects_count; index++) {
 		const struct attestation_evidence_supported_object *object = &caps->objects[index];
 
+		if (att->provider->evidence_object_supported &&
+		    !att->provider->evidence_object_supported(att, object))
+			continue;
+
 		if (object->blob_class == blob_class && object->has_blob_instance == has_instance &&
 		    (!has_instance || object->blob_instance == instance) &&
 		    !strcmp(object->format, format))
@@ -85,6 +90,10 @@ static int attestation_device_caps_put(struct attestation *att, struct ctlv_curs
 			const struct attestation_evidence_supported_object *cap =
 				&caps->objects[index];
 
+			if (provider->evidence_object_supported &&
+			    !provider->evidence_object_supported(att, cap))
+				continue;
+
 			if (!ctlv_attestation_cap_object_start(&group, &object)) {
 				ok = false;
 				break;
@@ -184,7 +193,7 @@ attestation_evidence_decode_objects(struct attestation *att, struct ctlv_op_ctx
 		if (has_instance)
 			instance = ctlv_attestation_evidence_get_object_evidence_blob_instance_get(
 				&object);
-		supported = attestation_evidence_object_find(caps, blob_class, has_instance,
+		supported = attestation_evidence_object_find(att, blob_class, has_instance,
 							     instance, format);
 		if (!supported || !attestation_representation_in(supported, representation)) {
 			kfree(objects);
include/linux/attestation.h +4 -0
index dede1b53f67a..364ab5029277 100644
@@ -233,6 +233,8 @@ struct attestation_evidence_result {
 
 /**
  * struct attestation_provider - Immutable provider operations and properties.
+ * @evidence_object_supported: optionally restrict advertised and requested
+ *	evidence objects per instance; called under the instance lock
  * @evidence_get: produce requested evidence
  * @evidence_accept: record acceptance of the current evidence
  * @evidence_accept_clear: clear a recorded acceptance
@@ -247,6 +249,8 @@ struct attestation_evidence_result {
  * @mrs_count: number of fixed MRs
  */
 struct attestation_provider {
+	bool (*evidence_object_supported)(struct attestation *att,
+		const struct attestation_evidence_supported_object *object);
 	int (*evidence_get)(struct attestation *att,
 			    const struct attestation_evidence_request *request,
 			    struct attestation_evidence_result *result);
Signature · 1 line
2.43.0
#4 lore raw

Re: [RFC PATCH v1 02/11] attestation: Filter evidence objects per instance

Quoted 18 lines · Wed, Oct 28, 2026 at 04:26:09PM +0100, [email protected] wrote:

Allow an attestation instance to expose only the evidence objects its device supports. Add an optional evidence_object_supported() callback and use it both when building DEVICE_GET capabilities and when checking EVIDENCE_GET requests.

The following PCI/TSM patch uses one attestation implementation for devices that support different evidence objects. Its list includes certificate slots 0-7, SPDM VCA, measurements, and a TDISP interface report. Each device must expose only the objects it supports, for example:

Instance Supported evidence objects
------------------------ ------------------------------------------ Host link using slot zero Certificate slot 0
CCA guest device Certificate slot 0, VCA, measurements,
TDISP interface report
Host PF0 after discovery Certificate slots reported populated by SPDM

Quoted 4 lines

PCI/TSM checks the TSM driver's evidence_types bitmap or, after SPDM discovery on a host PF0, the populated-slot bitmap. The preceding patch controls whether EVIDENCE_GET is enabled; this patch controls which objects that operation can retrieve.

For SPDM slots, I think it would make sense to distinguish supported slot addresses from discovered population. DEVICE_GET describes fixed object tuples, formats and limits; SPDM_GET reports the populated slots and their certificate digests. An empty supported slot returns -ENOENT.

Fixed differences between backend instances can be selected before publication. Please do not make the capability set follow a populated-slot bitmap that can change during the instance lifetime.

[..]

author maintainer
#5 lore raw

Re: [RFC PATCH v1 02/11] attestation: Filter evidence objects per instance

Quoted 30 lines · Jiri Pirko
Quoted 18 lines · Wed, Oct 28, 2026 at 04:26:09PM +0100, [email protected] wrote:

Allow an attestation instance to expose only the evidence objects its device supports. Add an optional evidence_object_supported() callback and use it both when building DEVICE_GET capabilities and when checking EVIDENCE_GET requests.

The following PCI/TSM patch uses one attestation implementation for devices that support different evidence objects. Its list includes certificate slots 0-7, SPDM VCA, measurements, and a TDISP interface report. Each device must expose only the objects it supports, for example:

Instance Supported evidence objects
------------------------ ------------------------------------------ Host link using slot zero Certificate slot 0
CCA guest device Certificate slot 0, VCA, measurements,
TDISP interface report
Host PF0 after discovery Certificate slots reported populated by SPDM

Quoted 4 lines

PCI/TSM checks the TSM driver's evidence_types bitmap or, after SPDM discovery on a host PF0, the populated-slot bitmap. The preceding patch controls whether EVIDENCE_GET is enabled; this patch controls which objects that operation can retrieve.

For SPDM slots, I think it would make sense to distinguish supported slot addresses from discovered population. DEVICE_GET describes fixed object tuples, formats and limits; SPDM_GET reports the populated slots and their certificate digests. An empty supported slot returns -ENOENT.

Quoted 5 lines

Fixed differences between backend instances can be selected before publication. Please do not make the capability set follow a populated-slot bitmap that can change during the instance lifetime.

[..]

We still need to filter objects such as measurements and the interface report based on the backend instance. Are you suggesting that we do this in attestation_evidence_object_find()?

supported = attestation_evidence_object_find(att, blob_class, has_instance,

					     instance, format);
if (!supported || !attestation_representation_in(supported, representation)) {
	kfree(objects);
	return -EOPNOTSUPP;
}

So the backend will have some way to indicate which objects it supports, such as the TSM driver's evidence_types bitmap, with the host setting it to indicate support for only CERT_CHAIN[0-7]?

-aneesh

#6 lore raw

Re: [RFC PATCH v1 02/11] attestation: Filter evidence objects per instance

Quoted 50 lines · Sat, Oct 31, 2026 at 07:24:47AM +0100, [email protected] wrote:
Quoted 30 lines · Jiri Pirko
Quoted 18 lines · Wed, Oct 28, 2026 at 04:26:09PM +0100, [email protected] wrote:

Allow an attestation instance to expose only the evidence objects its device supports. Add an optional evidence_object_supported() callback and use it both when building DEVICE_GET capabilities and when checking EVIDENCE_GET requests.

The following PCI/TSM patch uses one attestation implementation for devices that support different evidence objects. Its list includes certificate slots 0-7, SPDM VCA, measurements, and a TDISP interface report. Each device must expose only the objects it supports, for example:

Instance Supported evidence objects
------------------------ ------------------------------------------ Host link using slot zero Certificate slot 0
CCA guest device Certificate slot 0, VCA, measurements,
TDISP interface report
Host PF0 after discovery Certificate slots reported populated by SPDM

Quoted 4 lines

PCI/TSM checks the TSM driver's evidence_types bitmap or, after SPDM discovery on a host PF0, the populated-slot bitmap. The preceding patch controls whether EVIDENCE_GET is enabled; this patch controls which objects that operation can retrieve.

For SPDM slots, I think it would make sense to distinguish supported slot addresses from discovered population. DEVICE_GET describes fixed object tuples, formats and limits; SPDM_GET reports the populated slots and their certificate digests. An empty supported slot returns -ENOENT.

Quoted 5 lines

Fixed differences between backend instances can be selected before publication. Please do not make the capability set follow a populated-slot bitmap that can change during the instance lifetime.

[..]

We still need to filter objects such as measurements and the interface report based on the backend instance. Are you suggesting that we do this in attestation_evidence_object_find()?

supported = attestation_evidence_object_find(att, blob_class, has_instance,

					     instance, format);
if (!supported || !attestation_representation_in(supported, representation)) {
	kfree(objects);
	return -EOPNOTSUPP;
}
Quoted 3 lines

So the backend will have some way to indicate which objects it supports, such as the TSM driver's evidence_types bitmap, with the host setting it to indicate support for only CERT_CHAIN[0-7]?

Yes, that is what I meant. DEVICE_GET and attestation_evidence_object_find() should apply the same per-instance support filter. Using the TSM driver's evidence_types bitmap to select the provider's supported-object descriptions is fine.

That handles backend differences such as support for measurements and the interface report. For a host backend supporting all eight slot addresses, the supported set can contain CERT_CHAIN[0-7].

My concern was only using the populated-slot bitmap for this filtering. The supported set should be fixed before publication; current slot population is separate state reported by SPDM_GET.

A request for an unsupported object returns -EOPNOTSUPP. A request for a supported certificate slot that is currently empty returns -ENOENT.

author maintainer
#7 lore raw

RFC PATCH 3/11 attestation: Enter secure operation when evidence is accepted

In reply to Aneesh Kumar K.V (Arm)

Use EVIDENCE_ACCEPT to enter secure operation after userspace has checked the evidence. Check the referenced device and evidence generations before calling the driver, and record acceptance on success.

Remove the separate DEVSEC_RUN command and callback.

Keep DEVSEC_STATE_GET so userspace can read both evidence acceptance and device state. Clearing acceptance does not undo the device state change. For example, with the following PCI/TSM patches:

  Operation              Evidence acceptance  TDISP state
  ---------------------  -------------------  -----------
  EVIDENCE_ACCEPT        Recorded             RUN
  EVIDENCE_ACCEPT_CLEAR  Cleared              RUN

After EVIDENCE_ACCEPT_CLEAR, DEVSEC_STATE_GET omits the accepted evidence generation but still reports TDISP RUN.

Cc Jiri Pirko <[email protected]>
Cc Kiryl Shutsemau <[email protected]>
Cc Rick Edgecombe <[email protected]>
Cc Dave Hansen <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  | 38 ++++-----
 .../ctlv/specs/ctlv-attestation.yaml          | 25 ------
 drivers/attestation/core/core.c               | 17 ----
 drivers/attestation/core/ctlv.c               | 45 +---------
 .../generated/ctlv_attestation_gen.c          | 29 ++-----
 .../generated/ctlv_attestation_gen.h          | 26 +-----
 drivers/attestation/providers/sim.c           | 35 ++------
 drivers/attestation/tests/core_kunit.c        | 19 +----
 include/linux/attestation.h                   |  5 +-
 include/uapi/linux/ctlv_attestation_gen.h     | 16 ----
 .../selftests/attestation/attestation_test.c  | 28 ++-----
 .../ctlv_attestation_sim_user_gen.c           | 83 +------------------
 .../ctlv_attestation_tdx_user_gen.c           | 83 +------------------
 .../attestation/ctlv_attestation_user_gen.c   | 83 +------------------
 .../attestation/ctlv_attestation_user_gen.h   | 52 +-----------
 15 files changed, 51 insertions(+), 533 deletions(-)
Documentation/ctlv/abi/ctlv-attestation.yaml +14 -24
index 31dbffa59416..0220c2e131d4 100644
@@ -22,7 +22,6 @@ flags:
   mr-device-caps: {read: 0, extend: 1, dynamic-topology: 2}
   mr-caps: {read: 0, extend: 1, removable: 2}
   mr-state-flags: {ima-active: 0}
-  devsec-caps: {run: 0}
   evidence-caps: {evidence-accept: 0, mr-selection: 1, privlevel: 2, service: 3}
 attr-groups:
   device:
@@ -47,10 +46,8 @@ attr-groups:
                        max-occurrences: 2}
       max-length: {id: 11, type: uint}
       evidence-flags: {id: 12, flags: evidence-caps}
-      devsec: {id: 13, type: nested}
-      devsec-flags: {id: 14, flags: devsec-caps}
-      blob-instance: {id: 15, type: uint, max: 4294967295}
-      hash-algo: {id: 16, type: uint, max: 20, max-occurrences: 21}
+      blob-instance: {id: 13, type: uint, max: 4294967295}
+      hash-algo: {id: 14, type: uint, max: 20, max-occurrences: 21}
   evidence:
     id: 3
     attrs:
@@ -138,18 +135,16 @@ attr-sets:
   device-state-event: {id: 20}
   mr-log-event: {id: 21}
   mr-log-ima: {id: 22}
-  devsec-cap: {id: 23}
-  evidence-generation-request: {id: 24}
-  devsec-state-reply: {id: 25}
-  devsec-run-reply: {id: 26}
-  evidence-state-event: {id: 27}
-  evidence-options: {id: 28, stub: true}
-  mr-options: {id: 29, stub: true}
-  evidence-mr-selection: {id: 30}
-  record-data: {id: 31}
-  mr-log-selection: {id: 32}
-  mr-log-request: {id: 33}
-  mr-log-reply: {id: 34}
+  evidence-generation-request: {id: 23}
+  devsec-state-reply: {id: 24}
+  evidence-state-event: {id: 25}
+  evidence-options: {id: 26, stub: true}
+  mr-options: {id: 27, stub: true}
+  evidence-mr-selection: {id: 28}
+  record-data: {id: 29}
+  mr-log-selection: {id: 30}
+  mr-log-request: {id: 31}
+  mr-log-reply: {id: 32}
 ops:
   device-get:
     abi: {id: 1, type: action, fd-access: read}
@@ -169,7 +164,6 @@ ops:
                         optional-attrs: [cap.blob-instance, cap.hash-algo]
                   optional-attrs: [cap.evidence-flags]
               - cap.mr: {attrs: [cap.mr-flags]}
-              - cap.devsec: {attrs: [cap.devsec-flags]}
         - device.info: {attrs: [device-info]}
   evidence-get:
     abi: {id: 2, type: action, availability: dynamic, optional: true}
@@ -256,17 +250,13 @@ ops:
       attrs: [device.generation]
       optional-attrs: [evidence.generation, evidence.accepted-generation,
                        devsec.tdisp-state, devsec.device-trust]
-  devsec-run:
-    abi: {id: 12, type: action, availability: dynamic, optional: true}
-    request: {attrs: [device.generation, evidence.generation]}
-    reply: {attrs: [device.generation]}
   evidence-state-changed:
-    abi: {id: 13, type: event}
+    abi: {id: 12, type: event}
     reply:
       attrs: [device.generation]
       optional-attrs: [evidence.generation, evidence.accepted-generation]
   mr-log-get:
-    abi: {id: 14, type: query, fd-access: read, optional: true}
+    abi: {id: 13, type: query, fd-access: read, optional: true}
     request:
       optional-attrs:
         - mr.selection: {attrs: [mr.id]}
Documentation/ctlv/specs/ctlv-attestation.yaml +0 -25
index 570a97b7538f..8494f716f9e9 100644
@@ -62,10 +62,6 @@ flags:
     values:
       - ima-active
 
-  - name: devsec-caps
-    values:
-      - run
-
   - name: evidence-caps
     values:
       - evidence-accept
@@ -115,10 +111,6 @@ attr-groups:
         type: uint
       - name: evidence-flags
         flags: evidence-caps
-      - name: devsec
-        attrs: devsec-cap
-      - name: devsec-flags
-        flags: devsec-caps
       - name: blob-instance
         type: uint
         max: 4294967295
@@ -306,7 +298,6 @@ attr-sets:
     optional-attrs:
       - cap.evidence
       - cap.mr
-      - cap.devsec
 
   - name: device-reply
     attrs:
@@ -429,9 +420,6 @@ attr-sets:
       - log.template-data
       - log.violation
 
-  - name: devsec-cap
-    attrs: cap.devsec-flags
-
   - name: evidence-generation-request
     attrs:
       - device.generation
@@ -445,9 +433,6 @@ attr-sets:
       - devsec.tdisp-state
       - devsec.device-trust
 
-  - name: devsec-run-reply
-    attrs: device.generation
-
   - name: evidence-state-event
     attrs: device.generation
     optional-attrs:
@@ -571,16 +556,6 @@ ops:
       attrs: devsec-state-reply
     suggested-reply-length: 1024
 
-  - name: devsec-run
-    type: action
-    fd-access: read-write
-    availability: dynamic
-    optional: true
-    request:
-      attrs: evidence-generation-request
-    reply:
-      attrs: devsec-run-reply
-
   - name: evidence-state-changed
     type: event
     reply:
drivers/attestation/core/core.c +0 -17
index 37d5d3233cf0..a1de19dcab7d 100644
@@ -137,9 +137,6 @@ int attestation_provider_validate(const struct attestation_provider *provider)
 		if (ret)
 			return ret;
 	}
-	if (provider->devsec_run && (!provider->evidence_accept ||
-				     (!provider->tdisp_state_get && !provider->device_trust_get)))
-		return -EINVAL;
 	return 0;
 }
 
@@ -478,9 +475,6 @@ static unsigned int attestation_provider_features(const struct attestation_provi
 	if (provider->device_trust_get)
 		features |= ATTESTATION_FEATURE_DEVICE_TRUST;
 
-	if (provider->devsec_run)
-		features |= ATTESTATION_FEATURE_DEVSEC_RUN;
-
 	return features;
 }
 
@@ -498,10 +492,6 @@ int attestation_features_set_locked(struct attestation *att, unsigned int featur
 	    !(features & ATTESTATION_FEATURE_EVIDENCE))
 		return -EINVAL;
 
-	if ((features & ATTESTATION_FEATURE_DEVSEC_RUN) &&
-	    (!(features & ATTESTATION_FEATURE_EVIDENCE_ACCEPT) || !(features & state)))
-		return -EINVAL;
-
 	att->features = features;
 	if (supported & ATTESTATION_FEATURE_EVIDENCE)
 		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_EVIDENCE_GET,
@@ -520,9 +510,6 @@ int attestation_features_set_locked(struct attestation *att, unsigned int featur
 		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
 					 !(features & state));
 
-	if (supported & ATTESTATION_FEATURE_DEVSEC_RUN)
-		ctlv_device_op_state_set(att->ctlvdev, CTLV_ATTESTATION_OP_DEVSEC_RUN,
-					 !(features & ATTESTATION_FEATURE_DEVSEC_RUN));
 	return 0;
 }
 EXPORT_SYMBOL_NS_GPL(attestation_features_set_locked, "ATTESTATION");
@@ -562,10 +549,6 @@ void attestation_provide_ops(struct attestation *att)
 		ctlv_attestation_devsec_state_get_provide(ctlvdev);
 		ctlv_attestation_devsec_state_get_enable(ctlvdev);
 	}
-	if (provider->devsec_run) {
-		ctlv_attestation_devsec_run_provide(ctlvdev);
-		ctlv_attestation_devsec_run_enable(ctlvdev);
-	}
 }
 
 int attestation_core_init(struct attestation *att, struct ctlv_device *ctlvdev,
drivers/attestation/core/ctlv.c +1 -44

60 lines not on this page · raw message

drivers/attestation/generated/ctlv_attestation_gen.c +5 -24
index 5882dd2c93a7..4afd0433e26f 100644
@@ -23,21 +23,18 @@ static const struct ctlv_node_schema device_get_request_node_schemas[] = {
 static const struct ctlv_node_schema device_get_reply_node_schemas[] = {
 	CTLV_NODE_SCHEMA_ROOT(1, 3, 3),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_DEVICE_CAPS, 1, 4, 3, 0),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_DEVICE_CAPS, 1, 4, 2, 0),
 	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_DEVICE_INFO, 1, 0, 0, 0),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_EVIDENCE, 1, 7, 5, 4),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_MR, 1, 12, 1, 1),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_DEVSEC, 1, 13, 1, 1),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_EVIDENCE, 1, 6, 5, 4),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_MR, 1, 11, 1, 1),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_CAP_MIN_CHALLENGE_LENGTH, 1, 0, U64_MAX),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_CAP_MAX_CHALLENGE_LENGTH, 1, 0, U64_MAX),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_CAP_MAX_TOTAL_OUTPUT_LENGTH, 1, 0, U64_MAX),
-	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_OBJECT, 16, 14, 6, 4),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_CAP_OBJECT, 16, 12, 6, 4),
 	CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS, 1,
 			       CTLV_ATTESTATION_EVIDENCE_CAPS_MASK),
 	CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_ATTR_CAP_MR_FLAGS, 1,
 			       CTLV_ATTESTATION_MR_DEVICE_CAPS_MASK),
-	CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS, 1,
-			       CTLV_ATTESTATION_DEVSEC_CAPS_MASK),
 	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_ATTR_CAP_BLOB_CLASS, 1, 9),
 	CTLV_NODE_SCHEMA_STRING(CTLV_ATTESTATION_ATTR_CAP_FORMAT, 1, 1, 128),
 	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_ATTR_CAP_REPRESENTATION, 2, 2),
@@ -218,17 +215,6 @@ static const struct ctlv_node_schema devsec_state_get_reply_node_schemas[] = {
 	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_ATTR_DEVSEC_DEVICE_TRUST, 1, 5),
 };
 
-static const struct ctlv_node_schema devsec_run_request_node_schemas[] = {
-	CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
-	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
-	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION, 1, 0, U32_MAX),
-};
-
-static const struct ctlv_node_schema devsec_run_reply_node_schemas[] = {
-	CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
-	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
-};
-
 static const struct ctlv_node_schema evidence_state_changed_reply_node_schemas[] = {
 	CTLV_NODE_SCHEMA_ROOT(1, 3, 1),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
@@ -273,7 +259,7 @@ static const struct ctlv_node_schema mr_log_get_reply_node_schemas[] = {
 /* The ops this family declares, in declaration order. */
 const struct ctlv_op_schema ctlv_attestation_op_schemas[] = {
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_DEVICE_GET, CTLV_OP_FD_ACCESS_READ,
-			      CTLV_OP_AVAILABILITY_FIXED, 16, 12936,
+			      CTLV_OP_AVAILABILITY_FIXED, 16, 12896,
 			      CTLV_SCHEMA_TREE(device_get_request),
 			      CTLV_SCHEMA_TREE_STUBS(device_get_reply)),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_GET, CTLV_OP_FD_ACCESS_READ_WRITE,
@@ -311,10 +297,6 @@ const struct ctlv_op_schema ctlv_attestation_op_schemas[] = {
 			     CTLV_OP_AVAILABILITY_DYNAMIC, 16, 40, 1024,
 			     CTLV_SCHEMA_TREE(devsec_state_get_request),
 			     CTLV_SCHEMA_TREE(devsec_state_get_reply), .optional = true),
-	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_DEVSEC_RUN, CTLV_OP_FD_ACCESS_READ_WRITE,
-			      CTLV_OP_AVAILABILITY_DYNAMIC, 64, 40,
-			      CTLV_SCHEMA_TREE(devsec_run_request),
-			      CTLV_SCHEMA_TREE(devsec_run_reply), .optional = true),
 	CTLV_OP_SCHEMA_EVENT(CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED, 112,
 			     CTLV_SCHEMA_TREE(evidence_state_changed_reply)),
 	CTLV_OP_SCHEMA_QUERY(CTLV_ATTESTATION_OP_MR_LOG_GET, CTLV_OP_FD_ACCESS_READ,
@@ -351,7 +333,6 @@ static const struct ctlv_op ctlv_attestation_ops[] = {
 	CTLV_OP_ACTION(ctlv_attestation_evidence_accept),
 	CTLV_OP_ACTION(ctlv_attestation_evidence_accept_clear),
 	CTLV_OP_QUERY(ctlv_attestation_devsec_state_get),
-	CTLV_OP_ACTION(ctlv_attestation_devsec_run),
 	CTLV_OP_QUERY(ctlv_attestation_mr_log_get),
 };
 
drivers/attestation/generated/ctlv_attestation_gen.h +4 -22
index a1f125690005..12716a7fa7af 100644
@@ -43,9 +43,6 @@ int ctlv_attestation_evidence_accept_clear_action(void *priv, struct ctlv_op_ctx
 int ctlv_attestation_devsec_state_get_query(void *priv, struct ctlv_op_ctx *ctx,
 					    const struct ctlv_cursor *request,
 					    struct ctlv_cursor *reply);
-int ctlv_attestation_devsec_run_action(void *priv, struct ctlv_op_ctx *ctx,
-				       const struct ctlv_cursor *request,
-				       struct ctlv_cursor *reply);
 int ctlv_attestation_mr_log_get_query(void *priv, struct ctlv_op_ctx *ctx,
 				      const struct ctlv_cursor *request, struct ctlv_cursor *reply);
 
@@ -81,9 +78,6 @@ CTLV_OP_DISABLE(ctlv_attestation_evidence_accept_clear, CTLV_ATTESTATION_OP_EVID
 CTLV_OP_PROVIDE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
 CTLV_OP_ENABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
 CTLV_OP_DISABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_STATE_GET)
-CTLV_OP_PROVIDE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
-CTLV_OP_ENABLE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
-CTLV_OP_DISABLE(ctlv_attestation_devsec_run, CTLV_ATTESTATION_OP_DEVSEC_RUN)
 CTLV_OP_PROVIDE(ctlv_attestation_mr_log_get, CTLV_ATTESTATION_OP_MR_LOG_GET)
 
 /* Reading a validated message. A blob has a resolver instead of a getter. */
@@ -231,29 +225,20 @@ CTLV_IN_REF(ctlv_attestation_evidence_accept_evidence_generation,
 	    1, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_evidence_accept_evidence_generation)
 CTLV_IN_U64(ctlv_attestation_evidence_accept_evidence_generation)
-CTLV_IN_REF(ctlv_attestation_devsec_run_device_generation, &ctlv_attestation_op_schemas[11].request,
-	    CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 0, 0, CTLV_SCHEMA_UINT)
-CTLV_IN_HAS(ctlv_attestation_devsec_run_device_generation)
-CTLV_IN_U64(ctlv_attestation_devsec_run_device_generation)
-CTLV_IN_REF(ctlv_attestation_devsec_run_evidence_generation,
-	    &ctlv_attestation_op_schemas[11].request, CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION, 0,
-	    1, CTLV_SCHEMA_UINT)
-CTLV_IN_HAS(ctlv_attestation_devsec_run_evidence_generation)
-CTLV_IN_U64(ctlv_attestation_devsec_run_evidence_generation)
-CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_selection, &ctlv_attestation_op_schemas[13].request,
+CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_selection, &ctlv_attestation_op_schemas[12].request,
 	    CTLV_ATTESTATION_ATTR_MR_SELECTION, 0, 0, CTLV_SCHEMA_NEST)
 #define ctlv_attestation_mr_log_get_mr_selection_for_each(_attr, _nest) \
 	CTLV_IN_FOR_EACH(ctlv_attestation_mr_log_get_mr_selection, _attr, _nest)
 CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_start_generation,
-	    &ctlv_attestation_op_schemas[13].request, CTLV_ATTESTATION_ATTR_MR_START_GENERATION, 0,
+	    &ctlv_attestation_op_schemas[12].request, CTLV_ATTESTATION_ATTR_MR_START_GENERATION, 0,
 	    1, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_mr_log_get_mr_start_generation)
 CTLV_IN_U64(ctlv_attestation_mr_log_get_mr_start_generation)
-CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_end_generation, &ctlv_attestation_op_schemas[13].request,
+CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_end_generation, &ctlv_attestation_op_schemas[12].request,
 	    CTLV_ATTESTATION_ATTR_MR_END_GENERATION, 0, 2, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_mr_log_get_mr_end_generation)
 CTLV_IN_U64(ctlv_attestation_mr_log_get_mr_end_generation)
-CTLV_IN_REF(ctlv_attestation_mr_log_get_selection_mr_id, &ctlv_attestation_op_schemas[13].request,
+CTLV_IN_REF(ctlv_attestation_mr_log_get_selection_mr_id, &ctlv_attestation_op_schemas[12].request,
 	    CTLV_ATTESTATION_ATTR_MR_ID, 1, 0, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_mr_log_get_selection_mr_id)
 CTLV_IN_U64(ctlv_attestation_mr_log_get_selection_mr_id)
@@ -282,9 +267,6 @@ CTLV_PUT_U64(ctlv_attestation_cap_max_length, CTLV_ATTESTATION_ATTR_CAP_MAX_LENG
 	     CTLV_SCHEMA_UINT)
 CTLV_PUT_U64(ctlv_attestation_cap_evidence_flags, CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS,
 	     CTLV_SCHEMA_FLAGS)
-CTLV_NEST_START(ctlv_attestation_cap_devsec, CTLV_ATTESTATION_ATTR_CAP_DEVSEC, CTLV_SCHEMA_NEST)
-CTLV_PUT_U64(ctlv_attestation_cap_devsec_flags, CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS,
-	     CTLV_SCHEMA_FLAGS)
 CTLV_PUT_U64(ctlv_attestation_cap_blob_instance, CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE,
 	     CTLV_SCHEMA_UINT)
 CTLV_PUT_U64(ctlv_attestation_cap_hash_algo, CTLV_ATTESTATION_ATTR_CAP_HASH_ALGO, CTLV_SCHEMA_UINT)
drivers/attestation/providers/sim.c +5 -30
index 5cf312285e85..7c35b1611f10 100644
@@ -33,7 +33,6 @@ enum attestation_sim_callback {
 	ATTESTATION_SIM_CB_MR_EXTEND,
 	ATTESTATION_SIM_CB_TDISP_STATE_GET,
 	ATTESTATION_SIM_CB_DEVICE_TRUST_GET,
-	ATTESTATION_SIM_CB_DEVSEC_RUN,
 	ATTESTATION_SIM_CB_COUNT,
 };
 
@@ -55,7 +54,6 @@ struct attestation_sim {
 	struct attestation_sim_mr fixed[2];
 	enum attestation_tdisp_state tdisp_state;
 	enum attestation_device_trust_state trust_state;
-	bool pending_verdict;
 	bool notification_pending;
 };
 
@@ -196,7 +194,6 @@ static const char * const attestation_sim_callback_names[] = {
 	[ATTESTATION_SIM_CB_MR_EXTEND] = "mr_extend",
 	[ATTESTATION_SIM_CB_TDISP_STATE_GET] = "tdisp_state_get",
 	[ATTESTATION_SIM_CB_DEVICE_TRUST_GET] = "device_trust_get",
-	[ATTESTATION_SIM_CB_DEVSEC_RUN] = "devsec_run",
 };
 
 static enum attestation_sim_callback
@@ -585,16 +582,16 @@ static int attestation_sim_evidence_accept(struct attestation *att)
 	guard(mutex)(&sim->hardware_lock);
 	if (sim->notification_pending)
 		return -EAGAIN;
-	sim->pending_verdict = true;
+	if (sim->tdisp_state != ATTESTATION_TDISP_STATE_LOCKED &&
+	    sim->tdisp_state != ATTESTATION_TDISP_STATE_RUN)
+		return -EACCES;
+	sim->tdisp_state = ATTESTATION_TDISP_STATE_RUN;
 	return 0;
 }
 
 static void attestation_sim_evidence_accept_clear(struct attestation *att)
 {
-	struct attestation_sim *sim = attestation_sim_from_att(att);
-
-	guard(mutex)(&sim->hardware_lock);
-	sim->pending_verdict = false;
+	/* The common core owns the verdict; clearing it does not leave RUN. */
 }
 
 static int
@@ -633,26 +630,6 @@ attestation_sim_device_trust_get(
 	return 0;
 }
 
-static int attestation_sim_devsec_run(struct attestation *att)
-{
-	struct attestation_sim *sim = attestation_sim_from_att(att);
-	int ret;
-
-	ret = attestation_sim_callback_enter(
-		sim, ATTESTATION_SIM_CB_DEVSEC_RUN);
-	if (ret)
-		return ret;
-	guard(mutex)(&sim->hardware_lock);
-	if (sim->notification_pending)
-		return -EAGAIN;
-	if (sim->tdisp_state != ATTESTATION_TDISP_STATE_LOCKED ||
-	    !sim->pending_verdict)
-		return -EACCES;
-	sim->tdisp_state = ATTESTATION_TDISP_STATE_RUN;
-	sim->pending_verdict = false;
-	return 0;
-}
-
 static const struct attestation_provider attestation_sim_provider = {
 	.evidence_get = attestation_sim_evidence_get,
 	.evidence_accept = attestation_sim_evidence_accept,
@@ -661,7 +638,6 @@ static const struct attestation_provider attestation_sim_provider = {
 	.mr_extend = attestation_sim_mr_extend,
 	.tdisp_state_get = attestation_sim_tdisp_state_get,
 	.device_trust_get = attestation_sim_device_trust_get,
-	.devsec_run = attestation_sim_devsec_run,
 	.evidence_caps = &attestation_sim_evidence_caps,
 	.mr_caps = &attestation_sim_mr_caps,
 	.mrs = attestation_sim_mrs,
@@ -937,7 +913,6 @@ static ssize_t attestation_sim_state_write(struct file *file,
 		fixture->sim->notification_pending = true;
 		fixture->sim->tdisp_state = tdisp;
 		fixture->sim->trust_state = trust;
-		fixture->sim->pending_verdict = false;
 	}
 	att = ctlv_priv_parent(fixture->sim);
 	attestation_state_changed(att);
drivers/attestation/tests/core_kunit.c +2 -17
index 7e4710370cfe..08cb5eabee1b 100644
@@ -231,7 +231,7 @@ static void attestation_test_instance_features(struct kunit *test)
 	KUNIT_EXPECT_EQ(test, fixture->att->features, features);
 	/* A child cannot enable a callback that its provider did not supply. */
 	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
-			features | ATTESTATION_FEATURE_DEVSEC_RUN), -EINVAL);
+			features | ATTESTATION_FEATURE_TDISP_STATE), -EINVAL);
 	KUNIT_EXPECT_EQ(test, fixture->att->features, features);
 	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att, 0), 0);
 	KUNIT_EXPECT_EQ(test, fixture->att->features, 0U);
@@ -250,36 +250,21 @@ static int attestation_test_tdisp_state(struct attestation *att,
 	return 0;
 }
 
-static int attestation_test_run(struct attestation *att)
-{
-	return 0;
-}
-
 static void attestation_test_secure_features(struct kunit *test)
 {
 	struct attestation_test *fixture = test->priv;
 	unsigned int evidence = ATTESTATION_FEATURE_EVIDENCE |
 				ATTESTATION_FEATURE_EVIDENCE_ACCEPT;
 	unsigned int state = ATTESTATION_FEATURE_TDISP_STATE;
-	unsigned int run = ATTESTATION_FEATURE_DEVSEC_RUN;
 
 	attestation_test_provider_init(fixture);
 	fixture->provider.tdisp_state_get = attestation_test_tdisp_state;
-	fixture->provider.devsec_run = attestation_test_run;
 	KUNIT_ASSERT_EQ(test, attestation_test_create_provider(
 				test, fixture, "attestation-secure-features"), 0);
-	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state | run);
-	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att, run), -EINVAL);
-	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
-			evidence | run), -EINVAL);
-	KUNIT_EXPECT_EQ(test, attestation_features_set(fixture->att,
-			state | run), -EINVAL);
+	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state);
 	/* Unlocked, locked, RUN, and host verdict-only capability sets. */
 	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att, state), 0);
 	KUNIT_EXPECT_EQ(test, fixture->att->features, state);
-	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att,
-			evidence | state | run), 0);
-	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state | run);
 	KUNIT_ASSERT_EQ(test, attestation_features_set(fixture->att,
 			evidence | state), 0);
 	KUNIT_EXPECT_EQ(test, fixture->att->features, evidence | state);
include/linux/attestation.h +1 -4
index 364ab5029277..9813361ddd29 100644
@@ -20,7 +20,6 @@ enum attestation_feature {
 	ATTESTATION_FEATURE_EVIDENCE_ACCEPT = 1U << 1,
 	ATTESTATION_FEATURE_TDISP_STATE = 1U << 2,
 	ATTESTATION_FEATURE_DEVICE_TRUST = 1U << 3,
-	ATTESTATION_FEATURE_DEVSEC_RUN = 1U << 4,
 };
 
 enum attestation_evidence_representation {
@@ -236,13 +235,12 @@ struct attestation_evidence_result {
  * @evidence_object_supported: optionally restrict advertised and requested
  *	evidence objects per instance; called under the instance lock
  * @evidence_get: produce requested evidence
- * @evidence_accept: record acceptance of the current evidence
+ * @evidence_accept: accept current evidence and enable secure operation
  * @evidence_accept_clear: clear a recorded acceptance
  * @mr_read: read one MR
  * @mr_extend: extend one MR
  * @tdisp_state_get: get the TDISP state
  * @device_trust_get: get the provider trust state
- * @devsec_run: enter the provider's secure state
  * @evidence_caps: evidence capabilities, or NULL
  * @mr_caps: MR capabilities, or NULL
  * @mrs: fixed MR properties
@@ -266,7 +264,6 @@ struct attestation_provider {
 			       enum attestation_tdisp_state *state);
 	int (*device_trust_get)(struct attestation *att,
 				enum attestation_device_trust_state *trust);
-	int (*devsec_run)(struct attestation *att);
 	const struct attestation_evidence_caps *evidence_caps;
 	const struct attestation_mr_caps *mr_caps;
 	const struct attestation_mr_props *mrs;
include/uapi/linux/ctlv_attestation_gen.h +0 -16
index 2921bea50151..627c6f5f9377 100644
@@ -100,14 +100,6 @@ enum _ctlv_attestation_mr_state_flags_bit {
 	_BITULL(CTLV_ATTESTATION_MR_STATE_FLAGS_IMA_ACTIVE_BIT)
 #define CTLV_ATTESTATION_MR_STATE_FLAGS_MASK CTLV_ATTESTATION_MR_STATE_FLAGS_IMA_ACTIVE
 
-/* flags devsec-caps */
-enum _ctlv_attestation_devsec_caps_bit {
-	CTLV_ATTESTATION_DEVSEC_CAPS_RUN_BIT,
-};
-
-#define CTLV_ATTESTATION_DEVSEC_CAPS_RUN _BITULL(CTLV_ATTESTATION_DEVSEC_CAPS_RUN_BIT)
-#define CTLV_ATTESTATION_DEVSEC_CAPS_MASK CTLV_ATTESTATION_DEVSEC_CAPS_RUN
-
 /* flags evidence-caps */
 enum _ctlv_attestation_evidence_caps_bit {
 	CTLV_ATTESTATION_EVIDENCE_CAPS_EVIDENCE_ACCEPT_BIT,
@@ -174,8 +166,6 @@ enum _ctlv_attestation_attr_cap {
 	_CTLV_ATTESTATION_ATTR_CAP_REPRESENTATION,
 	_CTLV_ATTESTATION_ATTR_CAP_MAX_LENGTH,
 	_CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS,
-	_CTLV_ATTESTATION_ATTR_CAP_DEVSEC,
-	_CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS,
 	_CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE,
 	_CTLV_ATTESTATION_ATTR_CAP_HASH_ALGO,
 };
@@ -195,8 +185,6 @@ enum _ctlv_attestation_attr_cap {
 #define CTLV_ATTESTATION_ATTR_CAP_REPRESENTATION _CTLV_ATTESTATION_ATTR_CAP(REPRESENTATION)
 #define CTLV_ATTESTATION_ATTR_CAP_MAX_LENGTH _CTLV_ATTESTATION_ATTR_CAP(MAX_LENGTH)
 #define CTLV_ATTESTATION_ATTR_CAP_EVIDENCE_FLAGS _CTLV_ATTESTATION_ATTR_CAP(EVIDENCE_FLAGS)
-#define CTLV_ATTESTATION_ATTR_CAP_DEVSEC _CTLV_ATTESTATION_ATTR_CAP(DEVSEC)
-#define CTLV_ATTESTATION_ATTR_CAP_DEVSEC_FLAGS _CTLV_ATTESTATION_ATTR_CAP(DEVSEC_FLAGS)
 #define CTLV_ATTESTATION_ATTR_CAP_BLOB_INSTANCE _CTLV_ATTESTATION_ATTR_CAP(BLOB_INSTANCE)
 #define CTLV_ATTESTATION_ATTR_CAP_HASH_ALGO _CTLV_ATTESTATION_ATTR_CAP(HASH_ALGO)
 
@@ -368,10 +356,8 @@ enum _ctlv_attestation_set {
 	CTLV_ATTESTATION_SET_DEVICE_STATE_EVENT,
 	CTLV_ATTESTATION_SET_MR_LOG_EVENT,
 	CTLV_ATTESTATION_SET_MR_LOG_IMA,
-	CTLV_ATTESTATION_SET_DEVSEC_CAP,
 	CTLV_ATTESTATION_SET_EVIDENCE_GENERATION_REQUEST,
 	CTLV_ATTESTATION_SET_DEVSEC_STATE_REPLY,
-	CTLV_ATTESTATION_SET_DEVSEC_RUN_REPLY,
 	CTLV_ATTESTATION_SET_EVIDENCE_STATE_EVENT,
 	CTLV_ATTESTATION_SET_EVIDENCE_OPTIONS,
 	CTLV_ATTESTATION_SET_MR_OPTIONS,
@@ -395,7 +381,6 @@ enum _ctlv_attestation_op {
 	_CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT,
 	_CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
 	_CTLV_ATTESTATION_OP_DEVSEC_STATE_GET,
-	_CTLV_ATTESTATION_OP_DEVSEC_RUN,
 	_CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED,
 	_CTLV_ATTESTATION_OP_MR_LOG_GET,
 };
@@ -412,7 +397,6 @@ enum _ctlv_attestation_op {
 #define CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT _CTLV_ATTESTATION_OP(EVIDENCE_ACCEPT)
 #define CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR _CTLV_ATTESTATION_OP(EVIDENCE_ACCEPT_CLEAR)
 #define CTLV_ATTESTATION_OP_DEVSEC_STATE_GET _CTLV_ATTESTATION_OP(DEVSEC_STATE_GET)
-#define CTLV_ATTESTATION_OP_DEVSEC_RUN _CTLV_ATTESTATION_OP(DEVSEC_RUN)
 #define CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED _CTLV_ATTESTATION_OP(EVIDENCE_STATE_CHANGED)
 #define CTLV_ATTESTATION_OP_MR_LOG_GET _CTLV_ATTESTATION_OP(MR_LOG_GET)
 
tools/testing/selftests/attestation/attestation_test.c +9 -19
index 5f1caaac859e..bd831b0ef14f 100644
@@ -154,7 +154,7 @@ FIXTURE_TEARDOWN(attestation)
 
 TEST_F(attestation, discovery_and_caps)
 {
-	const struct ctlv_attr *caps, *evidence, *mr, *devsec, *info;
+	const struct ctlv_attr *caps, *evidence, *mr, *info;
 	struct ctlv_reply *reply;
 	const char *name = NULL;
 	__u64 generation = 1;
@@ -175,10 +175,8 @@ TEST_F(attestation, discovery_and_caps)
 	ASSERT_NE(NULL, caps);
 	evidence = ctlv_attestation_cap_evidence_get(caps);
 	mr = ctlv_attestation_cap_mr_get(caps);
-	devsec = ctlv_attestation_cap_devsec_get(caps);
 	ASSERT_NE(NULL, evidence);
 	ASSERT_NE(NULL, mr);
-	ASSERT_NE(NULL, devsec);
 	EXPECT_TRUE(ctlv_attestation_cap_evidence_flags_get(evidence, &flags,
 							    &unknown));
 	EXPECT_EQ(0, unknown);
@@ -526,6 +524,7 @@ TEST_F(attestation, evidence_verdict_and_run)
 		unsigned char data[EVIDENCE_MAX];
 	} output;
 	struct ctlv_reply *reply;
+	const struct ctlv_attr *root;
 	struct ctlv_msg *request;
 	const __u64 subscriptions[] = {
 		CTLV_ATTESTATION_OP_DEVICE_STATE_CHANGED,
@@ -557,6 +556,13 @@ TEST_F(attestation, evidence_verdict_and_run)
 	expect_event(_metadata, self->dev,
 		     CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED);
 
+	ASSERT_EQ(0, ctlv_attestation_devsec_state_get(self->dev, NULL, &reply, &self->error));
+	root = ctlv_reply_root(reply);
+	EXPECT_TRUE(ctlv_attestation_devsec_tdisp_state_get(root, &state, &known));
+	EXPECT_TRUE(known);
+	EXPECT_EQ(CTLV_ATTESTATION_TDISP_STATE_RUN, state);
+	ctlv_reply_free(reply);
+
 	ASSERT_EQ(0,
 		  ctlv_attestation_evidence_accept_clear_request_new(&request));
 	ASSERT_EQ(0, ctlv_attestation_evidence_accept_clear(
@@ -569,22 +575,6 @@ TEST_F(attestation, evidence_verdict_and_run)
 	expect_event(_metadata, self->dev,
 		     CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED);
 
-	ASSERT_EQ(0, ctlv_attestation_devsec_run_request_new(&request));
-	ctlv_attestation_device_generation_put(request, device_generation);
-	ctlv_attestation_evidence_generation_put(
-		request, evidence_generation);
-	ASSERT_EQ(0, ctlv_attestation_devsec_run(
-			  self->dev, request, &reply, &self->error));
-	ctlv_msg_free(request);
-	EXPECT_TRUE(ctlv_attestation_device_generation_get(
-		ctlv_reply_root(reply), &device_generation));
-	EXPECT_EQ(2, device_generation);
-	ctlv_reply_free(reply);
-	expect_event(_metadata, self->dev,
-		     CTLV_ATTESTATION_OP_DEVICE_STATE_CHANGED);
-	expect_event(_metadata, self->dev,
-		     CTLV_ATTESTATION_OP_EVIDENCE_STATE_CHANGED);
-
 	ASSERT_EQ(0, ctlv_attestation_devsec_state_get(
 			  self->dev, NULL, &reply, &self->error));
 	EXPECT_TRUE(ctlv_attestation_devsec_tdisp_state_get(
tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c +3 -80

123 lines not on this page · raw message

tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c +3 -80

123 lines not on this page · raw message

tools/testing/selftests/attestation/ctlv_attestation_user_gen.c +3 -80

123 lines not on this page · raw message

tools/testing/selftests/attestation/ctlv_attestation_user_gen.h +1 -51

76 lines not on this page · raw message

Signature · 1 line
2.43.0
#8 lore raw

Re: [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted

Quoted 9 lines · Wed, Oct 28, 2026 at 04:26:10PM +0100, [email protected] wrote:

Use EVIDENCE_ACCEPT to enter secure operation after userspace has checked the evidence. Check the referenced device and evidence generations before calling the driver, and record acceptance on success.

Remove the separate DEVSEC_RUN command and callback.

Keep DEVSEC_STATE_GET so userspace can read both evidence acceptance and device state. Clearing acceptance does not undo the device state change. For example, with the following PCI/TSM patches:

Note that the published wip_attestation_all_1 baseline still has DEVSEC_RUN. I am planning this merge into EVIDENCE_ACCEPT for my next revision, which is not pushed yet.

Quoted 8 lines
 Operation              Evidence acceptance  TDISP state
 ---------------------  -------------------  -----------
 EVIDENCE_ACCEPT        Recorded             RUN
 EVIDENCE_ACCEPT_CLEAR  Cleared              RUN

After EVIDENCE_ACCEPT_CLEAR, DEVSEC_STATE_GET omits the accepted evidence generation but still reports TDISP RUN.

What exactly is the clear good for now?

[..]

Quoted 24 lines
drivers/attestation/core/ctlv.c +0 -24
-int ctlv_attestation_devsec_run_action(void *priv, struct ctlv_op_ctx *ctx,
-				       const struct ctlv_cursor *request, struct ctlv_cursor *reply)
-{
-	struct attestation_event_batch events = {};
-	struct attestation *att = priv;
-	u64 device, evidence;
-	int ret;
-
-	device = ctlv_attestation_devsec_run_device_generation_get(request);
-	evidence = ctlv_attestation_devsec_run_evidence_generation_get(request);
-	if (device > UINT_MAX || evidence > UINT_MAX)
-		return -ESTALE;
-	if (!att->verdict_recorded || att->accepted_device_generation != device ||
-	    att->accepted_evidence_generation != evidence)
-		return -ESTALE;
-	ret = attestation_evidence_validate(att, device, evidence);
-	if (ret)
-		return ret;
-	ret = attestation_preflight(att, true, false, false);
-	if (ret)
-		return ret;
-	ret = attestation_events_prepare(att, &events, true, true, false);
-	if (ret)
-		return ret;

drivers/attestation/core/ctlv.c int ctlv_attestation_devsec_state_get_query(void *priv, struct ctlv_op_ctx *ctx, · line 997

Quoted 10 lines
drivers/attestation/core/ctlv.c +0 -10
-	ret = att->provider->devsec_run(att);
-	if (ret)
-		goto out;
-	attestation_device_changed(att);
-	attestation_device_event_emit(&events, att->device_generation);
-	attestation_evidence_event_emit(&events, att);
-	if (!ctlv_attestation_device_generation_put(reply, att->device_generation))
-		ret = -EMSGSIZE;
-out:
-	attestation_events_abort(&events);

As this function performed generation preflight, advancing device-generation, emitting the device and evidence events, and returning the new generation, I think that this bookkeeping needs to move to evidence_accept function.

[..]

maintainer
#9 lore raw

Re: [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted

Quoted 7 lines · Jiri Pirko
Quoted 6 lines · Wed, Oct 28, 2026 at 04:26:10PM +0100, [email protected] wrote:

Use EVIDENCE_ACCEPT to enter secure operation after userspace has checked the evidence. Check the referenced device and evidence generations before calling the driver, and record acceptance on success.

Remove the separate DEVSEC_RUN command and callback.

Quoted 7 lines
Quoted 3 lines

Keep DEVSEC_STATE_GET so userspace can read both evidence acceptance and device state. Clearing acceptance does not undo the device state change. For example, with the following PCI/TSM patches:

Note that the published wip_attestation_all_1 baseline still has DEVSEC_RUN. I am planning this merge into EVIDENCE_ACCEPT for my next revision, which is not pushed yet.

I think this is a good idea.

There are many use cases of SPDM, and the "ACCEPT" is a generic point where we say the SPDM Session is not authenticated and Whatever happens next should go forward.

This could be setting link IDE keys, or TDISP RUN, it could loading a decryption secret into a drive, who knows.

But the thing that instantiated the SPDM session should get a hook callback op when userspace signals accept..

Jason

author maintainer
#10 lore raw

Re: [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted

In reply to Jiri Pirko
Quoted 7 lines · Jiri Pirko
Quoted 6 lines · Wed, Oct 28, 2026 at 04:26:10PM +0100, [email protected] wrote:

Use EVIDENCE_ACCEPT to enter secure operation after userspace has checked the evidence. Check the referenced device and evidence generations before calling the driver, and record acceptance on success.

Remove the separate DEVSEC_RUN command and callback.

Quoted 8 lines
Quoted 3 lines

Keep DEVSEC_STATE_GET so userspace can read both evidence acceptance and device state. Clearing acceptance does not undo the device state change. For example, with the following PCI/TSM patches:

Note that the published wip_attestation_all_1 baseline still has DEVSEC_RUN. I am planning this merge into EVIDENCE_ACCEPT for my next revision, which is not pushed yet.

I agree with your comments here and in the other emails regarding attestation. I will wait for the next revision of your attestation series. If you can accommodate these changes there, I can rework my patches accordingly.

Could you also add the ctlv-attestation-spdm family so that I can base the PCI/TSM changes on top of it?

-aneesh

#11 lore raw

Re: [RFC PATCH v1 03/11] attestation: Enter secure operation when evidence is accepted

Quoted 18 lines · Sat, Oct 31, 2026 at 05:09:30AM +0100, [email protected] wrote:
Quoted 7 lines · Jiri Pirko
Quoted 6 lines · Wed, Oct 28, 2026 at 04:26:10PM +0100, [email protected] wrote:

Use EVIDENCE_ACCEPT to enter secure operation after userspace has checked the evidence. Check the referenced device and evidence generations before calling the driver, and record acceptance on success.

Remove the separate DEVSEC_RUN command and callback.

Quoted 8 lines
Quoted 3 lines

Keep DEVSEC_STATE_GET so userspace can read both evidence acceptance and device state. Clearing acceptance does not undo the device state change. For example, with the following PCI/TSM patches:

Note that the published wip_attestation_all_1 baseline still has DEVSEC_RUN. I am planning this merge into EVIDENCE_ACCEPT for my next revision, which is not pushed yet.

Quoted 7 lines

I agree with your comments here and in the other emails regarding attestation. I will wait for the next revision of your attestation series. If you can accommodate these changes there, I can rework my patches accordingly.

Could you also add the ctlv-attestation-spdm family so that I can base the PCI/TSM changes on top of it?

Yep.

author maintainer
#12 lore raw

RFC PATCH 4/11 attestation: Bind object selection to accepted evidence snapshots

In reply to Aneesh Kumar K.V (Arm)

Follow-up PCI/TSM patches expose multiple populated SPDM certificate slots and let userspace choose which chain to authorize. Device and evidence generations identify an evidence snapshot, but cannot express which certificate chain userspace selected from that snapshot.

Allow EVIDENCE_ACCEPT to select a logical evidence object by class, format, and optional instance. For example, a request can name device generation D, evidence generation E, and CERT_CHAIN with format "spdm-cert-chain" and instance 3. Pass the resolved object to the provider so PCI/TSM can call pci_tsm_evidence_accept(pdev, 3), allowing the backend to authenticate the selected chain and establish streams using it.

Current object availability is not enough to authorize that choice. The selected object must also have been retrieved in the referenced evidence snapshot. Track the retrieved object set in evidence generation ranges and reject selectors absent from that snapshot:

  Retrieved in snapshot E  Selected object  Membership check
  -----------------------  ---------------  ----------------
  Certificate slot 0       Slot 0           Pass
  Certificate slot 0       Slot 3           -ESTALE
  Certificate slots 0, 3   Slot 3           Pass

Reject slot 3 in the second case even if the instance currently supports it. Per-instance object filtering checks availability; snapshot membership checks that the selected object was included in the referenced retrieval. Passing the membership check does not bypass the existing generation or provider checks.

Include the selected object in verdict idempotence checks. Otherwise, accepting slot 0 and then selecting slot 3 with the same device and evidence generations would incorrectly return success without calling the provider again. Let the provider handle the changed selection instead.

Advertise object selection through evidence capabilities and enable it in the simulator for regression coverage.

Cc Jiri Pirko <[email protected]>
Cc Kiryl Shutsemau <[email protected]>
Cc Rick Edgecombe <[email protected]>
Cc Dave Hansen <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  | 12 ++-
 .../ctlv/specs/ctlv-attestation.yaml          | 10 +++
 drivers/attestation/core/core.c               |  1 +
 drivers/attestation/core/ctlv.c               | 83 +++++++++++++++++--
 drivers/attestation/core/internal.h           |  2 +
 .../generated/ctlv_attestation_gen.c          |  8 +-
 .../generated/ctlv_attestation_gen.h          | 22 +++++
 drivers/attestation/providers/sim.c           |  5 +-
 drivers/attestation/tests/core_kunit.c        |  4 +-
 include/linux/attestation.h                   |  8 +-
 include/uapi/linux/ctlv_attestation_gen.h     |  9 +-
 .../ctlv_attestation_sim_user_gen.c           | 52 +++++++++++-
 .../ctlv_attestation_tdx_user_gen.c           | 52 +++++++++++-
 .../attestation/ctlv_attestation_user_gen.c   | 52 +++++++++++-
 .../attestation/ctlv_attestation_user_gen.h   | 18 +++-
 15 files changed, 314 insertions(+), 24 deletions(-)
Documentation/ctlv/abi/ctlv-attestation.yaml +10 -2
index 0220c2e131d4..018417fe3682 100644
@@ -22,7 +22,8 @@ flags:
   mr-device-caps: {read: 0, extend: 1, dynamic-topology: 2}
   mr-caps: {read: 0, extend: 1, removable: 2}
   mr-state-flags: {ima-active: 0}
-  evidence-caps: {evidence-accept: 0, mr-selection: 1, privlevel: 2, service: 3}
+  evidence-caps: {evidence-accept: 0, mr-selection: 1, privlevel: 2, service: 3,
+                  object-selection: 4}
 attr-groups:
   device:
     id: 1
@@ -69,6 +70,7 @@ attr-groups:
       service-guid: {id: 15, type: binary, length: 16}
       service-manifest-version: {id: 16, type: uint, max: 4294967295}
       options: {id: 17, type: nested}
+      accept-object: {id: 18, type: nested}
   mr:
     id: 4
     attrs:
@@ -145,6 +147,7 @@ attr-sets:
   mr-log-selection: {id: 30}
   mr-log-request: {id: 31}
   mr-log-reply: {id: 32}
+  evidence-object-selector: {id: 33}
 ops:
   device-get:
     abi: {id: 1, type: action, fd-access: read}
@@ -240,7 +243,12 @@ ops:
               - record.item: {attrs: [record.format, record.data]}
   evidence-accept:
     abi: {id: 9, type: action, availability: dynamic, optional: true}
-    request: {attrs: [device.generation, evidence.generation]}
+    request:
+      attrs: [device.generation, evidence.generation]
+      optional-attrs:
+        - evidence.accept-object:
+            attrs: [evidence.blob-class, evidence.format]
+            optional-attrs: [evidence.blob-instance]
   evidence-accept-clear:
     abi: {id: 10, type: action, availability: dynamic, optional: true}
   devsec-state-get:
Documentation/ctlv/specs/ctlv-attestation.yaml +10 -0
index 8494f716f9e9..98d6753d878f 100644
@@ -68,6 +68,7 @@ flags:
       - mr-selection
       - privlevel
       - service
+      - object-selection
 
 attr-groups:
   - name: device
@@ -175,6 +176,8 @@ attr-groups:
         max: 4294967295
       - name: options
         attrs: evidence-options
+      - name: accept-object
+        attrs: evidence-object-selector
 
   - name: mr
     attrs:
@@ -424,6 +427,7 @@ attr-sets:
     attrs:
       - device.generation
       - evidence.generation
+    optional-attrs: evidence.accept-object
 
   - name: devsec-state-reply
     attrs: device.generation
@@ -470,6 +474,12 @@ attr-sets:
       - mr.end-generation
       - log.entry
 
+  - name: evidence-object-selector
+    attrs:
+      - evidence.blob-class
+      - evidence.format
+    optional-attrs: evidence.blob-instance
+
 ops:
   - name: device-get
     type: action
drivers/attestation/core/core.c +1 -0
index a1de19dcab7d..914f29693aee 100644
@@ -248,6 +248,7 @@ void attestation_evidence_accept_clear_locked(struct attestation *att)
 		return;
 	att->provider->evidence_accept_clear(att);
 	att->verdict_recorded = false;
+	att->accepted_object = NULL;
 }
 
 static void attestation_evidence_ranges_clear(struct attestation *att, bool dependent_only)
drivers/attestation/core/ctlv.c +75 -8

183 lines not on this page · raw message

drivers/attestation/core/internal.h +2 -0
index 9e531e8483f7..7b444d61d996 100644
@@ -25,6 +25,7 @@ struct attestation_evidence_range {
 	unsigned int end_generation;
 	bool mr_dependent;
 	unsigned int mr_generation;
+	unsigned long objects[];
 };
 
 struct attestation_mr_log_entry {
@@ -82,6 +83,7 @@ struct attestation {
 	bool verdict_recorded;
 	unsigned int accepted_device_generation;
 	unsigned int accepted_evidence_generation;
+	const struct attestation_evidence_supported_object *accepted_object;
 	unsigned int mr_read_generation;
 	unsigned int devsec_version;
 
drivers/attestation/generated/ctlv_attestation_gen.c +6 -2
index 4afd0433e26f..a858a84da135 100644
@@ -193,9 +193,13 @@ static const struct ctlv_node_schema mr_log_entry_appended_reply_node_schemas[]
 };
 
 static const struct ctlv_node_schema evidence_accept_request_node_schemas[] = {
-	CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
+	CTLV_NODE_SCHEMA_ROOT(1, 3, 2),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_EVIDENCE_GENERATION, 1, 0, U32_MAX),
+	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT, 1, 4, 3, 2),
+	CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS, 1, 9),
+	CTLV_NODE_SCHEMA_STRING(CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT, 1, 1, 128),
+	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE, 1, 0, U32_MAX),
 };
 
 static const struct ctlv_node_schema evidence_accept_clear_request_node_schemas[] = {
@@ -286,7 +290,7 @@ const struct ctlv_op_schema ctlv_attestation_op_schemas[] = {
 	CTLV_OP_SCHEMA_EVENT(CTLV_ATTESTATION_OP_MR_LOG_ENTRY_APPENDED, 73416,
 			     CTLV_SCHEMA_TREE(mr_log_entry_appended_reply)),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT, CTLV_OP_FD_ACCESS_READ_WRITE,
-			      CTLV_OP_AVAILABILITY_DYNAMIC, 64, 0,
+			      CTLV_OP_AVAILABILITY_DYNAMIC, 280, 0,
 			      CTLV_SCHEMA_TREE(evidence_accept_request), CTLV_SCHEMA_TREE_NONE(),
 			      .optional = true),
 	CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_CLEAR,
drivers/attestation/generated/ctlv_attestation_gen.h +22 -0
index 12716a7fa7af..95cb6a083633 100644
@@ -225,6 +225,26 @@ CTLV_IN_REF(ctlv_attestation_evidence_accept_evidence_generation,
 	    1, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_evidence_accept_evidence_generation)
 CTLV_IN_U64(ctlv_attestation_evidence_accept_evidence_generation)
+CTLV_IN_REF(ctlv_attestation_evidence_accept_evidence_accept_object,
+	    &ctlv_attestation_op_schemas[8].request, CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT,
+	    0, 2, CTLV_SCHEMA_NEST)
+CTLV_IN_HAS(ctlv_attestation_evidence_accept_evidence_accept_object)
+CTLV_IN_NEST(ctlv_attestation_evidence_accept_evidence_accept_object)
+CTLV_IN_REF(ctlv_attestation_evidence_accept_accept_object_evidence_blob_class,
+	    &ctlv_attestation_op_schemas[8].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS, 3,
+	    0, CTLV_SCHEMA_ENUM)
+CTLV_IN_HAS(ctlv_attestation_evidence_accept_accept_object_evidence_blob_class)
+CTLV_IN_U64(ctlv_attestation_evidence_accept_accept_object_evidence_blob_class)
+CTLV_IN_REF(ctlv_attestation_evidence_accept_accept_object_evidence_format,
+	    &ctlv_attestation_op_schemas[8].request, CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT, 3, 1,
+	    CTLV_SCHEMA_STRING)
+CTLV_IN_HAS(ctlv_attestation_evidence_accept_accept_object_evidence_format)
+CTLV_IN_STRING(ctlv_attestation_evidence_accept_accept_object_evidence_format)
+CTLV_IN_REF(ctlv_attestation_evidence_accept_accept_object_evidence_blob_instance,
+	    &ctlv_attestation_op_schemas[8].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE,
+	    3, 2, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_evidence_accept_accept_object_evidence_blob_instance)
+CTLV_IN_U64(ctlv_attestation_evidence_accept_accept_object_evidence_blob_instance)
 CTLV_IN_REF(ctlv_attestation_mr_log_get_mr_selection, &ctlv_attestation_op_schemas[12].request,
 	    CTLV_ATTESTATION_ATTR_MR_SELECTION, 0, 0, CTLV_SCHEMA_NEST)
 #define ctlv_attestation_mr_log_get_mr_selection_for_each(_attr, _nest) \
@@ -302,6 +322,8 @@ CTLV_PUT_U64(ctlv_attestation_evidence_service_manifest_version,
 	     CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_MANIFEST_VERSION, CTLV_SCHEMA_UINT)
 CTLV_NEST_START(ctlv_attestation_evidence_options, CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS,
 		CTLV_SCHEMA_NEST)
+CTLV_NEST_START(ctlv_attestation_evidence_accept_object,
+		CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT, CTLV_SCHEMA_NEST)
 CTLV_PUT_U64(ctlv_attestation_mr_id, CTLV_ATTESTATION_ATTR_MR_ID, CTLV_SCHEMA_UINT)
 CTLV_PUT_STRING(ctlv_attestation_mr_name, CTLV_ATTESTATION_ATTR_MR_NAME, CTLV_SCHEMA_STRING)
 CTLV_PUT_U64(ctlv_attestation_mr_hash_algo, CTLV_ATTESTATION_ATTR_MR_HASH_ALGO, CTLV_SCHEMA_UINT)
drivers/attestation/providers/sim.c +4 -1
index 7c35b1611f10..4a541462dc7e 100644
@@ -179,6 +179,7 @@ static const struct attestation_evidence_caps attestation_sim_evidence_caps = {
 	.supports_mr_selection = true,
 	.supports_privlevel = true,
 	.supports_service_attestation = true,
+	.supports_object_accept = true,
 	.objects = attestation_sim_evidence_objects,
 	.objects_count = ARRAY_SIZE(attestation_sim_evidence_objects),
 };
@@ -570,7 +571,9 @@ attestation_sim_evidence_get(struct attestation *att,
 	return ret;
 }
 
-static int attestation_sim_evidence_accept(struct attestation *att)
+static int
+attestation_sim_evidence_accept(struct attestation *att,
+		const struct attestation_evidence_supported_object *object)
 {
 	struct attestation_sim *sim = attestation_sim_from_att(att);
 	int ret;
drivers/attestation/tests/core_kunit.c +3 -1
index 08cb5eabee1b..898f5b34d32f 100644
@@ -95,7 +95,9 @@ attestation_test_evidence_get(
 	return 0;
 }
 
-static int attestation_test_evidence_accept(struct attestation *att)
+static int
+attestation_test_evidence_accept(struct attestation *att,
+		const struct attestation_evidence_supported_object *object)
 {
 	return 0;
 }
include/linux/attestation.h +6 -2
index 9813361ddd29..09ebd2af3a1e 100644
@@ -154,6 +154,7 @@ struct attestation_evidence_supported_object {
  * @supports_mr_selection: requests may select MRs
  * @supports_privlevel: requests may select a privilege level
  * @supports_service_attestation: requests may identify a service
+ * @supports_object_accept: acceptance may select an object from its snapshot
  * @objects: supported evidence objects
  * @objects_count: number of objects
  */
@@ -164,6 +165,7 @@ struct attestation_evidence_caps {
 	bool supports_mr_selection;
 	bool supports_privlevel;
 	bool supports_service_attestation;
+	bool supports_object_accept;
 	const struct attestation_evidence_supported_object *objects;
 	size_t objects_count;
 };
@@ -235,7 +237,8 @@ struct attestation_evidence_result {
  * @evidence_object_supported: optionally restrict advertised and requested
  *	evidence objects per instance; called under the instance lock
  * @evidence_get: produce requested evidence
- * @evidence_accept: accept current evidence and enable secure operation
+ * @evidence_accept: accept evidence and enable secure operation; the optional
+ *		   object selector belongs to the referenced evidence snapshot
  * @evidence_accept_clear: clear a recorded acceptance
  * @mr_read: read one MR
  * @mr_extend: extend one MR
@@ -252,7 +255,8 @@ struct attestation_provider {
 	int (*evidence_get)(struct attestation *att,
 			    const struct attestation_evidence_request *request,
 			    struct attestation_evidence_result *result);
-	int (*evidence_accept)(struct attestation *att);
+	int (*evidence_accept)(struct attestation *att,
+		const struct attestation_evidence_supported_object *object);
 	void (*evidence_accept_clear)(struct attestation *att);
 	int (*mr_read)(struct attestation *att, unsigned int mr_id,
 		       const struct ctlv_cursor *mr_options,
include/uapi/linux/ctlv_attestation_gen.h +8 -1
index 627c6f5f9377..f993def4fe57 100644
@@ -106,6 +106,7 @@ enum _ctlv_attestation_evidence_caps_bit {
 	CTLV_ATTESTATION_EVIDENCE_CAPS_MR_SELECTION_BIT,
 	CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL_BIT,
 	CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE_BIT,
+	CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION_BIT,
 };
 
 #define CTLV_ATTESTATION_EVIDENCE_CAPS_EVIDENCE_ACCEPT \
@@ -115,11 +116,14 @@ enum _ctlv_attestation_evidence_caps_bit {
 #define CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL \
 	_BITULL(CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL_BIT)
 #define CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE _BITULL(CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE_BIT)
+#define CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION \
+	_BITULL(CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION_BIT)
 #define CTLV_ATTESTATION_EVIDENCE_CAPS_MASK \
 	(CTLV_ATTESTATION_EVIDENCE_CAPS_EVIDENCE_ACCEPT | \
 	 CTLV_ATTESTATION_EVIDENCE_CAPS_MR_SELECTION | \
 	 CTLV_ATTESTATION_EVIDENCE_CAPS_PRIVLEVEL | \
-	 CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE)
+	 CTLV_ATTESTATION_EVIDENCE_CAPS_SERVICE | \
+	 CTLV_ATTESTATION_EVIDENCE_CAPS_OBJECT_SELECTION)
 
 /* attr groups */
 enum _ctlv_attestation_group {
@@ -211,6 +215,7 @@ enum _ctlv_attestation_attr_evidence {
 	_CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_GUID,
 	_CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_MANIFEST_VERSION,
 	_CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS,
+	_CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT,
 };
 
 #define CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE _CTLV_ATTESTATION_ATTR_EVIDENCE(CHALLENGE)
@@ -234,6 +239,7 @@ enum _ctlv_attestation_attr_evidence {
 #define CTLV_ATTESTATION_ATTR_EVIDENCE_SERVICE_MANIFEST_VERSION \
 	_CTLV_ATTESTATION_ATTR_EVIDENCE(SERVICE_MANIFEST_VERSION)
 #define CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS _CTLV_ATTESTATION_ATTR_EVIDENCE(OPTIONS)
+#define CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT _CTLV_ATTESTATION_ATTR_EVIDENCE(ACCEPT_OBJECT)
 
 /* attrs of group mr */
 #define _CTLV_ATTESTATION_ATTR_MR(_attr) \
@@ -366,6 +372,7 @@ enum _ctlv_attestation_set {
 	CTLV_ATTESTATION_SET_MR_LOG_SELECTION,
 	CTLV_ATTESTATION_SET_MR_LOG_REQUEST,
 	CTLV_ATTESTATION_SET_MR_LOG_REPLY,
+	CTLV_ATTESTATION_SET_EVIDENCE_OBJECT_SELECTOR,
 };
 
 /* ops */
tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c +50 -2

82 lines not on this page · raw message

tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c +50 -2

82 lines not on this page · raw message

tools/testing/selftests/attestation/ctlv_attestation_user_gen.c +50 -2

82 lines not on this page · raw message

tools/testing/selftests/attestation/ctlv_attestation_user_gen.h +17 -1
index 67b8820b3efc..88d8ac5048d8 100644
@@ -115,7 +115,7 @@ static inline int ctlv_attestation_mr_extend(struct ctlv_dev *dev, struct ctlv_m
 	return ctlv_action(dev, CTLV_ATTESTATION_OP_MR_EXTEND, request, reply, error);
 }
 
-#define CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_REQUEST_MAX_LEN 64
+#define CTLV_ATTESTATION_OP_EVIDENCE_ACCEPT_REQUEST_MAX_LEN 280
 
 static inline int ctlv_attestation_evidence_accept_request_new(struct ctlv_msg **request)
 {
@@ -373,6 +373,11 @@ static inline int ctlv_attestation_evidence_options_start(struct ctlv_msg *msg)
 	return ctlv_nest_start(msg, CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS);
 }
 
+static inline int ctlv_attestation_evidence_accept_object_start(struct ctlv_msg *msg)
+{
+	return ctlv_nest_start(msg, CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT);
+}
+
 static inline int ctlv_attestation_mr_id_put(struct ctlv_msg *msg, __u64 value)
 {
 	return ctlv_put_u64(msg, CTLV_ATTESTATION_ATTR_MR_ID, value);
@@ -913,6 +918,17 @@ ctlv_attestation_evidence_options_get(const struct ctlv_attr *nest)
 	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_EVIDENCE_OPTIONS);
 }
 
+static inline bool ctlv_attestation_evidence_accept_object_present(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT);
+}
+
+static inline const struct ctlv_attr *
+ctlv_attestation_evidence_accept_object_get(const struct ctlv_attr *nest)
+{
+	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_EVIDENCE_ACCEPT_OBJECT);
+}
+
 static inline bool ctlv_attestation_mr_id_present(const struct ctlv_attr *nest)
 {
 	return ctlv_attr_find(nest, CTLV_ATTESTATION_ATTR_MR_ID);
Signature · 1 line
2.43.0
author maintainer
#13 lore raw

RFC PATCH 5/11 attestation: Make the evidence-get challenge attribute optional

In reply to Aneesh Kumar K.V (Arm)

The following PCI/TSM integration retrieves cached evidence through EVIDENCE_GET. A challenge is used when refreshing that evidence, not when reading the current cache, so requiring evidence.challenge forces callers to supply an otherwise unused empty attribute.

Make evidence.challenge optional in the common request schema.

Cc Jiri Pirko <[email protected]>
Cc Kiryl Shutsemau <[email protected]>
Cc Rick Edgecombe <[email protected]>
Cc Dave Hansen <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 Documentation/ctlv/abi/ctlv-attestation.yaml  |  2 +-
 .../ctlv/specs/ctlv-attestation.yaml          |  2 +-
 Documentation/driver-api/attestation.rst      | 12 ++++++++++
 drivers/attestation/core/ctlv.c               |  7 +++---
 .../generated/ctlv_attestation_gen.c          |  4 ++--
 .../generated/ctlv_attestation_gen.h          | 22 +++++++++----------
 .../selftests/attestation/attestation_test.c  |  4 +---
 .../ctlv_attestation_sim_user_gen.c           | 20 ++++++++---------
 .../ctlv_attestation_tdx_user_gen.c           | 20 ++++++++---------
 .../attestation/ctlv_attestation_user_gen.c   | 20 ++++++++---------
 .../attestation/tdx_attestation_test.c        |  9 ++++++--
 11 files changed, 69 insertions(+), 53 deletions(-)
Documentation/ctlv/abi/ctlv-attestation.yaml +1 -1
index 018417fe3682..373614f97021 100644
@@ -172,12 +172,12 @@ ops:
     abi: {id: 2, type: action, availability: dynamic, optional: true}
     request:
       attrs:
-        - evidence.challenge
         - evidence.object:
             attrs: [evidence.blob-class, evidence.format,
                     evidence.representation, evidence.data]
             optional-attrs: [evidence.blob-instance, evidence.hash-algo]
       optional-attrs:
+        - evidence.challenge
         - device.generation
         - evidence.selection: {attrs: [mr.id, evidence.hash-algo]}
         - evidence.privlevel
Documentation/ctlv/specs/ctlv-attestation.yaml +1 -1
index 98d6753d878f..e2371813ec25 100644
@@ -320,9 +320,9 @@ attr-sets:
 
   - name: evidence-request
     attrs:
-      - evidence.challenge
       - evidence.object
     optional-attrs:
+      - evidence.challenge
       - device.generation
       - evidence.selection
       - evidence.privlevel
Documentation/driver-api/attestation.rst +12 -0
index 06af8df23a46..d23b7c65d35b 100644
@@ -29,6 +29,18 @@ dynamic topology retains handles returned by ``attestation_mr_create()`` and
 passes each handle exactly once to ``attestation_mr_destroy()``.  Property
 storage remains valid for the handle's lifetime.
 
+Evidence retrieval
+==================
+
+``EVIDENCE_GET`` requests may omit ``evidence.challenge``. The core treats
+an omitted challenge as zero bytes and still enforces the provider's minimum
+and maximum challenge lengths. Providers that require a challenge, such as
+TDX, retain that requirement.
+
+Cached-evidence providers such as PCI/TSM can fetch the current evidence
+without a challenge. A challenge for fresh PCI/TSM evidence belongs to the
+separate evidence-refresh operation.
+
 Per-instance features
 =====================
 
drivers/attestation/core/ctlv.c +4 -3
index f0238c4c4cf2..50aa3cb84ddd 100644
@@ -350,13 +350,14 @@ static int attestation_evidence_request_decode(struct attestation *att, struct c
 					       struct ctlv_cursor *options)
 {
 	const struct attestation_evidence_caps *caps = att->provider->evidence_caps;
-	u32 challenge_length, guid_length;
+	u32 challenge_length = 0, guid_length;
 	const void *guid;
 	bool service;
 	int ret;
 
-	decoded->challenge =
-		ctlv_attestation_evidence_get_evidence_challenge_get(request, &challenge_length);
+	if (ctlv_attestation_evidence_get_evidence_challenge_present(request))
+		decoded->challenge = ctlv_attestation_evidence_get_evidence_challenge_get(
+			request, &challenge_length);
 	decoded->challenge_length = challenge_length;
 	if (challenge_length < caps->min_challenge_length ||
 	    challenge_length > caps->max_challenge_length)
drivers/attestation/generated/ctlv_attestation_gen.c +2 -2
index a858a84da135..0bd4bc76b718 100644
@@ -49,9 +49,9 @@ static const struct ctlv_stub_schema device_get_reply_stub_schemas[] = {
 };
 
 static const struct ctlv_node_schema evidence_get_request_node_schemas[] = {
-	CTLV_NODE_SCHEMA_ROOT(1, 9, 2),
-	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE, 1, 0, 4096),
+	CTLV_NODE_SCHEMA_ROOT(1, 9, 1),
 	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT, 16, 10, 6, 4),
+	CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE, 1, 0, 4096),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 1, 0, U32_MAX),
 	CTLV_NODE_SCHEMA_NEST(CTLV_ATTESTATION_ATTR_EVIDENCE_SELECTION, 256, 16, 2, 2),
 	CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_ATTR_EVIDENCE_PRIVLEVEL, 1, 0, U32_MAX),
drivers/attestation/generated/ctlv_attestation_gen.h +11 -11
index 95cb6a083633..bd55158d8819 100644
@@ -81,15 +81,15 @@ CTLV_OP_DISABLE(ctlv_attestation_devsec_state_get, CTLV_ATTESTATION_OP_DEVSEC_ST
 CTLV_OP_PROVIDE(ctlv_attestation_mr_log_get, CTLV_ATTESTATION_OP_MR_LOG_GET)
 
 /* Reading a validated message. A blob has a resolver instead of a getter. */
+CTLV_IN_REF(ctlv_attestation_evidence_get_evidence_object, &ctlv_attestation_op_schemas[1].request,
+	    CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT, 0, 0, CTLV_SCHEMA_NEST)
+#define ctlv_attestation_evidence_get_evidence_object_for_each(_attr, _nest) \
+	CTLV_IN_FOR_EACH(ctlv_attestation_evidence_get_evidence_object, _attr, _nest)
 CTLV_IN_REF(ctlv_attestation_evidence_get_evidence_challenge,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE, 0, 0,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE, 0, 1,
 	    CTLV_SCHEMA_BINARY)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_evidence_challenge)
 CTLV_IN_BINARY(ctlv_attestation_evidence_get_evidence_challenge)
-CTLV_IN_REF(ctlv_attestation_evidence_get_evidence_object, &ctlv_attestation_op_schemas[1].request,
-	    CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT, 0, 1, CTLV_SCHEMA_NEST)
-#define ctlv_attestation_evidence_get_evidence_object_for_each(_attr, _nest) \
-	CTLV_IN_FOR_EACH(ctlv_attestation_evidence_get_evidence_object, _attr, _nest)
 CTLV_IN_REF(ctlv_attestation_evidence_get_device_generation,
 	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_DEVICE_GENERATION, 0, 2,
 	    CTLV_SCHEMA_UINT)
@@ -125,32 +125,32 @@ CTLV_IN_REF(ctlv_attestation_evidence_get_evidence_options, &ctlv_attestation_op
 CTLV_IN_HAS(ctlv_attestation_evidence_get_evidence_options)
 CTLV_IN_NEST(ctlv_attestation_evidence_get_evidence_options)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_blob_class,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS, 2,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_CLASS, 1,
 	    0, CTLV_SCHEMA_ENUM)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_blob_class)
 CTLV_IN_U64(ctlv_attestation_evidence_get_object_evidence_blob_class)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_format,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT, 2, 1,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_FORMAT, 1, 1,
 	    CTLV_SCHEMA_STRING)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_format)
 CTLV_IN_STRING(ctlv_attestation_evidence_get_object_evidence_format)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_representation,
 	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_REPRESENTATION,
-	    2, 2, CTLV_SCHEMA_ENUM)
+	    1, 2, CTLV_SCHEMA_ENUM)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_representation)
 CTLV_IN_U64(ctlv_attestation_evidence_get_object_evidence_representation)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_data,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_DATA, 2, 3,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_DATA, 1, 3,
 	    CTLV_SCHEMA_BLOB)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_data)
 CTLV_IN_BLOB(ctlv_attestation_evidence_get_object_evidence_data)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_blob_instance,
 	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_BLOB_INSTANCE,
-	    2, 4, CTLV_SCHEMA_UINT)
+	    1, 4, CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_blob_instance)
 CTLV_IN_U64(ctlv_attestation_evidence_get_object_evidence_blob_instance)
 CTLV_IN_REF(ctlv_attestation_evidence_get_object_evidence_hash_algo,
-	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_HASH_ALGO, 2, 5,
+	    &ctlv_attestation_op_schemas[1].request, CTLV_ATTESTATION_ATTR_EVIDENCE_HASH_ALGO, 1, 5,
 	    CTLV_SCHEMA_UINT)
 CTLV_IN_HAS(ctlv_attestation_evidence_get_object_evidence_hash_algo)
 CTLV_IN_U64(ctlv_attestation_evidence_get_object_evidence_hash_algo)
tools/testing/selftests/attestation/attestation_test.c +1 -3
index bd831b0ef14f..40be7fd0c492 100644
@@ -374,15 +374,13 @@ TEST_F(attestation, extension_record_log_and_event)
 static int evidence_get(struct ctlv_dev *dev, void *output, size_t output_length,
 			struct ctlv_reply **reply, struct ctlv_error *error)
 {
-	static const unsigned char challenge[] = "freshness";
 	struct ctlv_msg *request;
 	int ret;
 
 	ret = ctlv_attestation_evidence_get_request_new(&request);
 	if (ret)
 		return ret;
-	ctlv_attestation_evidence_challenge_put(
-		request, challenge, sizeof(challenge) - 1);
+	/* Providers with a zero minimum accept an omitted challenge. */
 	ctlv_attestation_evidence_object_start(request);
 	ctlv_attestation_evidence_blob_class_put(
 		request, CTLV_ATTESTATION_EVIDENCE_BLOB_CLASS_PRIMARY);
tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c +10 -10
index 795cf514c761..89b7db4df55b 100644
@@ -279,14 +279,6 @@ static const struct ctlv_node_schema evidence_get_request_selection_attr_node_sc
 
 /* root, attrs */
 static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
-		.kind = CTLV_SCHEMA_BINARY,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
-		.min_length = 0,
-		.max_length = 4096,
-	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT,
 		.kind = CTLV_SCHEMA_NEST,
@@ -300,6 +292,14 @@ static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] =
 
 /* root, optional attrs */
 static const struct ctlv_node_schema evidence_get_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
 		.kind = CTLV_SCHEMA_UINT,
@@ -359,9 +359,9 @@ static const struct ctlv_node_schema evidence_get_request_node_schema = {
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_get_request_attr_node_schemas,
-	.attrs_count = 2,
+	.attrs_count = 1,
 	.optional_attrs = evidence_get_request_optional_attr_node_schemas,
-	.optional_attrs_count = 7,
+	.optional_attrs_count = 8,
 };
 
 /* root, attrs */
tools/testing/selftests/attestation/ctlv_attestation_tdx_user_gen.c +10 -10
index 787edd7d8ce4..cdf54af49a81 100644
@@ -295,14 +295,6 @@ static const struct ctlv_node_schema evidence_get_request_selection_attr_node_sc
 
 /* root, attrs */
 static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
-		.kind = CTLV_SCHEMA_BINARY,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
-		.min_length = 0,
-		.max_length = 4096,
-	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT,
 		.kind = CTLV_SCHEMA_NEST,
@@ -316,6 +308,14 @@ static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] =
 
 /* root, optional attrs */
 static const struct ctlv_node_schema evidence_get_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
 		.kind = CTLV_SCHEMA_UINT,
@@ -375,9 +375,9 @@ static const struct ctlv_node_schema evidence_get_request_node_schema = {
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_get_request_attr_node_schemas,
-	.attrs_count = 2,
+	.attrs_count = 1,
 	.optional_attrs = evidence_get_request_optional_attr_node_schemas,
-	.optional_attrs_count = 7,
+	.optional_attrs_count = 8,
 };
 
 /* root, attrs */
tools/testing/selftests/attestation/ctlv_attestation_user_gen.c +10 -10
index b278511f5776..077f8c7fbc81 100644
@@ -265,14 +265,6 @@ static const struct ctlv_node_schema evidence_get_request_selection_attr_node_sc
 
 /* root, attrs */
 static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] = {
-	{
-		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
-		.kind = CTLV_SCHEMA_BINARY,
-		.max_occurrences = 1,
-		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
-		.min_length = 0,
-		.max_length = 4096,
-	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_OBJECT,
 		.kind = CTLV_SCHEMA_NEST,
@@ -286,6 +278,14 @@ static const struct ctlv_node_schema evidence_get_request_attr_node_schemas[] =
 
 /* root, optional attrs */
 static const struct ctlv_node_schema evidence_get_request_optional_attr_node_schemas[] = {
+	{
+		.attr_id = CTLV_ATTESTATION_ATTR_EVIDENCE_CHALLENGE,
+		.kind = CTLV_SCHEMA_BINARY,
+		.max_occurrences = 1,
+		.present = CTLV_NODE_SCHEMA_HAS_MIN_LENGTH | CTLV_NODE_SCHEMA_HAS_MAX_LENGTH,
+		.min_length = 0,
+		.max_length = 4096,
+	},
 	{
 		.attr_id = CTLV_ATTESTATION_ATTR_DEVICE_GENERATION,
 		.kind = CTLV_SCHEMA_UINT,
@@ -345,9 +345,9 @@ static const struct ctlv_node_schema evidence_get_request_node_schema = {
 	.kind = CTLV_SCHEMA_NEST,
 	.max_occurrences = 1,
 	.attrs = evidence_get_request_attr_node_schemas,
-	.attrs_count = 2,
+	.attrs_count = 1,
 	.optional_attrs = evidence_get_request_optional_attr_node_schemas,
-	.optional_attrs_count = 7,
+	.optional_attrs_count = 8,
 };
 
 /* root, attrs */
tools/testing/selftests/attestation/tdx_attestation_test.c +7 -2
index a06315af2021..1a370a529f50 100644
@@ -165,8 +165,9 @@ static int tdx_evidence_get(struct ctlv_dev *dev, const char *format,
 	ret = ctlv_attestation_evidence_get_request_new(&request);
 	if (ret)
 		return ret;
-	ctlv_attestation_evidence_challenge_put(request, challenge,
-						challenge_length);
+	if (challenge)
+		ctlv_attestation_evidence_challenge_put(request, challenge,
+							challenge_length);
 	ctlv_attestation_evidence_object_start(request);
 	ctlv_attestation_evidence_blob_class_put(request,
 						 CTLV_ATTESTATION_EVIDENCE_BLOB_CLASS_PRIMARY);
@@ -244,6 +245,10 @@ TEST_F(tdx_attestation, common_validation_precedes_native_calls)
 	struct ctlv_reply *reply;
 	struct ctlv_msg *request;
 
+	EXPECT_EQ(-EINVAL,
+		  tdx_evidence_get(self->dev, "tdx-tdreport-1.0", NULL, 0,
+				   report.bytes, sizeof(report.bytes), &reply,
+				   &self->error));
 	EXPECT_EQ(-EINVAL,
 		  tdx_evidence_get(self->dev, "tdx-tdreport-1.0", challenge,
 				   sizeof(challenge) - 1, report.bytes,
Signature · 1 line
2.43.0
author maintainer
#14 lore raw

RFC PATCH 6/11 PCI/TSM: Describe the PCI attestation control family

In reply to Aneesh Kumar K.V (Arm)

Register ctlv-attestation-pci as a child of the common attestation family. Add PCI state reporting and an object-selecting evidence refresh operation with an optional challenge. Keep connect, disconnect, lock and unlock in sysfs; IDE setup and TDISP run transition is driven by evidence acceptance.

Cc Jiri Pirko <[email protected]>
Cc Dan Williams <[email protected]>
Cc Bjorn Helgaas <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 .../ctlv/abi/ctlv-attestation-pci.yaml        |   43 +
 Documentation/ctlv/abi/families.registry.yaml |    1 +
 Documentation/ctlv/families.yaml              |    1 +
 Documentation/ctlv/generated-files.yaml       |   11 +
 .../ctlv/specs/ctlv-attestation-pci.yaml      |   79 +
 drivers/pci/tsm/ctlv_attestation_pci_gen.c    |  111 ++
 drivers/pci/tsm/ctlv_attestation_pci_gen.h    |  100 +
 include/uapi/linux/ctlv_attestation_pci_gen.h |   74 +
 include/uapi/linux/ctlv_families_gen.h        |    1 +
 tools/ctlv/tests/support.py                   |    3 +-
 tools/ctlv/tests/test_outputs.py              |    6 +-
 tools/ctlv/tests/test_semantics.py            |   38 +
 tools/ctlv/tests/test_spec.py                 |    1 +
 .../ctlv_attestation_pci_user_gen.c           | 1697 +++++++++++++++++
 .../ctlv_attestation_pci_user_gen.h           |  230 +++
 15 files changed, 2392 insertions(+), 4 deletions(-)
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation-pci.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation-pci.yaml
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.c
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.h
 create mode 100644 include/uapi/linux/ctlv_attestation_pci_gen.h
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h
Documentation/ctlv/abi/ctlv-attestation-pci.yaml new file +43 -0
new file mode 100644
index 000000000000..04fe5c0ab8fa
@@ -0,0 +1,43 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+# Checked-in ABI snapshot of the ctlv-attestation-pci family, written by
+# "make -C tools/ctlv abi-update".
+#
+# The checker compares this family's declarations and inherited stub fills
+# against this file. Updating it is how a UAPI change gets reviewed; an update
+# that removes or renumbers anything, narrows a bound, or makes a placement
+# required is an ABI break, whatever this file says afterwards.
+family: {name: ctlv-attestation-pci, id: 6, inherits: ctlv-attestation,
+         pre-init: true}
+attr-groups:
+  pci:
+    id: 1
+    attrs:
+      name: {id: 1, type: string, min-length: 1, max-length: 31}
+      generation: {id: 2, type: uint, max: 4294967295}
+      tsm-id: {id: 3, type: uint, max: 2147483647}
+      connected: {id: 4, type: uint, max: 1}
+      locked: {id: 5, type: uint, max: 1}
+      accepted: {id: 6, type: uint, max: 1}
+      dsm: {id: 7, type: string, min-length: 1, max-length: 31}
+      object: {id: 8, enum: evidence-blob-class}
+      challenge: {id: 9, type: binary, length: 32}
+attr-sets:
+  change-reply: {id: 1}
+  state-reply: {id: 2}
+  refresh-request: {id: 3}
+fills:
+  device-info: {attrs: [pci.name]}
+ops:
+  state-get:
+    abi: {id: 1, type: action, fd-access: read}
+    reply:
+      attrs: [pci.generation, pci.connected, pci.locked, pci.accepted]
+      optional-attrs: [pci.tsm-id, pci.dsm]
+  evidence-refresh:
+    abi: {id: 2, type: action, availability: dynamic, optional: true}
+    request:
+      attrs: [pci.generation, pci.object]
+      optional-attrs: [pci.challenge]
+    reply: {attrs: [pci.generation]}
Documentation/ctlv/abi/families.registry.yaml +1 -0
index 7cdae176f5b4..a1a0836cef7e 100644
@@ -10,3 +10,4 @@ families:
   - ctlv-test
   - ctlv-test-child
   - ctlv-attestation-sim
+  - ctlv-attestation-pci
Documentation/ctlv/families.yaml +1 -0
index db8b5185af11..8c1efa5e66bb 100644
@@ -16,3 +16,4 @@ families:
   - ctlv-test
   - ctlv-test-child
   - ctlv-attestation-sim
+  - ctlv-attestation-pci
Documentation/ctlv/generated-files.yaml +11 -0
index 3e6cda5fd91f..505e1db064e1 100644
@@ -67,3 +67,14 @@ families:
         tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.h
       user-source: >-
         tools/testing/selftests/attestation/ctlv_attestation_sim_user_gen.c
+
+  - name: ctlv-attestation-pci
+    source: Documentation/ctlv/specs/ctlv-attestation-pci.yaml
+    outputs:
+      uapi-header: include/uapi/linux/ctlv_attestation_pci_gen.h
+      kernel-header: drivers/pci/tsm/ctlv_attestation_pci_gen.h
+      kernel-source: drivers/pci/tsm/ctlv_attestation_pci_gen.c
+      user-header: >-
+        tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h
+      user-source: >-
+        tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +79 -0

79 lines not on this page · raw message

drivers/pci/tsm/ctlv_attestation_pci_gen.c new file +111 -0

111 lines not on this page · raw message

drivers/pci/tsm/ctlv_attestation_pci_gen.h new file +100 -0

100 lines not on this page · raw message

include/uapi/linux/ctlv_attestation_pci_gen.h new file +74 -0

74 lines not on this page · raw message

include/uapi/linux/ctlv_families_gen.h +1 -0
index 19c478660429..bad143ec85fd 100644
@@ -15,6 +15,7 @@ enum _ctlv_family {
 	CTLV_FAMILY_TEST,
 	CTLV_FAMILY_TEST_CHILD,
 	CTLV_FAMILY_ATTESTATION_SIM,
+	CTLV_FAMILY_ATTESTATION_PCI,
 };
 
 #endif /* _UAPI_LINUX_CTLV_FAMILIES_GEN_H */
tools/ctlv/tests/support.py +2 -1
index c50bb6b5b831..ddddefe48d4c 100644
@@ -31,7 +31,8 @@ FIXTURE_REGISTRY = TESTS_DIR / 'families.yaml'
 
 # Every registered family, in registry order.
 FAMILIES = ('ctlv', 'ctlv-attestation', 'ctlv-attestation-tdx',
-            'ctlv-test', 'ctlv-test-child', 'ctlv-attestation-sim')
+            'ctlv-test', 'ctlv-test-child', 'ctlv-attestation-sim',
+            'ctlv-attestation-pci')
 
 # The fixture families, in fixture registry order: a parent holding one of
 # every shape the language allows, and a child that inherits it.
tools/ctlv/tests/test_outputs.py +3 -3
index 5be773189ae1..c2443b2963db 100644
@@ -67,9 +67,9 @@ class TestGeneratedOutputs(unittest.TestCase):
     def test_manifest_is_complete_and_unique(self):
         plan = outputs.load(self.paths)
 
-        self.assertEqual(37, len(plan))
-        self.assertEqual(37, len({item.path for item in plan}))
-        self.assertEqual(37, len({(item.source, item.flavour)
+        self.assertEqual(42, len(plan))
+        self.assertEqual(42, len({item.path for item in plan}))
+        self.assertEqual(42, len({(item.source, item.flavour)
                                  for item in plan}))
         registry_outputs = [item for item in plan if item.family is None]
         self.assertEqual(
tools/ctlv/tests/test_semantics.py +38 -0
index 70f0a99f44f6..c0fb0b336db7 100644
@@ -576,6 +576,44 @@ class TestRecursion(unittest.TestCase):
                                  'the same members, node for node')
 
 
+class TestPciAttestationPolicy(unittest.TestCase):
+    def test_connection_controls_are_not_in_ctlv(self):
+        family = support.load('ctlv-attestation-pci')
+        names = {op.name for op in family.ops}
+        self.assertNotIn('connect', names)
+        self.assertNotIn('disconnect', names)
+        self.assertNotIn('lock', names)
+        self.assertNotIn('unlock', names)
+        self.assertNotIn('devsec-run', names)
+
+    def test_backend_dependent_operations_are_dynamic(self):
+        family = support.load('ctlv-attestation-pci')
+        for name in ('evidence-refresh', 'evidence-get', 'evidence-accept',
+                     'evidence-accept-clear', 'devsec-state-get'):
+            with self.subTest(op=name):
+                op = support.op(family, name)
+                self.assertEqual('dynamic', op.availability)
+                self.assertTrue(op.optional)
+
+    def test_refresh_selects_an_object_with_an_optional_challenge(self):
+        family = support.load('ctlv-attestation-pci')
+        request = support.op(family, 'evidence-refresh').request
+        target = support.placement(request, 'pci.object')
+        challenge = support.placement(request, 'pci.challenge')
+        self.assertTrue(target.required)
+        self.assertFalse(challenge.required)
+        self.assertEqual(family.attrs['evidence.blob-class'].decl.ref,
+                         target.attr.decl.ref)
+
+    def test_state_operations_are_fixed(self):
+        family = support.load('ctlv-attestation-pci')
+        for name in ('state-get', 'device-get'):
+            with self.subTest(op=name):
+                op = support.op(family, name)
+                self.assertEqual('fixed', op.availability)
+                self.assertFalse(op.optional)
+
+
 class TestDump(unittest.TestCase):
     def test_repeated_renders_are_identical(self):
         for name in support.ALL_FAMILIES:
tools/ctlv/tests/test_spec.py +1 -0
index d80b16673675..248db73b044d 100644
@@ -347,6 +347,7 @@ class TestRegistry(unittest.TestCase):
         self.assertEqual(3, registry.family_id('ctlv-test'))
         self.assertEqual(4, registry.family_id('ctlv-test-child'))
         self.assertEqual(5, registry.family_id('ctlv-attestation-sim'))
+        self.assertEqual(6, registry.family_id('ctlv-attestation-pci'))
 
     def test_the_fixture_registry_numbers_the_fixture_families(self):
         registry = spec.load_registry(
tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c new file +1697 -0

1,697 lines not on this page · raw message

tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h new file +230 -0

230 lines not on this page · raw message

Signature · 1 line
2.43.0
#15 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:

[..]

Quoted 67 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +61 -0
new file mode 100644
index 000000000000..edb35cede0c2
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+family:
+  name: ctlv-attestation-pci
+  inherits: ctlv-attestation
+  pre-init: true
+
+attr-groups:
+  - name: pci
+    attrs:
+      - name: name
+        type: string
+        min-length: 1
+        max-length: 31
+      - name: generation
+        type: uint
+        max: 4294967295
+      - name: tsm-id
+        type: uint
+        max: 2147483647
+      - name: connected
+        type: uint
+        max: 1
+      - name: locked
+        type: uint
+        max: 1
+      - name: accepted
+        type: uint
+        max: 1
+      - name: dsm
+        type: string
+        min-length: 1
+        max-length: 31
+
+      - name: object
+        enum: evidence-blob-class
+      - name: challenge
+        type: binary
+        min-length: 32
+        max-length: 32
+
+attr-sets:
+  - name: device-info
+    attrs: pci.name
+  - name: change-reply
+    attrs: pci.generation
+  - name: state-reply
+    attrs:
+      - pci.generation
+      - pci.connected
+      - pci.locked
+      - pci.accepted
+    optional-attrs:
+      - pci.tsm-id
+      - pci.dsm
+
+  - name: refresh-request
+    attrs:
+      - pci.generation
+      - pci.object

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 71

Quoted 10 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +10 -0
+    optional-attrs:
+      - pci.challenge
+
+ops:
+  - name: state-get
+    type: action
+    fd-access: read
+    reply:
+      attrs: state-reply
+  - name: evidence-refresh

I'm missing why exactly you need this op. Can't you do whatever is needed in the provider evidence_get implementation? Why it needs to be 2 step.

Also, why do you think this is provider-specific op?

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 79

Quoted 8 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +8 -0
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      attrs: refresh-request
+    reply:
+      attrs: change-reply

This differs from what I had in mind: a transport-neutral ctlv-attestation-spdm family with SPDM_GET and SPDM_AUTHENTICATE.

SPDM_GET would report discovery BaseHash, discovered slots and their certificate digests, authentication state, and the effective authentication slot. SPDM_AUTHENTICATE would take device-generation and the selected slot. EVIDENCE_GET would retrieve CERT_CHAIN with blob-instance=slot without selecting the authentication slot.

That discovery view is not present in the posted schema; STATE_GET does not provide it.

Would this split work here? It would also allow non-PCI SPDM adapters to share the contract. I understand that multi-slot discovery is deferred, but a slot-zero implementation could still use this family and operation layout.

[..]

maintainer
#16 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

Quoted 10 lines · Jiri Pirko

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 79

Quoted 8 lines · Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +8 -0
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      attrs: refresh-request
+    reply:
+      attrs: change-reply
Quoted 2 lines

This differs from what I had in mind: a transport-neutral ctlv-attestation-spdm family with SPDM_GET and SPDM_AUTHENTICATE.

Yes, we should try very hard to keep all the SPDM stuff as transport neutral as possible.

Quoted 5 lines

SPDM_GET would report discovery BaseHash, discovered slots and their certificate digests, authentication state, and the effective authentication slot. SPDM_AUTHENTICATE would take device-generation and the selected slot. EVIDENCE_GET would retrieve CERT_CHAIN with blob-instance=slot without selecting the authentication slot.

Do you have a mapping to ARM RMM operations already you can share with Aneesh?

Like I said in the other email I imagine a pre-discovery phase to learn the slot number (with rustspdm), a connecting phase using the slot number, then a post connect authentication to inspect the certificate chain and evidence.

The certificate chain should be available at the first phase and the subsequent stages should not allow it to change, ideally..

Jason

#17 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

Quoted 17 lines · Fri, Oct 30, 2026 at 04:43:53PM +0100, [email protected] wrote:
Quoted 10 lines · Jiri Pirko

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 79

Quoted 8 lines · Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +8 -0
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      attrs: refresh-request
+    reply:
+      attrs: change-reply
Quoted 2 lines

This differs from what I had in mind: a transport-neutral ctlv-attestation-spdm family with SPDM_GET and SPDM_AUTHENTICATE.

Yes, we should try very hard to keep all the SPDM stuff as transport neutral as possible.

Quoted 8 lines
Quoted 5 lines

SPDM_GET would report discovery BaseHash, discovered slots and their certificate digests, authentication state, and the effective authentication slot. SPDM_AUTHENTICATE would take device-generation and the selected slot. EVIDENCE_GET would retrieve CERT_CHAIN with blob-instance=slot without selecting the authentication slot.

Do you have a mapping to ARM RMM operations already you can share with Aneesh?

Yes. For SPDM-capable PCIe endpoints, this is the mapping I figured out with LLM, using the existing RMM 2.0-bet3 interface:

sysfs connect (PCI/TSM and kernel SPDM requester):
Perform preliminary negotiation and GET_DIGESTS/GET_CERTIFICATE. Retain the discovery snapshot and destroy the temporary requester. Do not create the endpoint PDEV yet.

SPDM_GET (attestation provider):
Return the retained discovery information and connection state. There is no corresponding RMI discovery command.

EVIDENCE_GET(CERT_CHAIN, blob-instance=N):
Return the retained DER certificate chain for slot N.

SPDM_AUTHENTICATE(device-generation, slot):
Validate the generation and selected slot.
RMI_PDEV_CREATE(id_index=slot)
RMI_PDEV_COMMUNICATE until PDEV_NEEDS_KEY

    -> RMM performs fresh negotiation and retrieves the selected chain
    -> CCA host driver relays requests over DOE and caches the chain
  Compare its DER certificates with the selected discovery identity.
    -> reject a mismatch before supplying the key

Extract the leaf key; RMI_PDEV_SET_PUBKEY -> PDEV_HAS_KEY RMI_PDEV_COMMUNICATE until PDEV_READY

Prepare the root-port PDEV to READY and configure PCI IDE resources. RMI_PDEV_STREAM_CONNECT
RMI_PDEV_COMMUNICATE on both PDEVs, honoring STREAM_WAIT RMI_PDEV_STREAM_COMPLETE
Enable the endpoint IDE stream.
Only then publish authenticated state and the new device-generation.

[..]

maintainer
#18 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

Quoted 24 lines · Jiri Pirko
Quoted 23 lines · Fri, Oct 30, 2026 at 04:43:53PM +0100, [email protected] wrote:
Quoted 10 lines · Jiri Pirko

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 79

Quoted 8 lines · Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +8 -0
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      attrs: refresh-request
+    reply:
+      attrs: change-reply
Quoted 2 lines

This differs from what I had in mind: a transport-neutral ctlv-attestation-spdm family with SPDM_GET and SPDM_AUTHENTICATE.

Yes, we should try very hard to keep all the SPDM stuff as transport neutral as possible.

Quoted 5 lines

SPDM_GET would report discovery BaseHash, discovered slots and their certificate digests, authentication state, and the effective authentication slot. SPDM_AUTHENTICATE would take device-generation and the selected slot. EVIDENCE_GET would retrieve CERT_CHAIN with blob-instance=slot without selecting the authentication slot.

Quoted 5 lines
Quoted 2 lines

Do you have a mapping to ARM RMM operations already you can share with Aneesh?

Yes. For SPDM-capable PCIe endpoints, this is the mapping I figured out with LLM, using the existing RMM 2.0-bet3 interface:

Lets talk about this in this thread:

https://lore.kernel.org/all/[email protected]/

Quoted 4 lines

sysfs connect (PCI/TSM and kernel SPDM requester):
Perform preliminary negotiation and GET_DIGESTS/GET_CERTIFICATE. Retain the discovery snapshot and destroy the temporary requester. Do not create the endpoint PDEV yet.

If we want to do what I called option a, with user space repeat, then we need a CTLV command to start the flow, not sysfs.

I have been thinking sysfs should only create the attestation end point and not do any RMM operations

Quoted 6 lines

SPDM_GET (attestation provider):
Return the retained discovery information and connection state. There is no corresponding RMI discovery command.

EVIDENCE_GET(CERT_CHAIN, blob-instance=N):
Return the retained DER certificate chain for slot N.

I think these two are my #2

Quoted 3 lines

SPDM_AUTHENTICATE(device-generation, slot):
Validate the generation and selected slot.
RMI_PDEV_CREATE(id_index=slot)

Maybe PDEV_CREATE is in my #1?

Quoted 7 lines

RMI_PDEV_COMMUNICATE until PDEV_NEEDS_KEY

    -> RMM performs fresh negotiation and retrieves the selected chain
    -> CCA host driver relays requests over DOE and caches the chain
  Compare its DER certificates with the selected discovery identity.
    -> reject a mismatch before supplying the key

Extract the leaf key; RMI_PDEV_SET_PUBKEY -> PDEV_HAS_KEY RMI_PDEV_COMMUNICATE until PDEV_READY

This is my #3

Quoted 6 lines

Prepare the root-port PDEV to READY and configure PCI IDE resources. RMI_PDEV_STREAM_CONNECT
RMI_PDEV_COMMUNICATE on both PDEVs, honoring STREAM_WAIT RMI_PDEV_STREAM_COMPLETE
Enable the endpoint IDE stream.
Only then publish authenticated state and the new device-generation.

Oh is IDE under the control of the OS for RMM? I hadn't read that far ahead. Then it should be done as part of the "ACCEPT" step #5

ie the same place we would do RUN

Jason

#19 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

Quoted 40 lines · Tue, Nov 03, 2026 at 04:45:39PM +0100, [email protected] wrote:
Quoted 24 lines · Jiri Pirko
Quoted 23 lines · Fri, Oct 30, 2026 at 04:43:53PM +0100, [email protected] wrote:
Quoted 10 lines · Jiri Pirko

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 79

Quoted 8 lines · Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +8 -0
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      attrs: refresh-request
+    reply:
+      attrs: change-reply
Quoted 2 lines

This differs from what I had in mind: a transport-neutral ctlv-attestation-spdm family with SPDM_GET and SPDM_AUTHENTICATE.

Yes, we should try very hard to keep all the SPDM stuff as transport neutral as possible.

Quoted 5 lines

SPDM_GET would report discovery BaseHash, discovered slots and their certificate digests, authentication state, and the effective authentication slot. SPDM_AUTHENTICATE would take device-generation and the selected slot. EVIDENCE_GET would retrieve CERT_CHAIN with blob-instance=slot without selecting the authentication slot.

Quoted 5 lines
Quoted 2 lines

Do you have a mapping to ARM RMM operations already you can share with Aneesh?

Yes. For SPDM-capable PCIe endpoints, this is the mapping I figured out with LLM, using the existing RMM 2.0-bet3 interface:

Lets talk about this in this thread:

https://lore.kernel.org/all/[email protected]/

Quoted 4 lines

sysfs connect (PCI/TSM and kernel SPDM requester):
Perform preliminary negotiation and GET_DIGESTS/GET_CERTIFICATE. Retain the discovery snapshot and destroy the temporary requester. Do not create the endpoint PDEV yet.

Quoted 5 lines

If we want to do what I called option a, with user space repeat, then we need a CTLV command to start the flow, not sysfs.

I have been thinking sysfs should only create the attestation end point and not do any RMM operations

Okay, sysfs connect can just associate the TSM and create the attestation instance. Starting/restarting the protocol would be driven through CTLV, Is SPDM_GET op the one you have in mind, or something additional done before it?

Quoted 8 lines
Quoted 7 lines

SPDM_GET (attestation provider):
Return the retained discovery information and connection state. There is no corresponding RMI discovery command.

EVIDENCE_GET(CERT_CHAIN, blob-instance=N):
Return the retained DER certificate chain for slot N.

Quoted 7 lines

I think these two are my #2

Quoted 3 lines

SPDM_AUTHENTICATE(device-generation, slot):
Validate the generation and selected slot.
RMI_PDEV_CREATE(id_index=slot)

Maybe PDEV_CREATE is in my #1?

For current RMM, PDEV_CREATE fixes the certificate slot through id_index. For option (a), I would therefore leave it in #3:
START is a no-op; FINISH(slot) creates the PDEV and runs to PDEV_READY.

Quoted 18 lines
Quoted 8 lines

RMI_PDEV_COMMUNICATE until PDEV_NEEDS_KEY

    -> RMM performs fresh negotiation and retrieves the selected chain
    -> CCA host driver relays requests over DOE and caches the chain
  Compare its DER certificates with the selected discovery identity.
    -> reject a mismatch before supplying the key

Extract the leaf key; RMI_PDEV_SET_PUBKEY -> PDEV_HAS_KEY RMI_PDEV_COMMUNICATE until PDEV_READY

This is my #3

Quoted 6 lines

Prepare the root-port PDEV to READY and configure PCI IDE resources. RMI_PDEV_STREAM_CONNECT
RMI_PDEV_COMMUNICATE on both PDEVs, honoring STREAM_WAIT RMI_PDEV_STREAM_COMPLETE
Enable the endpoint IDE stream.
Only then publish authenticated state and the new device-generation.

Quoted 2 lines

Oh is IDE under the control of the OS for RMM? I hadn't read that far ahead. Then it should be done as part of the "ACCEPT" step #5

Yes, the host drives IDE setup through the RMI stream operations; RMM handles key management. We can move that setup to host EVIDENCE_ACCEPT, after appraising the session identity.

Quoted 4 lines

ie the same place we would do RUN

Jason

maintainer
#20 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

Quoted 10 lines · Jiri Pirko
Quoted 5 lines

If we want to do what I called option a, with user space repeat, then we need a CTLV command to start the flow, not sysfs.

I have been thinking sysfs should only create the attestation end point and not do any RMM operations

Okay, sysfs connect can just associate the TSM and create the attestation instance. Starting/restarting the protocol would be driven through CTLV, Is SPDM_GET op the one you have in mind, or something additional done before it?

I haven't got so far as to think about good names. Will work on it

Quoted 13 lines
Quoted 13 lines
Quoted 6 lines

SPDM_GET (attestation provider):
Return the retained discovery information and connection state. There is no corresponding RMI discovery command.

EVIDENCE_GET(CERT_CHAIN, blob-instance=N):
Return the retained DER certificate chain for slot N.

I think these two are my #2

Quoted 3 lines

SPDM_AUTHENTICATE(device-generation, slot):
Validate the generation and selected slot.
RMI_PDEV_CREATE(id_index=slot)

Quoted 6 lines
Quoted 1 line

Maybe PDEV_CREATE is in my #1?

For current RMM, PDEV_CREATE fixes the certificate slot through id_index. For option (a), I would therefore leave it in #3:
START is a no-op; FINISH(slot) creates the PDEV and runs to PDEV_READY.

Really? Yuk, I think that is a mistake, the slot should have been on RMI_PDEV_SET_PUBKEY.

But OK, yeah I think it works OK at step #3. I was thinking that the PDEV_CREATE should happen at sysfs time as part of creating the attestation device. Maybe in future.

There is also a complication that we cannot destroy the PDEV while a VDEV/VIOMMU exists so the RMM driver will have to handle this with some refcounting. EBUSY on #3 I guess.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

Prepare the root-port PDEV to READY and configure PCI IDE resources. RMI_PDEV_STREAM_CONNECT
RMI_PDEV_COMMUNICATE on both PDEVs, honoring STREAM_WAIT RMI_PDEV_STREAM_COMPLETE
Enable the endpoint IDE stream.
Only then publish authenticated state and the new device-generation.

Quoted 6 lines
Quoted 2 lines

Oh is IDE under the control of the OS for RMM? I hadn't read that far ahead. Then it should be done as part of the "ACCEPT" step #5

Yes, the host drives IDE setup through the RMI stream operations; RMM handles key management. We can move that setup to host EVIDENCE_ACCEPT, after appraising the session identity.

Perfect

Jason

author maintainer
#21 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

In reply to Jiri Pirko
Quoted 111 lines · Jiri Pirko

Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:

[..]

Quoted 67 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +61 -0
new file mode 100644
index 000000000000..edb35cede0c2
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+family:
+  name: ctlv-attestation-pci
+  inherits: ctlv-attestation
+  pre-init: true
+
+attr-groups:
+  - name: pci
+    attrs:
+      - name: name
+        type: string
+        min-length: 1
+        max-length: 31
+      - name: generation
+        type: uint
+        max: 4294967295
+      - name: tsm-id
+        type: uint
+        max: 2147483647
+      - name: connected
+        type: uint
+        max: 1
+      - name: locked
+        type: uint
+        max: 1
+      - name: accepted
+        type: uint
+        max: 1
+      - name: dsm
+        type: string
+        min-length: 1
+        max-length: 31
+
+      - name: object
+        enum: evidence-blob-class
+      - name: challenge
+        type: binary
+        min-length: 32
+        max-length: 32
+
+attr-sets:
+  - name: device-info
+    attrs: pci.name
+  - name: change-reply
+    attrs: pci.generation
+  - name: state-reply
+    attrs:
+      - pci.generation
+      - pci.connected
+      - pci.locked
+      - pci.accepted
+    optional-attrs:
+      - pci.tsm-id
+      - pci.dsm
+
+  - name: refresh-request
+    attrs:
+      - pci.generation
+      - pci.object

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 71

Quoted 10 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +10 -0
+    optional-attrs:
+      - pci.challenge
+
+ops:
+  - name: state-get
+    type: action
+    fd-access: read
+    reply:
+      attrs: state-reply
+  - name: evidence-refresh

I'm missing why exactly you need this op. Can't you do whatever is needed in the provider evidence_get implementation? Why it needs to be 2 step.

Also, why do you think this is provider-specific op?

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 79

Quoted 8 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +8 -0
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      attrs: refresh-request
+    reply:
+      attrs: change-reply

This differs from what I had in mind: a transport-neutral ctlv-attestation-spdm family with SPDM_GET and SPDM_AUTHENTICATE.

SPDM_GET would report discovery BaseHash, discovered slots and their certificate digests, authentication state, and the effective authentication slot. SPDM_AUTHENTICATE would take device-generation and the selected slot. EVIDENCE_GET would retrieve CERT_CHAIN with blob-instance=slot without selecting the authentication slot.

Quoted 8 lines

That discovery view is not present in the posted schema; STATE_GET does not provide it.

Would this split work here? It would also allow non-PCI SPDM adapters to share the contract. I understand that multi-slot discovery is deferred, but a slot-zero implementation could still use this family and operation layout.

The only point that remains unclear to me is the distinction between EVIDENCE_ACCEPT and SPDM_AUTHENTICATE. Also, would EVIDENCE_GET retrieve fresh evidence on every call? If so, we do not need evidence-refresh.

-aneesh

#22 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

Quoted 122 lines · Sat, Oct 31, 2026 at 05:05:26AM +0100, [email protected] wrote:
Quoted 111 lines · Jiri Pirko

Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:

[..]

Quoted 67 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +61 -0
new file mode 100644
index 000000000000..edb35cede0c2
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+family:
+  name: ctlv-attestation-pci
+  inherits: ctlv-attestation
+  pre-init: true
+
+attr-groups:
+  - name: pci
+    attrs:
+      - name: name
+        type: string
+        min-length: 1
+        max-length: 31
+      - name: generation
+        type: uint
+        max: 4294967295
+      - name: tsm-id
+        type: uint
+        max: 2147483647
+      - name: connected
+        type: uint
+        max: 1
+      - name: locked
+        type: uint
+        max: 1
+      - name: accepted
+        type: uint
+        max: 1
+      - name: dsm
+        type: string
+        min-length: 1
+        max-length: 31
+
+      - name: object
+        enum: evidence-blob-class
+      - name: challenge
+        type: binary
+        min-length: 32
+        max-length: 32
+
+attr-sets:
+  - name: device-info
+    attrs: pci.name
+  - name: change-reply
+    attrs: pci.generation
+  - name: state-reply
+    attrs:
+      - pci.generation
+      - pci.connected
+      - pci.locked
+      - pci.accepted
+    optional-attrs:
+      - pci.tsm-id
+      - pci.dsm
+
+  - name: refresh-request
+    attrs:
+      - pci.generation
+      - pci.object

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 71

Quoted 10 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +10 -0
+    optional-attrs:
+      - pci.challenge
+
+ops:
+  - name: state-get
+    type: action
+    fd-access: read
+    reply:
+      attrs: state-reply
+  - name: evidence-refresh

I'm missing why exactly you need this op. Can't you do whatever is needed in the provider evidence_get implementation? Why it needs to be 2 step.

Also, why do you think this is provider-specific op?

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 79

Quoted 8 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +8 -0
+    type: action
+    fd-access: read-write
+    availability: dynamic
+    optional: true
+    request:
+      attrs: refresh-request
+    reply:
+      attrs: change-reply

This differs from what I had in mind: a transport-neutral ctlv-attestation-spdm family with SPDM_GET and SPDM_AUTHENTICATE.

SPDM_GET would report discovery BaseHash, discovered slots and their certificate digests, authentication state, and the effective authentication slot. SPDM_AUTHENTICATE would take device-generation and the selected slot. EVIDENCE_GET would retrieve CERT_CHAIN with blob-instance=slot without selecting the authentication slot.

Quoted 8 lines

That discovery view is not present in the posted schema; STATE_GET does not provide it.

Would this split work here? It would also allow non-PCI SPDM adapters to share the contract. I understand that multi-slot discovery is deferred, but a slot-zero implementation could still use this family and operation layout.

Quoted 3 lines

The only point that remains unclear to me is the distinction between EVIDENCE_ACCEPT and SPDM_AUTHENTICATE. Also, would EVIDENCE_GET retrieve fresh evidence on every call? If so, we do not need evidence-refresh.

The distinction I had in mind is between authenticating the connection and approving evidence before allowing device use.

SPDM_AUTHENTICATE would select the certificate slot, authenticate that identity, and complete the required SPDM/session/IDE setup. It would not authorize TDISP RUN. A preceding EVIDENCE_GET would be optional, for example if userspace already knows which slot it wants.

EVIDENCE_ACCEPT would reference an exact EVIDENCE_GET result. After userspace appraises it, acceptance would perform the native transition allowing device use, such as moving a locked guest TDI into RUN.

Typically:

Host: connect -> discover/select slot -> SPDM_AUTHENTICATE
Host: connect -> SPDM_GET -> [EVIDENCE_GET/appraise chains]

      -> select slot -> SPDM_AUTHENTICATE

Guest: lock -> EVIDENCE_GET -> appraise -> EVIDENCE_ACCEPT -> RUN

This is an API design choice, not a protocol requirement. Using EVIDENCE_ACCEPT for both is an alternative, but then generic acceptance would also to carry the SPDM-specific identity selection. Better keep that separate.

maintainer
#23 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

In reply to Aneesh Kumar K.V
Quoted 8 lines · Aneesh Kumar K.V
Quoted 4 lines

Would this split work here? It would also allow non-PCI SPDM adapters to share the contract. I understand that multi-slot discovery is deferred, but a slot-zero implementation could still use this family and operation layout.

The only point that remains unclear to me is the distinction between EVIDENCE_ACCEPT and SPDM_AUTHENTICATE. Also, would EVIDENCE_GET retrieve fresh evidence on every call? If so, we do not need evidence-refresh.

Lets talk about the desired flow over in this thread:

https://lore.kernel.org/all/[email protected]/

I proposed that LOCK and RUN fit into the normal SPDM flow.

LOCK is effectively starting a SPDM session since it is LOCK that provides the mechanism to confirm the host hasn't tampered with the SPDM while we are evaluating it. It has to be tied into the SPDM flow somehow or it doesn't make sense.

RUN is the natural consequence of finally accepting the SPDM connection after checking the post handshake signed evidence and maybe a measurement.

Every SPDM flow should end in some kind of 'RUN' step. It signals to whatever is waiting that the SPDM session was authenticated by userspace.

It is a bit unfortunate that the IDE setup was bundled into the CONNECT, before we have a chance to inspect the evidence, but I don't think there is an practical downside to that. Though we shouldn't consider that the right approach.

Jason

author maintainer
#24 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

In reply to Jiri Pirko
Quoted 88 lines · Jiri Pirko

Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:

[..]

Quoted 67 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +61 -0
new file mode 100644
index 000000000000..edb35cede0c2
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+family:
+  name: ctlv-attestation-pci
+  inherits: ctlv-attestation
+  pre-init: true
+
+attr-groups:
+  - name: pci
+    attrs:
+      - name: name
+        type: string
+        min-length: 1
+        max-length: 31
+      - name: generation
+        type: uint
+        max: 4294967295
+      - name: tsm-id
+        type: uint
+        max: 2147483647
+      - name: connected
+        type: uint
+        max: 1
+      - name: locked
+        type: uint
+        max: 1
+      - name: accepted
+        type: uint
+        max: 1
+      - name: dsm
+        type: string
+        min-length: 1
+        max-length: 31
+
+      - name: object
+        enum: evidence-blob-class
+      - name: challenge
+        type: binary
+        min-length: 32
+        max-length: 32
+
+attr-sets:
+  - name: device-info
+    attrs: pci.name
+  - name: change-reply
+    attrs: pci.generation
+  - name: state-reply
+    attrs:
+      - pci.generation
+      - pci.connected
+      - pci.locked
+      - pci.accepted
+    optional-attrs:
+      - pci.tsm-id
+      - pci.dsm
+
+  - name: refresh-request
+    attrs:
+      - pci.generation
+      - pci.object

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 71

Quoted 10 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +10 -0
+    optional-attrs:
+      - pci.challenge
+
+ops:
+  - name: state-get
+    type: action
+    fd-access: read
+    reply:
+      attrs: state-reply
+  - name: evidence-refresh

I'm missing why exactly you need this op. Can't you do whatever is needed in the provider evidence_get implementation? Why it needs to be 2 step.

Quoted 2 lines

Also, why do you think this is provider-specific op?

I was considering evidence-get to return the evidence generated by evidence-refresh. Without an explicit refresh, evidence-get would return the previously fetched evidence. There is no particular reason for this two-step approach; evidence-get could always fetch fresh evidence, making evidence-refresh redundant.

-aneesh

#25 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

Quoted 90 lines · Sat, Oct 31, 2026 at 05:22:48AM +0100, [email protected] wrote:
Quoted 88 lines · Jiri Pirko

Wed, Oct 28, 2026 at 04:26:13PM +0100, [email protected] wrote:

[..]

Quoted 67 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +61 -0
new file mode 100644
index 000000000000..edb35cede0c2
@@ -0,0 +1,79 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+family:
+  name: ctlv-attestation-pci
+  inherits: ctlv-attestation
+  pre-init: true
+
+attr-groups:
+  - name: pci
+    attrs:
+      - name: name
+        type: string
+        min-length: 1
+        max-length: 31
+      - name: generation
+        type: uint
+        max: 4294967295
+      - name: tsm-id
+        type: uint
+        max: 2147483647
+      - name: connected
+        type: uint
+        max: 1
+      - name: locked
+        type: uint
+        max: 1
+      - name: accepted
+        type: uint
+        max: 1
+      - name: dsm
+        type: string
+        min-length: 1
+        max-length: 31
+
+      - name: object
+        enum: evidence-blob-class
+      - name: challenge
+        type: binary
+        min-length: 32
+        max-length: 32
+
+attr-sets:
+  - name: device-info
+    attrs: pci.name
+  - name: change-reply
+    attrs: pci.generation
+  - name: state-reply
+    attrs:
+      - pci.generation
+      - pci.connected
+      - pci.locked
+      - pci.accepted
+    optional-attrs:
+      - pci.tsm-id
+      - pci.dsm
+
+  - name: refresh-request
+    attrs:
+      - pci.generation
+      - pci.object

Documentation/ctlv/specs/ctlv-attestation-pci.yaml line 71

Quoted 10 lines
Documentation/ctlv/specs/ctlv-attestation-pci.yaml new file +10 -0
+    optional-attrs:
+      - pci.challenge
+
+ops:
+  - name: state-get
+    type: action
+    fd-access: read
+    reply:
+      attrs: state-reply
+  - name: evidence-refresh

I'm missing why exactly you need this op. Can't you do whatever is needed in the provider evidence_get implementation? Why it needs to be 2 step.

Quoted 8 lines
Quoted 2 lines

Also, why do you think this is provider-specific op?

I was considering evidence-get to return the evidence generated by evidence-refresh. Without an explicit refresh, evidence-get would return the previously fetched evidence. There is no particular reason for this two-step approach; evidence-get could always fetch fresh evidence, making evidence-refresh redundant.

Okay. The aim of evidence-get is to do get fresh evidence upon each call. I will document this more clearly.

maintainer
#26 lore raw

Re: [RFC PATCH v1 06/11] PCI/TSM: Describe the PCI attestation control family

In reply to Aneesh Kumar K.V
Quoted 5 lines · Aneesh Kumar K.V

I was considering evidence-get to return the evidence generated by evidence-refresh. Without an explicit refresh, evidence-get would return the previously fetched evidence. There is no particular reason for this two-step approach; evidence-get could always fetch fresh evidence, making evidence-refresh redundant.

I would strongly prefer we have two things:

1) Immutable 'transcript' data that reflects the SPDM startup sequence. This is not "cache" because there is no refresh and no eviction.

2) Fresh collection that that kernel does not store. This requires a single shot IOCTL to transfer unsized data to userspace. We cannot double a collection without breaking the sequence numbers RMM keeps.

I think the TSM cached/refresh/get scheme is a work around for #2, I'm hoping CTLV can solve it directly somehow :)

Jiri had a sketch to return data into a memfd, that's an appealing thought, if we can provide a memfd for all the unsized fresh evidence then kernel can just fill with with whatever size it has, never an issue.

Jason

author maintainer
#27 lore raw

RFC PATCH 7/11 attestation: Add attestation helpers for PCI/TSM integration

In reply to Aneesh Kumar K.V (Arm)

Add helpers that let the following PCI/TSM patch coordinate device state changes with attestation state.

PCI child-family operations use ctlv_attestation_op_pre() to acquire the attestation lock before invoking the backend. They need to validate the requested device generation before changing state and invalidating previous evidence.

The existing attestation_state_changed() takes the attestation lock itself. Factor out a locked variant so those operations can report a state change without acquiring the same mutex again.

Also expose helpers to check for device-generation overflow, read the current device generation, and acquire the instance mutex for notifications outside CTLV dispatch.

Cc Jiri Pirko <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 drivers/attestation/core/core.c | 40 +++++++++++++++++++++++++---
 include/linux/attestation.h     | 47 +++++++++++++++++++++++++++++++++
 2 files changed, 84 insertions(+), 3 deletions(-)
drivers/attestation/core/core.c +37 -3
index 914f29693aee..e35c45bc715d 100644
@@ -430,14 +430,13 @@ void attestation_mr_destroy(struct attestation_mr *mr)
 }
 EXPORT_SYMBOL_NS_GPL(attestation_mr_destroy, "ATTESTATION");
 
-void attestation_state_changed(struct attestation *att)
+void attestation_state_changed_locked(struct attestation *att)
 {
 	struct attestation_event_batch events = {};
 	bool evidence;
 	int ret;
 
-	guard(mutex)
-		(&att->lock);
+	lockdep_assert_held(&att->lock);
 	if (!attestation_registered(att))
 		return;
 	evidence = att->evidence_generation > att->evidence_floor || att->verdict_recorded;
@@ -458,8 +457,43 @@ void attestation_state_changed(struct attestation *att)
 		attestation_evidence_event_emit(&events, att);
 	attestation_events_abort(&events);
 }
+EXPORT_SYMBOL_NS_GPL(attestation_state_changed_locked, "ATTESTATION");
+
+void attestation_state_changed(struct attestation *att)
+{
+	guard(mutex)(&att->lock);
+	attestation_state_changed_locked(att);
+}
 EXPORT_SYMBOL_NS_GPL(attestation_state_changed, "ATTESTATION");
 
+void attestation_lock(struct attestation *att)
+{
+	mutex_lock(&att->lock);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_lock, "ATTESTATION");
+
+void attestation_unlock(struct attestation *att)
+{
+	mutex_unlock(&att->lock);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_unlock, "ATTESTATION");
+
+int attestation_state_change_prepare(struct attestation *att)
+{
+	lockdep_assert_held(&att->lock);
+	if (!attestation_registered(att))
+		return -ENODEV;
+	return attestation_preflight(att, true, false, false);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_state_change_prepare, "ATTESTATION");
+
+unsigned int attestation_get_device_generation(struct attestation *att)
+{
+	lockdep_assert_held(&att->lock);
+	return att->device_generation;
+}
+EXPORT_SYMBOL_NS_GPL(attestation_get_device_generation, "ATTESTATION");
+
 static unsigned int attestation_provider_features(const struct attestation_provider *provider)
 {
 	unsigned int features = 0;
include/linux/attestation.h +47 -0

53 lines not on this page · raw message

Signature · 1 line
2.43.0
author maintainer
#28 lore raw

RFC PATCH 8/11 PCI/TSM: Bind CTLV attestation to connected and locked contexts

In reply to Aneesh Kumar K.V (Arm)

Create a PCI attestation instance only after sysfs connect or lock succeeds. Export cached evidence through the common blob API and replace the netlink evidence interface. Backends declare their evidence capability bitmap; the common provider advertises and accepts only those objects.

Route evidence acceptance to a link backend callback or guest ACCEPT. Support an optional evidence.accept-object selector for any certificate slot declared by the link backend, while the common core checks that the object belongs to the retrieved evidence generation. Pass the selected slot through pci_tsm.evidence.slot before invoking the backend.

Cc Ashish Kalra <[email protected]>
Cc Tom Lendacky <[email protected]>
Cc John Allen <[email protected]>
Cc Bjorn Helgaas <[email protected]>
Cc Catalin Marinas <[email protected]>
Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 Documentation/ABI/testing/sysfs-bus-pci   | 108 ++---
 Documentation/ABI/testing/sysfs-class-tsm |   9 +-
 Documentation/driver-api/pci/tsm.rst      | 104 ++++-
 Documentation/netlink/specs/pci-tsm.yaml  | 151 -------
 drivers/crypto/ccp/sev-dev-tsm.c          |   1 +
 drivers/pci/Kconfig                       |   3 +
 drivers/pci/tsm/Makefile                  |   6 +-
 drivers/pci/tsm/attestation.c             | 525 ++++++++++++++++++++++
 drivers/pci/tsm/core.c                    | 430 ++++++++++--------
 drivers/pci/tsm/evidence.c                | 274 -----------
 drivers/pci/tsm/internal.h                |  26 ++
 drivers/pci/tsm/netlink.c                 |  43 --
 drivers/pci/tsm/netlink.h                 |  23 -
 drivers/virt/coco/arm-cca-guest/main.c    |  34 +-
 drivers/virt/coco/arm-cca-guest/rsi-da.c  |  16 +-
 include/linux/pci-tsm.h                   |  86 +++-
 include/linux/pci.h                       |   2 +
 include/uapi/linux/pci-tsm-netlink.h      | 101 -----
 samples/devsec/common.c                   |   1 -
 samples/devsec/link_tsm.c                 |   1 +
 samples/devsec/tsm.c                      |  32 +-
 21 files changed, 1075 insertions(+), 901 deletions(-)
 delete mode 100644 Documentation/netlink/specs/pci-tsm.yaml
 create mode 100644 drivers/pci/tsm/attestation.c
 delete mode 100644 drivers/pci/tsm/evidence.c
 create mode 100644 drivers/pci/tsm/internal.h
 delete mode 100644 drivers/pci/tsm/netlink.c
 delete mode 100644 drivers/pci/tsm/netlink.h
 delete mode 100644 include/uapi/linux/pci-tsm-netlink.h
Documentation/ABI/testing/sysfs-bus-pci +40 -68
index adce5b6cba9f..56b1d92427ef 100644
@@ -625,49 +625,56 @@ Description:
 What:		/sys/bus/pci/devices/.../tsm/
 Contact:	[email protected]
 Description:
-		This directory only appears if a physical device function
-		supports authentication (PCIe CMA-SPDM), interface security
-		(PCIe TDISP), and is accepted for secure operation by the
-		platform TSM driver. This attribute directory appears
-		dynamically after the platform TSM driver loads. So, only after
-		the /sys/class/tsm/tsm0 device arrives can tools assume that
-		devices without a tsm/ attribute directory will never have one;
-		before that, the security capabilities of the device relative to
-		the platform TSM are unknown. See
-		Documentation/ABI/testing/sysfs-class-tsm.
+		Link TSM control and status. This directory appears when a link
+		TSM is registered for a capable PCIe function or its managed
+		subfunctions. The dsm attribute identifies the connected DSM.
+		Host PF0 exposes connect and disconnect controls. Evidence
+		retrieval and acceptance use CTLV attestation; see
+		Documentation/driver-api/pci/tsm.rst.
 
 What:		/sys/bus/pci/devices/.../tsm/connect
 Contact:	[email protected]
 Description:
-		(RW) Write the name of a TSM (TEE Security Manager) device from
-		/sys/class/tsm to this file to establish a connection with the
-		device.  This typically includes an SPDM (DMTF Security
-		Protocols and Data Models) session over PCIe DOE (Data Object
-		Exchange) and may also include PCIe IDE (Integrity and Data
-		Encryption) establishment. Reads from this attribute return the
-		name of the connected TSM or the empty string if not
-		connected. A TSM device signals its readiness to accept PCI
-		connection via a KOBJ_CHANGE event. This is a "link" TSM
-		attribute, see Documentation/ABI/testing/sysfs-class-tsm.
+		(RW) On host PF0, write a link TSM name (tsmN) to associate the
+		device with that TSM and collect its supported identity evidence.
+		Reads return the associated TSM name, or an empty string.
+		Successful connect creates the CTLV attestation instance.
+		Backends may defer secure-session and stream setup until
+		userspace verifies the chain and issues CTLV evidence-accept.
 
 What:		/sys/bus/pci/devices/.../tsm/disconnect
 Contact:	[email protected]
 Description:
-		(WO) Write the name of the TSM device that was specified
-		to 'connect' to teardown the connection.
-		The write fails with EBUSY while any vdevice depends on the
-		connection. Userspace must destroy those vdevices before
-		disconnecting the link. This is a
-		"link" TSM attribute, see
-		Documentation/ABI/testing/sysfs-class-tsm.
+		(WO) On host PF0, write the associated TSM name to release the
+		connection, including any established stream. Works before
+		evidence acceptance too. Successful disconnect removes the CTLV
+		attestation instance. Returns EBUSY while IOMMUFD vdevices
+		hold connection contexts.
+
+What:		/sys/bus/pci/devices/.../tsm/lock
+Contact:	[email protected]
+Description:	(RW) Lock a TEE-capable device with a device-security TSM by
+		writing its name, for example "tsm0". Requires the device driver
+		to be detached. Reads return the associated TSM name or an empty
+		string. Successful locking collects evidence and creates the
+		device's CTLV attestation instance. CTLV evidence-accept enters
+		RUN after userspace has verified evidence from evidence-get.
+
+What:		/sys/bus/pci/devices/.../tsm/unlock
+Contact:	[email protected]
+Description:	(WO) Unlock a device by writing its associated TSM name.
+		Requires the device driver to be detached. Successful unlock
+		revokes confidential-device acceptance and removes the CTLV
+		attestation instance. Failed unlock retains the error context
+		for retry through this file.
 
 What:		/sys/bus/pci/devices/.../tsm/dsm
 Contact:	[email protected]
 Description:	(RO) Return PCI device name of this device's DSM (Device
 		Security Manager). When a device is in the connected state it
-		indicates that the platform TSM (TEE Security Manager) has made
-		a secure-session connection with a device's DSM. A DSM is always
-		physical function 0 and when the device supports TDISP (TEE
+		identifies the DSM associated with the platform TSM (TEE Security
+		Manager). This association can precede secure-session setup.
+		A DSM is always physical function 0 and when the device supports TDISP (TEE
 		Device Interface Security Protocol) its managed functions also
 		populate this tsm/dsm attribute. The managed functions of a DSM
 		are SR-IOV (Single Root I/O Virtualization) virtual functions,
@@ -692,45 +699,10 @@ Description:
 		available authentication methods for the device.
 
 		When present and the tsm/ attribute directory is present, the
-		authenticated attribute is an alias for the device 'connect'
-		state. See the 'tsm/connect' attribute for more details.
+		authenticated attribute reports the TSM name only after secure
+		session and stream setup succeed, otherwise an empty string.
+		Connection control uses tsm/connect and tsm/disconnect.
 		This is a "link" TSM attribute, see
 		Documentation/ABI/testing/sysfs-class-tsm.
 
-What:		/sys/bus/pci/devices/.../tsm/lock
-Contact:	[email protected]
-Description:
-		(RW) Write the name of a TSM (TEE Security Manager) device
-		instance to this file to request that the platform transition
-		the PCIe device configuration to the TDISP LOCKED state. This
-		puts the device in a state where security sensitive
-		configuration setting can not be changed without transitioning
-		the device the PCIe TDISP ERROR state. Reads from this attribute
-		return the name of the TSM device instance that has arranged for
-		the device to be locked, or the empty string if not locked. A
-		TSM class device signals its readiness for lock requests via a
-		KOBJ_CHANGE event. Writes fail with EBUSY if this device is
-		bound to a driver. This is a "devsec" TSM attribute, see
-		Documentation/ABI/testing/sysfs-class-tsm. See
-		Documentation/driver-api/pci/tsm.rst for more details.
-
-What:		/sys/bus/pci/devices/.../tsm/unlock
-Contact:	[email protected]
-Description:
-		(WO) Write the name of the TSM device that was specified to
-		'lock' to teardown the connection. Writes fail with EBUSY if
-		this device is bound to a driver. This is a "devsec" TSM
-		attribute, see Documentation/ABI/testing/sysfs-class-tsm.
 
-What:		/sys/bus/pci/devices/.../tsm/accept
-Contact:	[email protected]
-Description:
-		(RW) Write "1" (or any boolean "true" string) to this file to
-		request that TSM transition the device from the TDISP LOCKED
-		state to the RUN state and arrange the for the secure IOMMU to
-		accept requests with T=1 in the PCIe packet header (TLP)
-		targeting private memory. Per TDISP the only exits from the RUN
-		state are via an explicit unlock request or an event that
-		transitions the device to the ERROR state. Writes fail with
-		EBUSY if this device is bound to a driver. This is a "devsec"
-		TSM attribute, see Documentation/ABI/testing/sysfs-class-tsm.
Documentation/ABI/testing/sysfs-class-tsm +5 -4
index 76704501f082..854efd3f1b28 100644
@@ -35,7 +35,8 @@ Description:
 			      transition assigned devices through the TDISP
 			      state machine UNLOCKED->LOCKED->RUN.
 
-		See the "tsm/" entries in
-		Documentation/ABI/testing/sysfs-bus-pci for the available PCI
-		device attributes when a TSM with the given "pci_mode" is
-		registered.
+		Host connection control uses the PCI device's tsm/connect and
+		tsm/disconnect sysfs files. Guest lifecycle operations use
+		ctlv-attestation-pci. The numeric suffix of tsmN is its
+		pci.tsm-id. See
+		Documentation/driver-api/pci/tsm.rst.
Documentation/driver-api/pci/tsm.rst +94 -10

119 lines not on this page · raw message

Documentation/netlink/specs/pci-tsm.yaml deleted +0 -151

151 lines not on this page · raw message

drivers/crypto/ccp/sev-dev-tsm.c +1 -0
index 6d089714900c..e905ed9de9b5 100644
@@ -307,6 +307,7 @@ static int dsm_connect(struct pci_dev *pdev)
 
 	tc_mask = streams_setup(dev_data->ide, ids);
 
+	dev_data->cert_slot = pdev->tsm->evidence.slot;
 	ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);
 	ret = sev_tio_spdm_cmd(dsm, ret);
 	if (ret)
drivers/pci/Kconfig +3 -0
index f7b7a6e269a2..b605eb2d4d5e 100644
@@ -131,6 +131,9 @@ config PCI_TSM
 	select PCI_IDE
 	select PCI_DOE
 	select TSM
+	select CTLV
+	select ATTESTATION
+	select CRYPTO_HASH_INFO
 	help
 	  The TEE (Trusted Execution Environment) Device Interface
 	  Security Protocol (TDISP) defines a "TSM" as a platform agent
drivers/pci/tsm/Makefile +3 -3
index afa775224b8d..62181b8ca635 100644
@@ -4,6 +4,6 @@
 
 obj-$(CONFIG_PCI_TSM) += tsm.o
 
-tsm-y := core.o
-tsm-$(CONFIG_NET) += netlink.o
-tsm-$(CONFIG_NET) += evidence.o
+ccflags-y += -I$(srctree)/drivers/attestation/generated
+
+tsm-y := core.o attestation.o ctlv_attestation_pci_gen.o
drivers/pci/tsm/attestation.c new file +525 -0

525 lines not on this page · raw message

drivers/pci/tsm/core.c +246 -184

725 lines not on this page · raw message

drivers/pci/tsm/evidence.c deleted +0 -274

274 lines not on this page · raw message

drivers/pci/tsm/internal.h new file +26 -0
new file mode 100644
index 000000000000..613267a84b8f
@@ -0,0 +1,26 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _PCI_TSM_INTERNAL_H
+#define _PCI_TSM_INTERNAL_H
+
+#include <linux/types.h>
+
+struct pci_dev;
+struct list_head;
+struct tsm_dev;
+
+/* All helpers require pci_tsm_rwsem held for write. */
+bool pci_tsm_can_lock(struct tsm_dev *tsm_dev);
+void pci_tsm_attestation_update(struct pci_dev *pdev);
+int pci_tsm_attestation_add(struct pci_dev *pdev);
+void pci_tsm_attestation_remove(struct pci_dev *pdev, struct list_head *retired);
+int pci_tsm_evidence_accept(struct pci_dev *pdev, unsigned int slot);
+int pci_tsm_disconnect(struct pci_dev *pdev);
+int pci_tsm_lock(struct pci_dev *pdev, struct tsm_dev *tsm_dev);
+int pci_tsm_unlock(struct pci_dev *pdev);
+int pci_tsm_accept(struct pci_dev *pdev);
+void pci_tsm_attestation_notify_state_change(struct pci_dev *pdev);
+
+/* Call after dropping PCI and device locks to drain retired CTLV requests. */
+void pci_tsm_attestation_destroy(struct list_head *retired);
+
+#endif
drivers/pci/tsm/netlink.c deleted +0 -43

43 lines not on this page · raw message

drivers/pci/tsm/netlink.h deleted +0 -23
deleted file mode 100644
index 34f3fb6ba2b7..000000000000
@@ -1,23 +0,0 @@
-/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
-/* Do not edit directly, auto-generated from: */
-/*	Documentation/netlink/specs/pci-tsm.yaml */
-/* YNL-GEN kernel header */
-/* To regenerate run: tools/net/ynl/ynl-regen.sh */
-
-#ifndef _LINUX_PCI_TSM_GEN_H
-#define _LINUX_PCI_TSM_GEN_H
-
-#include <net/netlink.h>
-#include <net/genetlink.h>
-
-#include <uapi/linux/pci-tsm-netlink.h>
-
-int pci_tsm_nl_evidence_read_pre(struct netlink_callback *cb);
-int pci_tsm_nl_evidence_read_post(struct netlink_callback *cb);
-
-int pci_tsm_nl_evidence_read_dumpit(struct sk_buff *skb,
-				    struct netlink_callback *cb);
-
-extern struct genl_family pci_tsm_nl_family;
-
-#endif /* _LINUX_PCI_TSM_GEN_H */
drivers/virt/coco/arm-cca-guest/main.c +25 -9
index 50a21317c941..f50f3e660bbc 100644
@@ -340,8 +340,7 @@ static int cca_collect_dev_evidence(struct pci_dev *pdev, struct cca_guest_dsc *
 	down_write(&evidence->lock);
 	evidence->generation = dev_info->meas_seq;
 
-	/* we default to slot 0 in pdev_create */
-	obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+	obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0 + evidence->slot];
 	WARN_ON(obj->data);
 	obj->data = certificate;
 	obj->len = certificate_size;
@@ -367,6 +366,11 @@ static int cca_collect_dev_evidence(struct pci_dev *pdev, struct cca_guest_dsc *
 	memcpy(dsc->dev_info.vca_digest, dev_info->protocol_data_digest, SHA512_DIGEST_SIZE);
 	memcpy(dsc->dev_info.meas_digest, dev_info->meas_digest, SHA512_DIGEST_SIZE);
 	memcpy(dsc->dev_info.report_digest, dev_info->report_digest, SHA512_DIGEST_SIZE);
+	evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0 + evidence->slot].digest =
+		dsc->dev_info.cert_digest;
+	evidence->obj[PCI_TSM_EVIDENCE_TYPE_VCA].digest = dsc->dev_info.vca_digest;
+	evidence->obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS].digest = dsc->dev_info.meas_digest;
+	evidence->obj[PCI_TSM_EVIDENCE_TYPE_REPORT].digest = dsc->dev_info.report_digest;
 	up_write(&evidence->lock);
 
 	kfree(dev_info);
@@ -416,6 +420,11 @@ static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pde
 		goto free_dev_info;
 	}
 
+	if (dev_info->id_index) {
+		ret = -EOPNOTSUPP;
+		goto free_dev_info;
+	}
+
 	/* Collect the lock sequence number. */
 	cca_dsc->dev_info.lock_seq = dev_info->lock_seq;
 
@@ -450,7 +459,7 @@ static struct pci_tsm *cca_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *pde
 	return ERR_PTR(ret);
 }
 
-static void cca_tsm_unlock(struct pci_tsm *tsm)
+static int cca_tsm_unlock(struct pci_tsm *tsm)
 {
 	long ret;
 	struct pci_dev *pdev = tsm->pdev;
@@ -460,20 +469,22 @@ static void cca_tsm_unlock(struct pci_tsm *tsm)
 	ret = cca_unmap_evidence_report_range(tsm->pdev);
 	if (ret) {
 		pci_err(tsm->pdev, "failed to invalidate the interface report\n");
-		goto err_out;
+		return ret;
 	}
 
-	cca_device_unlock(tsm->pdev);
+	ret = cca_device_unlock(tsm->pdev);
+	if (ret)
+		return ret;
+
 	if (cca_dsc->pci.mmio)
 		pci_tsm_mmio_teardown(cca_dsc->pci.mmio);
 
-err_out:
-	/*
-	 * No error handling from this function. Leave the device locked
-	 */
 	if (cca_dsc->pci.mmio)
 		pci_tsm_mmio_free(tsm->pdev, cca_dsc->pci.mmio);
+	for (int i = 0; i <= PCI_TSM_EVIDENCE_TYPE_MAX; i++)
+		kvfree(tsm->evidence.obj[i].data);
 	kfree(cca_dsc);
+	return 0;
 }
 
 static int __cca_tsm_accept(struct pci_dev *pdev, unsigned long lock_seq)
@@ -496,6 +507,11 @@ static int cca_tsm_accept(struct pci_dev *pdev)
 }
 
 static struct pci_tsm_ops cca_devsec_pci_ops = {
+	.owner = THIS_MODULE,
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_VCA) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_REPORT),
 	.lock = cca_tsm_lock,
 	.unlock = cca_tsm_unlock,
 	.accept	 = cca_tsm_accept,
drivers/virt/coco/arm-cca-guest/rsi-da.c +7 -9
index b83b0b654ac3..f7a3c24817bb 100644
@@ -268,28 +268,26 @@ int cca_device_accept(struct pci_dev *pdev, unsigned long lock_seq)
 		goto err_mmio_setup;
 	}
 
+	/* Keep partial mappings reachable until a successful UNLOCK. */
+	dsc->pci.mmio = tsm_mmio;
 	ret = cca_map_evidence_report_range(pdev, tsm_mmio);
 	if (ret) {
 		pci_err(pdev, "failed to validate the interface report\n");
-		goto err_evidence_report_map;
+		return ret;
 	}
 
 	ret = rhi_vdev_set_tdi_state(pdev, RHI_DA_TDI_CONFIG_RUN);
 	if (ret) {
 		pci_err(pdev, "failed to switch the device (%u) to RUN state\n", ret);
-		goto err_tdi_config_run;
+		return ret;
 	}
 
-	if (rsi_vdev_enable_dma(vdev_id, &dsc->dev_info))
+	if (rsi_vdev_enable_dma(vdev_id, &dsc->dev_info)) {
 		pci_err(pdev, "failed to enable DMA from the device\n");
-
-	dsc->pci.mmio = no_free_ptr(tsm_mmio);
+		return -EIO;
+	}
 	return 0;
 
-err_tdi_config_run:
-	cca_unmap_evidence_report_range(pdev);
-err_evidence_report_map:
-	pci_tsm_mmio_teardown(tsm_mmio);
 err_mmio_setup:
 	pci_tsm_mmio_free(pdev, tsm_mmio);
 	return ret;
include/linux/pci-tsm.h +67 -19

175 lines not on this page · raw message

include/linux/pci.h +2 -0
index d31a8d107b1e..39541d217fac 100644
@@ -580,6 +580,8 @@ struct pci_dev {
 #endif
 #ifdef CONFIG_PCI_TSM
 	struct pci_tsm *tsm;		/* TSM operation state */
+	struct pci_tsm_attestation *tsm_attestation; /* Connected/locked CTLV instance */
+	bool tsm_attestation_removing; /* CTLV teardown is draining requests */
 #endif
 	u16		acs_cap;	/* ACS Capability offset */
 	u16		acs_capabilities; /* ACS Capabilities */
include/uapi/linux/pci-tsm-netlink.h deleted +0 -101

101 lines not on this page · raw message

samples/devsec/common.c +0 -1
index 5dc4152e8b99..641266ca0b7f 100644
@@ -5,7 +5,6 @@
 #include <linux/pci.h>
 #include <linux/pci-tsm.h>
 #include <linux/vmalloc.h>
-#include <uapi/linux/pci-tsm-netlink.h>
 
 #include "devsec.h"
 
samples/devsec/link_tsm.c +1 -0
index 864ddf592db4..61cf6c16e1b5 100644
@@ -231,6 +231,7 @@ static void devsec_link_tsm_disconnect(struct pci_dev *pdev)
 }
 
 static struct pci_tsm_ops devsec_link_pci_ops = {
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
 	.probe = devsec_link_tsm_pci_probe,
 	.remove = devsec_link_tsm_pci_remove,
 	.connect = devsec_link_tsm_connect,
samples/devsec/tsm.c +30 -2
index 7d480a306e13..6ff88ca83906 100644
@@ -2,6 +2,7 @@
 /* Copyright (C) 2024 - 2026 Intel Corporation */
 
 #define dev_fmt(fmt) "devsec: " fmt
+#include <crypto/hash_info.h>
 #include <linux/device/faux.h>
 #include <linux/pci-tsm.h>
 #include <linux/module.h>
@@ -10,6 +11,13 @@
 #include <linux/tsm.h>
 #include "devsec.h"
 
+static bool fail_accept;
+module_param(fail_accept, bool, 0600);
+MODULE_PARM_DESC(fail_accept, "Fail native ACCEPT for recovery testing");
+static bool fail_unlock;
+module_param(fail_unlock, bool, 0600);
+MODULE_PARM_DESC(fail_unlock, "Fail native UNLOCK while retaining its context");
+
 struct devsec_dev_data {
 	struct pci_tsm_devsec pci;
 };
@@ -32,17 +40,30 @@ static struct pci_tsm *devsec_tsm_lock(struct tsm_dev *tsm_dev, struct pci_dev *
 	if (rc)
 		return ERR_PTR(rc);
 
+	devsec_evidence_busy();
+	pci_tsm_init_evidence(&devsec_data->pci.base_tsm.evidence, 0, HASH_ALGO_SHA384);
+	devsec_init_evidence(&devsec_data->pci.base_tsm.evidence);
+	/* The sample uses the same synthetic payload for all guest reports. */
+	devsec_data->pci.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_VCA] =
+		devsec_data->pci.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS];
+	devsec_data->pci.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_REPORT] =
+		devsec_data->pci.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS];
 	return &no_free_ptr(devsec_data)->pci.base_tsm;
 }
 
-static void devsec_tsm_unlock(struct pci_tsm *tsm)
+static int devsec_tsm_unlock(struct pci_tsm *tsm)
 {
 	struct devsec_dev_data *devsec_data = to_devsec_data(tsm);
 	struct pci_tsm_devsec *devsec_tsm = to_pci_tsm_devsec(tsm);
 
-	pci_tsm_mmio_teardown(devsec_tsm->mmio);
+	if (READ_ONCE(fail_unlock))
+		return -EIO;
+	if (devsec_tsm->mmio)
+		pci_tsm_mmio_teardown(devsec_tsm->mmio);
 	kfree(devsec_tsm->mmio);
+	devsec_evidence_idle();
 	kfree(devsec_data);
+	return 0;
 }
 
 static int devsec_tsm_accept(struct pci_dev *pdev)
@@ -54,6 +75,8 @@ static int devsec_tsm_accept(struct pci_dev *pdev)
 		kzalloc(struct_size(mmio, mmio, PCI_NUM_RESOURCES), GFP_KERNEL);
 	if (!mmio)
 		return -ENOMEM;
+	if (READ_ONCE(fail_accept))
+		return -EIO;
 
 	/*
 	 * Typically this range request would come from the TDISP Interface
@@ -78,6 +101,11 @@ static int devsec_tsm_accept(struct pci_dev *pdev)
 }
 
 static struct pci_tsm_ops devsec_pci_ops = {
+	.owner = THIS_MODULE,
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_VCA) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS) |
+			  BIT(PCI_TSM_EVIDENCE_TYPE_REPORT),
 	.lock = devsec_tsm_lock,
 	.unlock = devsec_tsm_unlock,
 	.accept = devsec_tsm_accept,
Signature · 1 line
2.43.0
#29 lore raw

Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts

On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote:

< -- snip -->

Quoted 228 lines
drivers/pci/tsm/attestation.c new file +222 -0
new file mode 100644
index 000000000000..993c26664498
@@ -0,0 +1,525 @@
+// SPDX-License-Identifier: GPL-2.0
+/* PCI lifecycle and cached SPDM/TDISP evidence through CTLV attestation. */
+#include <crypto/hash_info.h>
+#include <linux/attestation.h>
+#include <linux/ctlv.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/pci-tsm.h>
+#include <linux/tsm.h>
+
+#include "../pci.h"
+#include "ctlv_attestation_pci_gen.h"
+#include "internal.h"
+
+struct pci_tsm_attestation {
+	struct pci_dev *pdev;
+	struct ctlv_device *ctlvdev;
+	struct list_head retired;
+	bool attached;
+	/* Protected by pci_tsm_rwsem. */
+	bool active;
+	bool removing;
+};
+
+static void pci_attestation_update_locked(struct pci_tsm_attestation *pa,
+		bool att_locked)
+{
+	struct pci_dev *pdev = pa->pdev;
+	struct pci_tsm *tsm = pdev->tsm;
+	const struct pci_tsm_ops *ops = tsm ? tsm->tsm_dev->pci_ops : NULL;
+	bool link = tsm && tsm->dsm_dev;
+	bool devsec = tsm && !link;
+	bool accepted = device_cc_accepted(&pdev->dev);
+	bool present = !pa->removing && !pci_dev_is_disconnected(pdev);
+	unsigned int features = 0;
+
+	lockdep_assert_held_write(&pci_tsm_rwsem);
+
+	if (devsec) {
+		ctlv_device_op_state_set(pa->ctlvdev, CTLV_ATTESTATION_PCI_OP_EVIDENCE_REFRESH,
+					 !present || !tsm || tsm->error ||
+					 accepted || !ops->refresh_evidence);
+	}
+
+	if (present) {
+		if (tsm && !tsm->error) {
+			features |= ATTESTATION_FEATURE_EVIDENCE;
+			if ((devsec && ops->accept) || (link && is_pci_tsm_pf0(pdev)))
+				features |= ATTESTATION_FEATURE_EVIDENCE_ACCEPT;
+		}
+
+		if (devsec)
+			features |= ATTESTATION_FEATURE_TDISP_STATE;
+	}
+
+	if (att_locked)
+		attestation_features_set_locked(ctlv_priv_parent(pa), features);
+	else
+		attestation_features_set(ctlv_priv_parent(pa), features);
+}
+
+void pci_tsm_attestation_update(struct pci_dev *pdev)
+{
+	lockdep_assert_held_write(&pci_tsm_rwsem);
+	if (pdev->tsm_attestation && !pdev->tsm_attestation->removing)
+		pci_attestation_update_locked(pdev->tsm_attestation, false);
+}
+
+void pci_tsm_attestation_notify_state_change(struct pci_dev *pdev)
+{
+	struct pci_tsm_attestation *pa = pdev->tsm_attestation;
+	struct attestation *att;
+
+	lockdep_assert_held_write(&pci_tsm_rwsem);
+	if (!pa || pa->removing)
+		return;
+
+	att = ctlv_priv_parent(pa);
+	/* The active CTLV operation already holds this instance's mutex. */
+	if (pa->active) {
+		attestation_state_changed_locked(att);
+		pci_attestation_update_locked(pa, true);
+		return;
+	}
+
+	/* Native notifications outside dispatch must acquire the mutex. */
+	attestation_lock(att);
+	attestation_state_changed_locked(att);
+	pci_attestation_update_locked(pa, true);
+	attestation_unlock(att);
+}
+
+void pci_tsm_attestation_remove(struct pci_dev *pdev, struct list_head *retired)
+{
+	struct pci_tsm_attestation *pa = pdev->tsm_attestation;
+
+	lockdep_assert_held_write(&pci_tsm_rwsem);
+	if (!pa || pa->removing)
+		return;
+
+	/* Revoke old descriptors before another session can be installed. */
+	pa->removing = true;
+	pdev->tsm_attestation_removing = true;
+	list_add_tail(&pa->retired, retired);
+}
+
+void pci_tsm_attestation_destroy(struct list_head *retired)
+{
+	struct pci_tsm_attestation *pa, *next;
+
+	/* ctlv_destroy() drains callbacks that may be waiting for pci_tsm_rwsem. */
+	list_for_each_entry_safe(pa, next, retired, retired) {
+		struct pci_dev *pdev = pci_dev_get(pa->pdev);
+
+		list_del(&pa->retired);
+		ctlv_destroy(pa->ctlvdev);
+
+		/* The common registry has now retired this parent as well. */
+		scoped_guard(rwsem_write, &pci_tsm_rwsem)
+			pdev->tsm_attestation_removing = false;
+
+		pci_dev_put(pdev);
+	}
+}
+
+int ctlv_attestation_pci_op_pre(void *priv, struct ctlv_op_ctx *ctx)
+{
+	struct pci_tsm_attestation *pa = priv;
+	int ret;
+
+	/* All native paths, including notifications, use PCI -> attestation. */
+	ret = down_write_killable(&pci_tsm_rwsem);
+	if (ret)
+		return ret;
+
+	if (pa->removing || !pa->pdev->tsm || pci_dev_is_disconnected(pa->pdev)) {
+		up_write(&pci_tsm_rwsem);
+		return -ENODEV;
+	}
+
+	ret = ctlv_attestation_op_pre(ctlv_priv_parent(pa), ctx);
+	if (ret) {
+		up_write(&pci_tsm_rwsem);
+		return ret;
+	}
+
+	pa->active = true;
+	pci_attestation_update_locked(pa, true);
+	return 0;
+}
+
+void ctlv_attestation_pci_op_post(void *priv, struct ctlv_op_ctx *ctx)
+{
+	struct pci_tsm_attestation *pa = priv;
+
+	pci_attestation_update_locked(pa, true);
+	pa->active = false;
+	ctlv_attestation_op_post(ctlv_priv_parent(pa), ctx);
+	up_write(&pci_tsm_rwsem);
+}
+
+static int pci_attestation_prepare(struct pci_tsm_attestation *pa, u64 generation)
+{
+	struct attestation *att = ctlv_priv_parent(pa);
+
+	if (generation != attestation_get_device_generation(att))
+		return -ESTALE;
+
+	return attestation_state_change_prepare(att);
+}
+
+static int pci_attestation_reply(struct pci_tsm_attestation *pa,
+		struct ctlv_cursor *reply)
+{
+	struct attestation *att = ctlv_priv_parent(pa);
+
+	if (!ctlv_attestation_pci_pci_generation_put(reply,
+				attestation_get_device_generation(att)))
+		return -EMSGSIZE;
+
+	return 0;
+}
+
+int ctlv_attestation_pci_evidence_refresh_action(void *priv, struct ctlv_op_ctx *ctx,
+		const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+	struct pci_tsm_attestation *pa = priv;
+	struct pci_tsm *tsm = pa->pdev->tsm;
+	const struct pci_tsm_ops *ops;
+	const void *challenge;
+	enum pci_tsm_evidence_type type;
+	u64 object;
+	u32 length = 0;
+	int ret;
+
+	object = ctlv_attestation_pci_evidence_refresh_object_get(request);
+	switch (object) {
+	case CTLV_ATTESTATION_EVIDENCE_BLOB_CLASS_SPDM_MEASUREMENTS:
+		type = PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS;
+		break;
+	case CTLV_ATTESTATION_EVIDENCE_BLOB_CLASS_TDISP_REPORT:
+		type = PCI_TSM_EVIDENCE_TYPE_REPORT;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	challenge = ctlv_attestation_pci_evidence_refresh_challenge_get(request, &length);
+	if ((type == PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS && length != 32) ||
+	    (type == PCI_TSM_EVIDENCE_TYPE_REPORT && length))
+		return -EINVAL;
+
+	ret = pci_attestation_prepare(pa,
+			ctlv_attestation_pci_evidence_refresh_generation_get(request));
+	if (ret)
+		return ret;
+
+	if (!tsm)
+		return -ENOTCONN;
+
+	if (tsm->error || device_cc_accepted(&pa->pdev->dev))

drivers/pci/tsm/attestation.c line 232

Quoted 10 lines
drivers/pci/tsm/attestation.c new file +10 -0
+		return -EBUSY;
+
+	ops = tsm->tsm_dev->pci_ops;
+	if (!ops->refresh_evidence)
+		return -EOPNOTSUPP;
+
+	ret = ops->refresh_evidence(tsm, type, 0, (void *)challenge, length);
+	/* Even a failed refresh may have changed device state. Retire all verdicts. */
+	if (ret)
+		tsm->error = true;

Does this make any refresh failure fatal until unlock? The CCA refresh in 11/11 says "failure preserve the old cache", but with tsm->error set I don't think the cache can be read or accepted?

The cached evidence is still the lock's evidence, collected after the lock. And per the cover letter a failed refresh already invalidates the earlier generations. Should the backend decide instead, and set tsm->error itself when it can't tell what happened to the device?

Also, this builds on struct pci_tsm_evidence from Dan's March posting [1]. The TDX guest RFC [2] and AMD's SEV-TIO guest RFC [3] both use device_evidence_create() from his July posting [4]. Is the plan to go with pci_tsm_evidence and drop device_evidence? Would help to know which one to build the TDX guest side on.

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/
[3] https://lore.kernel.org/all/[email protected]/
[4] https://lore.kernel.org/all/[email protected]/

DJ

author maintainer
#30 lore raw

Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts

Quoted 2 lines · Dave Jiang

On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote:

[ ... 232 lines skipped ... ]

Quoted 4 lines
Quoted 4 lines
drivers/pci/tsm/attestation.c new file +4 -0
+	ops = tsm->tsm_dev->pci_ops;
+	if (!ops->refresh_evidence)
+		return -EOPNOTSUPP;
+
Quoted 9 lines

drivers/pci/tsm/attestation.c line 232

Quoted 4 lines
drivers/pci/tsm/attestation.c new file +4 -0
+	ret = ops->refresh_evidence(tsm, type, 0, (void *)challenge, length);
+	/* Even a failed refresh may have changed device state. Retire all verdicts. */
+	if (ret)
+		tsm->error = true;

Does this make any refresh failure fatal until unlock? The CCA refresh in 11/11 says "failure preserve the old cache", but with tsm->error set I don't think the cache can be read or accepted?

Yes. The generic layer treats an evidence refresh failure as fatal. The backend can decide how to present that failure.

Quoted 6 lines

The cached evidence is still the lock's evidence, collected after the lock. And per the cover letter a failed refresh already invalidates the earlier generations. Should the backend decide instead, and set tsm->error itself when it can't tell what happened to the device?

For the CCA backend, evidence is replaced only after successful validation. If validation fails, the cached evidence remains unchanged. However, we could instead discard the existing evidence cache when a refresh starts.

Quoted 7 lines

Also, this builds on struct pci_tsm_evidence from Dan's March posting [1]. The TDX guest RFC [2] and AMD's SEV-TIO guest RFC [3] both use device_evidence_create() from his July posting [4]. Is the plan to go with pci_tsm_evidence and drop device_evidence? Would help to know which one to build the TDX guest side on.

Quoted 5 lines

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/
[3] https://lore.kernel.org/all/[email protected]/
[4] https://lore.kernel.org/all/[email protected]/

The include/linux/attestation.h do cover

enum attestation_device_trust_state {
ATTESTATION_DEVICE_TRUST_UNSET =
CTLV_ATTESTATION_DEVICE_TRUST_STATE_UNSET,
ATTESTATION_DEVICE_TRUST_NONE =
CTLV_ATTESTATION_DEVICE_TRUST_STATE_NONE,
ATTESTATION_DEVICE_TRUST_ADVERSARY =
CTLV_ATTESTATION_DEVICE_TRUST_STATE_ADVERSARY,
ATTESTATION_DEVICE_TRUST_AUTO =
CTLV_ATTESTATION_DEVICE_TRUST_STATE_AUTO,
ATTESTATION_DEVICE_TRUST_TCB = CTLV_ATTESTATION_DEVICE_TRUST_STATE_TCB,
};

I am still unclear how this will interact with generic device trust. We can only determine that after reviewing the device trust series.

Leon,
are there any plans to post it?

For the current PCI/TSM CTLV implementation, however, it makes sense to treat evidence as local to the TSM and keep it in pci_tsm_evidence.

-aneesh

maintainer
#31 lore raw

Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts

Quoted 20 lines · Aneesh Kumar K.V
Quoted 2 lines · Dave Jiang

On 10/28/26 8:26 AM, Aneesh Kumar K.V (Arm) wrote:

[ ... 232 lines skipped ... ]

Quoted 4 lines
Quoted 4 lines
drivers/pci/tsm/attestation.c new file +4 -0
+	ops = tsm->tsm_dev->pci_ops;
+	if (!ops->refresh_evidence)
+		return -EOPNOTSUPP;
+
Quoted 8 lines

drivers/pci/tsm/attestation.c line 232

Quoted 4 lines
drivers/pci/tsm/attestation.c new file +4 -0
+	ret = ops->refresh_evidence(tsm, type, 0, (void *)challenge, length);
+	/* Even a failed refresh may have changed device state. Retire all verdicts. */
+	if (ret)
+		tsm->error = true;

Does this make any refresh failure fatal until unlock? The CCA refresh in 11/11 says "failure preserve the old cache", but with tsm->error set I don't think the cache can be read or accepted?

Quoted 2 lines

Yes. The generic layer treats an evidence refresh failure as fatal. The backend can decide how to present that failure.

The locked generation scheme should fully protect against any desync issues though? Do we need to care here?

I also continue to dislike the use of the word 'cached' here, why is this like this? If someone wants evidence they should get the fresh evidence with their nonce returned. I can understand hanging on to certificates and other unsigned data but then what would even trigger a refresh of that? Something you can't refresh isn't a cache, it is just the immutable data.

Needs some more explanation I suppose, or maybe I haven't found the explanation yet.

Quoted 9 lines
Quoted 4 lines

The cached evidence is still the lock's evidence, collected after the lock. And per the cover letter a failed refresh already invalidates the earlier generations. Should the backend decide instead, and set tsm->error itself when it can't tell what happened to the device?

For the CCA backend, evidence is replaced only after successful validation. If validation fails, the cached evidence remains unchanged. However, we could instead discard the existing evidence cache when a refresh starts.

What validation?

Quoted 26 lines
Quoted 11 lines

Also, this builds on struct pci_tsm_evidence from Dan's March posting [1]. The TDX guest RFC [2] and AMD's SEV-TIO guest RFC [3] both use device_evidence_create() from his July posting [4]. Is the plan to go with pci_tsm_evidence and drop device_evidence? Would help to know which one to build the TDX guest side on.

[1] https://lore.kernel.org/all/[email protected]/
[2] https://lore.kernel.org/all/[email protected]/
[3] https://lore.kernel.org/all/[email protected]/
[4] https://lore.kernel.org/all/[email protected]/

The include/linux/attestation.h do cover

enum attestation_device_trust_state {
ATTESTATION_DEVICE_TRUST_UNSET =
CTLV_ATTESTATION_DEVICE_TRUST_STATE_UNSET,
ATTESTATION_DEVICE_TRUST_NONE =
CTLV_ATTESTATION_DEVICE_TRUST_STATE_NONE,
ATTESTATION_DEVICE_TRUST_ADVERSARY =
CTLV_ATTESTATION_DEVICE_TRUST_STATE_ADVERSARY,
ATTESTATION_DEVICE_TRUST_AUTO =
CTLV_ATTESTATION_DEVICE_TRUST_STATE_AUTO,
ATTESTATION_DEVICE_TRUST_TCB = CTLV_ATTESTATION_DEVICE_TRUST_STATE_TCB,
};

Quoted 2 lines

I am still unclear how this will interact with generic device trust. We can only determine that after reviewing the device trust series.

I don't know why attestation subsystem would have an opinion or interfaces for device trust?

Jason

#32 lore raw

Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts

In reply to Aneesh Kumar K.V (Arm)

Wed, Oct 28, 2026 at 04:26:15PM +0100, [email protected] wrote:

[..]

Quoted 14 lines
drivers/pci/tsm/attestation.c new file +14 -0
+int ctlv_attestation_pci_state_get_action(void *priv, struct ctlv_op_ctx *ctx,
+		const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+	struct pci_tsm_attestation *pa = priv;
+	struct pci_dev *pdev = pa->pdev;
+	struct pci_tsm *tsm = pdev->tsm;
+	bool accepted = device_cc_accepted(&pdev->dev);
+	bool ok = !pci_attestation_reply(pa, reply);
+
+	if (tsm && tsm->dsm_dev == pdev) {
+		struct pci_tsm_pf0 *pf0 = container_of(tsm, struct pci_tsm_pf0, base_tsm);
+
+		accepted = pf0->stream_connected;
+	}

drivers/pci/tsm/attestation.c line 261

Quoted 10 lines
drivers/pci/tsm/attestation.c new file +10 -0
+
+	ok &= ctlv_attestation_pci_pci_connected_put(reply, tsm && tsm->dsm_dev);
+	ok &= ctlv_attestation_pci_pci_locked_put(reply, tsm && !tsm->dsm_dev);
+	ok &= ctlv_attestation_pci_pci_accepted_put(reply, accepted);
+
+	if (tsm) {
+		ok &= ctlv_attestation_pci_pci_tsm_id_put(reply, tsm->tsm_dev->id);
+		if (tsm->dsm_dev)
+			ok &= ctlv_attestation_pci_pci_dsm_put(reply, pci_name(tsm->dsm_dev));
+	}

I had in mind SPDM_GET for DISCOVERED/AUTHENTICATED and the selected authentication slot, with guest TDISP state coming from DEVSEC_STATE_GET. Instance lifetime already follows connect/lock and disconnect/unlock.

Could we use those state models instead of adding another PCI-specific connected/locked/accepted model? Also, pci.generation appears to be the common device-generation under another attribute name, not a separate counter, odd.

[..]

#33 lore raw

Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts

In reply to Aneesh Kumar K.V (Arm)

Wed, Oct 28, 2026 at 04:26:15PM +0100, [email protected] wrote:

[..]

Quoted 10 lines

available authentication methods for the device.

When present and the tsm/ attribute directory is present, the

Documentation/ABI/testing/sysfs-bus-pci +3 -2
-		authenticated attribute is an alias for the device 'connect'
-		state. See the 'tsm/connect' attribute for more details.
+		authenticated attribute reports the TSM name only after secure
+		session and stream setup succeed, otherwise an empty string.
+		Connection control uses tsm/connect and tsm/disconnect.

This is a "link" TSM attribute, see Documentation/ABI/testing/sysfs-class-tsm.

[..]

Quoted 25 lines

static ssize_t authenticated_show(struct device *dev,
struct device_attribute *attr, char *buf)
{

drivers/pci/tsm/core.c +17 -5
-	/*
-	 * When the SPDM session established via TSM the 'authenticated' state
-	 * of the device is identical to the connect state.
-	 */
-	return connect_show(dev, attr, buf);
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct pci_tsm_pf0 *pf0;
+	int rc;
+
+	ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+	rc = ACQUIRE_ERR(rwsem_read_intr, &lock);
+	if (rc)
+		return rc;
+
+	if (!pdev->tsm || !pdev->tsm->dsm_dev)
+		return sysfs_emit(buf, "\n");
+
+	pf0 = to_pci_tsm_pf0(pdev->tsm);
+	if (!pf0 || !pf0->stream_connected || pf0->base_tsm.error)
+		return sysfs_emit(buf, "\n");
+
+	return sysfs_emit(buf, "%s\n", dev_name(&pdev->tsm->tsm_dev->dev));
Quoted 2 lines

}
static DEVICE_ATTR_RO(authenticated);

I had in mind removing this attribute alongside the staged connect change and reporting authentication state through SPDM_GET.

This instead retains the attribute and changes when it reports the TSM name. Could we settle that together with the SPDM_GET interface rather than maintaining parallel authentication-state queries?

Jason, what do you think?

[..]

maintainer
#34 lore raw

Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts

Quoted 48 lines · Jiri Pirko

Wed, Oct 28, 2026 at 04:26:15PM +0100, [email protected] wrote:

[..]

Quoted 10 lines

available authentication methods for the device.

When present and the tsm/ attribute directory is present, the

Documentation/ABI/testing/sysfs-bus-pci +3 -2
-		authenticated attribute is an alias for the device 'connect'
-		state. See the 'tsm/connect' attribute for more details.
+		authenticated attribute reports the TSM name only after secure
+		session and stream setup succeed, otherwise an empty string.
+		Connection control uses tsm/connect and tsm/disconnect.

This is a "link" TSM attribute, see Documentation/ABI/testing/sysfs-class-tsm.

[..]

Quoted 25 lines

static ssize_t authenticated_show(struct device *dev,
struct device_attribute *attr, char *buf)
{

drivers/pci/tsm/core.c +17 -5
-	/*
-	 * When the SPDM session established via TSM the 'authenticated' state
-	 * of the device is identical to the connect state.
-	 */
-	return connect_show(dev, attr, buf);
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct pci_tsm_pf0 *pf0;
+	int rc;
+
+	ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+	rc = ACQUIRE_ERR(rwsem_read_intr, &lock);
+	if (rc)
+		return rc;
+
+	if (!pdev->tsm || !pdev->tsm->dsm_dev)
+		return sysfs_emit(buf, "\n");
+
+	pf0 = to_pci_tsm_pf0(pdev->tsm);
+	if (!pf0 || !pf0->stream_connected || pf0->base_tsm.error)
+		return sysfs_emit(buf, "\n");
+
+	return sysfs_emit(buf, "%s\n", dev_name(&pdev->tsm->tsm_dev->dev));
Quoted 2 lines

}
static DEVICE_ATTR_RO(authenticated);

I had in mind removing this attribute alongside the staged connect change and reporting authentication state through SPDM_GET.

Quoted 5 lines

This instead retains the attribute and changes when it reports the TSM name. Could we settle that together with the SPDM_GET interface rather than maintaining parallel authentication-state queries?

Jason, what do you think?

I'm not keen on any of the sysfs, I'd like to understand who is expected to use this.

If we imagine a systemd-device-accept-d kind of flow I don't see any need to duplicate things in attestation subsystem in sysfs..

I'm not sure 'stream connected' is even right, as in my last note we should not consider the thing authenticated until we actually pass userspace EVIDENCE_ACCEPT, even if the TSM has established a SPDM session.

Jason

#35 lore raw

Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts

Quoted 58 lines · Fri, Oct 30, 2026 at 04:39:00PM +0100, [email protected] wrote:
Quoted 48 lines · Jiri Pirko

Wed, Oct 28, 2026 at 04:26:15PM +0100, [email protected] wrote:

[..]

Quoted 10 lines

available authentication methods for the device.

When present and the tsm/ attribute directory is present, the

Documentation/ABI/testing/sysfs-bus-pci +3 -2
-		authenticated attribute is an alias for the device 'connect'
-		state. See the 'tsm/connect' attribute for more details.
+		authenticated attribute reports the TSM name only after secure
+		session and stream setup succeed, otherwise an empty string.
+		Connection control uses tsm/connect and tsm/disconnect.

This is a "link" TSM attribute, see Documentation/ABI/testing/sysfs-class-tsm.

[..]

Quoted 25 lines

static ssize_t authenticated_show(struct device *dev,
struct device_attribute *attr, char *buf)
{

drivers/pci/tsm/core.c +17 -5
-	/*
-	 * When the SPDM session established via TSM the 'authenticated' state
-	 * of the device is identical to the connect state.
-	 */
-	return connect_show(dev, attr, buf);
+	struct pci_dev *pdev = to_pci_dev(dev);
+	struct pci_tsm_pf0 *pf0;
+	int rc;
+
+	ACQUIRE(rwsem_read_intr, lock)(&pci_tsm_rwsem);
+	rc = ACQUIRE_ERR(rwsem_read_intr, &lock);
+	if (rc)
+		return rc;
+
+	if (!pdev->tsm || !pdev->tsm->dsm_dev)
+		return sysfs_emit(buf, "\n");
+
+	pf0 = to_pci_tsm_pf0(pdev->tsm);
+	if (!pf0 || !pf0->stream_connected || pf0->base_tsm.error)
+		return sysfs_emit(buf, "\n");
+
+	return sysfs_emit(buf, "%s\n", dev_name(&pdev->tsm->tsm_dev->dev));
Quoted 2 lines

}
static DEVICE_ATTR_RO(authenticated);

I had in mind removing this attribute alongside the staged connect change and reporting authentication state through SPDM_GET.

Quoted 5 lines

This instead retains the attribute and changes when it reports the TSM name. Could we settle that together with the SPDM_GET interface rather than maintaining parallel authentication-state queries?

Jason, what do you think?

I'm not keen on any of the sysfs, I'd like to understand who is expected to use this.

Quoted 7 lines

If we imagine a systemd-device-accept-d kind of flow I don't see any need to duplicate things in attestation subsystem in sysfs..

I'm not sure 'stream connected' is even right, as in my last note we should not consider the thing authenticated until we actually pass userspace EVIDENCE_ACCEPT, even if the TSM has established a SPDM session.

Agreed. The question if we can legally get rid of it at this point...

maintainer
#36 lore raw

Re: [RFC PATCH v1 08/11] PCI/TSM: Bind CTLV attestation to connected and locked contexts

Quoted 6 lines · Jiri Pirko
Quoted 4 lines

I'm not sure 'stream connected' is even right, as in my last note we should not consider the thing authenticated until we actually pass userspace EVIDENCE_ACCEPT, even if the TSM has established a SPDM session.

Agreed. The question if we can legally get rid of it at this point...

There is no functioning upstream implementation behind this I think we can purge it freely.

Jason

author maintainer
#37 lore raw

RFC PATCH 9/11 crypto/ccp: Defer SEV-TIO link setup until evidence acceptance

In reply to Aneesh Kumar K.V (Arm)

AMD SEV-TIO currently establishes its SPDM session and enables IDE during connect, before userspace can retrieve or accept the device identity. Move AMD link setup behind its evidence_accept callback.

During connect, reserve TC0's stream ID, create the firmware context, and forward only unencrypted identity discovery requests. Cache the complete slot-zero certificate chain as firmware receives it. Once firmware consumes that chain and yields its next request, pause the exchange without transmitting an authentication or session-establishment request. No IDE registers are programmed during this identity collection.

Keep the generic callback optional until the following patch converts CCA, the remaining legacy link backend, and requires evidence_accept for every link backend.

Cc Ashish Kalra <[email protected]>
Cc Tom Lendacky <[email protected]>
Cc John Allen <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 drivers/crypto/ccp/sev-dev-tio.c |  17 ++-
 drivers/crypto/ccp/sev-dev-tio.h |   5 +
 drivers/crypto/ccp/sev-dev-tsm.c | 189 ++++++++++++++++++++++++++-----
 3 files changed, 176 insertions(+), 35 deletions(-)
drivers/crypto/ccp/sev-dev-tio.c +8 -9
index c84e23982b43..ff51f09961c4 100644
@@ -639,25 +639,23 @@ static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data)
 static void spdm_ctrl_free(struct tsm_dsm_tio *dev_data)
 {
 	struct sev_tio_status *tio_status = to_tio_status(dev_data);
-	size_t len = tio_status->spdm_req_size_max -
-		(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
-		 sizeof(struct sla_buffer_hdr));
 	struct tsm_spdm *spdm = &dev_data->spdm;
 
 	sla_buffer_unmap(dev_data->resp, dev_data->respbuf);
 	sla_buffer_unmap(dev_data->req, dev_data->reqbuf);
 	spdm->rsp = NULL;
 	spdm->req = NULL;
-	sla_free(dev_data->req, len, true);
-	sla_free(dev_data->resp, len, false);
-	sla_free(dev_data->scratch, tio_status->spdm_scratch_size_max, true);
+	sla_free(dev_data->req, tio_status->spdm_req_size_max, true);
+	sla_free(dev_data->resp, tio_status->spdm_req_size_max, false);
+	sla_free(dev_data->scratch, dev_data->scratch_len, true);
 
 	dev_data->req.sla = 0;
 	dev_data->resp.sla = 0;
 	dev_data->scratch.sla = 0;
 	dev_data->respbuf = NULL;
 	dev_data->reqbuf = NULL;
-	sla_free(dev_data->output, tio_status->spdm_out_size_max, true);
+	sla_free(dev_data->output, dev_data->output_len, true);
+	dev_data->output = SLA_NULL;
 }
 
 static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
@@ -674,7 +672,7 @@ static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
 	dev_data->output = sla_alloc(dev_data->output_len, true);
 
 	if (IS_SLA_NULL(dev_data->req) || IS_SLA_NULL(dev_data->resp) ||
-	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->dev_ctx)) {
+	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->output)) {
 		ret = -ENOMEM;
 		goto free_spdm_exit;
 	}
@@ -773,7 +771,8 @@ int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id,
 free_data_pg_exit:
 	snp_free_firmware_page(data_pg);
 free_ctx_exit:
-	sla_free(create.dev_ctx_sla, tio_status->devctx_size, true);
+	sla_free(dev_data->dev_ctx, tio_status->devctx_size, true);
+	dev_data->dev_ctx = SLA_NULL;
 	return ret;
 }
 
drivers/crypto/ccp/sev-dev-tio.h +5 -0
index 67512b3dbc53..237da723483c 100644
@@ -58,6 +58,11 @@ struct tio_dsm {
 	struct pci_tsm_pf0 tsm;
 	struct tsm_dsm_tio data;
 	struct sev_device *sev;
+	bool session_connected;
+	bool certificate_pending;
+	u16 certificate_request_len;
+	size_t certificate_offset;
+	int pending_spdm;
 };
 
 /* Data object IDs */
drivers/crypto/ccp/sev-dev-tsm.c +163 -26

249 lines not on this page · raw message

Signature · 1 line
2.43.0
#38 lore raw

Re: [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance

Quoted 50 lines · Aneesh Kumar K.V (Arm)

AMD SEV-TIO currently establishes its SPDM session and enables IDE during connect, before userspace can retrieve or accept the device identity. Move AMD link setup behind its evidence_accept callback.

During connect, reserve TC0's stream ID, create the firmware context, and forward only unencrypted identity discovery requests. Cache the complete slot-zero certificate chain as firmware receives it. Once firmware consumes that chain and yields its next request, pause the exchange without transmitting an authentication or session-establishment request. No IDE registers are programmed during this identity collection.

Keep the generic callback optional until the following patch converts CCA, the remaining legacy link backend, and requires evidence_accept for every link backend.

Cc Ashish Kalra <[email protected]>
Cc Tom Lendacky <[email protected]>
Cc John Allen <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
  drivers/crypto/ccp/sev-dev-tio.c |  17 ++-
  drivers/crypto/ccp/sev-dev-tio.h |   5 +
  drivers/crypto/ccp/sev-dev-tsm.c | 189 ++++++++++++++++++++++++++-----
  3 files changed, 176 insertions(+), 35 deletions(-)
drivers/crypto/ccp/sev-dev-tio.c +2 -6
index c84e23982b43..ff51f09961c4 100644
@@ -639,25 +639,23 @@ static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data)
  static void spdm_ctrl_free(struct tsm_dsm_tio *dev_data)
  {
  	struct sev_tio_status *tio_status = to_tio_status(dev_data);
-	size_t len = tio_status->spdm_req_size_max -
-		(sla_dobj_id_to_size(SPDM_DOBJ_ID_RESP) +
-		 sizeof(struct sla_buffer_hdr));
  	struct tsm_spdm *spdm = &dev_data->spdm;
 
  	sla_buffer_unmap(dev_data->resp, dev_data->respbuf);
  	sla_buffer_unmap(dev_data->req, dev_data->reqbuf);
  	spdm->rsp = NULL;
  	spdm->req = NULL;
-	sla_free(dev_data->req, len, true);
-	sla_free(dev_data->resp, len, false);
-	sla_free(dev_data->scratch, tio_status->spdm_scratch_size_max, true);
+	sla_free(dev_data->req, tio_status->spdm_req_size_max, true);
+	sla_free(dev_data->resp, tio_status->spdm_req_size_max, false);

drivers/crypto/ccp/sev-dev-tio.c static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data) · line 658

Quoted 10 lines
drivers/crypto/ccp/sev-dev-tio.c +3 -1
+	sla_free(dev_data->scratch, dev_data->scratch_len, true);
 
  	dev_data->req.sla = 0;
  	dev_data->resp.sla = 0;
  	dev_data->scratch.sla = 0;
  	dev_data->respbuf = NULL;
  	dev_data->reqbuf = NULL;
-	sla_free(dev_data->output, tio_status->spdm_out_size_max, true);
+	sla_free(dev_data->output, dev_data->output_len, true);
+	dev_data->output = SLA_NULL;

This is a bugfix (thanks for pointing!) and does not belong here. Want to repost, or I can (and should, really)?

drivers/crypto/ccp/sev-dev-tio.c static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data) · line 675

Quoted 9 lines
drivers/crypto/ccp/sev-dev-tio.c +1 -1
  }
 
  static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
@@ -674,7 +672,7 @@ static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
  	dev_data->output = sla_alloc(dev_data->output_len, true);
 
  	if (IS_SLA_NULL(dev_data->req) || IS_SLA_NULL(dev_data->resp) ||
-	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->dev_ctx)) {
+	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->output)) {

the same.

drivers/crypto/ccp/sev-dev-tio.c int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id, · line 774

Quoted 9 lines
drivers/crypto/ccp/sev-dev-tio.c +1 -1
  		ret = -ENOMEM;
  		goto free_spdm_exit;
  	}
@@ -773,7 +771,8 @@ int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id,
  free_data_pg_exit:
  	snp_free_firmware_page(data_pg);
  free_ctx_exit:
-	sla_free(create.dev_ctx_sla, tio_status->devctx_size, true);
+	sla_free(dev_data->dev_ctx, tio_status->devctx_size, true);

and this. Is this codex? :)

Quoted 154 lines
drivers/crypto/ccp/sev-dev-tio.c +1 -0
+	dev_data->dev_ctx = SLA_NULL;
  	return ret;
  }
 
drivers/crypto/ccp/sev-dev-tio.h +5 -0
index 67512b3dbc53..237da723483c 100644
@@ -58,6 +58,11 @@ struct tio_dsm {
  	struct pci_tsm_pf0 tsm;
  	struct tsm_dsm_tio data;
  	struct sev_device *sev;
+	bool session_connected;
+	bool certificate_pending;
+	u16 certificate_request_len;
+	size_t certificate_offset;
+	int pending_spdm;
  };
 
  /* Data object IDs */
drivers/crypto/ccp/sev-dev-tsm.c +104 -1
index e905ed9de9b5..14d7ef84ee36 100644
@@ -9,6 +9,7 @@
  #include <linux/pci-doe.h>
  #include <linux/bitfield.h>
  #include <linux/module.h>
+#include <linux/unaligned.h>
 
  #include <asm/sev-common.h>
  #include <asm/sev.h>
@@ -163,7 +164,7 @@ static int streams_register(struct pci_ide **ide)
  static void streams_unregister(struct pci_ide **ide)
  {
  	for (int i = 0; i < TIO_IDE_MAX_TC; ++i)
-		if (ide[i])
+		if (ide[i] && ide[i]->name)
  			pci_ide_stream_unregister(ide[i]);
  }
 
@@ -253,6 +254,7 @@ static void dsm_remove(struct pci_tsm *tsm)
  	if (is_pci_tsm_pf0(pdev)) {
  		struct tio_dsm *dsm = container_of(tsm, struct tio_dsm, tsm.base_tsm);
 
+		kvfree(tsm->evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0].data);
  		pci_tsm_pf0_destructor(&dsm->tsm);
  		kfree(dsm);
  	}
@@ -283,12 +285,120 @@ static int dsm_create(struct tio_dsm *dsm)
  	return sev_tio_dev_create(&dsm->data, device_id, root_port_id, segment_id);
  }
 
+/* Only identity discovery requests may leave the host before acceptance. */
+static int dsm_identity_request(struct tio_dsm *dsm)
+{
+	struct tsm_spdm *spdm = &dsm->data.spdm;
+	const u8 *req = spdm->req;
+
+	if (spdm->req_len < 4)
+		return -EPROTO;
+
+	switch (req[1]) {
+	case 0x84: /* GET_VERSION */
+	case 0xe1: /* GET_CAPABILITIES */
+	case 0xe3: /* NEGOTIATE_ALGORITHMS */
+	case 0x81: /* GET_DIGESTS */
+		dsm->certificate_pending = false;
+		return 0;
+	case 0x82: /* GET_CERTIFICATE */
+		if (spdm->req_len < 8 || (req[2] & 0xf) ||
+		    get_unaligned_le16(req + 4) != dsm->certificate_offset)
+			return -EPROTO;
+		dsm->certificate_request_len = get_unaligned_le16(req + 6);
+		if (!dsm->certificate_request_len)
+			return -EPROTO;
+		dsm->certificate_pending = true;
+		return 0;
+	case 0xff: /* RESPOND_IF_READY preserves the pending certificate request. */
+		return 0;
+	default:
+		return -EPROTO;
+	}
+}
+
+static int dsm_cache_certificate(struct tio_dsm *dsm)
+{
+	struct pci_tsm_evidence_object *object =
+		&dsm->tsm.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+	struct tsm_spdm *spdm = &dsm->data.spdm;
+	const u8 *rsp = spdm->rsp;
+	size_t portion, remainder, total;
+
+	if (spdm->rsp_len < 4)
+		return -EPROTO;
+	if (rsp[1] != 0x02) /* CERTIFICATE */
+		return 0;
+	if (!dsm->certificate_pending || spdm->rsp_len < 8 || (rsp[2] & 0xf))
+		return -EPROTO;
+
+	portion = get_unaligned_le16(rsp + 4);
+	remainder = get_unaligned_le16(rsp + 6);
+	total = dsm->certificate_offset + portion + remainder;
+	if (!portion || portion > dsm->certificate_request_len ||
+	    portion > spdm->rsp_len - 8 || total > U16_MAX)
+		return -EPROTO;
+
+	if (!object->data) {
+		object->data = kvmalloc(total, GFP_KERNEL);
+		if (!object->data)
+			return -ENOMEM;
+		object->len = total;
+	} else if (total != object->len) {
+		return -EPROTO;
+	}
+
+	memcpy((u8 *)object->data + dsm->certificate_offset, rsp + 8, portion);
+	dsm->certificate_offset += portion;
+	dsm->certificate_pending = false;
+	if (!remainder && (object->len < 4 ||
+			   get_unaligned_le16(object->data) != object->len))
+		return -EPROTO;
+
+	return 0;
+}
+
+static int dsm_collect_identity(struct tio_dsm *dsm, int ret)
+{
+	struct pci_tsm_evidence_object *object =
+		&dsm->tsm.base_tsm.evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+	struct tsm_spdm *spdm = &dsm->data.spdm;
+
+	while (ret == PCI_DOE_FEATURE_CMA || ret == PCI_DOE_FEATURE_SSESSION) {
+		/* Firmware consumed this exact chain; resume only after acceptance. */
+		if (object->len && dsm->certificate_offset == object->len) {
+			dsm->pending_spdm = ret;
+			return 0;
+		}
+		if (ret != PCI_DOE_FEATURE_CMA)
+			return -EPROTO;
+		ret = dsm_identity_request(dsm);
+		if (ret)
+			return ret;
+
+		ret = pci_doe(dsm->tsm.doe_mb, PCI_VENDOR_ID_PCI_SIG,
+			      PCI_DOE_FEATURE_CMA, spdm->req, spdm->req_len,
+			      spdm->rsp, spdm->rsp_len);
+		if (ret < 0)
+			return ret;
+		spdm->rsp_len = ret;
+		ret = dsm_cache_certificate(dsm);
+		if (ret)
+			return ret;
+		ret = sev_tio_continue(&dsm->data);

drivers/crypto/ccp/sev-dev-tsm.c static int dsm_create(struct tio_dsm *dsm) · line 398

Quoted 10 lines
drivers/crypto/ccp/sev-dev-tsm.c +8 -0
+	}
+
+	/* Connect must stop before session establishment, with a complete chain. */
+	return ret < 0 ? ret : -EPROTO;
+}
+
+static void dsm_disconnect(struct pci_dev *pdev);
+
  static int dsm_connect(struct pci_dev *pdev)
  {

(indent)

Quoted 13 lines
drivers/crypto/ccp/sev-dev-tsm.c +0 -2
  	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
  	struct tsm_dsm_tio *dev_data = &dsm->data;
  	u8 ids[TIO_IDE_MAX_TC];
-	u8 tc_mask;
  	int ret;
 
  	if (pci_find_doe_mailbox(pdev, PCI_VENDOR_ID_PCI_SIG,
@@ -300,35 +410,54 @@ static int dsm_connect(struct pci_dev *pdev)
  	ret = stream_alloc(pdev, dev_data->ide, 0);
  	if (ret)
  		return ret;
-
  	ret = dsm_create(dsm);

drivers/crypto/ccp/sev-dev-tsm.c static int dsm_connect(struct pci_dev *pdev) · line 422

Quoted 10 lines
drivers/crypto/ccp/sev-dev-tsm.c +8 -1
  	if (ret)
-		goto ide_free_exit;
+		goto disconnect;
+
+	/* Reserve TC0's stream ID without programming or enabling IDE. */
+	memset(ids, 0xff, sizeof(ids));
+	ids[0] = dev_data->ide[0]->stream_id;
+	dev_data->cert_slot = 0;
+	ret = sev_tio_dev_connect(dev_data, BIT(0), ids, dev_data->cert_slot);
+	ret = dsm_collect_identity(dsm, ret);

Cannot librspdm do that collection, directly via DOE? Ideally in the generic code? There nothing special for the PSP to do and stopping SEV-TIO's connect half way is weird.

Quoted 18 lines
drivers/crypto/ccp/sev-dev-tsm.c +13 -3
+	if (!ret)
+		return 0;
+
+disconnect:
+	dsm_disconnect(pdev);
+	return ret;
+}
 
-	tc_mask = streams_setup(dev_data->ide, ids);
+static int dsm_evidence_accept(struct pci_dev *pdev)
+{
+	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
+	struct tsm_dsm_tio *dev_data = &dsm->data;
+	u8 ids[TIO_IDE_MAX_TC];
+	int ret;
 
-	dev_data->cert_slot = pdev->tsm->evidence.slot;
-	ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);

drivers/crypto/ccp/sev-dev-tsm.c static int dsm_connect(struct pci_dev *pdev) · line 443

Quoted 10 lines
drivers/crypto/ccp/sev-dev-tsm.c +5 -4
-	ret = sev_tio_spdm_cmd(dsm, ret);
-	if (ret)
-		goto free_exit;
+	if (pdev->tsm->evidence.slot)
+		return -EOPNOTSUPP;
+	if (!dsm->pending_spdm)
+		return -ENODATA;
 
-	streams_enable(dev_data->ide);
+	streams_setup(dev_data->ide, ids);

I kinda get the appeal of not enabling IDE before accepting but in practice the host still has to have a knob in IOMMU to prevent a malicious device from DMAing T=1 trafic so accept() would trigger enable_dma() hook.

Quoted 57 lines
drivers/crypto/ccp/sev-dev-tsm.c +23 -15
+	ret = sev_tio_spdm_cmd(dsm, dsm->pending_spdm);
+	dsm->pending_spdm = 0;
+	if (ret)
+		goto err_session;
+	dsm->session_connected = true;
 
+	ret = streams_enable(dev_data->ide);
+	if (ret)
+		goto err_session;
  	ret = streams_register(dev_data->ide);
  	if (ret)
-		goto free_exit;
-
+		goto err_session;
  	return 0;
 
-free_exit:
-	sev_tio_dev_reclaim(dev_data);
-
-	streams_disable(dev_data->ide);
-ide_free_exit:
-
-	streams_teardown(dev_data->ide);
-
+err_session:
+	/* Disconnect owns cleanup after firmware or IDE setup has started. */
+	pdev->tsm->error = true;
  	return ret;
  }
 
@@ -339,14 +468,20 @@ static void dsm_disconnect(struct pci_dev *pdev)
  	struct tsm_dsm_tio *dev_data = &dsm->data;
  	int ret;
 
-	ret = sev_tio_dev_disconnect(dev_data, force);
-	ret = sev_tio_spdm_cmd(dsm, ret);
-	if (ret && !force) {
-		ret = sev_tio_dev_disconnect(dev_data, true);
-		sev_tio_spdm_cmd(dsm, ret);
+	/* A paused or failed connect needs forced firmware cleanup. */
+	if (dev_data->reqbuf && dev_data->respbuf) {
+		force |= !dsm->session_connected;
+		ret = sev_tio_dev_disconnect(dev_data, force);
+		ret = sev_tio_spdm_cmd(dsm, ret);
+		if (ret && !force) {
+			ret = sev_tio_dev_disconnect(dev_data, true);
+			sev_tio_spdm_cmd(dsm, ret);
+		}
  	}
 
  	sev_tio_dev_reclaim(dev_data);
+	dsm->session_connected = false;
+	dsm->pending_spdm = 0;
 
  	streams_disable(dev_data->ide);
  	streams_unregister(dev_data->ide);

drivers/crypto/ccp/sev-dev-tsm.c static struct pci_tsm_ops sev_tsm_ops = { · line 495

Quoted 4 lines
drivers/crypto/ccp/sev-dev-tsm.c +1 -0
@@ -357,6 +492,8 @@ static struct pci_tsm_ops sev_tsm_ops = {
  	.probe = dsm_probe,
  	.remove = dsm_remove,
  	.connect = dsm_connect,
+	.evidence_accept = dsm_evidence_accept,

I saw in your branch you are ditching the connect() hook, is that the plan? Thanks,

Quoted 4 lines
drivers/crypto/ccp/sev-dev-tsm.c +1 -0
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
  	.disconnect = dsm_disconnect,
  };
 
Signature · 1 line
Alexey
author maintainer
#39 lore raw

Re: [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance

Quoted 4 lines · Alexey Kardashevskiy
Quoted 3 lines · Aneesh Kumar K.V (Arm)

AMD SEV-TIO currently establishes its SPDM session and enables IDE during connect, before userspace can retrieve or accept the device identity. Move AMD link setup behind its evidence_accept callback.

[ ... 49 lines skipped ... ]

Quoted 23 lines

drivers/crypto/ccp/sev-dev-tio.c static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data) · line 658

Quoted 8 lines
  	dev_data->req.sla = 0;
  	dev_data->resp.sla = 0;
  	dev_data->scratch.sla = 0;
  	dev_data->respbuf = NULL;
  	dev_data->reqbuf = NULL;
drivers/crypto/ccp/sev-dev-tio.c +2 -1
-	sla_free(dev_data->output, tio_status->spdm_out_size_max, true);
+	sla_free(dev_data->output, dev_data->output_len, true);
+	dev_data->output = SLA_NULL;

This is a bugfix (thanks for pointing!) and does not belong here. Want to repost, or I can (and should, really)?

drivers/crypto/ccp/sev-dev-tio.c static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data) · line 675

Quoted 9 lines
  }

static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)

drivers/crypto/ccp/sev-dev-tio.c +1 -1
@@ -674,7 +672,7 @@ static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
  	dev_data->output = sla_alloc(dev_data->output_len, true);
 
  	if (IS_SLA_NULL(dev_data->req) || IS_SLA_NULL(dev_data->resp) ||
-	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->dev_ctx)) {
+	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->output)) {

the same.

Quoted 12 lines

drivers/crypto/ccp/sev-dev-tio.c int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id, · line 774

Quoted 9 lines
drivers/crypto/ccp/sev-dev-tio.c +1 -1
  		ret = -ENOMEM;
  		goto free_spdm_exit;
  	}
@@ -773,7 +771,8 @@ int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id,
  free_data_pg_exit:
  	snp_free_firmware_page(data_pg);
  free_ctx_exit:
-	sla_free(create.dev_ctx_sla, tio_status->devctx_size, true);
+	sla_free(dev_data->dev_ctx, tio_status->devctx_size, true);

and this. Is this codex? :)

Yes.

Quoted 5 lines

drivers/crypto/ccp/sev-dev-tio.c int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id, · line 778

Quoted 5 lines
drivers/crypto/ccp/sev-dev-tio.c +1 -0
+	dev_data->dev_ctx = SLA_NULL;
  	return ret;
  }
 

[ ... 179 lines skipped ... ]

Quoted 12 lines

drivers/crypto/ccp/sev-dev-tsm.c static int dsm_connect(struct pci_dev *pdev) · line 422

Quoted 7 lines
drivers/crypto/ccp/sev-dev-tsm.c +7 -0
+
+	/* Reserve TC0's stream ID without programming or enabling IDE. */
+	memset(ids, 0xff, sizeof(ids));
+	ids[0] = dev_data->ide[0]->stream_id;
+	dev_data->cert_slot = 0;
+	ret = sev_tio_dev_connect(dev_data, BIT(0), ids, dev_data->cert_slot);
+	ret = dsm_collect_identity(dsm, ret);

Cannot librspdm do that collection, directly via DOE? Ideally in the generic code? There nothing special for the PSP to do and stopping SEV-TIO's connect half way is weird.

This patch does not use rust-spdm and avoids introducing that dependency. It uses slot 0 to initiate the session, retrieve the certificate chain, and return it to the host. After host approval, it resumes the session and enables IDE. Only slot 0 is supported, and the host can reject the device identity before IDE is enabled.

Quoted 28 lines
Quoted 28 lines
drivers/crypto/ccp/sev-dev-tsm.c +17 -6
+	if (!ret)
+		return 0;
+
+disconnect:
+	dsm_disconnect(pdev);
+	return ret;
+}
 
-	tc_mask = streams_setup(dev_data->ide, ids);
+static int dsm_evidence_accept(struct pci_dev *pdev)
+{
+	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
+	struct tsm_dsm_tio *dev_data = &dsm->data;
+	u8 ids[TIO_IDE_MAX_TC];
+	int ret;
 
-	dev_data->cert_slot = pdev->tsm->evidence.slot;
-	ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);
-	ret = sev_tio_spdm_cmd(dsm, ret);
-	if (ret)
-		goto free_exit;
+	if (pdev->tsm->evidence.slot)
+		return -EOPNOTSUPP;
+	if (!dsm->pending_spdm)
+		return -ENODATA;
Quoted 9 lines

drivers/crypto/ccp/sev-dev-tsm.c static int dsm_connect(struct pci_dev *pdev) · line 443

Quoted 2 lines
drivers/crypto/ccp/sev-dev-tsm.c +1 -1
-	streams_enable(dev_data->ide);
+	streams_setup(dev_data->ide, ids);

I kinda get the appeal of not enabling IDE before accepting but in practice the host still has to have a knob in IOMMU to prevent a malicious device from DMAing T=1 trafic so accept() would trigger enable_dma() hook.

This enables IDE; it is not the TDISP RUN transition. Am I missing something here?

Quoted 56 lines
Quoted 56 lines
drivers/crypto/ccp/sev-dev-tsm.c +23 -15
+	ret = sev_tio_spdm_cmd(dsm, dsm->pending_spdm);
+	dsm->pending_spdm = 0;
+	if (ret)
+		goto err_session;
+	dsm->session_connected = true;
 
+	ret = streams_enable(dev_data->ide);
+	if (ret)
+		goto err_session;
  	ret = streams_register(dev_data->ide);
  	if (ret)
-		goto free_exit;
-
+		goto err_session;
  	return 0;
 
-free_exit:
-	sev_tio_dev_reclaim(dev_data);
-
-	streams_disable(dev_data->ide);
-ide_free_exit:
-
-	streams_teardown(dev_data->ide);
-
+err_session:
+	/* Disconnect owns cleanup after firmware or IDE setup has started. */
+	pdev->tsm->error = true;
  	return ret;
  }
@@ -339,14 +468,20 @@ static void dsm_disconnect(struct pci_dev *pdev)
  	struct tsm_dsm_tio *dev_data = &dsm->data;
  	int ret;
 
-	ret = sev_tio_dev_disconnect(dev_data, force);
-	ret = sev_tio_spdm_cmd(dsm, ret);
-	if (ret && !force) {
-		ret = sev_tio_dev_disconnect(dev_data, true);
-		sev_tio_spdm_cmd(dsm, ret);
+	/* A paused or failed connect needs forced firmware cleanup. */
+	if (dev_data->reqbuf && dev_data->respbuf) {
+		force |= !dsm->session_connected;
+		ret = sev_tio_dev_disconnect(dev_data, force);
+		ret = sev_tio_spdm_cmd(dsm, ret);
+		if (ret && !force) {
+			ret = sev_tio_dev_disconnect(dev_data, true);
+			sev_tio_spdm_cmd(dsm, ret);
+		}
  	}
 
  	sev_tio_dev_reclaim(dev_data);
+	dsm->session_connected = false;
+	dsm->pending_spdm = 0;
 
Quoted 10 lines

drivers/crypto/ccp/sev-dev-tsm.c static struct pci_tsm_ops sev_tsm_ops = { · line 495

Quoted 7 lines
drivers/crypto/ccp/sev-dev-tsm.c +1 -0
  	streams_disable(dev_data->ide);
  	streams_unregister(dev_data->ide);
@@ -357,6 +492,8 @@ static struct pci_tsm_ops sev_tsm_ops = {
  	.probe = dsm_probe,
  	.remove = dsm_remove,
  	.connect = dsm_connect,
+	.evidence_accept = dsm_evidence_accept,

I saw in your branch you are ditching the connect() hook, is that the plan? Thanks,

Yes. In the full branch, once rust-spdm is integrated, this entire flow moves to the SPDM accept/evidence_accept phase.

Quoted 7 lines

drivers/crypto/ccp/sev-dev-tsm.c static struct pci_tsm_ops sev_tsm_ops = { · line 499

Quoted 6 lines
drivers/crypto/ccp/sev-dev-tsm.c +1 -0
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
  	.disconnect = dsm_disconnect,
  };
 

-aneesh

maintainer
#40 lore raw

Re: [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance

Quoted 11 lines · Aneesh Kumar K.V
Quoted 10 lines

drivers/crypto/ccp/sev-dev-tsm.c static int dsm_connect(struct pci_dev *pdev) · line 422

Quoted 6 lines
drivers/crypto/ccp/sev-dev-tsm.c +6 -0
+	/* Reserve TC0's stream ID without programming or enabling IDE. */
+	memset(ids, 0xff, sizeof(ids));
+	ids[0] = dev_data->ide[0]->stream_id;
+	dev_data->cert_slot = 0;
+	ret = sev_tio_dev_connect(dev_data, BIT(0), ids, dev_data->cert_slot);
+	ret = dsm_collect_identity(dsm, ret);

Cannot librspdm do that collection, directly via DOE? Ideally in the generic code? There nothing special for the PSP to do and stopping SEV-TIO's connect half way is weird.

Quoted 5 lines

This patch does not use rust-spdm and avoids introducing that dependency. It uses slot 0 to initiate the session, retrieve the certificate chain, and return it to the host. After host approval, it resumes the session and enables IDE. Only slot 0 is supported, and the host can reject the device identity before IDE is enabled.

I'm working toward and exact flow on what all the drivers should do, including the roadmap to SPDM 1.3 multikey. That should answer these questions. It wil look like close to this:

https://lore.kernel.org/r/[email protected]

AFAICT AMD's current stuff fits fine, and the PSP protocol will need the same adjustment as everyone else for 1.3 multikey.

Jason

#41 lore raw

Re: [RFC PATCH v1 09/11] crypto/ccp: Defer SEV-TIO link setup until evidence acceptance

In reply to Aneesh Kumar K.V
Quoted 19 lines · Aneesh Kumar K.V
Quoted 4 lines · Alexey Kardashevskiy
Quoted 3 lines · Aneesh Kumar K.V (Arm)

AMD SEV-TIO currently establishes its SPDM session and enables IDE during connect, before userspace can retrieve or accept the device identity. Move AMD link setup behind its evidence_accept callback.

[ ... 49 lines skipped ... ]

Quoted 10 lines

drivers/crypto/ccp/sev-dev-tio.c static void spdm_ctrl_init(struct spdm_ctrl *ctrl, struct tsm_dsm_tio *dev_data) · line 658

Quoted 8 lines
   	dev_data->req.sla = 0;
   	dev_data->resp.sla = 0;
   	dev_data->scratch.sla = 0;
   	dev_data->respbuf = NULL;
   	dev_data->reqbuf = NULL;
drivers/crypto/ccp/sev-dev-tio.c +2 -1
-	sla_free(dev_data->output, tio_status->spdm_out_size_max, true);
+	sla_free(dev_data->output, dev_data->output_len, true);
+	dev_data->output = SLA_NULL;

This is a bugfix (thanks for pointing!) and does not belong here. Want to repost, or I can (and should, really)?

still ^^^

Quoted 48 lines
Quoted 13 lines

drivers/crypto/ccp/sev-dev-tio.c static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data) · line 675

Quoted 10 lines
   }

static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)

drivers/crypto/ccp/sev-dev-tio.c +1 -1
@@ -674,7 +672,7 @@ static int spdm_ctrl_alloc(struct tsm_dsm_tio *dev_data)
   	dev_data->output = sla_alloc(dev_data->output_len, true);
 
   	if (IS_SLA_NULL(dev_data->req) || IS_SLA_NULL(dev_data->resp) ||
-	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->dev_ctx)) {
+	    IS_SLA_NULL(dev_data->scratch) || IS_SLA_NULL(dev_data->output)) {

the same.

Quoted 12 lines

drivers/crypto/ccp/sev-dev-tio.c int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id, · line 774

Quoted 9 lines
drivers/crypto/ccp/sev-dev-tio.c +1 -1
   		ret = -ENOMEM;
   		goto free_spdm_exit;
   	}
@@ -773,7 +771,8 @@ int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id,
   free_data_pg_exit:
   	snp_free_firmware_page(data_pg);
   free_ctx_exit:
-	sla_free(create.dev_ctx_sla, tio_status->devctx_size, true);
+	sla_free(dev_data->dev_ctx, tio_status->devctx_size, true);

and this. Is this codex? :)

Yes.>>

Quoted 4 lines

drivers/crypto/ccp/sev-dev-tio.c int sev_tio_dev_create(struct tsm_dsm_tio *dev_data, u16 device_id, · line 778

Quoted 4 lines
drivers/crypto/ccp/sev-dev-tio.c +1 -0
+	dev_data->dev_ctx = SLA_NULL;
   	return ret;
   }
 

[ ... 179 lines skipped ... ]

Quoted 12 lines

drivers/crypto/ccp/sev-dev-tsm.c static int dsm_connect(struct pci_dev *pdev) · line 422

Quoted 7 lines
drivers/crypto/ccp/sev-dev-tsm.c +7 -0
+
+	/* Reserve TC0's stream ID without programming or enabling IDE. */
+	memset(ids, 0xff, sizeof(ids));
+	ids[0] = dev_data->ide[0]->stream_id;
+	dev_data->cert_slot = 0;
+	ret = sev_tio_dev_connect(dev_data, BIT(0), ids, dev_data->cert_slot);
+	ret = dsm_collect_identity(dsm, ret);

Cannot librspdm do that collection, directly via DOE? Ideally in the generic code? There nothing special for the PSP to do and stopping SEV-TIO's connect half way is weird.

Quoted 5 lines

This patch does not use rust-spdm and avoids introducing that dependency. It uses slot 0 to initiate the session, retrieve the certificate chain, and return it to the host. After host approval, it resumes the session and enables IDE. Only slot 0 is supported, and the host can reject the device identity before IDE is enabled.

What is the plan now? Have only TSM do DOE and use rspdm for parsing DOE trafic to keep the host OS informed? I went through these two threads and got a bit lost :)

Quoted 39 lines
Quoted 29 lines
Quoted 29 lines
drivers/crypto/ccp/sev-dev-tsm.c +17 -6
+	if (!ret)
+		return 0;
+
+disconnect:
+	dsm_disconnect(pdev);
+	return ret;
+}
 
-	tc_mask = streams_setup(dev_data->ide, ids);
+static int dsm_evidence_accept(struct pci_dev *pdev)
+{
+	struct tio_dsm *dsm = pdev_to_tio_dsm(pdev);
+	struct tsm_dsm_tio *dev_data = &dsm->data;
+	u8 ids[TIO_IDE_MAX_TC];
+	int ret;
 
-	dev_data->cert_slot = pdev->tsm->evidence.slot;
-	ret = sev_tio_dev_connect(dev_data, tc_mask, ids, dev_data->cert_slot);
-	ret = sev_tio_spdm_cmd(dsm, ret);
-	if (ret)
-		goto free_exit;
+	if (pdev->tsm->evidence.slot)
+		return -EOPNOTSUPP;
+	if (!dsm->pending_spdm)
+		return -ENODATA;
Quoted 9 lines

drivers/crypto/ccp/sev-dev-tsm.c static int dsm_connect(struct pci_dev *pdev) · line 443

Quoted 2 lines
drivers/crypto/ccp/sev-dev-tsm.c +1 -1
-	streams_enable(dev_data->ide);
+	streams_setup(dev_data->ide, ids);

I kinda get the appeal of not enabling IDE before accepting but in practice the host still has to have a knob in IOMMU to prevent a malicious device from DMAing T=1 trafic so accept() would trigger enable_dma() hook.

Quoted 2 lines

This enables IDE; it is not the TDISP RUN transition. Am I missing something here?

Correct, it enables IDE only but the code prevents IDE key programming. I wonder why. Is it because before the host accepted the device - bad things can happen? What things? If we suspect the device - then block it at IOMMU level and unblock upon acceptance.

This stopping DEV_CONNECT half way is weird imho.

Quoted 67 lines
Quoted 55 lines
Quoted 55 lines
drivers/crypto/ccp/sev-dev-tsm.c +23 -15
+	ret = sev_tio_spdm_cmd(dsm, dsm->pending_spdm);
+	dsm->pending_spdm = 0;
+	if (ret)
+		goto err_session;
+	dsm->session_connected = true;
 
+	ret = streams_enable(dev_data->ide);
+	if (ret)
+		goto err_session;
   	ret = streams_register(dev_data->ide);
   	if (ret)
-		goto free_exit;
-
+		goto err_session;
   	return 0;
 
-free_exit:
-	sev_tio_dev_reclaim(dev_data);
-
-	streams_disable(dev_data->ide);
-ide_free_exit:
-
-	streams_teardown(dev_data->ide);
-
+err_session:
+	/* Disconnect owns cleanup after firmware or IDE setup has started. */
+	pdev->tsm->error = true;
   	return ret;
   }
@@ -339,14 +468,20 @@ static void dsm_disconnect(struct pci_dev *pdev)
   	struct tsm_dsm_tio *dev_data = &dsm->data;
   	int ret;
 
-	ret = sev_tio_dev_disconnect(dev_data, force);
-	ret = sev_tio_spdm_cmd(dsm, ret);
-	if (ret && !force) {
-		ret = sev_tio_dev_disconnect(dev_data, true);
-		sev_tio_spdm_cmd(dsm, ret);
+	/* A paused or failed connect needs forced firmware cleanup. */
+	if (dev_data->reqbuf && dev_data->respbuf) {
+		force |= !dsm->session_connected;
+		ret = sev_tio_dev_disconnect(dev_data, force);
+		ret = sev_tio_spdm_cmd(dsm, ret);
+		if (ret && !force) {
+			ret = sev_tio_dev_disconnect(dev_data, true);
+			sev_tio_spdm_cmd(dsm, ret);
+		}
   	}
 
   	sev_tio_dev_reclaim(dev_data);
+	dsm->session_connected = false;
+	dsm->pending_spdm = 0;
 
Quoted 10 lines

drivers/crypto/ccp/sev-dev-tsm.c static struct pci_tsm_ops sev_tsm_ops = { · line 495

Quoted 7 lines
drivers/crypto/ccp/sev-dev-tsm.c +1 -0
   	streams_disable(dev_data->ide);
   	streams_unregister(dev_data->ide);
@@ -357,6 +492,8 @@ static struct pci_tsm_ops sev_tsm_ops = {
   	.probe = dsm_probe,
   	.remove = dsm_remove,
   	.connect = dsm_connect,
+	.evidence_accept = dsm_evidence_accept,

I saw in your branch you are ditching the connect() hook, is that the plan? Thanks,

Quoted 2 lines

Yes. In the full branch, once rust-spdm is integrated, this entire flow moves to the SPDM accept/evidence_accept phase.

So now we are teaching the TSM driver to stop DEV_CONNECT half way? uff...

Quoted 9 lines
Quoted 7 lines

drivers/crypto/ccp/sev-dev-tsm.c static struct pci_tsm_ops sev_tsm_ops = { · line 499

Quoted 6 lines
drivers/crypto/ccp/sev-dev-tsm.c +1 -0
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
   	.disconnect = dsm_disconnect,
   };
 

-aneesh

Signature · 1 line
Alexey
author maintainer
#42 lore raw

RFC PATCH 10/11 virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Aneesh Kumar K.V (Arm)

Declare certificate slot zero as the CCA link backend evidence capability. Limit connect to PDEV creation and slot-zero identity collection. Copy the chain into the PCI evidence cache before publishing the CTLV device.

Complete public-key submission, secure-session setup and stream connection through evidence_accept after userspace verifies the chain. Keep the evidence snapshot separate from the RMM communication cache and release it on context removal.

With AMD converted in the previous patch, CCA is the last link backend that still establishes streams during connect. Now require evidence_accept for every link backend and initialize newly connected contexts without active streams. Reject link providers that omit the callback, and remove the legacy connect-time session and IDE exception from the API comments and documentation. Connect remains required at this point; a later patch allows shared-discovery backends to omit it.

Cc Jiri Pirko <[email protected]>
Cc Bjorn Helgaas <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 Documentation/ABI/testing/sysfs-bus-pci  |   2 +-
 Documentation/driver-api/attestation.rst |  30 +++++
 Documentation/driver-api/pci/tsm.rst     |  70 +++++++++--
 drivers/pci/tsm/core.c                   |   5 +-
 drivers/virt/coco/arm-cca-host/main.c    | 143 ++++++++++++++++-------
 drivers/virt/coco/arm-cca-host/rmi-da.c  |   7 +-
 drivers/virt/coco/arm-cca-host/rmi-da.h  |   2 +
 include/linux/pci-tsm.h                  |   6 +-
 samples/devsec/link_tsm.c                |  16 ++-
 9 files changed, 222 insertions(+), 59 deletions(-)
Documentation/ABI/testing/sysfs-bus-pci +1 -1
index 56b1d92427ef..391807e609f9 100644
@@ -639,7 +639,7 @@ Description:
 		device with that TSM and collect its supported identity evidence.
 		Reads return the associated TSM name, or an empty string.
 		Successful connect creates the CTLV attestation instance.
-		Backends may defer secure-session and stream setup until
+		Link backends defer secure-session and stream setup until
 		userspace verifies the chain and issues CTLV evidence-accept.
 
 What:		/sys/bus/pci/devices/.../tsm/disconnect
Documentation/driver-api/attestation.rst +30 -0
index d23b7c65d35b..2ab1d2bf031e 100644
@@ -130,6 +130,36 @@ The next kernel requires the same selector and restored lifetime, rebuilds
 deduplication from committed IMA journal entries, and never imports the legacy
 IMA measurement-list buffer for this mode.
 
+PCI host identity and acceptance
+================================
+
+Host connections use ``tsm/connect`` and ``tsm/disconnect`` in the PCI device's
+sysfs directory. Write the link TSM name, such as ``tsm0``, to either file.
+Backends declare the evidence objects and certificate slots they expose;
+DEVICE_GET advertises these capabilities and EVIDENCE_GET exports the cached
+evidence. The common provider does not restrict host certificates to a fixed
+slot.
+
+After verifying a chain, issue EVIDENCE_ACCEPT with the device and evidence
+generations returned by EVIDENCE_GET. The ``evidence.accept-object`` selector
+identifies the certificate slot and must belong to the retrieved snapshot.
+The link backend's ``evidence_accept`` callback receives the selected slot in
+``pci_tsm.evidence.slot`` and establishes the secure session and streams.
+Every link backend must provide this callback; connect only prepares identity
+evidence and must defer secure-session and IDE setup until acceptance.
+Successful acceptance makes the connection available to IOMMUFD consumers.
+Repeating acceptance does not allocate another stream, and selecting a
+different slot after stream setup returns EBUSY.
+
+EVIDENCE_ACCEPT_CLEAR clears the recorded verifier verdict only. It leaves an
+established stream active; accepting the evidence again reuses that stream.
+Writing the TSM name to ``tsm/disconnect`` releases either an identity-only
+connection or its stream and identity resources. It returns EBUSY while
+IOMMUFD vdevices hold connection contexts.
+
+See Documentation/driver-api/pci/tsm.rst for backend-specific behavior and
+``ctlvctl`` examples of evidence retrieval and acceptance.
+
 Simulation provider
 ===================
 
Documentation/driver-api/pci/tsm.rst +63 -7

82 lines not on this page · raw message

drivers/pci/tsm/core.c +3 -2
index 170e043b1d3b..983463dd0811 100644
@@ -261,7 +261,7 @@ static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
 	if (rc)
 		return rc;
 
-	tsm_pf0->stream_connected = !ops->evidence_accept;
+	tsm_pf0->stream_connected = false;
 	pdev->tsm = no_free_ptr(pci_tsm);
 
 	/*
@@ -315,7 +315,8 @@ static bool pci_tsm_can_connect(struct tsm_dev *tsm_dev)
 {
 	const struct pci_tsm_ops *ops = tsm_dev ? tsm_dev->pci_ops : NULL;
 
-	return is_link_tsm(tsm_dev) && ops->remove && ops->connect && ops->disconnect;
+	return is_link_tsm(tsm_dev) && ops->remove && ops->connect &&
+		ops->evidence_accept && ops->disconnect;
 }
 
 static ssize_t connect_store(struct device *dev, struct device_attribute *attr,
drivers/virt/coco/arm-cca-host/main.c +100 -43

237 lines not on this page · raw message

drivers/virt/coco/arm-cca-host/rmi-da.c +5 -2
index c4f505753bf9..d18812b91c31 100644
@@ -92,8 +92,9 @@ static int init_pdev_params(struct pci_dev *pdev, struct rmi_pdev_params *params
 	params->pdev_id = rid;
 	params->hb_base = cfg->res.start;
 	params->routing_id = pci_ide_segment(pdev);
-	/* slot number for certificate chain default to zero */
-	params->id_index = 0;
+	/* Root ports do not use an SPDM certificate chain. */
+	if (params->flags & RMI_PDEV_FLAGS_SPDM)
+		params->id_index = 0;
 	params->hash_algo = RMI_HASH_SHA_256;
 	/* no multi function device here. */
 	params->rid_base = rid;
@@ -921,6 +922,8 @@ int cca_pdev_stream_connect(struct pci_dev *pdev1, struct pci_dev *pdev2,
 	if (rmi_pdev_stream_connect(stream_params_phys, stream_handle))
 		return -EIO;
 
+	/* Keep the handle and peer alive even if communication fails below. */
+	to_cca_pf0_ep_dsc(pdev1)->stream_created = true;
 	return submit_stream_work(pdev1, pdev2, *stream_handle);
 }
 
drivers/virt/coco/arm-cca-host/rmi-da.h +2 -0
index 00a68afc0cb8..2024ca6cb9f3 100644
@@ -87,6 +87,7 @@ struct cca_host_pdev_dsc {
  * @pdev: pdev communication context
  * @sel_stream: Selective IDE Stream descriptor
  * @stream_handle: Stream handle returned by stream connect
+ * @stream_created: RMM allocated a stream, including incomplete setup
  * @rmi_signature_algorithm: Signature algorithm used for public key
  * @cert_chain: cetrificate chain
  * @vca: SPDM's Version-Capabilities-Algorithms cache object
@@ -96,6 +97,7 @@ struct cca_host_pf0_ep_dsc {
 	struct cca_host_pdev_dsc pdev;
 	struct pci_ide *sel_stream;
 	unsigned long stream_handle;
+	bool stream_created;
 
 	char *rmi_signature_algorithm;
 	struct {
include/linux/pci-tsm.h +3 -3
index 8fd209a2c32b..a2b439edbd0c 100644
@@ -88,11 +88,11 @@ struct pci_tsm_ops {
 	 * @probe: establish context with the TSM (allocate / wrap 'struct
 	 *	   pci_tsm') for follow-on link operations
 	 * @remove: destroy link operations context
-	 * @connect: collect device identity; required for link backends; legacy
-	 *	     backends may also establish IDE
+	 * @connect: collect device identity; required for link backends;
+	 *	     session and IDE setup must wait for @evidence_accept
 	 * @evidence_accept: establish the secure session and IDE after evidence
 	 *		  acceptance, using pci_tsm::evidence.slot as the selected
-	 *		  certificate slot; optional for legacy backends
+	 *		  certificate slot; required for link backends
 	 * @disconnect: teardown the secure link
 	 *
 	 * Context: All callbacks run under pci_tsm_rwsem held for write to sync
samples/devsec/link_tsm.c +15 -1
index 61cf6c16e1b5..a6644b276a88 100644
@@ -132,6 +132,10 @@ static void devsec_link_tsm_pci_remove(struct pci_tsm *tsm)
 static DECLARE_BITMAP(devsec_stream_ids, NR_TSM_STREAMS);
 static struct pci_ide *devsec_streams[NR_TSM_STREAMS];
 
+static bool fail_stream_connect;
+module_param(fail_stream_connect, bool, 0600);
+MODULE_PARM_DESC(fail_stream_connect, "Fail stream setup after identity collection");
+
 static unsigned long *alloc_devsec_stream_id(unsigned long *stream_id)
 {
 	unsigned long id;
@@ -146,7 +150,7 @@ static unsigned long *alloc_devsec_stream_id(unsigned long *stream_id)
 DEFINE_FREE(free_devsec_stream, unsigned long *, if (_T) clear_bit(*_T, devsec_stream_ids))
 
 /*
- * Reference consumer for a TSM driver "connect" operation callback. The
+ * Reference consumer for a TSM driver "evidence_accept" operation callback. The
  * low-level TSM driver understands details about the platform the PCI
  * core does not, like number of available streams that can be
  * established per host bridge. The expected flow is:
@@ -168,11 +172,20 @@ DEFINE_FREE(free_devsec_stream, unsigned long *, if (_T) clear_bit(*_T, devsec_s
  * or use model sequencing.
  */
 static int devsec_link_tsm_connect(struct pci_dev *pdev)
+{
+	/* probe() populated the synthetic identity; allocate streams on accept. */
+	return 0;
+}
+
+static int devsec_link_tsm_evidence_accept(struct pci_dev *pdev)
 {
 	struct pci_dev *rp = pcie_find_root_port(pdev);
 	unsigned long __stream_id;
 	int rc;
 
+	if (fail_stream_connect)
+		return -EIO;
+
 	dev_dbg(pci_tsm_host(pdev), "%s\n", pci_name(pdev));
 
 	unsigned long *stream_id __free(free_devsec_stream) =
@@ -235,6 +248,7 @@ static struct pci_tsm_ops devsec_link_pci_ops = {
 	.probe = devsec_link_tsm_pci_probe,
 	.remove = devsec_link_tsm_pci_remove,
 	.connect = devsec_link_tsm_connect,
+	.evidence_accept = devsec_link_tsm_evidence_accept,
 	.disconnect = devsec_link_tsm_disconnect,
 };
 
Signature · 1 line
2.43.0
#43 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 10 lines · Aneesh Kumar K.V (Arm)

Declare certificate slot zero as the CCA link backend evidence capability. Limit connect to PDEV creation and slot-zero identity collection. Copy the chain into the PCI evidence cache before publishing the CTLV device.

Complete public-key submission, secure-session setup and stream connection through evidence_accept after userspace verifies the chain. Keep the evidence snapshot separate from the RMM communication cache and release it on context removal.

Quoted 7 lines

With AMD converted in the previous patch, CCA is the last link backend that still establishes streams during connect. Now require evidence_accept for every link backend and initialize newly connected contexts without active streams. Reject link providers that omit the callback, and remove the legacy connect-time session and IDE exception from the API comments and documentation. Connect remains required at this point; a later patch allows shared-discovery backends to omit it.

TDX Connect is another one. The TDX host link [1] sets up the SPDM session and then the IDE streams in its connect (tdx_tsm_link_connect(), patches 27 and 30).

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages
without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference Spec 2.1 v0.66, 4.2.20.1).

For TDX, the chain can be checked after connect instead. A connected link doesn't make the device trusted by a guest. The guest still verifies the device before it accepts it. Could evidence_accept stay optional?

[1] https://lore.kernel.org/all/[email protected]/

DJ

Quoted 552 lines
Cc Jiri Pirko <[email protected]>
Cc Bjorn Helgaas <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 Documentation/ABI/testing/sysfs-bus-pci  |   2 +-
 Documentation/driver-api/attestation.rst |  30 +++++
 Documentation/driver-api/pci/tsm.rst     |  70 +++++++++--
 drivers/pci/tsm/core.c                   |   5 +-
 drivers/virt/coco/arm-cca-host/main.c    | 143 ++++++++++++++++-------
 drivers/virt/coco/arm-cca-host/rmi-da.c  |   7 +-
 drivers/virt/coco/arm-cca-host/rmi-da.h  |   2 +
 include/linux/pci-tsm.h                  |   6 +-
 samples/devsec/link_tsm.c                |  16 ++-
 9 files changed, 222 insertions(+), 59 deletions(-)
Documentation/ABI/testing/sysfs-bus-pci +1 -1
index 56b1d92427ef..391807e609f9 100644
@@ -639,7 +639,7 @@ Description:
 		device with that TSM and collect its supported identity evidence.
 		Reads return the associated TSM name, or an empty string.
 		Successful connect creates the CTLV attestation instance.
-		Backends may defer secure-session and stream setup until
+		Link backends defer secure-session and stream setup until
 		userspace verifies the chain and issues CTLV evidence-accept.
 
 What:		/sys/bus/pci/devices/.../tsm/disconnect
Documentation/driver-api/attestation.rst +30 -0
index d23b7c65d35b..2ab1d2bf031e 100644
@@ -130,6 +130,36 @@ The next kernel requires the same selector and restored lifetime, rebuilds
 deduplication from committed IMA journal entries, and never imports the legacy
 IMA measurement-list buffer for this mode.
 
+PCI host identity and acceptance
+================================
+
+Host connections use ``tsm/connect`` and ``tsm/disconnect`` in the PCI device's
+sysfs directory. Write the link TSM name, such as ``tsm0``, to either file.
+Backends declare the evidence objects and certificate slots they expose;
+DEVICE_GET advertises these capabilities and EVIDENCE_GET exports the cached
+evidence. The common provider does not restrict host certificates to a fixed
+slot.
+
+After verifying a chain, issue EVIDENCE_ACCEPT with the device and evidence
+generations returned by EVIDENCE_GET. The ``evidence.accept-object`` selector
+identifies the certificate slot and must belong to the retrieved snapshot.
+The link backend's ``evidence_accept`` callback receives the selected slot in
+``pci_tsm.evidence.slot`` and establishes the secure session and streams.
+Every link backend must provide this callback; connect only prepares identity
+evidence and must defer secure-session and IDE setup until acceptance.
+Successful acceptance makes the connection available to IOMMUFD consumers.
+Repeating acceptance does not allocate another stream, and selecting a
+different slot after stream setup returns EBUSY.
+
+EVIDENCE_ACCEPT_CLEAR clears the recorded verifier verdict only. It leaves an
+established stream active; accepting the evidence again reuses that stream.
+Writing the TSM name to ``tsm/disconnect`` releases either an identity-only
+connection or its stream and identity resources. It returns EBUSY while
+IOMMUFD vdevices hold connection contexts.
+
+See Documentation/driver-api/pci/tsm.rst for backend-specific behavior and
+``ctlvctl`` examples of evidence retrieval and acceptance.
+
 Simulation provider
 ===================
 
Documentation/driver-api/pci/tsm.rst +63 -7
index 28c9ec5dd4e6..03bee3098d19 100644
@@ -30,6 +30,11 @@ backend registration alone do not create instances.
 Operation availability
 ======================
 
+Every link backend must provide ``evidence_accept`` for secure-session and
+IDE setup after userspace verification. The ``connect`` callback prepares
+identity evidence without establishing the secure link. It is required for
+link backends.
+
 CTLV provides DEVICE_GET, PCI STATE_GET, EVIDENCE_GET, EVIDENCE_ACCEPT,
 EVIDENCE_ACCEPT_CLEAR and DEVSEC_STATE_GET on established contexts. Backends
 declare the evidence objects and certificate slots they expose. The common
@@ -70,13 +75,64 @@ DSM BDF. The TSM ID is the numeric suffix of /sys/class/tsm/tsmN. PCI
 EVIDENCE_REFRESH requires ``pci.generation`` from STATE_GET; a mismatch returns
 ESTALE before invoking the native backend.
 
-For a link backend that defers secure-link setup until evidence acceptance,
-write its TSM name to the DSM's ``tsm/connect`` sysfs file to collect identity
-evidence. DEVICE_GET advertises the backend's available objects and certificate
-slots; EVIDENCE_GET retrieves the cached chain. After verifying it, issue
-EVIDENCE_ACCEPT with the returned device and evidence generations to complete
-session and IDE setup. Writing the same TSM name to ``tsm/disconnect`` releases
-the context, including an identity context that has not been accepted.
+The host writes a link TSM name to its DSM's ``tsm/connect`` sysfs file.
+Connect prepares an identity context and caches the evidence objects
+declared by the backend, without establishing a secure session or IDE
+streams. Use DEVICE_GET to discover the available objects and certificate
+slots, then use EVIDENCE_GET to retrieve an advertised ``cert-chain``
+instance. Verify the chain under the relying party's policy before issuing
+EVIDENCE_ACCEPT with the returned device and evidence generations.
+Acceptance completes session and stream setup. The authenticated attribute
+remains empty until setup succeeds.
+
+Use EVIDENCE_ACCEPT with ``evidence.accept-object`` selecting ``cert-chain``,
+format ``spdm-cert-chain``, and an advertised certificate slot as
+``evidence.blob-instance``. The selected object must have been retrieved in the
+referenced evidence generation; otherwise acceptance returns ESTALE.
+
+For example, use ``ctlvctl`` to export an advertised certificate chain (slot 0
+in this example)::
+
+  ctlvctl --spec-dir=./specs exec ctlv0 evidence-get --json '{
+    "evidence.object": [{
+      "evidence.blob-class": "cert-chain",
+      "evidence.format": "spdm-cert-chain",
+      "evidence.blob-instance": 0,
+      "evidence.representation": "raw",
+      "evidence.data": {
+        "file": "./cert0.bin", "data-buf-size": 16777216
+      }
+    }]
+  }'
+
+Verify ``cert0.bin`` under the relying party's policy before accepting it.
+Use the ``device.generation`` and ``evidence.generation`` values returned by
+EVIDENCE_GET. If they are 0 and 1 respectively::
+
+  ctlvctl --spec-dir=./specs exec ctlv0 evidence-accept --json '{
+    "device.generation": 0,
+    "evidence.generation": 1,
+    "evidence.accept-object": {
+      "evidence.blob-class": "cert-chain",
+      "evidence.format": "spdm-cert-chain",
+      "evidence.blob-instance": 0
+    }
+  }'
+
+The commands use ``ctlv0`` and ``./specs`` as examples; use the discovered CTLV
+instance and the directory containing the matching specification files.
+Generation pairs are scoped to an instance lifetime. Keep the same CTLV file
+descriptor open from evidence collection through acceptance in a production
+verifier, and discard its generation pair on close or ENODEV. The separate
+``ctlvctl`` invocations illustrate the request format and assume the instance
+remains unchanged between commands. ``ctlvctl`` exports evidence and submits
+the verdict; certificate verification is the relying party's responsibility.
+
+Write the same TSM name to ``tsm/disconnect`` to release identity and stream
+resources. Disconnect works before evidence acceptance too, and returns EBUSY
+while an IOMMUFD vdevice holds a connection context. EVIDENCE_ACCEPT_CLEAR only
+clears the verdict; disconnect owns stream teardown. VM assignment and native
+TDISP transport remain in IOMMUFD and the platform TSM driver.
 
 A guest verifier follows this sequence:
 
drivers/pci/tsm/core.c +3 -2
index 170e043b1d3b..983463dd0811 100644
@@ -261,7 +261,7 @@ static int pci_tsm_connect(struct pci_dev *pdev, struct tsm_dev *tsm_dev)
 	if (rc)
 		return rc;
 
-	tsm_pf0->stream_connected = !ops->evidence_accept;
+	tsm_pf0->stream_connected = false;
 	pdev->tsm = no_free_ptr(pci_tsm);
 
 	/*
@@ -315,7 +315,8 @@ static bool pci_tsm_can_connect(struct tsm_dev *tsm_dev)
 {
 	const struct pci_tsm_ops *ops = tsm_dev ? tsm_dev->pci_ops : NULL;
 
-	return is_link_tsm(tsm_dev) && ops->remove && ops->connect && ops->disconnect;
+	return is_link_tsm(tsm_dev) && ops->remove && ops->connect &&
+		ops->evidence_accept && ops->disconnect;
 }
 
 static ssize_t connect_store(struct device *dev, struct device_attribute *attr,
drivers/virt/coco/arm-cca-host/main.c +100 -43
index 0bad6001654b..0f12353322fa 100644
@@ -13,6 +13,8 @@
 #include <linux/tsm.h>
 #include <linux/cleanup.h>
 #include <linux/pci-doe.h>
+#include <linux/slab.h>
+#include <linux/string.h>
 
 #include "rmi-da.h"
 
@@ -66,6 +68,7 @@ static void cca_tsm_pci_remove(struct pci_tsm *tsm)
 	if (is_pci_tsm_pf0(pdev)) {
 		struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
 
+		kvfree(tsm->evidence.obj[PCI_TSM_EVIDENCE_TYPE_CERT0].data);
 		pci_tsm_pf0_destructor(&pf0_ep_dsc->pci);
 		kfree(pf0_ep_dsc);
 	} else {
@@ -212,7 +215,6 @@ static int pci_dev_addr_range(struct pci_dev *pdev, struct rmi_addr_range *pdev_
 static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long stream_id)
 {
 	int ret;
-	long stream_handle;
 	struct cca_host_rp_dsc *rp_dsc;
 	struct rmi_pdev_stream_params *params;
 	struct pci_dev *rp = pcie_find_root_port(pdev);
@@ -245,11 +247,9 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
 	params->ide_sid = stream_id;
 	params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
 
-	ret = cca_pdev_stream_connect(pdev, rp, params, &stream_handle);
-	if (ret)
+	ret = cca_pdev_stream_connect(pdev, rp, params, &pf0_ep_dsc->stream_handle);
+	if (ret && !pf0_ep_dsc->stream_created)
 		cca_root_port_pdev_put(rp_dsc);
-	else
-		pf0_ep_dsc->stream_handle = stream_handle;
 
 	free_page((unsigned long)params);
 	return ret;
@@ -258,7 +258,6 @@ static int cca_pdev_create_ncoh_stream(struct pci_dev *pdev, unsigned long strea
 static int cca_pdev_create_ncoh_sys_stream(struct pci_dev *pdev)
 {
 	int ret;
-	long stream_handle;
 	struct rmi_pdev_stream_params *params;
 	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
 
@@ -273,9 +272,7 @@ static int cca_pdev_create_ncoh_sys_stream(struct pci_dev *pdev)
 	params->ide_sid = 0; /* ignored */
 	params->num_addr_range = pci_dev_addr_range(pdev, params->addr_range);
 
-	ret = cca_pdev_stream_connect(pdev, NULL, params, &stream_handle);
-	if (!ret)
-		pf0_ep_dsc->stream_handle = stream_handle;
+	ret = cca_pdev_stream_connect(pdev, NULL, params, &pf0_ep_dsc->stream_handle);
 
 	free_page((unsigned long)params);
 	return ret;
@@ -298,7 +295,65 @@ static inline bool cca_pdev_need_sel_ide_streams(struct pci_dev *pdev)
 	return pci_pcie_type(pdev) == PCI_EXP_TYPE_ENDPOINT;
 }
 
+static int cca_tsm_publish_certificate(struct pci_dev *pdev)
+{
+	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
+	struct pci_tsm_evidence *evidence = &pdev->tsm->evidence;
+	struct pci_tsm_evidence_object *obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_CERT0];
+	struct cache_object *cache;
+	void *data;
+
+	/* On-chip endpoints can connect without SPDM identity collection. */
+	if (!pf0_ep_dsc->pci.doe_mb)
+		return 0;
+
+	guard(mutex)(&pf0_ep_dsc->pdev.object_lock);
+	cache = pf0_ep_dsc->cert_chain.cache;
+	if (!cache || !cache->offset)
+		return -ENODATA;
+
+	/* Later RMM communication may reallocate the original cache. */
+	data = kvmemdup(cache->buf, cache->offset, GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	guard(rwsem_write)(&evidence->lock);
+	obj->data = data;
+	obj->len = cache->offset;
+	return 0;
+}
+
 static int cca_tsm_connect(struct pci_dev *pdev)
+{
+	struct cca_host_pf0_ep_dsc *pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
+	int ret;
+
+	ret = init_dev_communication_buffers(pdev, &pf0_ep_dsc->pdev.comm_data);
+	if (ret)
+		return ret;
+
+	ret = cca_pdev_create(pdev);
+	if (ret)
+		goto err_pdev_create;
+
+	ret = cca_pdev_collect_identity(pdev);
+	if (ret)
+		goto pdev_destroy;
+
+	ret = cca_tsm_publish_certificate(pdev);
+	if (ret)
+		goto pdev_destroy;
+
+	return 0;
+
+pdev_destroy:
+	cca_pdev_stop_and_destroy(pdev);
+err_pdev_create:
+	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
+	return ret;
+}
+
+static int cca_tsm_evidence_accept(struct pci_dev *pdev)
 {
 	struct pci_dev *rp = pcie_find_root_port(pdev);
 	struct cca_host_pf0_ep_dsc *pf0_ep_dsc;
@@ -309,9 +364,18 @@ static int cca_tsm_connect(struct pci_dev *pdev)
 	if (WARN_ON(!is_pci_tsm_pf0(pdev)))
 		return -EIO;
 
+	/* Connect collected identity from slot zero for this PDEV. */
+	if (pdev->tsm->evidence.slot != 0)
+		return -EOPNOTSUPP;
+
 	pf0_ep_dsc = to_cca_pf0_ep_dsc(pdev);
-	if (cca_pdev_need_sel_ide_streams(pdev)) {
+	if (cca_pdev_needs_key(pdev)) {
+		ret = cca_pdev_set_public_key(pdev);
+		if (ret)
+			goto err_key;
+	}
 
+	if (cca_pdev_need_sel_ide_streams(pdev)) {
 		ide = pci_ide_stream_alloc(pdev);
 		if (!ide) {
 			ret = -ENOMEM;
@@ -344,42 +408,26 @@ static int cca_tsm_connect(struct pci_dev *pdev)
 			goto err_tsm;
 	}
 
-	ret = init_dev_communication_buffers(pdev, &pf0_ep_dsc->pdev.comm_data);
-	if (ret)
-		goto err_comm_buff;
-	ret = cca_pdev_create(pdev);
-	if (ret)
-		goto err_pdev_create;
-
-	ret = cca_pdev_collect_identity(pdev);
-	if (ret)
-		goto pdev_destroy;
-
-	if (cca_pdev_needs_key(pdev)) {
-		ret = cca_pdev_set_public_key(pdev);
-		if (ret)
-			goto pdev_destroy;
-	}
 	/* Create IDE streams */
 	ret = cca_pdev_create_streams(pdev, stream_id);
 	if (ret)
-		goto pdev_destroy;
+		goto err_stream_connect;
 	/*
 	 * Once ide is setup, enable the stream at the endpoint
 	 * Root port will be done by RMM
 	 */
-	if (cca_pdev_need_sel_ide_streams(pdev))
-		pci_ide_stream_enable(pdev, ide);
+	if (cca_pdev_need_sel_ide_streams(pdev)) {
+		ret = pci_ide_stream_enable(pdev, ide);
+		if (ret)
+			goto err_stream_connect;
+	}
 
 	return 0;
 
-pdev_destroy:
-	cca_pdev_stop_and_destroy(pdev);
-err_pdev_create:
-	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
-err_comm_buff:
-	if (cca_pdev_need_sel_ide_streams(pdev))
-		tsm_ide_stream_unregister(ide);
+err_stream_connect:
+	/* Retain partial RMM/IDE state for sysfs disconnect to release. */
+	pdev->tsm->error = true;
+	return ret;
 err_tsm:
 	if (cca_pdev_need_sel_ide_streams(pdev)) {
 		pci_ide_stream_teardown(rp, ide);
@@ -391,7 +439,9 @@ static int cca_tsm_connect(struct pci_dev *pdev)
 		pci_ide_stream_free(ide);
 	pf0_ep_dsc->sel_stream = NULL;
 err_stream_alloc:
-
+	return ret;
+err_key:
+	pdev->tsm->error = true;
 	return ret;
 }
 
@@ -405,26 +455,33 @@ static void cca_tsm_disconnect(struct pci_dev *pdev)
 	if (!pf0_ep_dsc)
 		return;
 
-	if (cca_pdev_need_sel_ide_streams(pdev))
-		ide = pf0_ep_dsc->sel_stream;
+	ide = pf0_ep_dsc->sel_stream;
+	if (pf0_ep_dsc->stream_created) {
+		if (!cca_pdev_need_sel_ide_streams(pdev))
+			rp = NULL;
 
-	cca_pdev_disconnect_stream(pdev, rp, pf0_ep_dsc->stream_handle);
-	if (rp)
-		cca_root_port_pdev_put(to_cca_rp_dsc(rp));
+		cca_pdev_disconnect_stream(pdev, rp, pf0_ep_dsc->stream_handle);
+		if (rp)
+			cca_root_port_pdev_put(to_cca_rp_dsc(rp));
+
+		pf0_ep_dsc->stream_created = false;
+	}
 
 	cca_pdev_stop_and_destroy(pdev);
 	free_dev_communication_buffers(&pf0_ep_dsc->pdev.comm_data);
 
-	if (cca_pdev_need_sel_ide_streams(pdev)) {
+	if (ide) {
 		pci_ide_stream_release(ide);
 		pf0_ep_dsc->sel_stream = NULL;
 	}
 }
 
 static struct pci_tsm_ops cca_link_pci_ops = {
+	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0),
 	.probe = cca_tsm_pci_probe,
 	.remove = cca_tsm_pci_remove,
 	.connect = cca_tsm_connect,
+	.evidence_accept = cca_tsm_evidence_accept,
 	.disconnect = cca_tsm_disconnect,
 	.owner = THIS_MODULE,
 	.viommu_get_ops = cca_viommu_get_ops,
drivers/virt/coco/arm-cca-host/rmi-da.c +5 -2
index c4f505753bf9..d18812b91c31 100644
@@ -92,8 +92,9 @@ static int init_pdev_params(struct pci_dev *pdev, struct rmi_pdev_params *params
 	params->pdev_id = rid;
 	params->hb_base = cfg->res.start;
 	params->routing_id = pci_ide_segment(pdev);
-	/* slot number for certificate chain default to zero */
-	params->id_index = 0;
+	/* Root ports do not use an SPDM certificate chain. */
+	if (params->flags & RMI_PDEV_FLAGS_SPDM)
+		params->id_index = 0;
 	params->hash_algo = RMI_HASH_SHA_256;
 	/* no multi function device here. */
 	params->rid_base = rid;
@@ -921,6 +922,8 @@ int cca_pdev_stream_connect(struct pci_dev *pdev1, struct pci_dev *pdev2,
 	if (rmi_pdev_stream_connect(stream_params_phys, stream_handle))
 		return -EIO;
 
+	/* Keep the handle and peer alive even if communication fails below. */
+	to_cca_pf0_ep_dsc(pdev1)->stream_created = true;
 	return submit_stream_work(pdev1, pdev2, *stream_handle);
 }
 
drivers/virt/coco/arm-cca-host/rmi-da.h +2 -0
index 00a68afc0cb8..2024ca6cb9f3 100644
@@ -87,6 +87,7 @@ struct cca_host_pdev_dsc {
  * @pdev: pdev communication context
  * @sel_stream: Selective IDE Stream descriptor
  * @stream_handle: Stream handle returned by stream connect
+ * @stream_created: RMM allocated a stream, including incomplete setup
  * @rmi_signature_algorithm: Signature algorithm used for public key
  * @cert_chain: cetrificate chain
  * @vca: SPDM's Version-Capabilities-Algorithms cache object
@@ -96,6 +97,7 @@ struct cca_host_pf0_ep_dsc {
 	struct cca_host_pdev_dsc pdev;
 	struct pci_ide *sel_stream;
 	unsigned long stream_handle;
+	bool stream_created;
 
 	char *rmi_signature_algorithm;
 	struct {
include/linux/pci-tsm.h +3 -3
index 8fd209a2c32b..a2b439edbd0c 100644
@@ -88,11 +88,11 @@ struct pci_tsm_ops {
 	 * @probe: establish context with the TSM (allocate / wrap 'struct
 	 *	   pci_tsm') for follow-on link operations
 	 * @remove: destroy link operations context
-	 * @connect: collect device identity; required for link backends; legacy
-	 *	     backends may also establish IDE
+	 * @connect: collect device identity; required for link backends;
+	 *	     session and IDE setup must wait for @evidence_accept
 	 * @evidence_accept: establish the secure session and IDE after evidence
 	 *		  acceptance, using pci_tsm::evidence.slot as the selected
-	 *		  certificate slot; optional for legacy backends
+	 *		  certificate slot; required for link backends
 	 * @disconnect: teardown the secure link
 	 *
 	 * Context: All callbacks run under pci_tsm_rwsem held for write to sync
samples/devsec/link_tsm.c +15 -1
index 61cf6c16e1b5..a6644b276a88 100644
@@ -132,6 +132,10 @@ static void devsec_link_tsm_pci_remove(struct pci_tsm *tsm)
 static DECLARE_BITMAP(devsec_stream_ids, NR_TSM_STREAMS);
 static struct pci_ide *devsec_streams[NR_TSM_STREAMS];
 
+static bool fail_stream_connect;
+module_param(fail_stream_connect, bool, 0600);
+MODULE_PARM_DESC(fail_stream_connect, "Fail stream setup after identity collection");
+
 static unsigned long *alloc_devsec_stream_id(unsigned long *stream_id)
 {
 	unsigned long id;
@@ -146,7 +150,7 @@ static unsigned long *alloc_devsec_stream_id(unsigned long *stream_id)
 DEFINE_FREE(free_devsec_stream, unsigned long *, if (_T) clear_bit(*_T, devsec_stream_ids))
 
 /*
- * Reference consumer for a TSM driver "connect" operation callback. The
+ * Reference consumer for a TSM driver "evidence_accept" operation callback. The
  * low-level TSM driver understands details about the platform the PCI
  * core does not, like number of available streams that can be
  * established per host bridge. The expected flow is:
@@ -168,11 +172,20 @@ DEFINE_FREE(free_devsec_stream, unsigned long *, if (_T) clear_bit(*_T, devsec_s
  * or use model sequencing.
  */
 static int devsec_link_tsm_connect(struct pci_dev *pdev)
+{
+	/* probe() populated the synthetic identity; allocate streams on accept. */
+	return 0;
+}
+
+static int devsec_link_tsm_evidence_accept(struct pci_dev *pdev)
 {
 	struct pci_dev *rp = pcie_find_root_port(pdev);
 	unsigned long __stream_id;
 	int rc;
 
+	if (fail_stream_connect)
+		return -EIO;
+
 	dev_dbg(pci_tsm_host(pdev), "%s\n", pci_name(pdev));
 
 	unsigned long *stream_id __free(free_devsec_stream) =
@@ -235,6 +248,7 @@ static struct pci_tsm_ops devsec_link_pci_ops = {
 	.probe = devsec_link_tsm_pci_probe,
 	.remove = devsec_link_tsm_pci_remove,
 	.connect = devsec_link_tsm_connect,
+	.evidence_accept = devsec_link_tsm_evidence_accept,
 	.disconnect = devsec_link_tsm_disconnect,
 };
 
maintainer
#44 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 21 lines · Dave Jiang
Quoted 10 lines · Aneesh Kumar K.V (Arm)

Declare certificate slot zero as the CCA link backend evidence capability. Limit connect to PDEV creation and slot-zero identity collection. Copy the chain into the PCI evidence cache before publishing the CTLV device.

Complete public-key submission, secure-session setup and stream connection through evidence_accept after userspace verifies the chain. Keep the evidence snapshot separate from the RMM communication cache and release it on context removal.

Quoted 7 lines

With AMD converted in the previous patch, CCA is the last link backend that still establishes streams during connect. Now require evidence_accept for every link backend and initialize newly connected contexts without active streams. Reject link providers that omit the callback, and remove the legacy connect-time session and IDE exception from the API comments and documentation. Connect remains required at this point; a later patch allows shared-discovery backends to omit it.

Quoted 3 lines

TDX Connect is another one. The TDX host link [1] sets up the SPDM session and then the IDE streams in its connect (tdx_tsm_link_connect(), patches 27 and 30).

Yeah, everyone tried to do it this way, but that is what is changing.

Do not call TDH.SPDM.CONNECT when processing the sysfs connect operation. The sysfs operation only establishes what TSM driver is in use.

You call TDH.SPDM.CONNECT triggered by attestation subsystem with the slot id provided by userspace.

Don't get confused by the similar names :)

Quoted 6 lines

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference Spec 2.1 v0.66, 4.2.20.1).

Yeah, its fine. TDX will have to do what Aneesh proposed for ARM, and use use the Rust SPDM Driver to operate DOE prior to calling TDH.SPDM.CONNECT. This will allow discovering what is in the slots and passing that information to userspace.

Quoted 4 lines

For TDX, the chain can be checked after connect instead. A connected link doesn't make the device trusted by a guest. The guest still verifies the device before it accepts it. Could evidence_accept stay optional?

No, it is a key part of any crypto handshake, something in userspace has to acknowledge the SPDM session is "Authenticated" before any use of it progresses.

For these combined connect and IDE flows, "use" probably means allowing a kernel driver to bind. Ideally once we connec the TSM there will be no driver bound, and no driver can be bound until we accept it.

This probably isn't *strictly* necessary since userspace can sequence driver binding (assuming that doesn't get removed), more of a safety sequencing.

For a guest it would mean you are allowed to go to RUN (or maybe it implicitly goes to run?)

Jason

#45 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 11 lines
Quoted 6 lines

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference Spec 2.1 v0.66, 4.2.20.1).

Yeah, its fine. TDX will have to do what Aneesh proposed for ARM, and use use the Rust SPDM Driver to operate DOE prior to calling TDH.SPDM.CONNECT. This will allow discovering what is in the slots and passing that information to userspace.

I am somewhat confused by this.

1. You are saying that prior to establishing a
secure SPDM session, you will use unsecure DOE mechanism to discover available cert chains. Here you have to take into account that anything you get at this step is not trusted. A malicious device could send you any cert chains from another legitimate device.

2. Next, you will choose a cert slot to be used for SPDM authentication and secure session establishment (slot 0 initially in this patch) and allow everything to happen.

Now, who guarantees that a cert chain that you saw
at step 1 for the selected slot is the same that is going to be used in actual SPDM connection? Who is verifying/ensuring this? Kernel? Userspace?

Best Regards,
Elena.

maintainer
#46 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 12 lines · Elena
Quoted 11 lines
Quoted 6 lines

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference Spec 2.1 v0.66, 4.2.20.1).

Yeah, its fine. TDX will have to do what Aneesh proposed for ARM, and use use the Rust SPDM Driver to operate DOE prior to calling TDH.SPDM.CONNECT. This will allow discovering what is in the slots and passing that information to userspace.

Quoted 7 lines

I am somewhat confused by this.

1. You are saying that prior to establishing a
secure SPDM session, you will use unsecure DOE mechanism to discover available cert chains. Here you have to take into account that anything you get at this step is not trusted. A malicious device could send you any cert chains from another legitimate device.

I poked at this with AI for a while.. While nothing about SPDM slots seems particularly clean it can be made to work. It looks like 1.4 improves things meaningfully.

Quoted 3 lines

2. Next, you will choose a cert slot to be used for SPDM authentication and secure session establishment (slot 0 initially in this patch) and allow everything to happen.

Yep

Quoted 4 lines

Now, who guarantees that a cert chain that you saw
at step 1 for the selected slot is the same that is going to be used in actual SPDM connection? Who is verifying/ensuring this? Kernel? Userspace?

Not sure, it would make sense if the kernel can do some of this with rustspdm, but there are alot of details here that I can't guess what is best.

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

2) Do slot selection and use CHALLENGE to prevent downgrade attacks.
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
CHALLENGE / CHALLENGE_AUTH (using slot 0)
3) Kernel RustSPDM can check that CHALENGE_AUTH is self consistent
2) Supply the certificates to userspace
3) Userspace choose a slot
4) TSM does KEY_EXCHANGE
5) Confirm the certificate of the selected slot signs the transcript from TSM
6) Verifier confirms that the #1 transcript is acceptable

I think this is weaker than #1, but not that much..

3) Do slot selection without CHALLENGE and then do it again after establishing the session to confirm nothing has changed.

Requires TSM to be able to return slots after opening the session, CCA can't at least.

It may be we simply don't securely support multi-slot until 1.4 spec support in TSMs. Per 1.2 can be flagged as less secure in the API or something?

Jason

author maintainer
#47 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 43 lines · Jason Gunthorpe
Quoted 12 lines · Elena
Quoted 11 lines
Quoted 6 lines

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference Spec 2.1 v0.66, 4.2.20.1).

Yeah, its fine. TDX will have to do what Aneesh proposed for ARM, and use use the Rust SPDM Driver to operate DOE prior to calling TDH.SPDM.CONNECT. This will allow discovering what is in the slots and passing that information to userspace.

Quoted 7 lines

I am somewhat confused by this.

1. You are saying that prior to establishing a
secure SPDM session, you will use unsecure DOE mechanism to discover available cert chains. Here you have to take into account that anything you get at this step is not trusted. A malicious device could send you any cert chains from another legitimate device.

I poked at this with AI for a while.. While nothing about SPDM slots seems particularly clean it can be made to work. It looks like 1.4 improves things meaningfully.

Quoted 3 lines

2. Next, you will choose a cert slot to be used for SPDM authentication and secure session establishment (slot 0 initially in this patch) and allow everything to happen.

Yep

Quoted 4 lines

Now, who guarantees that a cert chain that you saw
at step 1 for the selected slot is the same that is going to be used in actual SPDM connection? Who is verifying/ensuring this? Kernel? Userspace?

Not sure, it would make sense if the kernel can do some of this with rustspdm, but there are alot of details here that I can't guess what is best.

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

Quoted 10 lines

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

I am currently doing something similar to the above.

https://git.gitlab.arm.com/linux-arm/linux-cca/-/commit/0657d167aee1ba43dd2765d99b5e3244b83c6ea6

-aneesh

#48 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 78 lines

-----Original Message-----
From: Aneesh Kumar K.V <[email protected]>
Sent: Saturday, October 31, 2026 5:47 AM
To: Jason Gunthorpe <[email protected]>; Reshetova, Elena <[email protected]>
Cc: Jiang, Dave <[email protected]>; [email protected]; Jiri Pirko <[email protected]>; Tian, Kevin <[email protected]>; Alexey Kardashevskiy <[email protected]>; Xu Yilun <[email protected]>; Catalin Marinas <[email protected]>; Suzuki K Poulose <[email protected]>; Steven Price <[email protected]>; Kiryl Shutsemau <[email protected]>; Edgecombe, Rick P <[email protected]>; Dave Hansen <[email protected]>; Jonathan Cameron <[email protected]>; Bjorn Helgaas <[email protected]>; Ashish Kalra <[email protected]>; Tom Lendacky <[email protected]>; John Allen <[email protected]>; Will Deacon <[email protected]>; Mark Rutland <[email protected]>; [email protected]
Subject: Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 6 lines · Jason Gunthorpe
Quoted 5 lines · Elena
Quoted 5 lines
Quoted 5 lines

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference

Spec

Quoted 37 lines
Quoted 7 lines
Quoted 6 lines
Quoted 1 line

2.1 v0.66, 4.2.20.1).

Yeah, its fine. TDX will have to do what Aneesh proposed for ARM, and use use the Rust SPDM Driver to operate DOE prior to calling TDH.SPDM.CONNECT. This will allow discovering what is in the slots and passing that information to userspace.

Quoted 7 lines

I am somewhat confused by this.

1. You are saying that prior to establishing a
secure SPDM session, you will use unsecure DOE mechanism to discover available cert chains. Here you have to take into account that anything you get at this step is not trusted. A malicious device could send you any cert chains from another legitimate device.

I poked at this with AI for a while.. While nothing about SPDM slots seems particularly clean it can be made to work. It looks like 1.4 improves things meaningfully.

Quoted 3 lines

2. Next, you will choose a cert slot to be used for SPDM authentication and secure session establishment (slot 0 initially in this patch) and allow everything to happen.

Yep

Quoted 4 lines

Now, who guarantees that a cert chain that you saw
at step 1 for the selected slot is the same that is going to be used in actual SPDM connection? Who is verifying/ensuring this? Kernel? Userspace?

Not sure, it would make sense if the kernel can do some of this with rustspdm, but there are alot of details here that I can't guess what is best.

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

Quoted 10 lines

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

Quoted 4 lines

I am currently doing something similar to the above.

https://git.gitlab.arm.com/linux-arm/linux-cca/-
/commit/0657d167aee1ba43dd2765d99b5e3244b83c6ea6

If I understand this correctly, this is not quite the option 1 that Jason proposed above. You dont use GET_DIGESTS and I dont see a binding between cert chain discovered prior to TSM SPDM session and the one during the TSM SPDM session.
Or am I misunderstanding it?

Best Regards,
Elena.

maintainer
#49 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 16 lines · Elena
Quoted 16 lines
Quoted 13 lines

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

I am currently doing something similar to the above.

Quoted 8 lines
Quoted 2 lines

https://git.gitlab.arm.com/linux-arm/linux-cca/-
/commit/0657d167aee1ba43dd2765d99b5e3244b83c6ea6

If I understand this correctly, this is not quite the option 1 that Jason proposed above. You dont use GET_DIGESTS and I dont see a binding between cert chain discovered prior to TSM SPDM session and the one during the TSM SPDM session.
Or am I misunderstanding it?

Right..

I have poked at this alot more and it seems a bit different than I originally was told, especially if we narrow it to RMM only.

For SPDM 1.2 AI found me this statement:

SPDM 1.2 para 304 (and 1.4 para 369 when multi-key is off) says the device "shall contain a single public-private key pair per supported algorithm for its leaf certificate, regardless of how many certificate chains are stored".

Since there is only ever one private key active, the only thing we are trying to do here is to prevent a "fake" certificate chain that isn't actually installed in the device. The 1.2 spec provides CHALLENGE which solves this issue, it signs all the certificates with the single device key.

So "all" we need to do is get the certificates with RustSPDM and then use CHALLENGE to get it signed by the single key in the device.

This is the best RMM supporting SPDM 1.2 can do.

This is almost simple, except that RMM and Rust SPDM may negotiate different algorithms and may see different certificates and private keys because of this. The main challenge will be synchronizing so that RustSPDM uses the same algorithms as RMM. The spec didn't really expect this split, it was supposed to be one session as far as I can tell. So it's hard.

RMM will need an ECN to have an optional negotiable feature to support 1.3 multikey/multislot. In this flow RMM must do the GET_DIGESTS/CERTIFICATES as part of its signed transcript when supporting multi-key.

So the logical design is to have RMM run its FSM past CERTIFICATES and then stop. Userspace gets to see all the stuff it collected and select the slot. Probably via RMI_PDEV_SET_PUBKEY we tell RMM the correct slot and mulit-key to use with KEY_EXCHANGE, instead of telling it during PDEV_CREATE.

From there it is a small step for RMM to support 1.2 devices by also having it issue the GET_DIGESTS/CERTIFICATES/CHALLENGE flow in the same way.

Further, if you have a transcript that includes GET_DIGESTS then you don't *have* to select the slot early. The early slot selection mixes the certificate hash into the KEY_EXCHANGE, but if we did GET_DIGESTS then we already have that mixed in. Once you have a signed GET_DIGESTS then you can just request measurements on whatever slot you like without any relay attack.

Unfortunately RMM doesn't provide an API for the guest to select the slot during GET_MEASUREMENTs and we don't have any way to pass the certificates from other slots to the guest. So from a guest RMM perspective the slot is fixed, attestation subsystem should report the single fixed slot (maybe not zero) and the other slots are blocked.

Anyhow, below is some AI assisted flow how you can incorporate rust spdm and properly bind the CHALLENGE. I'm not really sure this is worth implementing, I'd like to hear from ARM spec folks what the plan is for multi-slot and multi-key. Proper spec support, including guest discovery is far better.

So.. I think we should start with slot undiscoverable and effectively wired to zero. TSMs that want to support discoverable slot really should to incorporate the above into their spec'd SPDM flow and it will work without requiring RustSPDM.

RustSPDM Should be used to demonstrate the flow where we start a session, get to CERTIFICATES, stop, then let userspace select the slot, then continue to KEY_EXCHANGE. This guarentees that the certificates are hashed into the KEY_EXCHANGE for multi-key, so the uAPI must support this kind of flow.

Elena is it OK?

Jason

Host-side slot selection. The host is always responsible to choose the slot. RustSPDM controls slot selection, but the RMM establishes the operational SPDM session.

With a single device key, the slot chooses a certificate chain, not a key. Slot selection is therefore about getting the right certificate chain hashed into the RMM's transcript (H(chain[S]) in TH1) and attested as identity_digest. The goal is to make sure that chain is the one userspace selected, from a complete and untampered list of the device's chains. No attacker on the path can steer the selection by hiding slots, or substitute a different chain into the RMM's transcript.

The selected slot S defines the RMM's transcript: KEY_EXCHANGE(S) puts H(chain[S]) into TH1, and the RMM records chain[S] as identity_digest. With a single key, though, S does not decide which key the guest trusts. Every slot certifies the same K, and K is what the RMM attests (pubkey_digest) and what signs the session and measurements. The guest is not limited to chain[S]: any other slot's chain whose leaf is K is equally usable by the guest to establish trust in K, if the guest can learn those certificates. Those chains need no RMM attestation, because they authenticate themselves against K. The guest only has to check that the leaf is K and that the chain passes its policy. The host decides which chain the RMM attests, not which identities the guest may accept. Today RHI only delivers chain[S] (RHI_DA_DEV_CERTIFICATE), so other chains need a new RHI object or an out-of-band path. Since they do not need attestation, an unauthenticated delivery is fine.

The attacker is anything on the host<->device path (DOE mailbox, switches, interposers) that cannot sign with the device's leaf key. The host itself is not defended against, that is the guest's problem.

Approach: require a single leaf key. Without MULTI_KEY, SPDM mandates one public-private key pair per supported algorithm for all slots (SPDM 1.2 para 304, SPDM 1.4 para 369). Slots only differ in which CA chain vouches for that key. The binding rule is then:

The key the RMM verifies at KEY_EXCHANGE must be the same key K that signed RustSPDM's CHALLENGE_AUTH.

RustSPDM and the RMM run separate SPDM connections, sequentially.

Scope: this design is only for connections where MULTI_KEY_CONN_RSP is false:
- SPDM 1.2 and older devices, which have no multi-key support.
- SPDM 1.3+ devices with MULTI_KEY_CAP=00b.
- SPDM 1.3+ devices with MULTI_KEY_CAP=10b when the RMM leaves ResponderMultiKeyConn clear.

We expect an RMM ABI change before the RMM supports multi-key. The kernel must explicitly opt in to it, for example via a PDEV creation flag, and the default must remain ResponderMultiKeyConn clear. A kernel that does not opt in then always gets a single-key connection, and this design stays valid. A kernel that opts in takes on the DIGESTS-in-TH1 flow below and does not use this design. Until that ABI exists, check that ResponderMultiKeyConn is clear in the RMM's ALGORITHMS anyway (#3), and fail if it is not.

SPDM 1.3 added multi-key, and with it the right way to do this. When MULTI_KEY_CONN_RSP is true and the requester issues GET_DIGESTS immediately after VCA, that DIGESTS is included in TH1 (SPDM 1.4 section 12.4, para 1239):
TH1 = VCA || DIGESTS || H(chain[S]) || KEY_EXCHANGE || KEY_EXCHANGE_RSP
The KEY_EXCHANGE_RSP signature by slot S's key then covers the whole slot inventory, with freshness from the RMM's KEY_EXCHANGE random. No RustSPDM CHALLENGE is needed, and if the RMM cached and attested DIGESTS, the guest could verify the inventory too, without the replay problem in Limitation 4. However RMM 2.1-alp2 does not issue GET_DIGESTS (it was removed by FENIMORE-868). Using this needs an RMM spec change: issue GET_DIGESTS immediately after VCA when MultiKeyConn is negotiated, and cache and attest DIGESTS alongside the VCA.

Part of the multi-key enhancement for RMM must include moving the slot selectg from PDEV_CRETATE to later, after the RMM has issued GET_DIGESTS/GET_CERTIFICATES. Possibly to RMI_PDEV_SET_PUBKEY. This will avoid any need for RustSPDM on multi-key devices.

1. Run this sequence through RustSPDM:

    GET_VERSION / VERSION                          (VCA: Version)
    GET_CAPABILITIES / CAPABILITIES                (VCA: Capabilities)
    NEGOTIATE_ALGORITHMS / ALGORITHMS              (VCA: Algorithms)

GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all chunks)
CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH

M2 = VCA || DIGESTS || all CERTIFICATE || CHALLENGE/CHALLENGE_AUTH, the (A1, B1, C1) ordering in SPDM 1.2 Table 38.

NEGOTIATE_ALGORITHMS should offer the same BaseAsymAlgo/BaseHashAlgo set and version ceiling as the RMM's request, so both connections converge on the same algorithm and therefore the same key. A mismatch is not a security problem, it just makes #4 fail. Only offer algorithms that are acceptable, because each one is a separate key that an attacker could steer towards.

For 1.3+ responders:
- MULTI_KEY_CAP=01b (always multikey): Cannot be supported without RMM support, fail.
- MULTI_KEY_CAP=10b (requester chooses multikey): leave ResponderMultiKeyConn clear.

Verify:
- The CHALLENGE_AUTH signature verifies with slot 0's leaf key. Call this key K.
- CHALLENGE_AUTH Param2 slot mask matches DIGESTS, and CertChainHash == H(chain[0]).
- H(chain[n]) == DIGESTS[n] for every slot in the slot mask.
- Every chain[n] validates, and its leaf public key == K. This is the single key check that the whole design rests on. If it fails, the device is either multi-key or violating para 304, so reject it.

Retain the VCA, DIGESTS, all certificates, K and the CHALLENGE transcript immutably as part of the SPDM connection.

2. Provide the slot certificate information to userspace so it can select a slot S.

3. Initialize the RMM with the selected slot. From here on RustSPDM must not send anything to the device. The RMM's GET_VERSION ends RustSPDM's connection.

RMI_PDEV_CREATE(id_index = S)
RMM GET_VERSION / VERSION (RMM VCA)
RMM GET_CAPABILITIES / CAPABILITIES (RMM VCA)
RMM NEGOTIATE_ALGORITHMS / ALGORITHMS (RMM VCA)
RMM GET_CERTIFICATE / CERTIFICATE (slot S, all chunks)

The RMM asks the host to cache RMI_DEV_PROTOCOL_DATA (the VCA) and RMI_DEV_IDENTITY (chain[S]). Check:
- For 1.3+ responders, ResponderMultiKeyConn is clear in the RMM's ALGORITHMS.
- The DER certificates in the RMM's chain are byte-identical to chain[S] from #1. RootHash may differ if the RMM negotiated a different BaseHashAlgo, so compare the certificates portion. This check only confirms that userspace's view matches. It is not the security check, TH1 in #4 is.

Retain the RMM's VCA immutably on the SPDM connection.

4. When the RMM reaches PDEV_NEEDS_KEY, call RMI_PDEV_SET_PUBKEY with K from #1. Do not extract the key from the RMM's cached RMI_DEV_IDENTITY. The RMM does not parse X.509, it only checks that the key encoding fits the negotiated algorithm (RmiPublicKeyParamsIsValid).

    RMM  KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP   (clear text)
    RMM  FINISH / FINISH_RSP                       (in session, opaque to host)

The RMM verifies the KEY_EXCHANGE_RSP signature with K over:
TH1 = RMM VCA || H(RMM's chain[S]) || KEY_EXCHANGE || KEY_EXCHANGE_RSP
(excluding Signature and ResponderVerifyData)
The device computes TH1 with H(its own chain[S]), so a substituted chain, a modified VCA or a device without K all fail here.

If RustSPDM and the RMM negotiated different algorithms, or the RMM negotiated MultiKeyConn and slot S uses a different key, SET_PUBKEY or KEY_EXCHANGE fails.

KEY_EXCHANGE and KEY_EXCHANGE_RSP are clear text, so the host can also verify this signature with K. This is defence in depth against RMM bugs and makes the retained transcript self-verifying. It is not required by the security argument, which relies on the RMM's verification (DEN0137 IZJNMR).

5. Report to userspace that the connection is open with slot S, and allow it to view the immutable data from the connection: all #1 certificates, the slot layout and the RMM's negotiated version and algorithms.

The kernel retains the #1 CHALLENGE transcript and the RMM's KEY_EXCHANGE/KEY_EXCHANGE_RSP transcript. They are not needed by userspace, which trusts the kernel's verification. They exist for debugging and for a possible future RHI object that lets the guest verify the inventory itself (Limitation 4).

Security argument: K signs RustSPDM's slot discovery (VCA, DIGESTS, every certificate) and, through TH1, the RMM's VCA and selected chain. An attacker without K cannot:
- hide or alter a slot in #1, because DIGESTS and the slot mask are signed by K;
- substitute the chain the RMM fetches, because Ct in TH1 is signed by K;
- alter either connection's negotiation, because both VCAs are signed by K;
- put a different device behind the RMM, because it cannot sign with K.
The single key check in #1 ensures K is the key behind every slot, so the slot 0 challenge vouches for the whole inventory, including S.

The CHALLENGE is mandatory because it is the only thing that proves the certificate list is untampered. Each chain authenticates itself against K, so a modified or added chain already fails chain validation or the leaf == K check. Removal is different: without the signed DIGESTS and slot mask, an attacker can silently drop slots. The CHALLENGE proves the list userspace sees is complete and was produced by the device holding K, at the time of #1. Policies that depend on what is present, for example "the owner slot must be provisioned" or "reject if the vendor chain carries attribute X", or tooling that confirms a SET_CERTIFICATE took effect, rely on that completeness.

The host hands
K to the RMM at PDEV_NEEDS_KEY, which binds the RMM's session to the device and inventory that userspace selected from. Trust in K comes from the selected chain passing the verifier's trust policy.

Limitations:
1) Connections with MULTI_KEY_CONN_RSP true are not supported. They should use DIGESTS in TH1 (see Scope), which needs RMM support.
2) Slots only differ in which CA vouches for K. They protect against a compromised CA, not a compromised device key. A leaked K lets an attacker impersonate the entire device and SPDM session so everything is broken.
3) Trust in K comes only from the verifier's policy on the chain. An attacker holding any CA the verifier accepts can impersonate the whole device, with its own key and a self-consistent inventory that hides the other slots. Signing the inventory cannot detect this. Only policy (pinning the expected root/slot per device) can.
4) The guest only sees id_index, chain[S] (identity_digest), K (pubkey_digest) and the RMM's VCA (protocol_data_digest). The RMM never captures DIGESTS, so the guest cannot see the inventory directly.
With a single key this only matters to guest policies that depend on completeness (see Security argument). Which key is trusted does not depend on it. Every slot certifies the same K, so the host can hand the guest any other chain for K unauthenticated, and the guest just checks that its leaf is K. The host can still withhold chains, though.
Partial fix for completeness: give the guest the #1 CHALLENGE transcript over RHI. This needs a new RHI object type, because RHI 1.0-bet3 only has VCA, CERTIFICATE, MEASUREMENTS and INTERFACE_REPORT. It is signed by K, and K is RMM-attested, so the guest can verify the inventory.
However the host chooses the nonce, so the host could replay an older inventory.
5) Requires slot 0 to be provisioned (SPDM says it is by default) and the responder to set CHAL_CAP.

#50 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 32 lines · Elena
Quoted 16 lines
Quoted 16 lines
Quoted 13 lines

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

I am currently doing something similar to the above.

Quoted 8 lines
Quoted 2 lines

https://git.gitlab.arm.com/linux-arm/linux-cca/-
/commit/0657d167aee1ba43dd2765d99b5e3244b83c6ea6

If I understand this correctly, this is not quite the option 1 that Jason proposed above. You dont use GET_DIGESTS and I dont see a binding between cert chain discovered prior to TSM SPDM session and the one during the TSM SPDM session.
Or am I misunderstanding it?

Right..

I have poked at this alot more and it seems a bit different than I originally was told, especially if we narrow it to RMM only.

Quoted 6 lines

For SPDM 1.2 AI found me this statement:

SPDM 1.2 para 304 (and 1.4 para 369 when multi-key is off) says the device "shall contain a single public-private key pair per supported algorithm for its leaf certificate, regardless of how many certificate chains are stored".

Yes, this matches my understanding of SPDM. If multi-key is not used, then while device can have a number of populated cert slots, they all must have the same key (per negotiated algorithm). However, the cert chain to the same key can be different, i.e. certified by different intermediates, etc.

This changes with multi-key support: now cert chain slots can end up with a different key and each key can be independently certified.

Quoted 4 lines

Since there is only ever one private key active, the only thing we are trying to do here is to prevent a "fake" certificate chain that isn't actually installed in the device.

The ultimate goal of your proposed two step approach is to make sure that the SPDM session establishment in step 3 of your sequence has happened using the key/cert chain userspace selected to trust in step 2. It does not actually matter much if you allow multi-key or single key, you still need to do the binding between initial discovery, selection of key to trust and the actual SPDM session authentication in step 3. Would you agree?

Thinking more on this, one could argue that the only thing you need to do is to in step 3 to make sure SPDM connection established using a private key you trust and if you allow userspace at this point to do verification and acknowledgement of the key, then you do not need to have any bindings to step 1. Yes, malicious device can in this case send you in step 1 bogus keys and slots, but if you ultimately check key/cert chain after step 3, you are good, your decision is made at a correct point.

The flow really becomes as:

1) Do via RustSPDM:
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup
4) Verify the cert chain of public key used for SPDM session establishment to determine if the key is trusted

For this TSM must expose in a trusted way the full cert chain and pub key used for SPDM session establishment or only a full cert chain and guarantee that the pub key on left cert chain was used for session establishment. This is doable for TSM.

The 1.2 spec provides

Quoted 5 lines

CHALLENGE which solves this issue, it signs all the certificates with the single device key.

So "all" we need to do is get the certificates with RustSPDM and then use CHALLENGE to get it signed by the single key in the device.

Do you mean in step 1 of your above sequence?

Quoted 9 lines

This is the best RMM supporting SPDM 1.2 can do.

This is almost simple, except that RMM and Rust SPDM may negotiate different algorithms and may see different certificates and private keys because of this. The main challenge will be synchronizing so that RustSPDM uses the same algorithms as RMM. The spec didn't really expect this split, it was supposed to be one session as far as I can tell. So it's hard.

Yes, I agree with this, this creates a synchronization point between two different independent components. So ideally, you would really want to do this within a single SPDM session as SPDM is designed to do.

Quoted 12 lines

RMM will need an ECN to have an optional negotiable feature to support 1.3 multikey/multislot. In this flow RMM must do the GET_DIGESTS/CERTIFICATES as part of its signed transcript when supporting multi-key.

So the logical design is to have RMM run its FSM past CERTIFICATES and then stop. Userspace gets to see all the stuff it collected and select the slot. Probably via RMI_PDEV_SET_PUBKEY we tell RMM the correct slot and mulit-key to use with KEY_EXCHANGE, instead of telling it during PDEV_CREATE.

Quoted 3 lines

From there it is a small step for RMM to support 1.2 devices by also having it issue the GET_DIGESTS/CERTIFICATES/CHALLENGE flow in the same way.

I am not very familiar with RMM, but if you do things within a single SPDM session (just stopping after you have fetched the certs from all slots and passed it to userspace to select the key/slot), then you dont need to complicate matters by including GET_DIGESTS/CHALLENGE. TSM in this case just must ensure that SPDM session establishment has happened using the key corresponding to selected slot. TSM can do things like this because it has the final SPDM transcript as result of session establishment and can verify it by itself.

Quoted 7 lines

Further, if you have a transcript that includes GET_DIGESTS then you don't *have* to select the slot early. The early slot selection mixes the certificate hash into the KEY_EXCHANGE, but if we did GET_DIGESTS then we already have that mixed in. Once you have a signed GET_DIGESTS then you can just request measurements on whatever slot you like without any relay attack.

You lost me on this. If you mean final SPDM transcript in step 3, I dont understand how having GET_DIGESTS helps....

Quoted 6 lines

Unfortunately RMM doesn't provide an API for the guest to select the slot during GET_MEASUREMENTs and we don't have any way to pass the certificates from other slots to the guest. So from a guest RMM perspective the slot is fixed, attestation subsystem should report the single fixed slot (maybe not zero) and the other slots are blocked.

TDX currently also uses a fixed slot 0 also for step 3, BUT it does return you all cert chains from all slots, so you can actually make a full decision after step 3 whenever you trust the single key using any available cert chain slot. For a single key SPDM it seems like the best option in a single SPDM session. But we must design a proactive interface that would also work for multi-key and the current TDX one does not (multi-key is therefore disabled).

Quoted 6 lines

Anyhow, below is some AI assisted flow how you can incorporate rust spdm and properly bind the CHALLENGE. I'm not really sure this is worth implementing, I'd like to hear from ARM spec folks what the plan is for multi-slot and multi-key. Proper spec support, including guest discovery is far better.

Yes, I would like to also hear how others plan to implement this, ARM and AMD included.

Quoted 5 lines

So.. I think we should start with slot undiscoverable and effectively wired to zero. TSMs that want to support discoverable slot really should to incorporate the above into their spec'd SPDM flow and it will work without requiring RustSPDM.

If we start with a fixed slot 0, how the interface/flow would look like for userspace to accommodate future slot discovery?

Not sure what are the proper verbs/terms for this, but it would look like smth:

1. userspace triggers SPDM session establishment, which goes into TSM and it starts SPDM connection, does VCA steps, cert fetching from all slots. TSM returns cert chains back to kernel.

2. Kernel returns cert chains back to userspace and asks to select the slot.

3. Userspace selects the slot #, kernel returns this info to TSM, TSM continues to do AKE and establish session using the selected key.
In this step we can (for now) put a check that if userspace selects anything but slot 0, we stop and error out.

What we will have to do in TDX for now (to avoid instant changes) to accommodate above flow is to actually run the whole SPDM session establishment in step 1, and in step 3 if userspace selects anything but slot 0, we would have to tear down SPDM session before erroring out. If slot 0 is selected, we are good, nothing needs to be done anymore. @Jiang, Dave @Xu Yilun is it reasonable to do?

Quoted 6 lines

RustSPDM Should be used to demonstrate the flow where we start a session, get to CERTIFICATES, stop, then let userspace select the slot, then continue to KEY_EXCHANGE. This guarentees that the certificates are hashed into the KEY_EXCHANGE for multi-key, so the uAPI must support this kind of flow.

You mean you will just prototype the flow using RustSPDM?

Quoted 2 lines

Elena is it OK?

As said above, I strongly support not breaking SPDM cert discovery and actual session establishment into two independent steps. I also believe we need to put interface that handles cert slot selection for future.

Best Regards,
Elena.

Quoted 246 lines

Jason

Host-side slot selection. The host is always responsible to choose the slot. RustSPDM controls slot selection, but the RMM establishes the operational SPDM
session.

With a single device key, the slot chooses a certificate chain, not a key. Slot selection is therefore about getting the right certificate chain hashed into the RMM's transcript (H(chain[S]) in TH1) and attested as identity_digest. The goal is to make sure that chain is the one userspace selected, from a complete and untampered list of the device's chains. No attacker on the path can steer the selection by hiding slots, or substitute a different chain into the RMM's transcript.

The selected slot S defines the RMM's transcript: KEY_EXCHANGE(S) puts H(chain[S]) into TH1, and the RMM records chain[S] as identity_digest. With a single key, though, S does not decide which key the guest trusts. Every slot certifies the same K, and K is what the RMM attests (pubkey_digest) and what signs the session and measurements. The guest is not limited to chain[S]: any other slot's chain whose leaf is K is equally usable by the guest to establish trust in K, if the guest can learn those certificates. Those chains need no RMM attestation, because they authenticate themselves against K. The guest only has to check that the leaf is K and that the chain passes its policy. The host decides which chain the RMM attests, not which identities the guest may accept. Today RHI only delivers chain[S] (RHI_DA_DEV_CERTIFICATE), so other chains need a new RHI object or an out-of-band path. Since they do not need attestation, an unauthenticated delivery is fine.

The attacker is anything on the host<->device path (DOE mailbox, switches, interposers) that cannot sign with the device's leaf key. The host itself is not defended against, that is the guest's problem.

Approach: require a single leaf key. Without MULTI_KEY, SPDM mandates one public-private key pair per supported algorithm for all slots (SPDM 1.2 para 304, SPDM 1.4 para 369). Slots only differ in which CA chain vouches for that key. The binding rule is then:

The key the RMM verifies at KEY_EXCHANGE must be the same key K that
signed
RustSPDM's CHALLENGE_AUTH.

RustSPDM and the RMM run separate SPDM connections, sequentially.

Scope: this design is only for connections where MULTI_KEY_CONN_RSP is false:
- SPDM 1.2 and older devices, which have no multi-key support.
- SPDM 1.3+ devices with MULTI_KEY_CAP=00b.
- SPDM 1.3+ devices with MULTI_KEY_CAP=10b when the RMM leaves ResponderMultiKeyConn clear.

We expect an RMM ABI change before the RMM supports multi-key. The kernel must explicitly opt in to it, for example via a PDEV creation flag, and the default must remain ResponderMultiKeyConn clear. A kernel that does not opt in
then always gets a single-key connection, and this design stays valid. A kernel that opts in takes on the DIGESTS-in-TH1 flow below and does not use this design. Until that ABI exists, check that ResponderMultiKeyConn is clear in the RMM's ALGORITHMS anyway (#3), and fail if it is not.

SPDM 1.3 added multi-key, and with it the right way to do this. When MULTI_KEY_CONN_RSP is true and the requester issues GET_DIGESTS immediately
after VCA, that DIGESTS is included in TH1 (SPDM 1.4 section 12.4, para 1239):
TH1 = VCA || DIGESTS || H(chain[S]) || KEY_EXCHANGE ||
KEY_EXCHANGE_RSP
The KEY_EXCHANGE_RSP signature by slot S's key then covers the whole slot inventory, with freshness from the RMM's KEY_EXCHANGE random. No RustSPDM
CHALLENGE is needed, and if the RMM cached and attested DIGESTS, the guest could verify the inventory too, without the replay problem in Limitation 4. However RMM 2.1-alp2 does not issue GET_DIGESTS (it was removed by FENIMORE-868). Using this needs an RMM spec change: issue GET_DIGESTS immediately after VCA when MultiKeyConn is negotiated, and cache and attest DIGESTS alongside the VCA.

Part of the multi-key enhancement for RMM must include moving the slot selectg
from PDEV_CRETATE to later, after the RMM has issued GET_DIGESTS/GET_CERTIFICATES. Possibly to RMI_PDEV_SET_PUBKEY. This will avoid
any need for RustSPDM on multi-key devices.

1. Run this sequence through RustSPDM:

    GET_VERSION / VERSION                          (VCA: Version)
    GET_CAPABILITIES / CAPABILITIES                (VCA: Capabilities)
    NEGOTIATE_ALGORITHMS / ALGORITHMS              (VCA: Algorithms)

GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all
chunks)
CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH

   M2 = VCA || DIGESTS || all CERTIFICATE || CHALLENGE/CHALLENGE_AUTH,

the
(A1, B1, C1) ordering in SPDM 1.2 Table 38.

NEGOTIATE_ALGORITHMS should offer the same
BaseAsymAlgo/BaseHashAlgo set
and version ceiling as the RMM's request, so both connections converge on the same algorithm and therefore the same key. A mismatch is not a security problem, it just makes #4 fail. Only offer algorithms that are acceptable, because each one is a separate key that an attacker could steer towards.

For 1.3+ responders:
- MULTI_KEY_CAP=01b (always multikey): Cannot be supported without
RMM
support, fail.
- MULTI_KEY_CAP=10b (requester chooses multikey): leave ResponderMultiKeyConn clear.

Verify:
- The CHALLENGE_AUTH signature verifies with slot 0's leaf key. Call this key K.
- CHALLENGE_AUTH Param2 slot mask matches DIGESTS, and CertChainHash
==
H(chain[0]).
- H(chain[n]) == DIGESTS[n] for every slot in the slot mask.
- Every chain[n] validates, and its leaf public key == K. This is the single key check that the whole design rests on. If it fails, the device is either multi-key or violating para 304, so reject it.

Retain the VCA, DIGESTS, all certificates, K and the CHALLENGE transcript immutably as part of the SPDM connection.

2. Provide the slot certificate information to userspace so it can select a slot S.

3. Initialize the RMM with the selected slot. From here on RustSPDM must not send anything to the device. The RMM's GET_VERSION ends RustSPDM's connection.

RMI_PDEV_CREATE(id_index = S)
RMM GET_VERSION / VERSION (RMM VCA)
RMM GET_CAPABILITIES / CAPABILITIES (RMM VCA)
RMM NEGOTIATE_ALGORITHMS / ALGORITHMS (RMM VCA)
RMM GET_CERTIFICATE / CERTIFICATE (slot S, all chunks)

The RMM asks the host to cache RMI_DEV_PROTOCOL_DATA (the VCA) and RMI_DEV_IDENTITY (chain[S]). Check:
- For 1.3+ responders, ResponderMultiKeyConn is clear in the RMM's ALGORITHMS.
- The DER certificates in the RMM's chain are byte-identical to chain[S] from #1. RootHash may differ if the RMM negotiated a different BaseHashAlgo, so compare the certificates portion. This check only confirms that userspace's view matches. It is not the security check, TH1 in #4 is.

Retain the RMM's VCA immutably on the SPDM connection.

4. When the RMM reaches PDEV_NEEDS_KEY, call RMI_PDEV_SET_PUBKEY with K from
#1. Do not extract the key from the RMM's cached RMI_DEV_IDENTITY. The
RMM
does not parse X.509, it only checks that the key encoding fits the negotiated algorithm (RmiPublicKeyParamsIsValid).

    RMM  KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP   (clear text)
    RMM  FINISH / FINISH_RSP                       (in session, opaque to host)

The RMM verifies the KEY_EXCHANGE_RSP signature with K over:

     TH1 = RMM VCA || H(RMM's chain[S]) || KEY_EXCHANGE ||

KEY_EXCHANGE_RSP
(excluding Signature and ResponderVerifyData)
The device computes TH1 with H(its own chain[S]), so a substituted chain, a modified VCA or a device without K all fail here.

If RustSPDM and the RMM negotiated different algorithms, or the RMM negotiated MultiKeyConn and slot S uses a different key, SET_PUBKEY or KEY_EXCHANGE fails.

KEY_EXCHANGE and KEY_EXCHANGE_RSP are clear text, so the host can also verify this signature with K. This is defence in depth against RMM bugs and makes the retained transcript self-verifying. It is not required by the security argument, which relies on the RMM's verification (DEN0137 IZJNMR).

5. Report to userspace that the connection is open with slot S, and allow it to view the immutable data from the connection: all #1 certificates, the slot layout and the RMM's negotiated version and algorithms.

The kernel retains the #1 CHALLENGE transcript and the RMM's KEY_EXCHANGE/KEY_EXCHANGE_RSP transcript. They are not needed by
userspace,
which trusts the kernel's verification. They exist for debugging and for a possible future RHI object that lets the guest verify the inventory itself (Limitation 4).

Security argument: K signs RustSPDM's slot discovery (VCA, DIGESTS, every certificate) and, through TH1, the RMM's VCA and selected chain. An attacker without K cannot:
- hide or alter a slot in #1, because DIGESTS and the slot mask are signed by K;
- substitute the chain the RMM fetches, because Ct in TH1 is signed by K;
- alter either connection's negotiation, because both VCAs are signed by K;
- put a different device behind the RMM, because it cannot sign with K.
The single key check in #1 ensures K is the key behind every slot, so the slot 0 challenge vouches for the whole inventory, including S.

The CHALLENGE is mandatory because it is the only thing that proves the certificate list is untampered. Each chain authenticates itself against K, so a modified or added chain already fails chain validation or the leaf == K check. Removal is different: without the signed DIGESTS and slot mask, an attacker can silently drop slots. The CHALLENGE proves the list userspace sees is complete and was produced by the device holding K, at the time of #1. Policies that depend on what is present, for example "the owner slot must be provisioned" or "reject if the vendor chain carries attribute X", or tooling that confirms a SET_CERTIFICATE took effect, rely on that completeness.

The host hands
K to the RMM at PDEV_NEEDS_KEY, which binds the RMM's session to the device
and inventory that userspace selected from. Trust in K comes from the selected chain passing the verifier's trust policy.

Limitations:
1) Connections with MULTI_KEY_CONN_RSP true are not supported. They should use
DIGESTS in TH1 (see Scope), which needs RMM support.
2) Slots only differ in which CA vouches for K. They protect against a compromised CA, not a compromised device key. A leaked K lets an attacker impersonate the entire device and SPDM session so everything is broken.
3) Trust in K comes only from the verifier's policy on the chain. An attacker holding any CA the verifier accepts can impersonate the whole device, with its own key and a self-consistent inventory that hides the other slots. Signing the inventory cannot detect this. Only policy (pinning the expected root/slot per device) can.
4) The guest only sees id_index, chain[S] (identity_digest), K (pubkey_digest) and the RMM's VCA (protocol_data_digest). The RMM never captures
DIGESTS,
so the guest cannot see the inventory directly.
With a single key this only matters to guest policies that depend on completeness (see Security argument). Which key is trusted does not depend on it. Every slot certifies the same K, so the host can hand the guest any other chain for K unauthenticated, and the guest just checks that its leaf is K. The host can still withhold chains, though. Partial fix for completeness: give the guest the #1 CHALLENGE transcript over
RHI. This
needs a new RHI object type, because RHI 1.0-bet3 only has VCA, CERTIFICATE, MEASUREMENTS and INTERFACE_REPORT. It is signed by K, and
K
is RMM-attested, so the guest can verify the inventory.
However the host chooses the nonce, so the host could replay an older inventory.
5) Requires slot 0 to be provisioned (SPDM says it is by default) and the responder to set CHAL_CAP.

maintainer
#51 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 8 lines · Elena
Quoted 3 lines

Since there is only ever one private key active, the only thing we are trying to do here is to prevent a "fake" certificate chain that isn't actually installed in the device.

The ultimate goal of your proposed two step approach is to make sure that the SPDM session establishment in step 3 of your sequence has happened using the key/cert chain userspace selected to trust in step 2.

Sort of, in a 1.2 world you don't get to select the key, and the only purpose of early slot selection is to get that slot's certificates mixed into KEY_EXCHANGE so that the verifier has signed proof the certificates are in the device.

It seems like a theme in SPDM is that certificates are signed so the verifier can tell if they are authentically installed in the device or not.

Quoted 4 lines

It does not actually matter much if you allow multi-key or single key, you still need to do the binding between initial discovery, selection of key to trust and the actual SPDM session authentication in step 3. Would you agree?

Yes, everything needs to be bound together and ideally the verifier should see signatures that cover all of it in the transcript.

Quoted 7 lines

Thinking more on this, one could argue that the only thing you need to do is to in step 3 to make sure SPDM connection established using a private key you trust and if you allow userspace at this point to do verification and acknowledgement of the key, then you do not need to have any bindings to step 1. Yes, malicious device can in this case send you in step 1 bogus keys and slots, but if you ultimately check key/cert chain after step 3, you are good, your decision is made at a correct point.

Maybe, but this is not how SPDM seems to have been designed.

There is a very clear theme running through that the certificate chains are bound up in the transcript and presented to the verifier. There is little reason to have mixed in the slots certificate to KEY_EXCHANGE otherwise if authenticating the certificates was not a design goal.

If you read the slots after doing KEY_EXCHANGE then there is no longer any verifiable evidence of this binding when the data reaches the verifier.

I don't know why it is like this, but I'm very reluctant to see Linux ignore something the spec committee thought was important.

Quoted 9 lines

The flow really becomes as:

1) Do via RustSPDM:
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup
4) Verify the cert chain of public key used for SPDM session establishment to determine if the key is trusted

Quoted 4 lines

For this TSM must expose in a trusted way the full cert chain and pub key used for SPDM session establishment or only a full cert chain and guarantee that the pub key on left cert chain was used for session establishment. This is doable for TSM.

There is no signature covering the slots in this case. Userspace tested them blind without knowing they are actually in the device. It is not ideal.

Quoted 7 lines
Quoted 5 lines

The 1.2 spec provides CHALLENGE which solves this issue, it signs all the certificates with the single device key.

So "all" we need to do is get the certificates with RustSPDM and then use CHALLENGE to get it signed by the single key in the device.

Do you mean in step 1 of your above sequence?

Yes

Quoted 3 lines

two different independent components. So ideally, you would really want to do this within a single SPDM session as SPDM is designed to do.

Right, this is my thinking as well

Quoted 11 lines
Quoted 11 lines

RMM will need an ECN to have an optional negotiable feature to support 1.3 multikey/multislot. In this flow RMM must do the GET_DIGESTS/CERTIFICATES as part of its signed transcript when supporting multi-key.

So the logical design is to have RMM run its FSM past CERTIFICATES and then stop. Userspace gets to see all the stuff it collected and select the slot. Probably via RMI_PDEV_SET_PUBKEY we tell RMM the correct slot and mulit-key to use with KEY_EXCHANGE, instead of telling it during PDEV_CREATE.

Quoted 8 lines
Quoted 3 lines

From there it is a small step for RMM to support 1.2 devices by also having it issue the GET_DIGESTS/CERTIFICATES/CHALLENGE flow in the same way.

I am not very familiar with RMM, but if you do things within a single SPDM session (just stopping after you have fetched the certs from all slots and passed it to userspace to select the key/slot), then you dont need to complicate matters by including GET_DIGESTS/CHALLENGE.

The purpose of CHALLENGE is to authenticate the certificates to prevent any MIM from tampering them. It is cheap and closes this hole, I would definately expect it.

Quoted 4 lines

TSM in this case just must ensure that SPDM session establishment has happened using the key corresponding to selected slot. TSM can do things like this because it has the final SPDM transcript as result of session establishment and can verify it by itself.

At least in the RMM flow the OS provides the PUBKEY to the RMM so this is never in any doubt. I agree that the TSM implementation has to provide the property that the PUBKEY's signature is checked during KEY_EXCHANGE and the OS must definitively know what PUBKEY was used.

Quoted 9 lines
Quoted 6 lines

Further, if you have a transcript that includes GET_DIGESTS then you don't *have* to select the slot early. The early slot selection mixes the certificate hash into the KEY_EXCHANGE, but if we did GET_DIGESTS then we already have that mixed in. Once you have a signed GET_DIGESTS then you can just request measurements on whatever slot you like without any relay attack.

You lost me on this. If you mean final SPDM transcript in step 3, I dont understand how having GET_DIGESTS helps....

One of the issues with 1.2 is you get a signature over only one slot's certificates during KEY_EXCHANGE. So you can't use the other slots certificates because you don't have any signed evidence to share with the verifier.

In 1.3 we can do GET_DIGESTS and include it in the transcript that KEY_EXCHANGE signs. The clear purpose of this spec change is to sign all the slots certificates in a way the verifier can see.

The net result is you can now freely use GET_MEASUREMENTS with any slot without any binding issue. Both the slot's leaf key and the certificate chain are signed by KEY_EXCHANGE.

Quoted 10 lines
Quoted 5 lines

Unfortunately RMM doesn't provide an API for the guest to select the slot during GET_MEASUREMENTs and we don't have any way to pass the certificates from other slots to the guest. So from a guest RMM perspective the slot is fixed, attestation subsystem should report the single fixed slot (maybe not zero) and the other slots are blocked.

TDX currently also uses a fixed slot 0 also for step 3, BUT it does return you all cert chains from all slots, so you can actually make a full decision after step 3 whenever you trust the single key using any available cert chain slot.

IMHO this is not useful. Again without signing it is all forgable from the verifier perspective.

Quoted 10 lines
Quoted 5 lines

Anyhow, below is some AI assisted flow how you can incorporate rust spdm and properly bind the CHALLENGE. I'm not really sure this is worth implementing, I'd like to hear from ARM spec folks what the plan is for multi-slot and multi-key. Proper spec support, including guest discovery is far better.

Yes, I would like to also hear how others plan to implement this, ARM and AMD included.

Quoted 1 line
Quoted 7 lines
Quoted 4 lines

So.. I think we should start with slot undiscoverable and effectively wired to zero. TSMs that want to support discoverable slot really should to incorporate the above into their spec'd SPDM flow and it will work without requiring RustSPDM.

If we start with a fixed slot 0, how the interface/flow would look like for userspace to accommodate future slot discovery?

We will need some multi-step sequence from the uABI perspective:

1) CTLV command START CONNECT:
TSM does:
GET_VERSION / VERSION (VCA: Version)
GET_CAPABILITIES / CAPABILITIES (VCA: Capabilities)
NEGOTIATE_ALGORITHMS / ALGORITHMS (VCA: Algorithms)
GET_DIGESTS / DIGESTS

2) CTLV commands:
query certificates. TSM will issue on demand:
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all chunks)
query VCA items

3) CTLV commands:
FINISH CONNECT with slot:
TSM for 1.2 does:
CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (clear text)
FINISH / FINISH_RSP (in session, opaque to host)
Or for 1.3 multi-key does:
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (now includes DIGESTS and CERTIFICATE)
FINISH / FINISH_RSP (in session, opaque to host)

4) CTLV commands:
query certificates
* Only certificates kernel or TSM has been able to validate with a signature.
query final transcript items

Then there are five implementations of this:
a) Today w/ 1.2 and current TDX/RMM/SEV specs:
#1 is a NOP
#2 is EOPNOTSUPP
#3 does the full flow from GET_VERSION -> FINISH
no GET_DIGESTS
#4 returns only the slot S certificate that was signed
during the transcript

b) Improved 1.2 TSM:
#1 TSM can execute GET_DIGESTS and pause after
#2 Ideal TSM allows the OS to request each GET_CERTIFICATES command.
Less ideal is TSM pushes all GET_CERTIFICATES then pauses.
#3 TSM issues CHALLENGE and verifiers the signature.

c) 1.3 Multi-key flow:
#1/#2 is the same as b

#3 The function of CHALLENGE is absorbed in the updated
KEY_EXCHANGE which covers GET_DIGESTS when in multikey mode No CHALLENGE issued

d) RustSPDM flow:
#1 becomes a RustSPDM session and includes CHALLENGE #3 starts a new session
We have to solve the issue with ALGORITHMS mismatch somehow. There is no verifiable binding of certificates visible to the verifier

e) In the guest:
#1 is LOCK
#2 is preloaded with the transcript from the host. Currently
exactly one slot with the same number the host picked
#3 is NOP.
#4 is the same as #2, it doesn't change
#5 Additional ACCEPT command is TDISP RUN, after we have
logged the data in #4

CHALLENGE in 1.2 brings it up to the same level as 1.3 where the GET_DIGESTS is signed. It broadly gives the same security guarentees for both standards levels. If CHALLENGE and KEY_EXCHANGE have related nonces then the whole thing is bound together in a way the verifier can check.

A TSM that implements b or c should present the whole transcript, including GET_DIGESTS, CHALLENGE and all certificate chains to the guest. It should let the guest choose the slot when executing GET_MEASUREMENTS.

Since the transcript now signs the slots the guest can rely on the transcript to prove the pub key for any slot. For multi-key the verifier still has to accept the host's slot choice since the KEY_EXCHANGE signature is root of all the security. It is probably still a mistake that GET_MEASUREMENTS doesn't bind to the channel somehow, oh well.

For case A userspace has the option to run the entire SPDM flow multiple times through to FINISH CONNECT to enumerate all the slots, with signatures. I don't see a good reason to have the kernel do this, hopefully nobody ever needs this..

Quoted 7 lines
Quoted 5 lines

RustSPDM Should be used to demonstrate the flow where we start a session, get to CERTIFICATES, stop, then let userspace select the slot, then continue to KEY_EXCHANGE. This guarentees that the certificates are hashed into the KEY_EXCHANGE for multi-key, so the uAPI must support this kind of flow.

You mean you will just prototype the flow using RustSPDM?

Yes, if someone has hardware.

I fed all this to AI and asked it about AMD. It looks like AMD is basically the same as RMM with some minor difference. I expect they could do option b and c with similar minor spec changes. Hopefully TDX is in the same boat.

Also I suppose I should mention for completeness.. The systemd side of the flow would be like:

CTLV command START CONNECT
CTLV command FINISH CONNECT with slot 0
CTLV command query cerificates
CTLV command query transcript objects
systemd-pcr-extend <something something> 'device accept: transcript, certificates and so on' CTLV command ACCEPT
device trust = trusted
driver bind

Where systemd-pcr-extend would extend a measurement register in the guest's quote. Like a TDX RTMR

Jason

#52 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 30 lines

We will need some multi-step sequence from the uABI perspective:

1) CTLV command START CONNECT:
TSM does:
GET_VERSION / VERSION (VCA: Version)
GET_CAPABILITIES / CAPABILITIES (VCA: Capabilities)
NEGOTIATE_ALGORITHMS / ALGORITHMS (VCA: Algorithms)
GET_DIGESTS / DIGESTS

2) CTLV commands:
query certificates. TSM will issue on demand:
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all chunks)
query VCA items

3) CTLV commands:
FINISH CONNECT with slot:
TSM for 1.2 does:
CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (clear text)
FINISH / FINISH_RSP (in session, opaque to host)
Or for 1.3 multi-key does:
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (now includes DIGESTS and CERTIFICATE)
FINISH / FINISH_RSP (in session, opaque to host)

4) CTLV commands:
query certificates
* Only certificates kernel or TSM has been able to validate with a signature.
query final transcript items

Quoted 8 lines

Then there are five implementations of this:
a) Today w/ 1.2 and current TDX/RMM/SEV specs:
#1 is a NOP
#2 is EOPNOTSUPP
#3 does the full flow from GET_VERSION -> FINISH
no GET_DIGESTS
#4 returns only the slot S certificate that was signed
during the transcript

I see Elena's proposal [*] for current TDX:

#1 Does the full flow from GET_VERSION -> FINISH
no GET_DIGESTS
#2 Returns only the slot S certificate that was signed during the
transcript, this is my understanding, not specified by Elena.
#3 If user selects slot0, pass; other slots, fail. Actually user can
see only slot0.
#4 Same as #2

Seems same to me, any preference? or do I understand correctly?

[*] https://lore.kernel.org/linux-coco/DS4PPFA84FA90FC31B41ECA1F6ACF6D0600E7692@DS4PPFA84FA90FC.namprd11.prod.outlook.com/

Thanks,
Yilun

Quoted 30 lines

b) Improved 1.2 TSM:
#1 TSM can execute GET_DIGESTS and pause after
#2 Ideal TSM allows the OS to request each GET_CERTIFICATES command.
Less ideal is TSM pushes all GET_CERTIFICATES then pauses.
#3 TSM issues CHALLENGE and verifiers the signature.

c) 1.3 Multi-key flow:
#1/#2 is the same as b

#3 The function of CHALLENGE is absorbed in the updated
KEY_EXCHANGE which covers GET_DIGESTS when in multikey mode No CHALLENGE issued

d) RustSPDM flow:
#1 becomes a RustSPDM session and includes CHALLENGE #3 starts a new session
We have to solve the issue with ALGORITHMS mismatch somehow. There is no verifiable binding of certificates visible to the verifier

e) In the guest:
#1 is LOCK
#2 is preloaded with the transcript from the host. Currently
exactly one slot with the same number the host picked
#3 is NOP.
#4 is the same as #2, it doesn't change
#5 Additional ACCEPT command is TDISP RUN, after we have
logged the data in #4

#53 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 10 lines
Quoted 10 lines

We will need some multi-step sequence from the uABI perspective:

1) CTLV command START CONNECT:
TSM does:
GET_VERSION / VERSION (VCA: Version)
GET_CAPABILITIES / CAPABILITIES (VCA: Capabilities)
NEGOTIATE_ALGORITHMS / ALGORITHMS (VCA: Algorithms)
GET_DIGESTS / DIGESTS

Why do you need a break here? Why not merge 1 and 2?

Quoted 14 lines
Quoted 3 lines

2) CTLV commands:
query certificates. TSM will issue on demand:
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all

chunks)

Quoted 10 lines

query VCA items

3) CTLV commands:
FINISH CONNECT with slot:
TSM for 1.2 does:
CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (clear text)
FINISH / FINISH_RSP (in session, opaque to host)
Or for 1.3 multi-key does:
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (now includes DIGESTS

Quoted 17 lines

and CERTIFICATE)

Quoted 16 lines

FINISH / FINISH_RSP (in session, opaque to host)

4) CTLV commands:
query certificates
* Only certificates kernel or TSM has been able to validate with a signature.
query final transcript items

Then there are five implementations of this:
a) Today w/ 1.2 and current TDX/RMM/SEV specs:
#1 is a NOP
#2 is EOPNOTSUPP
#3 does the full flow from GET_VERSION -> FINISH
no GET_DIGESTS
#4 returns only the slot S certificate that was signed
during the transcript

Again, agree given the current limitations.

Quoted 11 lines

I see Elena's proposal [*] for current TDX:

#1 Does the full flow from GET_VERSION -> FINISH
no GET_DIGESTS
#2 Returns only the slot S certificate that was signed during the
transcript, this is my understanding, not specified by Elena.
#3 If user selects slot0, pass; other slots, fail. Actually user can
see only slot0.
#4 Same as #2

Quoted 1 line

Seems same to me, any preference? or do I understand correctly?

The main conceptual difference was that I merged 1 and 2 into one step. I asked Jason above why he splits it.

Quoted 5 lines
Quoted 5 lines

b) Improved 1.2 TSM:
#1 TSM can execute GET_DIGESTS and pause after
#2 Ideal TSM allows the OS to request each GET_CERTIFICATES command.
Less ideal is TSM pushes all GET_CERTIFICATES then pauses.

Why is it less ideal ?

Quoted 9 lines
Quoted 9 lines

#3 TSM issues CHALLENGE and verifiers the signature.

c) 1.3 Multi-key flow:
#1/#2 is the same as b

#3 The function of CHALLENGE is absorbed in the updated
KEY_EXCHANGE which covers GET_DIGESTS when in multikey mode No CHALLENGE issued

Quoted 6 lines
Quoted 6 lines

d) RustSPDM flow:
#1 becomes a RustSPDM session and includes CHALLENGE #3 starts a new session
We have to solve the issue with ALGORITHMS mismatch somehow. There is no verifiable binding of certificates visible to the verifier

But why we need the RustSPDM flow now given that we agree we should handle everything in single SPDM TSM session?

Best Regards,
Elena.

maintainer
#54 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 11 lines · Elena
Quoted 9 lines
Quoted 9 lines

We will need some multi-step sequence from the uABI perspective:

1) CTLV command START CONNECT:
TSM does:
GET_VERSION / VERSION (VCA: Version)
GET_CAPABILITIES / CAPABILITIES (VCA: Capabilities)
NEGOTIATE_ALGORITHMS / ALGORITHMS (VCA: Algorithms)
GET_DIGESTS / DIGESTS

Why do you need a break here? Why not merge 1 and 2?

Break gives the option for the OS to ask the TSM to transfer the certificates it actually wants to speed up session startup. Eg if the OS already has all the certificates cached then it doesn't need to fetch any. Or if it only wants to use slot 0 then it only needs to fetch slot 0.

Quoted 4 lines
Quoted 4 lines
Quoted 4 lines

2) CTLV commands:
query certificates. TSM will issue on demand:
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all chunks)
query VCA items

With the paus we can drive the certificate fetch from userspace, on demand.

I view it as optional. The TSM's implementation choice is invisible to userspace, but I'd like to gently encourage that we think about session startup performance :)

Quoted 9 lines
Quoted 8 lines
Quoted 8 lines

Then there are five implementations of this:
a) Today w/ 1.2 and current TDX/RMM/SEV specs:
#1 is a NOP
#2 is EOPNOTSUPP
#3 does the full flow from GET_VERSION -> FINISH
no GET_DIGESTS
#4 returns only the slot S certificate that was signed
during the transcript

Quoted 14 lines

Again, agree given the current limitations.

Quoted 12 lines

I see Elena's proposal [*] for current TDX:

#1 Does the full flow from GET_VERSION -> FINISH
no GET_DIGESTS
#2 Returns only the slot S certificate that was signed during the
transcript, this is my understanding, not specified by Elena.
#3 If user selects slot0, pass; other slots, fail. Actually user can
see only slot0.
#4 Same as #2

Seems same to me, any preference? or do I understand correctly?

I think it is similar, but the main drawback is that userspace can no longer detect that it is running option a. From userspace it looks like option b with the device having only a single slot since this makes #2 return certificates instead of EOPNOTSUPP.

Further, we completely loose the ability for userspace to specify a slot at all since #3 would be the only function accepting a slot number, but the session is already open.

Thus I think it is better to stick with the a above where all flows consistently issue KEY_EXCHANGE in step #3. IMHO that consistency is easier to reason about across all the options.

From a definitional perspective I would be very happy if the uAPI specified precisely what part of the SPDM sequence is performed at each step.

Again, if userspace detects it is in option a and really wants to do slot selection it can run through iterations of step #3 to enumerate the slots. I don't see we need the kernel to be involved in this, and that is a strong reason why option a should be discoverable.

Quoted 6 lines
Quoted 4 lines
Quoted 4 lines

b) Improved 1.2 TSM:
#1 TSM can execute GET_DIGESTS and pause after
#2 Ideal TSM allows the OS to request each GET_CERTIFICATES command.
Less ideal is TSM pushes all GET_CERTIFICATES then pauses.

Why is it less ideal ?

Only performance. It means every SPDM setup is forced to transfer every certificate bundle even if we don't care or already have it. That's all.

Quoted 16 lines
Quoted 9 lines
Quoted 9 lines

#3 TSM issues CHALLENGE and verifiers the signature.

c) 1.3 Multi-key flow:
#1/#2 is the same as b

#3 The function of CHALLENGE is absorbed in the updated
KEY_EXCHANGE which covers GET_DIGESTS when in multikey mode No CHALLENGE issued

Quoted 6 lines
Quoted 6 lines

d) RustSPDM flow:
#1 becomes a RustSPDM session and includes CHALLENGE #3 starts a new session
We have to solve the issue with ALGORITHMS mismatch somehow. There is no verifiable binding of certificates visible to the verifier

Quoted 2 lines

But why we need the RustSPDM flow now given that we agree we should handle everything in single SPDM TSM session?

Right, I'm showing it for completness, I don't think we should implement this. Maybe if someone strenuously objects to implement the FW changes for mulit-slot/multi-key in their TSM they would do this.

My feeling is for now we should sketch out option a/b/c in detail, everyone should confirm they can implement option a with current TSM specs. Everyone should go back to their arch teams and reflect if they agree to option c in future as some discoverable extension in their FW.

Agree?

Is there a PDF for TDX's SPDM flow someplace? I will try to draft a more complete document

Jason

#55 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

On 11/4/26 6:41 AM, Jason Gunthorpe wrote:
<--snip-->

Quoted 2 lines

Is there a PDF for TDX's SPDM flow someplace? I will try to draft a more complete document

TDX connect architecture spec
https://cdrdv2-public.intel.com/862706/Intel%20TDX%20Connect%20EAS%20354629%20004.pdf
2.7.5 Figure 2.6

TDX connect TEE-IO device guide
https://cdrdv2-public.intel.com/861654/whitepaper-tee-io-device-guide-v0-7-0.pdf
Appendix B.1.1 Table B-1

#56 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 8 lines · Dave Jiang

On 11/4/26 6:41 AM, Jason Gunthorpe wrote:
<--snip-->

Quoted 2 lines

Is there a PDF for TDX's SPDM flow someplace? I will try to draft a more complete document

Quoted 7 lines

TDX connect architecture spec
https://cdrdv2-public.intel.com/862706/Intel%20TDX%20Connect%20EAS%20354629%20004.pdf
2.7.5 Figure 2.6

TDX connect TEE-IO device guide
https://cdrdv2-public.intel.com/861654/whitepaper-tee-io-device-guide-v0-7-0.pdf
Appendix B.1.1 Table B-1

I generated a diagram that hopefully better reflects Option A with TDX better than the current public documentations:

Option (a) on TDX, with the current TDX Connect ABI: slot 0 only, no TDX change. Numbers in the left margin point to the notes below.

  ---->   call                 <- - -   return
  <---->  SPDM request and response in clear text, relayed by the host
  <====>  the same, inside the SPDM session

Userspace Host kernel TDX Module Device
(systemd, (PCI/TSM, (TSM) (DSM)
verifier) tdx-host)
| | | |
=== sysfs connect ==========================================================

     |                    |                       |                        |
(1)  | connect            |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
=== Step 1: START CONNECT ==================================================
     |                    |                       |                        |
     | START CONNECT      |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
     | 0 (no-op)          |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
=== Step 2: query certificates, query VCA items ============================
     |                    |                       |                        |
     | query certificates |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
     | -EOPNOTSUPP        |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
=== Step 3: FINISH CONNECT with slot S =====================================
     |                    |                       |                        |
     | FINISH CONNECT(S)  |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
     | if S != 0:         |                       |                        |
(2)  | refused            |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
     | if S == 0:         |                       |                        |
(3)  |                    | TDH.SPDM.CONNECT      |                        |
     |                    |---------------------->|                        |
     |                    |                       |                        |
(4)  |                    |                       | GET_VERSION            |
     |                    |                       |<---------------------->|
     |                    |                       | GET_CAPABILITIES       |
     |                    |                       |<---------------------->|
     |                    |                       | NEGOTIATE_ALGORITHMS   |
     |                    |                       |<---------------------->|
     |                    |                       | GET_CERTIFICATE slot 0 |
     |                    |                       |<---------------------->|
(5)  |                    |                       | KEY_EXCHANGE slot 0    |
     |                    |                       |<---------------------->|
     |                    |                       | FINISH                 |
     |                    |                       |<======================>|
     |                    |                       | GET_MEASUREMENTS       |
     |                    |                       |<======================>|
     |                    |                       | GET_TDISP_VERSION      |
     |                    |                       |<======================>|
     |                    |                       | GET_TDISP_CAPABILITIES |
     |                    |                       |<======================>|
     |                    |                       |                        |
(6)  |                    | device info           |                        |
     |                    |<- - - - - - - - - - - |                        |
     |                    |                       |                        |
     | 0                  |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
=== Step 4: query certificates, query final transcript items ===============
     |                    |                       |                        |
     | query certificates |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
     | slot 0's chain     |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
     | query transcript   |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
(6)  | device info        |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
     .--- optional: fresh measurements for a caller's nonce ---------------.
     |                    |                       |                        |
     | nonce              |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
(7)  |                    | TDH.SPDM.MNG(nonce)   |                        |
     |                    |---------------------->|                        |
     |                    |                       |                        |
     |                    |                       | GET_CERTIFICATE        |
     |                    |                       |<======================>|
     |                    |                       | GET_MEASUREMENTS       |
     |                    |                       |<======================>|
     |                    |                       |                        |
     |                    | new device info       |                        |
     |                    |<- - - - - - - - - - - |                        |
     |                    |                       |                        |
     | one-shot result    |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
     '---------------------------------------------------------------------'
     |                    |                       |                        |
=== Step 5: ACCEPT (host) ==================================================
     |                    |                       |                        |
     | ACCEPT             |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
(8)  |                    | TDH.IDE.STREAM.*      |                        |
     |                    |---------------------->|                        |
     |                    |                       |                        |
     |                    |                       | IDE_KM key programming |
     |                    |                       |<======================>|

Notes:
(1) Creates the attestation endpoint only; no TSM call.
(2) TDH.SPDM.CONNECT has no KEY_EXCHANGE slot input. CERTIFICATE_SLOT_MASK, the only slot field in SPDM_CONFIG_INFO_T, is the "Device certificate request slot mask" [ABI 3.3.2.2]: it picks which chains to read, not the slot. So userspace can't enumerate slots by repeating step 3.
(3) TDH.SPDM.CREATE runs first. The kernel sets CERTIFICATE_SLOT_MASK to 0x01, so the TSM reads only slot 0's chain. It could also read slots 1-7 inside the session [GUIDE B.1.1, Table B-1], but no signature would cover them: KEY_EXCHANGE hashes only the chain of the slot it names [SPDM 10.18.1]. So (a) doesn't report them.
(4) The host relays each message over DOE [ABI 3.3.3, 4.2.20]. No GET_DIGESTS and no CHALLENGE [ARCH 2.7.5, Figure 2.6]. The guide's session setup example also reads slot 0's chain before KEY_EXCHANGE [GUIDE B.1.1, Table B-1].
(5) The TSM verifies the KEY_EXCHANGE_RSP signature [SPDM para 630]. The host trusts that check, so it keeps no KEY_EXCHANGE messages.
(6) SPDM_DEVICE_ATTESTATION_INFO_T [ABI 3.3.2.4]: SPDM and TDISP capabilities, TDISP version, the requested chains, and MEASUREMENT. MEASUREMENT is the signed SPDM measurement log, Concatenate(VCA, GET_MEASUREMENTS_REQUEST1, MEASUREMENTS_RESPONSE1, ..., GET_MEASUREMENTS_REQUESTn, MEASUREMENTS_RESPONSEn) [SPDM 10.12.3]. The last MEASUREMENTS carries the signature. The TDX Module keeps the info's digest. The kernel keeps the info as immutable startup data.
(7) DEV_INFO_RECOLLECTION puts the caller's nonce in GET_MEASUREMENTS [ABI 3.3.2.3], and the TSM reads the chains again
[GUIDE B.1.5, Table B-5]. The device signs the new measurement log, nonce included [SPDM 10.12.3]. That signature is what protects the new measurements. The verifier must check it with the leaf key of slot 0, the slot KEY_EXCHANGE used, and must find its nonce in the log to know the result is fresh. The kernel hands the result over once and doesn't store it.
(8) TDH.IDE.STREAM.CREATE, then TDH.IDE.STREAM.KM. IDE setup moves from connect to ACCEPT.

Sources:
[ABI] TDX Connect ABI Reference Specification 2.1 V0.66

           https://cdrdv2.intel.com/v1/dl/getContent/858625
  [ARCH]   Intel TDX Connect Architecture Specification, 354629-003
           https://cdrdv2.intel.com/v1/dl/getContent/773614
  [GUIDE]  Intel TDX Connect TEE-IO Device Guide, 354272-002
           https://cdrdv2.intel.com/v1/dl/getContent/772642

[SPDM] DMTF DSP0274 1.4.1

maintainer
#57 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 19 lines · Dave Jiang
Quoted 8 lines · Dave Jiang

On 11/4/26 6:41 AM, Jason Gunthorpe wrote:
<--snip-->

Quoted 2 lines

Is there a PDF for TDX's SPDM flow someplace? I will try to draft a more complete document

Quoted 7 lines

TDX connect architecture spec
https://cdrdv2-public.intel.com/862706/Intel%20TDX%20Connect%20EAS%20354629%20004.pdf
2.7.5 Figure 2.6

TDX connect TEE-IO device guide
https://cdrdv2-public.intel.com/861654/whitepaper-tee-io-device-guide-v0-7-0.pdf
Appendix B.1.1 Table B-1

Quoted 8 lines

I generated a diagram that hopefully better reflects Option A with TDX better than the current public documentations:

Option (a) on TDX, with the current TDX Connect ABI: slot 0 only, no TDX change. Numbers in the left margin point to the notes below.

  ---->   call                 <- - -   return
  <---->  SPDM request and response in clear text, relayed by the host
  <====>  the same, inside the SPDM session

It looks OK, thanks.

I have a few thoughts

1) I would very much like it if the core SPDM code in the kernel can snoop the DOE messages from the TSM. If we do this then the kernel can directly capture the full transcript:

Quoted 9 lines
(4)  |                    |                       | GET_VERSION            |
     |                    |                       |<---------------------->|
     |                    |                       | GET_CAPABILITIES       |
     |                    |                       |<---------------------->|
     |                    |                       | NEGOTIATE_ALGORITHMS   |
     |                    |                       |<---------------------->|
     |                    |                       | GET_CERTIFICATE slot 0 |
     |                    |                       |<---------------------->|
(5)  |                    |                       | KEY_EXCHANGE slot 0    |

With perfect consistency on all vendors.

Further, it will also be able to capture the GET_CERTIFICATE during the preamble so again we get greater consistency for everyone.

I'm very interested in this because it frees the uABI from any variation in choices the TSM may have made. I have the full perfect transcript in the kernel and I will ALWAYS present the same uABI to userspace regardless what the TSM offers.

Like a whole bunch of TSM FW API just to do this:

Quoted 14 lines

=== Step 4: query certificates, query final transcript items ===============

     |                    |                       |                        |
     | query certificates |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
     | slot 0's chain     |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
     | query transcript   |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
(6)  | device info        |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |

Seems pointless to me, and could cause uABI variations I don't like.

What do you think? Am I off my rocker? At least AI says it looks OK (always optimistic!). Undoing the chunking is annoying.

This is close to what ARM did with CCA, it seems very smart, but they seem to have provided some help to the OS to locate the bytes in the messages, but I assume with some work (and we have Rust SPDM) Linux can decode them autonomously?

This would really slim down the TSM drivers since core code would build up all the SPDM data and the drivers just need to do the communicate loop and little else.

2) I do not like the idea to refetch certificates:

Quoted 8 lines
     | nonce              |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
(7)  |                    | TDH.SPDM.MNG(nonce)   |                        |
     |                    |---------------------->|                        |
     |                    |                       |                        |
     |                    |                       | GET_CERTIFICATE        |
     |                    |                       |<======================>|
Quoted 8 lines
     |                    |                       | GET_MEASUREMENTS       |
     |                    |                       |<======================>|
     |                    |                       |                        |
     |                    | new device info       |                        |
     |                    |<- - - - - - - - - - - |                        |
     |                    |                       |                        |
     | one-shot result    |                       |                        |
     |<- - - - - - - - - -|                       |                        |

That's just asking for trouble. Our general security statement is the kernel & TSM validates a signature over the certificates to verify their authenticity in the HW. Re-fetching defeats the whole point of this if the responder can supply something that differs from GET_DIGESTS. If it can't differ then it was a waste of time since we already have it.

Further it wrecks the point of the transcript and the 1.3 multikey signed GET_DIGESTS.

If you *really* want to do certificate refresh it has to be a dedicated operation, it has to be supported by the TSM, and it has to get back another SIGNED GET_DIGESTS. All that would ideally have to be dumped into the transcript again.

I feel it is alot of work and not valuable.

I generally feel a 2nd measurement is not a valuable activity in the first place. There is one meaningful security measurement. At boot, before DMA is allowed. It must be logged into a measurement register and then after you turn on DMA the device can replace every byte of software on the system. (hello ATS). Only the logged measurement can be relied on for security, any post-DMA measurement can be fradulent.

It is OK for auditing, but not for security, so why would you even look at the certificate chain for audit? For auditing you should check that the PUBKEY is stable.

I think any device that can loose its private key while an SPDM session is open (ie with live update or something) is fundamentally broken. If it wants to do this it has to break the SPDM.

3) Obviously TDX and everyone else will need a 1.3 multi-key upgrade I think they all look the same. I would like us to settle on this API here and then we can describe what a perfect TSM would look like and people can go write their specs for it.

Quoted 7 lines

Notes:
(1) Creates the attestation endpoint only; no TSM call.
(2) TDH.SPDM.CONNECT has no KEY_EXCHANGE slot input. CERTIFICATE_SLOT_MASK, the only slot field in SPDM_CONFIG_INFO_T, is the "Device certificate request slot mask" [ABI 3.3.2.2]: it picks which chains to read, not the slot. So userspace can't enumerate slots by repeating step 3.

Boo

Quoted 5 lines

(3) TDH.SPDM.CREATE runs first. The kernel sets CERTIFICATE_SLOT_MASK to 0x01, so the TSM reads only slot 0's chain. It could also read slots 1-7 inside the session [GUIDE B.1.1, Table B-1], but no signature would cover them: KEY_EXCHANGE hashes only the chain of the slot it names [SPDM 10.18.1]. So (a) doesn't report them.

? this is confusing. KEY_EXCHANGE slot=0 includes the hash of the certificate chain in slot 0. If the TSM doesn't even read that slot during the premable it is impossible to validate the KEY_EXCHANGE.

What prupose is there for this configurable "the kernel sets CERTIFICATE_SLOT_MASK to 0x01" ? If the kernel sets for slot 1 then KEY_EXCHANGE must fail?

Quoted 4 lines

(4) The host relays each message over DOE [ABI 3.3.3, 4.2.20]. No GET_DIGESTS and no CHALLENGE [ARCH 2.7.5, Figure 2.6]. The guide's session setup example also reads slot 0's chain before KEY_EXCHANGE [GUIDE B.1.1, Table B-1].

Yes, you have to because it is necessary to do this:

Quoted 2 lines

(5) The TSM verifies the KEY_EXCHANGE_RSP signature [SPDM para 630]. The host trusts that check, so it keeps no KEY_EXCHANGE messages.

Yes, but if we do #1 above I would keep it anyhow and optionally give it to the verifier. I agree it is fairly pointless, but it is really cheap to do if you have #1 and simply capturing the *entire* signed transcript is forward looking from an ABI design perspective.

It might even be useful if we want to use GET_DIGEST/CHALLENGE/KEY_EXCHANGE as the sequence to support multi-slot.

If people start adding more messages in later spec revs it simply flows through with no ABI change as additional transcript meassages.

Quoted 8 lines

(7) DEV_INFO_RECOLLECTION puts the caller's nonce in GET_MEASUREMENTS [ABI 3.3.2.3], and the TSM reads the chains again
[GUIDE B.1.5, Table B-5]. The device signs the new measurement log, nonce included [SPDM 10.12.3]. That signature is what protects the new measurements. The verifier must check it with the leaf key of slot 0, the slot KEY_EXCHANGE used, and must find its nonce in the log to know the result is fresh. The kernel hands the result over once and doesn't store it.

Yes, but in 1.2 there is only one private key so the slot doesn't matter. There is no reason to re-read the chains. Kernel would throw that information away, it already has a signed copy that it got at the start.

Jason

#58 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 47 lines · Jason Gunthorpe
Quoted 19 lines · Dave Jiang
Quoted 8 lines · Dave Jiang

On 11/4/26 6:41 AM, Jason Gunthorpe wrote:
<--snip-->

Quoted 2 lines

Is there a PDF for TDX's SPDM flow someplace? I will try to draft a more complete document

Quoted 7 lines

TDX connect architecture spec
https://cdrdv2-public.intel.com/862706/Intel%20TDX%20Connect%20EAS%20354629%20004.pdf
2.7.5 Figure 2.6

TDX connect TEE-IO device guide
https://cdrdv2-public.intel.com/861654/whitepaper-tee-io-device-guide-v0-7-0.pdf
Appendix B.1.1 Table B-1

Quoted 8 lines

I generated a diagram that hopefully better reflects Option A with TDX better than the current public documentations:

Option (a) on TDX, with the current TDX Connect ABI: slot 0 only, no TDX change. Numbers in the left margin point to the notes below.

  ---->   call                 <- - -   return
  <---->  SPDM request and response in clear text, relayed by the host
  <====>  the same, inside the SPDM session

It looks OK, thanks.

I have a few thoughts

1) I would very much like it if the core SPDM code in the kernel can snoop the DOE messages from the TSM. If we do this then the kernel can directly capture the full transcript:

Quoted 9 lines
(4)  |                    |                       | GET_VERSION            |
     |                    |                       |<---------------------->|
     |                    |                       | GET_CAPABILITIES       |
     |                    |                       |<---------------------->|
     |                    |                       | NEGOTIATE_ALGORITHMS   |
     |                    |                       |<---------------------->|
     |                    |                       | GET_CERTIFICATE slot 0 |
     |                    |                       |<---------------------->|
(5)  |                    |                       | KEY_EXCHANGE slot 0    |
Quoted 1 line

With perfect consistency on all vendors.

I think that works for TDX. tdx-host already relays every DOE message for the TDX Module, so it can hand them to core SPDM code. Only part of the flow is clear text though. With the current module I see the VCA, GET_CERTIFICATE for slot 0, and KEY_EXCHANGE/KEY_EXCHANGE_RSP in the clear. There's no GET_DIGESTS. FINISH, GET_MEASUREMENTS, the reads of slots 1-7 and the TDISP messages are all inside the session, so the kernel can't decode them.

So the kernel would get the VCA, slot 0's chain and the KEY_EXCHANGE transcript from snooping, and still take MEASUREMENTS from the TDX Module's device info. Would that be ok with you?

Quoted 3 lines

Further, it will also be able to capture the GET_CERTIFICATE during the preamble so again we get greater consistency for everyone.

That's the chain the KEY_EXCHANGE signature covers.

I tried disturbing the session from the host with a test hack in tdx-host, right before KEY_EXCHANGE. A bare GET_VERSION makes the device reject KEY_EXCHANGE. Replaying the VCA with one field changed gets a KEY_EXCHANGE_RSP back, but the TDX module rejects it. So KEY_EXCHANGE does blow up when the host interferes, as you said in the other reply.

Quoted 23 lines

I'm very interested in this because it frees the uABI from any variation in choices the TSM may have made. I have the full perfect transcript in the kernel and I will ALWAYS present the same uABI to userspace regardless what the TSM offers.

Like a whole bunch of TSM FW API just to do this:

Quoted 14 lines

=== Step 4: query certificates, query final transcript items ===============

     |                    |                       |                        |
     | query certificates |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
     | slot 0's chain     |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
     | query transcript   |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
(6)  | device info        |                       |                        |
     |<- - - - - - - - - -|                       |                        |
     |                    |                       |                        |
Quoted 4 lines

Seems pointless to me, and could cause uABI variations I don't like.

What do you think? Am I off my rocker? At least AI says it looks OK (always optimistic!). Undoing the chunking is annoying.

Seems reasonable. Every TSM gets the same uAPI, and on TDX the host already has every message in hand so the driver side should be small. It also lets the kernel check the KEY_EXCHANGE_RSP signature itself instead of taking TSM's word for it.

Quoted 5 lines

This is close to what ARM did with CCA, it seems very smart, but they seem to have provided some help to the OS to locate the bytes in the messages, but I assume with some work (and we have Rust SPDM) Linux can decode them autonomously?

For the clear-text part yes. Rust SPDM already handles the VCA and CERTIFICATE messages. KEY_EXCHANGE and KEY_EXCHANGE_RSP can be added. Everything after that is encrypted.

Quoted 34 lines

This would really slim down the TSM drivers since core code would build up all the SPDM data and the drivers just need to do the communicate loop and little else.

2) I do not like the idea to refetch certificates:

Quoted 8 lines
     | nonce              |                       |                        |
     |------------------->|                       |                        |
     |                    |                       |                        |
(7)  |                    | TDH.SPDM.MNG(nonce)   |                        |
     |                    |---------------------->|                        |
     |                    |                       |                        |
     |                    |                       | GET_CERTIFICATE        |
     |                    |                       |<======================>|
Quoted 8 lines
     |                    |                       | GET_MEASUREMENTS       |
     |                    |                       |<======================>|
     |                    |                       |                        |
     |                    | new device info       |                        |
     |                    |<- - - - - - - - - - - |                        |
     |                    |                       |                        |
     | one-shot result    |                       |                        |
     |<- - - - - - - - - -|                       |                        |

That's just asking for trouble. Our general security statement is the kernel & TSM validates a signature over the certificates to verify their authenticity in the HW. Re-fetching defeats the whole point of this if the responder can supply something that differs from GET_DIGESTS. If it can't differ then it was a waste of time since we already have it.

Further it wrecks the point of the transcript and the 1.3 multikey signed GET_DIGESTS.

Quoted 6 lines

If you *really* want to do certificate refresh it has to be a dedicated operation, it has to be supported by the TSM, and it has to get back another SIGNED GET_DIGESTS. All that would ideally have to be dumped into the transcript again.

I feel it is alot of work and not valuable.

On TDX the re-read of the chains comes with the measurement refresh. The kernel doesn't ask for it separately. For option (A) I think the kernel can drop the re-read chains and pass up only the new MEASUREMENTS. It already has the copy KEY_EXCHANGE signed.

Quoted 8 lines

I generally feel a 2nd measurement is not a valuable activity in the first place. There is one meaningful security measurement. At boot, before DMA is allowed. It must be logged into a measurement register and then after you turn on DMA the device can replace every byte of software on the system. (hello ATS). Only the logged measurement can be relied on for security, any post-DMA measurement can be fradulent.

Quoted 7 lines

It is OK for auditing, but not for security, so why would you even look at the certificate chain for audit? For auditing you should check that the PUBKEY is stable.

I think any device that can loose its private key while an SPDM session is open (ie with live update or something) is fundamentally broken. If it wants to do this it has to break the SPDM.

On the host side, should we drop the fresh-nonce collection before ACCEPT then, or keep it as audit only? I'd rather not carry a uAPI for it if nobody needs it. The guest's lock keeps its own fresh-nonce recollection, as you asked for earlier.

Quoted 22 lines

3) Obviously TDX and everyone else will need a 1.3 multi-key upgrade I think they all look the same. I would like us to settle on this API here and then we can describe what a perfect TSM would look like and people can go write their specs for it.

Quoted 7 lines

Notes:
(1) Creates the attestation endpoint only; no TSM call.
(2) TDH.SPDM.CONNECT has no KEY_EXCHANGE slot input. CERTIFICATE_SLOT_MASK, the only slot field in SPDM_CONFIG_INFO_T, is the "Device certificate request slot mask" [ABI 3.3.2.2]: it picks which chains to read, not the slot. So userspace can't enumerate slots by repeating step 3.

Boo

Quoted 5 lines

(3) TDH.SPDM.CREATE runs first. The kernel sets CERTIFICATE_SLOT_MASK to 0x01, so the TSM reads only slot 0's chain. It could also read slots 1-7 inside the session [GUIDE B.1.1, Table B-1], but no signature would cover them: KEY_EXCHANGE hashes only the chain of the slot it names [SPDM 10.18.1]. So (a) doesn't report them.

Quoted 7 lines

? this is confusing. KEY_EXCHANGE slot=0 includes the hash of the certificate chain in slot 0. If the TSM doesn't even read that slot during the premable it is impossible to validate the KEY_EXCHANGE.

What prupose is there for this configurable "the kernel sets CERTIFICATE_SLOT_MASK to 0x01" ? If the kernel sets for slot 1 then KEY_EXCHANGE must fail?

No, it doesn't fail, and the note was confusing. The mask doesn't control slot selection. It only picks which of slots 1-7 the module also returns. Slot 0's chain is always read and returned. The module reads it in the clear before KEY_EXCHANGE, even with bit 0 clear. Slots 1-7 are read inside the session after KEY_EXCHANGE. Setting 0x01 just skips reading chains nothing signs.

Quoted 8 lines
Quoted 5 lines

(4) The host relays each message over DOE [ABI 3.3.3, 4.2.20]. No GET_DIGESTS and no CHALLENGE [ARCH 2.7.5, Figure 2.6]. The guide's session setup example also reads slot 0's chain before KEY_EXCHANGE [GUIDE B.1.1, Table B-1].

Yes, you have to because it is necessary to do this:

Quoted 7 lines
Quoted 2 lines

(5) The TSM verifies the KEY_EXCHANGE_RSP signature [SPDM para 630]. The host trusts that check, so it keeps no KEY_EXCHANGE messages.

Yes, but if we do #1 above I would keep it anyhow and optionally give it to the verifier. I agree it is fairly pointless, but it is really cheap to do if you have #1 and simply capturing the *entire* signed transcript is forward looking from an ABI design perspective.

Agreed. With snooping the kernel has them anyway. I'll change (5) to keep them and hand them to the verifier.

DJ

Quoted 23 lines

It might even be useful if we want to use GET_DIGEST/CHALLENGE/KEY_EXCHANGE as the sequence to support multi-slot.

If people start adding more messages in later spec revs it simply flows through with no ABI change as additional transcript meassages.

Quoted 8 lines

(7) DEV_INFO_RECOLLECTION puts the caller's nonce in GET_MEASUREMENTS [ABI 3.3.2.3], and the TSM reads the chains again
[GUIDE B.1.5, Table B-5]. The device signs the new measurement log, nonce included [SPDM 10.12.3]. That signature is what protects the new measurements. The verifier must check it with the leaf key of slot 0, the slot KEY_EXCHANGE used, and must find its nonce in the log to know the result is fresh. The kernel hands the result over once and doesn't store it.

Yes, but in 1.2 there is only one private key so the slot doesn't matter. There is no reason to re-read the chains. Kernel would throw that information away, it already has a signed copy that it got at the start.

Jason

maintainer
#59 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 3 lines · Dave Jiang

I think that works for TDX. tdx-host already relays every DOE message for the TDX Module, so it can hand them to core SPDM code.

Everyone does this right? TDX does, I know ARM does, and AI tells me AMD does too. So this is universal.

Quoted 6 lines

Only part of the flow is clear text though. With the current module I see the VCA, GET_CERTIFICATE for slot 0, and KEY_EXCHANGE/KEY_EXCHANGE_RSP in the clear. There's no GET_DIGESTS. FINISH, GET_MEASUREMENTS, the reads of slots 1-7 and the TDISP messages are all inside the session, so the kernel can't decode them.

Yes, but in 1.3 there would be a get digests too.

Quoted 3 lines

So the kernel would get the VCA, slot 0's chain and the KEY_EXCHANGE transcript from snooping, and still take MEASUREMENTS from the TDX Module's device info. Would that be ok with you?

Yeah, this is what I was thinking about. I think this should be completly universal and uniform across every implementation.

Quoted 6 lines

I tried disturbing the session from the host with a test hack in tdx-host, right before KEY_EXCHANGE. A bare GET_VERSION makes the device reject KEY_EXCHANGE. Replaying the VCA with one field changed gets a KEY_EXCHANGE_RSP back, but the TDX module rejects it. So KEY_EXCHANGE does blow up when the host interferes, as you said in the other reply.

Yes, this is a critical security feature.

The host could inject GET_CERTIFICATES on its own right before KEY_EXCHANGE, and I'm wondering if we end up with this scheme if the 1.3 mode should simply have the host issue GET_CERTIFICATES and the TSM side just does GET_DIGESTS.

Quoted 4 lines

Seems reasonable. Every TSM gets the same uAPI, and on TDX the host already has every message in hand so the driver side should be small. It also lets the kernel check the KEY_EXCHANGE_RSP signature itself instead of taking TSM's word for it.

Yeah, not sure that has security value, but it is a nice debugging aide for sure.

Quoted 11 lines
Quoted 6 lines

If you *really* want to do certificate refresh it has to be a dedicated operation, it has to be supported by the TSM, and it has to get back another SIGNED GET_DIGESTS. All that would ideally have to be dumped into the transcript again.

I feel it is alot of work and not valuable.

On TDX the re-read of the chains comes with the measurement refresh. The kernel doesn't ask for it separately. For option (A) I think the kernel can drop the re-read chains and pass up only the new MEASUREMENTS. It already has the copy KEY_EXCHANGE signed.

I don't know why you guys do this, I would take it out, IMHO. Just wastes time. Yes, kernel should forward the chain it collected with KEY_EXCHANGE, it isn't allowed to change and there is no need to recollect it.

Quoted 8 lines
Quoted 3 lines

I think any device that can loose its private key while an SPDM session is open (ie with live update or something) is fundamentally broken. If it wants to do this it has to break the SPDM.

On the host side, should we drop the fresh-nonce collection before ACCEPT then, or keep it as audit only? I'd rather not carry a uAPI for it if nobody needs it. The guest's lock keeps its own fresh-nonce recollection, as you asked for earlier.

What does this mean?

Every single call to GET_MEASUREMENTS should come with a user provide nonce.

You need to provide two flows
1) /dev/attestation/xx on the host does a GET_MEASUREMENTS for the host's use, with out *any* disruption to *any* guest
2) iommufd command 'get attestation fd for vdev' ends up implementing the hypevisor side of guest/host channel and does GET_MEASUREMENTS with whatever anti-tamerping scheme TDX has.

Both of these need to accept a nonce.

No GET_MEASUREMENTS should be issued, and certainly not from accept, until userspace specifically asks for it with a user provided nonce.

Quoted 14 lines
Quoted 6 lines
Quoted 5 lines

(3) TDH.SPDM.CREATE runs first. The kernel sets CERTIFICATE_SLOT_MASK to 0x01, so the TSM reads only slot 0's chain. It could also read slots 1-7 inside the session [GUIDE B.1.1, Table B-1], but no signature would cover them: KEY_EXCHANGE hashes only the chain of the slot it names [SPDM 10.18.1]. So (a) doesn't report them.

Quoted 7 lines

? this is confusing. KEY_EXCHANGE slot=0 includes the hash of the certificate chain in slot 0. If the TSM doesn't even read that slot during the premable it is impossible to validate the KEY_EXCHANGE.

What prupose is there for this configurable "the kernel sets CERTIFICATE_SLOT_MASK to 0x01" ? If the kernel sets for slot 1 then KEY_EXCHANGE must fail?

Quoted 6 lines

No, it doesn't fail, and the note was confusing. The mask doesn't control slot selection. It only picks which of slots 1-7 the module also returns. Slot 0's chain is always read and returned. The module reads it in the clear before KEY_EXCHANGE, even with bit 0 clear. Slots 1-7 are read inside the session after KEY_EXCHANGE. Setting 0x01 just skips reading chains nothing signs.

OK.. Weird, not sure I see that as useful?

Quoted 7 lines
Quoted 4 lines

Yes, but if we do #1 above I would keep it anyhow and optionally give it to the verifier. I agree it is fairly pointless, but it is really cheap to do if you have #1 and simply capturing the *entire* signed transcript is forward looking from an ABI design perspective.

Agreed. With snooping the kernel has them anyway. I'll change (5) to keep them and hand them to the verifier.

So what I'm thinking is some kind of little library with a driver interface like the blow. It would be used instead of the driver just calling pci_doe() directly and does all the capturing magically and transparently.

Basically the only thing in the driver will be the communicate loop, and the little bit ARM needs to get the PUBKEY out.

I've got some AI running through to make a sketch of this to see if it is a terrible idea.

struct att_spdm_connection; /* opaque */

/*
* Called by the driver (not the TSM core) before the first GET_VERSION is
* issued. @doe_mb is used, not owned; it must outlive the connection.
* Returns ERR_PTR() on failure.
*/
struct att_spdm_connection *
att_spdm_connection_create(struct pci_doe_mb *doe_mb);

/*
* Release all capture storage. Must not race with att_spdm_doe_exchange().
* Sends nothing to the device. Evidence references already taken stay valid. */
void att_spdm_connection_destroy(struct att_spdm_connection *conn);

/*
* Forward one firmware-generated request over DOE and capture it. *
* @doe_type: PCI_DOE_FEATURE_CMA or PCI_DOE_FEATURE_SSESSION, as chosen by

 *            firmware. Any other value is -EINVAL.
 * @req:      may point into shared or firmware-owned memory. It is read
 *            exactly once, into private storage; nothing re-reads it.
 *
 * Returns 0 when a response is available through att_spdm_response_len() and
 * att_spdm_copy_response(). An SPDM ERROR reply (Busy, ResponseNotReady,
 * LargeResponse, InvalidRequest, ...) still returns 0: it is a response for
 * firmware to handle.
 *
 * Returns a negative errno when the exchange failed or was refused before
 * reaching DOE. The error is latched: every later call returns the same
 * value, and the caller must destroy the connection.
 *   -ECONNRESET  GET_VERSION restart, or RequestResynch
 *   -EPROTO      malformed or illegal message for the current state,
 *                including any plaintext request after KEY_EXCHANGE_RSP
 *   -EOPNOTSUPP  PSK or mutual-authentication flow
 *   -E2BIG       collection budget exhausted
 *   -ENOMEM      allocation failure
 *   other        DOE transport failure

*
* Sleeps. Takes no lock: the TSM core issues SPDM operations to the driver
* one at a time, so calls on a connection are never concurrent. */
int att_spdm_doe_exchange(struct att_spdm_connection *conn, u8 doe_type,

			  const void *req, size_t req_len);

/*
* Size in bytes of the latest raw DOE response, including DOE dword padding
* and any ERROR or chunk envelope. Valid until the next exchange or until
* the connection is destroyed. Returns 0 if no response is available. */
size_t att_spdm_response_len(struct att_spdm_connection *conn);

/*
* Copy the latest raw response into @dst. Writes @dst only; never reads it.
* Returns the number of bytes copied, -ENOSPC without copying anything if
* @dst_len is too small, or -ENODATA if no response is available. Never
* truncates. Can be called repeatedly; it does not consume the response.
* An undersized buffer is not latched: how to react is the driver's
* decision.
*/
ssize_t att_spdm_copy_response(struct att_spdm_connection *conn,

			       void *dst, size_t dst_len);

/*
* The driver calls this once firmware has reported that the SPDM session is
* established and valid, for example when the firmware connect command
* completes successfully. It is the only point where any future kernel-side
* validation of the captured evidence may run, and the only point where such
* validation may fail.
*
* Requires ATT_SPDM_CAPTURED. Calling it in any other state returns -EINVAL
* and latches the error. On success the status moves to
* ATT_SPDM_ESTABLISHED; on failure the error is latched.
*
* Evidence is only made available to attestation consumers after this
* call succeeds.
*/
int att_spdm_session_established(struct att_spdm_connection *conn);

Jason

#60 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

<snip>

Quoted 7 lines
Quoted 6 lines

I tried disturbing the session from the host with a test hack in tdx-host, right before KEY_EXCHANGE. A bare GET_VERSION makes the device reject KEY_EXCHANGE. Replaying the VCA with one field changed gets a KEY_EXCHANGE_RSP back, but the TDX module rejects it. So KEY_EXCHANGE does blow up when the host interferes, as you said in the other reply.

Quoted 6 lines

Yes, this is a critical security feature.

The host could inject GET_CERTIFICATES on its own right before KEY_EXCHANGE, and I'm wondering if we end up with this scheme if the 1.3 mode should simply have the host issue GET_CERTIFICATES and the TSM side just does GET_DIGESTS.

While it might be ok from security pov, it looks somewhat hacky...

Quoted 9 lines
Quoted 4 lines

On TDX the re-read of the chains comes with the measurement refresh. The kernel doesn't ask for it separately. For option (A) I think the kernel can drop the re-read chains and pass up only the new MEASUREMENTS. It already has the copy KEY_EXCHANGE signed.

I don't know why you guys do this, I would take it out, IMHO. Just wastes time. Yes, kernel should forward the chain it collected with KEY_EXCHANGE, it isn't allowed to change and there is no need to recollect it.

I wasn’t part of the original design, but as far as I understand it was designed this way to allow cases when *some* cert chains/keys can change during device runtime as a result of a device FW update. Especially when we think of multi-key case, different device keys may have different life spawn and *some* keys/chains will be changed when a FW update happens on a device. I think typically SPDM slot 0 (identity) remains stable, but rest of the keys can change.

But of course in order to stay secure in such cases you will need to have a cryptographic binding between the old and new keys. This can be done given that re-collection happens inside previously verified and established SPDM secure session and you can put additional security rules on top also if desired (i.e. reject if slot 0 key changed, required signature on updated measurements still using slot 0 key, etc.). Also, there is a non-spoken assumption here that if you trust the device at the time of SPDM connect (after verifying all the evidence) then you trust device security policy not to do fw runtime updates that can downgrade security of a device.

<snip>

Quoted 15 lines
Quoted 1 line
Quoted 1 line
Quoted 1 line

(3) TDH.SPDM.CREATE runs first. The kernel sets CERTIFICATE_SLOT_MASK

to

Quoted 13 lines
Quoted 12 lines
Quoted 4 lines

0x01, so the TSM reads only slot 0's chain. It could also read slots 1-7 inside the session [GUIDE B.1.1, Table B-1], but no signature would cover them: KEY_EXCHANGE hashes only the chain of the slot it names [SPDM 10.18.1]. So (a) doesn't report them.

? this is confusing. KEY_EXCHANGE slot=0 includes the hash of the certificate chain in slot 0. If the TSM doesn't even read that slot during the premable it is impossible to validate the KEY_EXCHANGE.

What prupose is there for this configurable "the kernel sets CERTIFICATE_SLOT_MASK to 0x01" ? If the kernel sets for slot 1 then KEY_EXCHANGE must fail?

Quoted 8 lines
Quoted 6 lines

No, it doesn't fail, and the note was confusing. The mask doesn't control slot selection. It only picks which of slots 1-7 the module also returns. Slot 0's chain is always read and returned. The module reads it in the clear before KEY_EXCHANGE, even with bit 0 clear. Slots 1-7 are read inside the session after KEY_EXCHANGE. Setting 0x01 just skips reading chains nothing signs.

OK.. Weird, not sure I see that as useful?

This is current design for single-key case (multi-key is not supported). Given this, it makes sense to use fixed slot 0 to always establish SPDM session (since other slots must have the same key anyhow), but allow VMM to configure if it wants single slot (faster, less fetching) or all slots or a subset of slots. IMO, in a single key case it makes sense to fetch all slots and chains available and pass this info to the guest to maximize chances that guest will find at least one of these chains acceptable per its trust policy.

Best Regards,
Elena.

maintainer
#61 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 6 lines · Elena
Quoted 4 lines

The host could inject GET_CERTIFICATES on its own right before KEY_EXCHANGE, and I'm wondering if we end up with this scheme if the 1.3 mode should simply have the host issue GET_CERTIFICATES and the TSM side just does GET_DIGESTS.

While it might be ok from security pov, it looks somewhat hacky...

It also feels hacky to build a FW call just to issue GET_CERTIFICATES in plaintext so that Linux can capture them and the FW throws the data away..

Quoted 5 lines
Quoted 5 lines
Quoted 4 lines

On TDX the re-read of the chains comes with the measurement refresh. The kernel doesn't ask for it separately. For option (A) I think the kernel can drop the re-read chains and pass up only the new MEASUREMENTS. It already has the copy KEY_EXCHANGE signed.

Quoted 8 lines
Quoted 4 lines

I don't know why you guys do this, I would take it out, IMHO. Just wastes time. Yes, kernel should forward the chain it collected with KEY_EXCHANGE, it isn't allowed to change and there is no need to recollect it.

I wasn’t part of the original design, but as far as I understand it was designed this way to allow cases when *some* cert chains/keys can change during device runtime as a result of a device FW update.

Who would ever check it? As I said there is only one security relevant use of the certs, at the start. You can audit later, but who cares if the certs have expired or changed during audit? What audit wants to check is that the device hasn't changed in a negative way. Loosing it's privkey absolutely should be considered an negative audit failing thing.

Quoted 4 lines

Especially when we think of multi-key case, different device keys may have different life spawn and *some* keys/chains will be changed when a FW update happens on a device. I think typically SPDM slot 0 (identity) remains stable, but rest of the keys can change.

And, again, IMHO, the device is fully broken if it changes the keys in the slot during operation. If the OS is doing auditing and requesting a signed GET_MEASUREMENTS every hour the signing key *can not change*. That would be completely insane from a security POV.

On FW live update the device has to retain any in progress session exactly as is. It can manifest the new keys/slots only once when the next GET_VERSION comes through and resets things. Think about the races any other solution would create :(

Quoted 4 lines

But of course in order to stay secure in such cases you will need to have a cryptographic binding between the old and new keys. This can be done given that re-collection happens inside previously verified and established SPDM secure session and you can put additional security

Nope, by the point you get to auditing the verifier can no longer make the same assumption that the OS is actually still secure. It's job is to prove that, it cannot assume it. This is different from a fresh boot right off DRTM that is still in the initrd where we can much more confidently say the OS is in a known state and we can trust it to have done all the measurements correctly before using the PCI devices.

However, if the OS has since been hacked later on then it would be trivial to just present new GET_DIGESTS, certificates, keys and measuremnts relayed from some other device to hide its hacked activities.

Allowing key change without a direct signed hand off is security unacceptable. If you want to do live slot updates then the old slots must be retained as is exactly while the connection lives, new slots can be added, and you'd need to collect a new GET_DIGESTS signed by the original slot. The spec doesn't really have a way to do any of this it seems.

And you need a log of all these events for the auditor to check everything. Overall it is super complicated for almost no benifit. I wouldn't do it.

But definately Linux should not be supporting a broken version of this, if Linux cannot directly confirm the signed authenticity of the keys the it must not present them to userspace.

IMHO the spec needs changes for this kind of use model, it is missing a pretty key operation: 'get digests signed' where you'd get the digest of every slot *AND* an array of sigantures from every slot over those digests.

Instead at best we get a single signature over all digests, so you end up with the OS and verifier locked to a single key the OS seleceted at startup.

Quoted 8 lines

This is current design for single-key case (multi-key is not supported). Given this, it makes sense to use fixed slot 0 to always establish SPDM session (since other slots must have the same key anyhow), but allow VMM to configure if it wants single slot (faster, less fetching) or all slots or a subset of slots. IMO, in a single key case it makes sense to fetch all slots and chains available and pass this info to the guest to maximize chances that guest will find at least one of these chains acceptable per its trust policy.

I'd prefer that this be done via CHALLENGE at the start when possible so we have a signed GET_DIGESTS to send to the guest and to the verifier. But as a backup reading them over the encrypted session and checking that every leaf PUBKEY is identical seems reasonable, but only for 1.2 and must not be done in 1.3 multikey.

However, the guest still needs a way to indicate what slot to use for its measurement report.

Jason

#62 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Jason Gunthorpe
Quoted 85 lines · Jason Gunthorpe
Quoted 3 lines · Dave Jiang

I think that works for TDX. tdx-host already relays every DOE message for the TDX Module, so it can hand them to core SPDM code.

Everyone does this right? TDX does, I know ARM does, and AI tells me AMD does too. So this is universal.

Quoted 6 lines

Only part of the flow is clear text though. With the current module I see the VCA, GET_CERTIFICATE for slot 0, and KEY_EXCHANGE/KEY_EXCHANGE_RSP in the clear. There's no GET_DIGESTS. FINISH, GET_MEASUREMENTS, the reads of slots 1-7 and the TDISP messages are all inside the session, so the kernel can't decode them.

Yes, but in 1.3 there would be a get digests too.

Quoted 3 lines

So the kernel would get the VCA, slot 0's chain and the KEY_EXCHANGE transcript from snooping, and still take MEASUREMENTS from the TDX Module's device info. Would that be ok with you?

Yeah, this is what I was thinking about. I think this should be completly universal and uniform across every implementation.

Quoted 6 lines

I tried disturbing the session from the host with a test hack in tdx-host, right before KEY_EXCHANGE. A bare GET_VERSION makes the device reject KEY_EXCHANGE. Replaying the VCA with one field changed gets a KEY_EXCHANGE_RSP back, but the TDX module rejects it. So KEY_EXCHANGE does blow up when the host interferes, as you said in the other reply.

Yes, this is a critical security feature.

The host could inject GET_CERTIFICATES on its own right before KEY_EXCHANGE, and I'm wondering if we end up with this scheme if the 1.3 mode should simply have the host issue GET_CERTIFICATES and the TSM side just does GET_DIGESTS.

Quoted 4 lines

Seems reasonable. Every TSM gets the same uAPI, and on TDX the host already has every message in hand so the driver side should be small. It also lets the kernel check the KEY_EXCHANGE_RSP signature itself instead of taking TSM's word for it.

Yeah, not sure that has security value, but it is a nice debugging aide for sure.

Quoted 11 lines
Quoted 6 lines

If you *really* want to do certificate refresh it has to be a dedicated operation, it has to be supported by the TSM, and it has to get back another SIGNED GET_DIGESTS. All that would ideally have to be dumped into the transcript again.

I feel it is alot of work and not valuable.

On TDX the re-read of the chains comes with the measurement refresh. The kernel doesn't ask for it separately. For option (A) I think the kernel can drop the re-read chains and pass up only the new MEASUREMENTS. It already has the copy KEY_EXCHANGE signed.

I don't know why you guys do this, I would take it out, IMHO. Just wastes time. Yes, kernel should forward the chain it collected with KEY_EXCHANGE, it isn't allowed to change and there is no need to recollect it.

Quoted 8 lines
Quoted 3 lines

I think any device that can loose its private key while an SPDM session is open (ie with live update or something) is fundamentally broken. If it wants to do this it has to break the SPDM.

On the host side, should we drop the fresh-nonce collection before ACCEPT then, or keep it as audit only? I'd rather not carry a uAPI for it if nobody needs it. The guest's lock keeps its own fresh-nonce recollection, as you asked for earlier.

What does this mean?

Every single call to GET_MEASUREMENTS should come with a user provide nonce.

You need to provide two flows
1) /dev/attestation/xx on the host does a GET_MEASUREMENTS for the host's use, with out *any* disruption to *any* guest
2) iommufd command 'get attestation fd for vdev' ends up implementing the hypevisor side of guest/host channel and does GET_MEASUREMENTS with whatever anti-tamerping scheme TDX has.

Quoted 4 lines

Both of these need to accept a nonce.

No GET_MEASUREMENTS should be issued, and certainly not from accept, until userspace specifically asks for it with a user provided nonce.

Sorry, that was unclear, and the guest part was wrong. The guest collects no measurements at lock, only the Interface Report. It collects measurements only when userspace asks, with userspace's nonce, so that's flow 2.

On the host I meant the optional fresh collection in step 4 of the diagram. That would be flow 1.

One thing on TDX is that TDH.SPDM.CONNECT also runs a GET_MEASUREMENTS of its own during the connect, and the kernel doesn't choose its nonce. The kernel wouldn't hand that one to userspace. Measurements only come out through the two flows, with a user nonce.

Quoted 15 lines
Quoted 15 lines
Quoted 14 lines
Quoted 6 lines

(3) TDH.SPDM.CREATE runs first. The kernel sets CERTIFICATE_SLOT_MASK to 0x01, so the TSM reads only slot 0's chain. It could also read slots 1-7 inside the session [GUIDE B.1.1, Table B-1], but no signature would cover them: KEY_EXCHANGE hashes only the chain of the slot it names [SPDM 10.18.1]. So (a) doesn't report them.

? this is confusing. KEY_EXCHANGE slot=0 includes the hash of the certificate chain in slot 0. If the TSM doesn't even read that slot during the premable it is impossible to validate the KEY_EXCHANGE.

What prupose is there for this configurable "the kernel sets CERTIFICATE_SLOT_MASK to 0x01" ? If the kernel sets for slot 1 then KEY_EXCHANGE must fail?

Quoted 8 lines
Quoted 6 lines

No, it doesn't fail, and the note was confusing. The mask doesn't control slot selection. It only picks which of slots 1-7 the module also returns. Slot 0's chain is always read and returned. The module reads it in the clear before KEY_EXCHANGE, even with bit 0 clear. Slots 1-7 are read inside the session after KEY_EXCHANGE. Setting 0x01 just skips reading chains nothing signs.

OK.. Weird, not sure I see that as useful?

For (a) it isn't, which is why the kernel sets 0x01. Elena explained the reason for the mask in her reply.

Quoted 6 lines
Quoted 6 lines
Quoted 5 lines

Yes, but if we do #1 above I would keep it anyhow and optionally give it to the verifier. I agree it is fairly pointless, but it is really cheap to do if you have #1 and simply capturing the *entire* signed transcript is forward looking from an ABI design perspective.

Quoted 7 lines
Quoted 2 lines

Agreed. With snooping the kernel has them anyway. I'll change (5) to keep them and hand them to the verifier.

So what I'm thinking is some kind of little library with a driver interface like the blow. It would be used instead of the driver just calling pci_doe() directly and does all the capturing magically and transparently.

That works for TDX. tdx-host's relay is one function today. It takes the module's request from a page shared with the module and calls pci_tsm_doe_transfer(), so att_spdm_doe_exchange() drops in there.

I tried a quick version of the capture in a test hack with the TDX Module. It rebuilt the VCA, slot 0's chain and KEY_EXCHANGE/KEY_EXCHANGE_RSP from the relayed messages, and the kernel's own check of the KEY_EXCHANGE_RSP signature passed. Agreed it's more of a debugging aid. Two things I encountered:

- The DOE payloads carry dword padding, so the capture has to trim each message to its SPDM length before hashing. KEY_EXCHANGE was 160 bytes as a DOE payload and 158 as an SPDM message.

- The chain hash in the KEY_EXCHANGE_RSP transcript is over the whole SPDM chain structure (length, reserved, root hash, certificates), not just the DER certificates. That's how the device and the TDX Module compute it.

Neither changes the proposed API. The library just needs to handle them.

DJ

Quoted 88 lines

Basically the only thing in the driver will be the communicate loop, and the little bit ARM needs to get the PUBKEY out.

I've got some AI running through to make a sketch of this to see if it is a terrible idea.

struct att_spdm_connection; /* opaque */

/*
* Called by the driver (not the TSM core) before the first GET_VERSION is
* issued. @doe_mb is used, not owned; it must outlive the connection.
* Returns ERR_PTR() on failure.
*/
struct att_spdm_connection *
att_spdm_connection_create(struct pci_doe_mb *doe_mb);

/*
* Release all capture storage. Must not race with att_spdm_doe_exchange().
* Sends nothing to the device. Evidence references already taken stay valid. */
void att_spdm_connection_destroy(struct att_spdm_connection *conn);

/*
* Forward one firmware-generated request over DOE and capture it. *
* @doe_type: PCI_DOE_FEATURE_CMA or PCI_DOE_FEATURE_SSESSION, as chosen by

 *            firmware. Any other value is -EINVAL.
 * @req:      may point into shared or firmware-owned memory. It is read
 *            exactly once, into private storage; nothing re-reads it.
 *
 * Returns 0 when a response is available through att_spdm_response_len() and
 * att_spdm_copy_response(). An SPDM ERROR reply (Busy, ResponseNotReady,
 * LargeResponse, InvalidRequest, ...) still returns 0: it is a response for
 * firmware to handle.
 *
 * Returns a negative errno when the exchange failed or was refused before
 * reaching DOE. The error is latched: every later call returns the same
 * value, and the caller must destroy the connection.
 *   -ECONNRESET  GET_VERSION restart, or RequestResynch
 *   -EPROTO      malformed or illegal message for the current state,
 *                including any plaintext request after KEY_EXCHANGE_RSP
 *   -EOPNOTSUPP  PSK or mutual-authentication flow
 *   -E2BIG       collection budget exhausted
 *   -ENOMEM      allocation failure
 *   other        DOE transport failure

*
* Sleeps. Takes no lock: the TSM core issues SPDM operations to the driver
* one at a time, so calls on a connection are never concurrent. */
int att_spdm_doe_exchange(struct att_spdm_connection *conn, u8 doe_type,

			  const void *req, size_t req_len);

/*
* Size in bytes of the latest raw DOE response, including DOE dword padding
* and any ERROR or chunk envelope. Valid until the next exchange or until
* the connection is destroyed. Returns 0 if no response is available. */
size_t att_spdm_response_len(struct att_spdm_connection *conn);

/*
* Copy the latest raw response into @dst. Writes @dst only; never reads it.
* Returns the number of bytes copied, -ENOSPC without copying anything if
* @dst_len is too small, or -ENODATA if no response is available. Never
* truncates. Can be called repeatedly; it does not consume the response.
* An undersized buffer is not latched: how to react is the driver's
* decision.
*/
ssize_t att_spdm_copy_response(struct att_spdm_connection *conn,

			       void *dst, size_t dst_len);

/*
* The driver calls this once firmware has reported that the SPDM session is
* established and valid, for example when the firmware connect command
* completes successfully. It is the only point where any future kernel-side
* validation of the captured evidence may run, and the only point where such
* validation may fail.
*
* Requires ATT_SPDM_CAPTURED. Calling it in any other state returns -EINVAL
* and latches the error. On success the status moves to
* ATT_SPDM_ESTABLISHED; on failure the error is latched.
*
* Evidence is only made available to attestation consumers after this
* call succeeds.
*/
int att_spdm_session_established(struct att_spdm_connection *conn);

Jason

maintainer
#63 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 5 lines · Dave Jiang

One thing on TDX is that TDH.SPDM.CONNECT also runs a GET_MEASUREMENTS of its own during the connect, and the kernel doesn't choose its nonce. The kernel wouldn't hand that one to userspace. Measurements only come out through the two flows, with a user nonce.

Why? Seems like another wasted operation..

Quoted 3 lines

That works for TDX. tdx-host's relay is one function today. It takes the module's request from a page shared with the module and calls pci_tsm_doe_transfer(), so att_spdm_doe_exchange() drops in there.

Yeah, it looks like it works for everyone.

I sketched out this library, and it is basically half or more of the in-kernel SPDM in just a different form. Basically all the reply message processing and de-framing abstracted into something a little different.

So in terms of *net* code needed it seems like very little

But it brings forward "rust spdm" or something in a new form as a pre-condition to do the TSMs. Which is annoying for sure.

Quoted 5 lines

I tried a quick version of the capture in a test hack with the TDX Module It rebuilt the VCA, slot 0's chain and KEY_EXCHANGE/KEY_EXCHANGE_RSP from the relayed messages, and the kernel's own check of the KEY_EXCHANGE_RSP signature passed. Agreed it's more of a debugging aid. Two things I encountered:

So you (AI'd?) something basic up to do the captures and showed that indeed the right data comes through and the general notion is OK?

That's great, it matches what all the analysis points to. I could find no blocking issue on this idea.

Quoted 3 lines

- The DOE payloads carry dword padding, so the capture has to trim each message to its SPDM length before hashing. KEY_EXCHANGE was 160 bytes as a DOE payload and 158 as an SPDM message.

Yeah, I couldn't belive it when my AI pointed to this. Nobody thought to preserve an actual length so any SPDM over DOE implementation needs to be very, very painfully calculate a length from the message content and algorith negotiation.

My AI predicts 1000-2000 lines of code very careful spec derived code just to remove this padding. Such a big spec mistake.

Quoted 4 lines

- The chain hash in the KEY_EXCHANGE_RSP transcript is over the whole SPDM chain structure (length, reserved, root hash, certificates), not just the DER certificates. That's how the device and the TDX Module compute it.

Definitely, it would need to capture the certificate stuff in just the right way.

The optional chunking features are a bit tricky, but I don't think a 1.2 only implementation needs them. They seem necesary for future post-quantum.

This also formalizes the version locking: the TSM FW cannot use a SPDM version that the kernel does not understand. I think this was already basically true, trying to do the 1.3 multi-slot flow is not going to work out with out kernel support to move the additional data. Maybe nobody noticed, but with this scheme it would be really formal.

In practice this means all the TSMs will need the kernel to tell it what SPDM versions it can go up to.

So I'm a little bit stuck here. Some thoughts:

1) I think this removes alot of code from the TSM drivers. All this stuff they have to go back and get the data after the fact, or the thing that is almost like this in RMM, goes away. The TSM drivers are very slim, just run their communicate loops.
2) Clear uniform path to 1.3, stop after GET_DIGESTS, let userspace inspect the certificates, continue with the slot. Again drivers can be super simple, just need to pause at the right point in the sequence.
3) The data collected and presented to userspace is perfectly uniform governed only by the SPDM specification. There is a clear path to adopt newer versions in the kernel with in-kernel SPDM that instantly with nearly no-code works with all the TSM drivers.
4) This is about half of in-kernel SPDM, the receive buffer reassembly, certificate chain reassembly, x509, padding stripping. Each a complicated topic on its own. We always imagined having in-kernel SPDM, so I view this as effectively free code - contigent on organizing the in-kernel SPDM implementation in a properly modular way.
5) Without AI this looks actually fairly scary. I can see why people wanted to avoid it with different schemes to have the TSM do some of the work. Yet we planned to do it anyhow for in-kernel SPDM so..
6) With AI this is probably a weeks work I suppose to really expertly polish and make excellent tests.
7) There is clear overlap with Alistair's work, I don't have a sense right now how close some of the parts in that series are to fitting this shape.

I will look more closely at Alistair's series to see how close it is already.

But, I am strongly tempted to make a draft placeholder in C with AI as a way to get alignment and try it out. Something we can throw out if a Rust version comes along.

Jason

#64 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 9 lines · Jason Gunthorpe
Quoted 5 lines · Dave Jiang

One thing on TDX is that TDH.SPDM.CONNECT also runs a GET_MEASUREMENTS of its own during the connect, and the kernel doesn't choose its nonce. The kernel wouldn't hand that one to userspace. Measurements only come out through the two flows, with a user nonce.

Why? Seems like another wasted operation..

The TDX module runs it as part of the connect and with the current module the host can't skip it. I'll discuss with Elena and see if this can be different.

Quoted 17 lines
Quoted 4 lines

That works for TDX. tdx-host's relay is one function today. It takes the module's request from a page shared with the module and calls pci_tsm_doe_transfer(), so att_spdm_doe_exchange() drops in there.

Yeah, it looks like it works for everyone.

I sketched out this library, and it is basically half or more of the in-kernel SPDM in just a different form. Basically all the reply message processing and de-framing abstracted into something a little different.

So in terms of *net* code needed it seems like very little

But it brings forward "rust spdm" or something in a new form as a pre-condition to do the TSMs. Which is annoying for sure.

Quoted 8 lines
Quoted 5 lines

I tried a quick version of the capture in a test hack with the TDX Module It rebuilt the VCA, slot 0's chain and KEY_EXCHANGE/KEY_EXCHANGE_RSP from the relayed messages, and the kernel's own check of the KEY_EXCHANGE_RSP signature passed. Agreed it's more of a debugging aid. Two things I encountered:

So you (AI'd?) something basic up to do the captures and showed that indeed the right data comes through and the general notion is OK?

Yes. LLM assisted test hack in tdx-host's relay.

Quoted 28 lines

That's great, it matches what all the analysis points to. I could find no blocking issue on this idea.

Quoted 3 lines

- The DOE payloads carry dword padding, so the capture has to trim each message to its SPDM length before hashing. KEY_EXCHANGE was 160 bytes as a DOE payload and 158 as an SPDM message.

Yeah, I couldn't belive it when my AI pointed to this. Nobody thought to preserve an actual length so any SPDM over DOE implementation needs to be very, very painfully calculate a length from the message content and algorith negotiation.

My AI predicts 1000-2000 lines of code very careful spec derived code just to remove this padding. Such a big spec mistake.

Quoted 4 lines

- The chain hash in the KEY_EXCHANGE_RSP transcript is over the whole SPDM chain structure (length, reserved, root hash, certificates), not just the DER certificates. That's how the device and the TDX Module compute it.

Definitely, it would need to capture the certificate stuff in just the right way.

The optional chunking features are a bit tricky, but I don't think a 1.2 only implementation needs them. They seem necesary for future post-quantum.

Quoted 8 lines

This also formalizes the version locking: the TSM FW cannot use a SPDM version that the kernel does not understand. I think this was already basically true, trying to do the 1.3 multi-slot flow is not going to work out with out kernel support to move the additional data. Maybe nobody noticed, but with this scheme it would be really formal.

In practice this means all the TSMs will need the kernel to tell it what SPDM versions it can go up to.

TDX has no input for that today. Will discuss with Elena.

Quoted 30 lines

So I'm a little bit stuck here. Some thoughts:

1) I think this removes alot of code from the TSM drivers. All this stuff they have to go back and get the data after the fact, or the thing that is almost like this in RMM, goes away. The TSM drivers are very slim, just run their communicate loops.
2) Clear uniform path to 1.3, stop after GET_DIGESTS, let userspace inspect the certificates, continue with the slot. Again drivers can be super simple, just need to pause at the right point in the sequence.
3) The data collected and presented to userspace is perfectly uniform governed only by the SPDM specification. There is a clear path to adopt newer versions in the kernel with in-kernel SPDM that instantly with nearly no-code works with all the TSM drivers.
4) This is about half of in-kernel SPDM, the receive buffer reassembly, certificate chain reassembly, x509, padding stripping. Each a complicated topic on its own. We always imagined having in-kernel SPDM, so I view this as effectively free code - contigent on organizing the in-kernel SPDM implementation in a properly modular way.
5) Without AI this looks actually fairly scary. I can see why people wanted to avoid it with different schemes to have the TSM do some of the work. Yet we planned to do it anyhow for in-kernel SPDM so..
6) With AI this is probably a weeks work I suppose to really expertly polish and make excellent tests.
7) There is clear overlap with Alistair's work, I don't have a sense right now how close some of the parts in that series are to fitting this shape.

Quoted 6 lines

I will look more closely at Alistair's series to see how close it is already.

But, I am strongly tempted to make a draft placeholder in C with AI as a way to get alignment and try it out. Something we can throw out if a Rust version comes along.

Sounds good. I can test on TDX when you have something.

Quoted 2 lines

Jason

maintainer
#65 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 5 lines · Dave Jiang
Quoted 1 line

Why? Seems like another wasted operation..

The TDX module runs it as part of the connect and with the current module the host can't skip it. I'll discuss with Elena and see if this can be different.

Does it come out anywhere and become visible to the host or guest?

Quoted 4 lines
Quoted 2 lines

In practice this means all the TSMs will need the kernel to tell it what SPDM versions it can go up to.

TDX has no input for that today. Will discuss with Elena.

Nobody does, but I'm interpreting that as meaning 1.2 doesn't require a flag and if anyone does 1.3 they will then need to add a 'enable 1.3' flag from the kernel, for example.

Jason

#66 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 9 lines · Dave Jiang
Quoted 5 lines
Quoted 1 line

Why? Seems like another wasted operation..

The TDX module runs it as part of the connect and with the current module the host can't skip it. I'll discuss with Elena and see if this can be different.

Does it come out anywhere and become visible to the host or guest?

Yes, it is visible to the host kernel as part of the initially returned SPDM_DEVICE_ATTESTATION_INFO_T structure. The guest can get the reference hash of this structure from the TDX Module/TSM.

This was done originally this way (as far as I understand the design) to support a single-step attestation at time of SPDM.CONNECT that a guest can get all information it needs from this single structure, and not required to ask TSM explicitly to do GET_MEASUREMENTS.

And yes, this might be redundant for Linux if you explicitly require separate call to GET_MEASUREMENTS, but other OSes might have different requirements and TSM architecture must support both.

Quoted 9 lines
Quoted 5 lines
Quoted 3 lines

In practice this means all the TSMs will need the kernel to tell it what SPDM versions it can go up to.

TDX has no input for that today. Will discuss with Elena.

Nobody does, but I'm interpreting that as meaning 1.2 doesn't require a flag and if anyone does 1.3 they will then need to add a 'enable 1.3' flag from the kernel, for example.

What about when it is not about SPDM version, but about capability (same what Alistair pointed out for kernel)? I.e. it supports 1.3, but does not support multi-key?

Best Regards,
Elena.

maintainer
#67 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 8 lines · Elena
Quoted 8 lines · Dave Jiang
Quoted 5 lines
Quoted 1 line

Why? Seems like another wasted operation..

The TDX module runs it as part of the connect and with the current module the host can't skip it. I'll discuss with Elena and see if this can be different.

Quoted 5 lines
Quoted 1 line

Does it come out anywhere and become visible to the host or guest?

Yes, it is visible to the host kernel as part of the initially returned SPDM_DEVICE_ATTESTATION_INFO_T structure. The guest can get the reference hash of this structure from the TDX Module/TSM.

That's security broken.

The guest must never see a measurement from *before* the guest did LOCKED.

The entire security argument of the guest is that it locks the device so the host cannot tamper with it. Collects a post-lock measurement to show what state it is in after locking, then makes a policy decision.

Capturing a measurement at SPDM session startup, while in unlocked, and then using TDX module to trick the guest into accepting it as a post-locked measurement is *entirely* unacceptable and I will oppose anything like that in Linux.

This is an *attack vector* from the host and the contract between guest/TDX must be able to definitively close it.

The guest must be able to affimitively say that a fresh measurement was collected after locked and when it gets the reference hash it needs to know it is fresh after locking.

Quoted 4 lines

This was done originally this way (as far as I understand the design) to support a single-step attestation at time of SPDM.CONNECT that a guest can get all information it needs from this single structure, and not required to ask TSM explicitly to do GET_MEASUREMENTS.

Someone really misunderstood this requirement if this is what was built?

ARM implemented something similar, they called it 'RMM controlled nonce'. In this mode the RMM itself decides the nonce and the guest cannot specify it. This allows the host to avoid recollection in certain cases.

However (AFAIK) the guest still has visibility into the sequence numbers and can tell that the sequence:

LOCKED
GET_MEASUREMENTS
CHECK_REFERNCE_HASH

Absolutely did a new collection and the trusted CHECK_REFERENCE_HASH is returning a fresh post-lock hash.

Again I insist that the Linux guest can tell that the measurements are post-locked. Nobody is going to get this 'TSM nonce' feature unless they can strongly prove that from the guest side.

(at least for PCIe devices, there is some other situation for platform HW that has to be accepted by platform firmware before Linux even boots)

Quoted 5 lines
Quoted 5 lines
Quoted 4 lines
Quoted 2 lines

In practice this means all the TSMs will need the kernel to tell it what SPDM versions it can go up to.

TDX has no input for that today. Will discuss with Elena.

Quoted 7 lines
Quoted 3 lines

Nobody does, but I'm interpreting that as meaning 1.2 doesn't require a flag and if anyone does 1.3 they will then need to add a 'enable 1.3' flag from the kernel, for example.

What about when it is not about SPDM version, but about capability (same what Alistair pointed out for kernel)? I.e. it supports 1.3, but does not support multi-key?

Things that change the SPDM flow in a way the kernel needs to interact with will need to be feature negotiated.

So multi-key where you change the flow to pause after GET_DIGESTS and allow the kernel to specify the slot later on is of course entirely different and must be opted into by the kernel.

But even if you just want to do 1.3 messages then you still would need to make sure the kernel can parse and size them to form the transcript.

There are other options here, and I think once we get this built people should take a careful look at how they design their 1.3 multikey API upgrade to do a better job for what Linux wants overall.

Maybe Linux side can recommend what the TSM FW flow should look like so we have some better ideas and consistency.

Jason

#68 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Jason Gunthorpe
Quoted 8 lines · Jason Gunthorpe
Quoted 5 lines · Dave Jiang

One thing on TDX is that TDH.SPDM.CONNECT also runs a GET_MEASUREMENTS of its own during the connect, and the kernel doesn't choose its nonce. The kernel wouldn't hand that one to userspace. Measurements only come out through the two flows, with a user nonce.

Quoted 7 lines

Why? Seems like another wasted operation..

Quoted 3 lines

That works for TDX. tdx-host's relay is one function today. It takes the module's request from a page shared with the module and calls pci_tsm_doe_transfer(), so att_spdm_doe_exchange() drops in there.

Yeah, it looks like it works for everyone.

Jason just pointed out this thread, I've tried to pickup what the discussion is currently up to, hopefully I've understood it all.

It sounds like the current thinking is the TSM will issue all of the SPDM requests and be in control of the session. The kernel will forward those requests to the device and Rust SPDM in the kernel will be used to decode the responses.

The TSM will be in charge of slot selection, bank selection and algorithm support.

Quoted 5 lines

I sketched out this library, and it is basically half or more of the in-kernel SPDM in just a different form. Basically all the reply message processing and de-framing abstracted into something a little different.

The current Rust SPM implementation has split out the message processing, so that should be somewhat easy to reuse. Basically (in Rust) you pass in the bytes of an SPDM response, specify what the reply is (CHALLENGE_RSP) and you get a nice destructed struct back (or an error if it's invalid). It's all called the Validator as it validates Untrusted data and returns an SPDM response.

One issue though, is that the Validator expects to be passed the SPDM Context struct. It uses this as part of the validation. For example the Validator will check the context.version negotiated to make sure it matches the response. Things like that. So some state is expected.

Quoted 5 lines

So in terms of *net* code needed it seems like very little

But it brings forward "rust spdm" or something in a new form as a pre-condition to do the TSMs. Which is annoying for sure.

The Rust SPDM code is hopefully really close! I'm planning on sending v6 today or tomorrow. The last remaining issue is just getting the Rust bindings all sorted. If there is a big push for it to be merged we might be able to speed things up.

Note it doesn't support MULTI_KEY or PQC yet though.

Quoted 13 lines
Quoted 6 lines

I tried a quick version of the capture in a test hack with the TDX Module It rebuilt the VCA, slot 0's chain and KEY_EXCHANGE/KEY_EXCHANGE_RSP from the relayed messages, and the kernel's own check of the KEY_EXCHANGE_RSP signature passed. Agreed it's more of a debugging aid. Two things I encountered:

So you (AI'd?) something basic up to do the captures and showed that indeed the right data comes through and the general notion is OK?

That's great, it matches what all the analysis points to. I could find no blocking issue on this idea.

Quoted 8 lines
Quoted 3 lines

- The DOE payloads carry dword padding, so the capture has to trim each message to its SPDM length before hashing. KEY_EXCHANGE was 160 bytes as a DOE payload and 158 as an SPDM message.

Yeah, I couldn't belive it when my AI pointed to this. Nobody thought to preserve an actual length so any SPDM over DOE implementation needs to be very, very painfully calculate a length from the message content and algorith negotiation.

Yep, no length information. You have to decode the entire packet...

Quoted 9 lines

My AI predicts 1000-2000 lines of code very careful spec derived code just to remove this padding. Such a big spec mistake.

Quoted 4 lines

- The chain hash in the KEY_EXCHANGE_RSP transcript is over the whole SPDM chain structure (length, reserved, root hash, certificates), not just the DER certificates. That's how the device and the TDX Module compute it.

Quoted 6 lines

Definitely, it would need to capture the certificate stuff in just the right way.

The optional chunking features are a bit tricky, but I don't think a 1.2 only implementation needs them. They seem necesary for future post-quantum.

Maybe for PQC, but generally this doesn't seem to come up much with DOE. So for now it should be fine.

Quoted 6 lines

This also formalizes the version locking: the TSM FW cannot use a SPDM version that the kernel does not understand. I think this was already basically true, trying to do the 1.3 multi-slot flow is not going to work out with out kernel support to move the additional data. Maybe nobody noticed, but with this scheme it would be really formal.

The kernel patches support 1.0 - 1.4, so that should be fine.

The issue will be the capabilities (like MULTI_KEY) that aren't supported, yet.

One other thing, the current Rust kernel code assumes it's issuing the requests, so if a responder uses a capability we don't support, that's an error. If the TSM is issuing the requests then we will want to be a little more graceful in handling capabilities. Not too hard, just something to do.

Quoted 6 lines

In practice this means all the TSMs will need the kernel to tell it what SPDM versions it can go up to.

So I'm a little bit stuck here. Some thoughts:

Quoted 14 lines

1) I think this removes alot of code from the TSM drivers. All this stuff they have to go back and get the data after the fact, or the thing that is almost like this in RMM, goes away. The TSM drivers are very slim, just run their communicate loops.
2) Clear uniform path to 1.3, stop after GET_DIGESTS, let userspace inspect the certificates, continue with the slot. Again drivers can be super simple, just need to pause at the right point in the sequence.
3) The data collected and presented to userspace is perfectly uniform governed only by the SPDM specification. There is a clear path to adopt newer versions in the kernel with in-kernel SPDM that instantly with nearly no-code works with all the TSM drivers.
4) This is about half of in-kernel SPDM, the receive buffer reassembly, certificate chain reassembly, x509, padding stripping.

That's mostly what the current patches implement as well, so once merged you should get most of that working (see the above points)

Quoted 4 lines

Each a complicated topic on its own. We always imagined having in-kernel SPDM, so I view this as effectively free code - contigent on organizing the in-kernel SPDM implementation in a properly modular way.

That might require some changes, but I don't think is impossible.

Quoted 11 lines

5) Without AI this looks actually fairly scary. I can see why people wanted to avoid it with different schemes to have the TSM do some of the work. Yet we planned to do it anyhow for in-kernel SPDM so..
6) With AI this is probably a weeks work I suppose to really expertly polish and make excellent tests.
7) There is clear overlap with Alistair's work, I don't have a sense right now how close some of the parts in that series are to fitting this shape.

I will look more closely at Alistair's series to see how close it is already.

Again, see above

Quoted 4 lines

But, I am strongly tempted to make a draft placeholder in C with AI as a way to get alignment and try it out. Something we can throw out if a Rust version comes along.

I would argue that you would be better spending that effort on making changes to the Rust code and re-use that. That at least would make sure it can be adjusted to do what you want and would be more useful :)

Alistair

Quoted 2 lines

Jason

maintainer
#69 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 17 lines · Alistair
Quoted 8 lines · Jason Gunthorpe
Quoted 5 lines · Dave Jiang

One thing on TDX is that TDH.SPDM.CONNECT also runs a GET_MEASUREMENTS of its own during the connect, and the kernel doesn't choose its nonce. The kernel wouldn't hand that one to userspace. Measurements only come out through the two flows, with a user nonce.

Quoted 7 lines

Why? Seems like another wasted operation..

Quoted 3 lines

That works for TDX. tdx-host's relay is one function today. It takes the module's request from a page shared with the module and calls pci_tsm_doe_transfer(), so att_spdm_doe_exchange() drops in there.

Yeah, it looks like it works for everyone.

Quoted 7 lines

Jason just pointed out this thread, I've tried to pickup what the discussion is currently up to, hopefully I've understood it all.

It sounds like the current thinking is the TSM will issue all of the SPDM requests and be in control of the session. The kernel will forward those requests to the device and Rust SPDM in the kernel will be used to decode the responses.

'capture' is the word I want to use here. We actually don't want to decode them. The goal is to get the message transcript and share it to user-space in a 100% consistent format derived from the spec.

If DOE had a more sane protocol this would be trivial, just unwrapping the DOE transport and recovering the exact SPDM messages then storing them away.

We don't actually want to decode them in the kernel. The padding issue means we need to decode them enough as part of sizing them :(

Quoted 2 lines

The TSM will be in charge of slot selection, bank selection and algorithm support.

kernel will do slot selection, but TSM will drive the slot numbers in the commands. Yes on algorithms/etc.

Quoted 6 lines

The current Rust SPM implementation has split out the message processing, so that should be somewhat easy to reuse. Basically (in Rust) you pass in the bytes of an SPDM response, specify what the reply is (CHALLENGE_RSP) and you get a nice destructed struct back (or an error if it's invalid). It's all called the Validator as it validates Untrusted data and returns an SPDM response.

This isn't quite the right thing, we want a module that undoes the transport chunking, padding, and GET_CERTIFICATE chunking to store pristine raw messages. We don't want to validate or decode them, beyond what is necessary to undo the padding.

The goal is to pass pristine message bytes to userspace as the transcript report.

Your series should have most of this, but I am guessing in not quite the right arrangement.

Quoted 5 lines
Quoted 2 lines

But it brings forward "rust spdm" or something in a new form as a pre-condition to do the TSMs. Which is annoying for sure.

The Rust SPDM code is hopefully really close! I'm planning on sending v6 today or tomorrow.

No TSM drivers will go ahead until all of this and the uAPI is fully agreed..

Plus now there is this issue if we need to reorganize things to support this capture flow.

Quoted 6 lines
Quoted 3 lines

The optional chunking features are a bit tricky, but I don't think a 1.2 only implementation needs them. They seem necesary for future post-quantum.

Maybe for PQC, but generally this doesn't seem to come up much with DOE. So for now it should be fine.

I gathered the PQC issue is the signatures can get really big and the biggest DOE chunk may not be sufficient anymore depending on implementation.

Quoted 7 lines
Quoted 5 lines

This also formalizes the version locking: the TSM FW cannot use a SPDM version that the kernel does not understand. I think this was already basically true, trying to do the 1.3 multi-slot flow is not going to work out with out kernel support to move the additional data. Maybe nobody noticed, but with this scheme it would be really formal.

The kernel patches support 1.0 - 1.4, so that should be fine.

Not support as in "I can be an initiator and do a limited subset"

Support as in "I can be an observer and accept *everything* in the spec the actual initiator might use"

For now all we need is a fairly complete 1.2 since that is the minimum rev for PCIe (by spec) and the only thing any TSM FW has implemented.

Quoted 7 lines
Quoted 3 lines

But, I am strongly tempted to make a draft placeholder in C with AI as a way to get alignment and try it out. Something we can throw out if a Rust version comes along.

I would argue that you would be better spending that effort on making changes to the Rust code and re-use that. That at least would make sure it can be adjusted to do what you want and would be more useful :)

Heh, well I don't know Rust so I'm useless. I also have no Rust knowers to recruit to such a project right now.

The best I can do is make a draft with AI reusing parts of your series that shows exactly what I'm thinking and we can talk about how to go from there.

I fear it will not be very good rust though. I fear this will be a huge delay :)

This direction would end up with two series, a core bit doing the message capture and a secondary one doing the in-kernel initiator. Maybe we don't need any new rust bindings just for capture..

But sure it is just AI $$ we can have a try.

Jason

#70 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Jason Gunthorpe
Quoted 42 lines · Jason Gunthorpe
Quoted 5 lines · Dave Jiang

One thing on TDX is that TDH.SPDM.CONNECT also runs a GET_MEASUREMENTS of its own during the connect, and the kernel doesn't choose its nonce. The kernel wouldn't hand that one to userspace. Measurements only come out through the two flows, with a user nonce.

Why? Seems like another wasted operation..

Quoted 3 lines

That works for TDX. tdx-host's relay is one function today. It takes the module's request from a page shared with the module and calls pci_tsm_doe_transfer(), so att_spdm_doe_exchange() drops in there.

Yeah, it looks like it works for everyone.

I sketched out this library, and it is basically half or more of the in-kernel SPDM in just a different form. Basically all the reply message processing and de-framing abstracted into something a little different.

So in terms of *net* code needed it seems like very little

But it brings forward "rust spdm" or something in a new form as a pre-condition to do the TSMs. Which is annoying for sure.

Quoted 5 lines

I tried a quick version of the capture in a test hack with the TDX Module It rebuilt the VCA, slot 0's chain and KEY_EXCHANGE/KEY_EXCHANGE_RSP from the relayed messages, and the kernel's own check of the KEY_EXCHANGE_RSP signature passed. Agreed it's more of a debugging aid. Two things I encountered:

So you (AI'd?) something basic up to do the captures and showed that indeed the right data comes through and the general notion is OK?

That's great, it matches what all the analysis points to. I could find no blocking issue on this idea.

Quoted 3 lines

- The DOE payloads carry dword padding, so the capture has to trim each message to its SPDM length before hashing. 1 was 160 bytes as a DOE payload and 158 as an SPDM message.

Quoted 7 lines

Yeah, I couldn't belive it when my AI pointed to this. Nobody thought to preserve an actual length so any SPDM over DOE implementation needs to be very, very painfully calculate a length from the message content and algorith negotiation.

My AI predicts 1000-2000 lines of code very careful spec derived code just to remove this padding. Such a big spec mistake.

Out of curiosity - why that many? There is only a handful of SPDM messages and secure SPDM has the size in the header.

Quoted 21 lines
Quoted 4 lines

- The chain hash in the KEY_EXCHANGE_RSP transcript is over the whole SPDM chain structure (length, reserved, root hash, certificates), not just the DER certificates. That's how the device and the TDX Module compute it.

Definitely, it would need to capture the certificate stuff in just the right way.

The optional chunking features are a bit tricky, but I don't think a 1.2 only implementation needs them. They seem necesary for future post-quantum.

This also formalizes the version locking: the TSM FW cannot use a SPDM version that the kernel does not understand. I think this was already basically true, trying to do the 1.3 multi-slot flow is not going to work out with out kernel support to move the additional data. Maybe nobody noticed, but with this scheme it would be really formal.

In practice this means all the TSMs will need the kernel to tell it what SPDM versions it can go up to.

Quoted 6 lines

So I'm a little bit stuck here. Some thoughts:

1) I think this removes alot of code from the TSM drivers. All this stuff they have to go back and get the data after the fact, or the thing that is almost like this in RMM, goes away. The TSM drivers are very slim, just run their communicate loops.

It is already like this pretty much, is not it? Well, on AMD.

Quoted 4 lines

2) Clear uniform path to 1.3, stop after GET_DIGESTS, let userspace inspect the certificates, continue with the slot. Again drivers can be super simple, just need to pause at the right point in the sequence.

The AMD PSP calls SPDM_DIGESTS first, then SPDM_CERTIFICATE (just like the PCIe spec depicts). May be stop at SPDM_KEY_EXCHANGE_RSP?

This is the order:
VERSION
CAPABILITIES
ALGORITHMS
DIGESTS
CERTIFICATE
CHALLENGE_AUTH
KEY_EXCHANGE_RSP
(secure) PSK_FINISH

Quoted 10 lines

3) The data collected and presented to userspace is perfectly uniform governed only by the SPDM specification. There is a clear path to adopt newer versions in the kernel with in-kernel SPDM that instantly with nearly no-code works with all the TSM drivers.
4) This is about half of in-kernel SPDM, the receive buffer reassembly, certificate chain reassembly, x509, padding stripping. Each a complicated topic on its own. We always imagined having in-kernel SPDM, so I view this as effectively free code - contigent on organizing the in-kernel SPDM implementation in a properly modular way.

Quoted 8 lines

5) Without AI this looks actually fairly scary. I can see why people wanted to avoid it with different schemes to have the TSM do some of the work. Yet we planned to do it anyhow for in-kernel SPDM so..
6) With AI this is probably a weeks work I suppose to really expertly polish and make excellent tests.
7) There is clear overlap with Alistair's work, I don't have a sense right now how close some of the parts in that series are to fitting this shape.

You want the above just to prevent IDE keys from being programmed into the device before accepting the device in the host os, or there is more to it? Thanks,

Quoted 8 lines

I will look more closely at Alistair's series to see how close it is already.

But, I am strongly tempted to make a draft placeholder in C with AI as a way to get alignment and try it out. Something we can throw out if a Rust version comes along.

Jason

Signature · 1 line
Alexey
maintainer
#71 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 5 lines · Alexey Kardashevskiy
Quoted 2 lines

My AI predicts 1000-2000 lines of code very careful spec derived code just to remove this padding. Such a big spec mistake.

Out of curiosity - why that many? There is only a handful of SPDM messages and secure SPDM has the size in the header.

It sounds like there are alot of options. Since nothing is sized in the message you have to parse the negotiated algorithms and spec version to figure out how big each crypto field is and manually sum everything up.

IDK I thought it was alot too, but it is just an estimate.

Quoted 6 lines
Quoted 4 lines

1) I think this removes alot of code from the TSM drivers. All this stuff they have to go back and get the data after the fact, or the thing that is almost like this in RMM, goes away. The TSM drivers are very slim, just run their communicate loops.

It is already like this pretty much, is not it? Well, on AMD.

Not quite, AMD doesn't have any way to return the session transcript to userspace, and it hides VCA under it's get measurements thing. It only looks simple because these issues haven't been addressed.

In terms of presenting a consistent uAPI for SPDM AMD is the furthest away from the rest and probably the best reason to do this design since it would bring it into alignment.

Quoted 8 lines
Quoted 4 lines

2) Clear uniform path to 1.3, stop after GET_DIGESTS, let userspace inspect the certificates, continue with the slot. Again drivers can be super simple, just need to pause at the right point in the sequence.

The AMD PSP calls SPDM_DIGESTS first, then SPDM_CERTIFICATE (just like the PCIe spec depicts). May be stop at SPDM_KEY_EXCHANGE_RSP?

That's wasteful too, GET_DIGESTS before KEY_EXCHANGE isn't signed so it can't really be used for anything. This is also after the OS has told PSP what the slot is, so it can't even help do slot selection.

Quoted 19 lines
Quoted 10 lines

3) The data collected and presented to userspace is perfectly uniform governed only by the SPDM specification. There is a clear path to adopt newer versions in the kernel with in-kernel SPDM that instantly with nearly no-code works with all the TSM drivers.
4) This is about half of in-kernel SPDM, the receive buffer reassembly, certificate chain reassembly, x509, padding stripping. Each a complicated topic on its own. We always imagined having in-kernel SPDM, so I view this as effectively free code - contigent on organizing the in-kernel SPDM implementation in a properly modular way.

Quoted 8 lines

5) Without AI this looks actually fairly scary. I can see why people wanted to avoid it with different schemes to have the TSM do some of the work. Yet we planned to do it anyhow for in-kernel SPDM so..
6) With AI this is probably a weeks work I suppose to really expertly polish and make excellent tests.
7) There is clear overlap with Alistair's work, I don't have a sense right now how close some of the parts in that series are to fitting this shape.

Quoted 3 lines

You want the above just to prevent IDE keys from being programmed into the device before accepting the device in the host os, or there is more to it?

I want a consistent uAPI that is 100% the same for all four implementations. I do not want ARM to show the VCA before measurements in a transcript and AMD to hide the VCA in an AMD specific measurement blob - for example.

I want a uAPI where the user cannot tell the difference between the four backends. When they operate the same PCIe device they all give exactly the same uAPI experiance.

Jason

#72 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 35 lines · Jason Gunthorpe
Quoted 5 lines · Alexey Kardashevskiy
Quoted 2 lines

My AI predicts 1000-2000 lines of code very careful spec derived code just to remove this padding. Such a big spec mistake.

Out of curiosity - why that many? There is only a handful of SPDM messages and secure SPDM has the size in the header.

It sounds like there are alot of options. Since nothing is sized in the message you have to parse the negotiated algorithms and spec version to figure out how big each crypto field is and manually sum everything up.

IDK I thought it was alot too, but it is just an estimate.

Quoted 6 lines
Quoted 4 lines

1) I think this removes alot of code from the TSM drivers. All this stuff they have to go back and get the data after the fact, or the thing that is almost like this in RMM, goes away. The TSM drivers are very slim, just run their communicate loops.

It is already like this pretty much, is not it? Well, on AMD.

Not quite, AMD doesn't have any way to return the session transcript to userspace, and it hides VCA under it's get measurements thing. It only looks simple because these issues haven't been addressed.

In terms of presenting a consistent uAPI for SPDM AMD is the furthest away from the rest and probably the best reason to do this design since it would bring it into alignment.

Quoted 6 lines
Quoted 4 lines

2) Clear uniform path to 1.3, stop after GET_DIGESTS, let userspace inspect the certificates, continue with the slot. Again drivers can be super simple, just need to pause at the right point in the sequence.

Quoted 5 lines
Quoted 2 lines

The AMD PSP calls SPDM_DIGESTS first, then SPDM_CERTIFICATE (just like the PCIe spec depicts). May be stop at SPDM_KEY_EXCHANGE_RSP?

That's wasteful too, GET_DIGESTS before KEY_EXCHANGE isn't signed so it can't really be used for anything.

The attacker could steer the slot selection to less optimal, is that the attack?

The TSM has to pick a slot to sign GET_DIGESTS which we then want to use to pick a better slot and switch to that?

Quoted 2 lines

This is also after the OS has
told PSP what the slot is, so it can't even help do slot selection.

How is pausing going to help with the slot selection?

Quoted 23 lines
Quoted 19 lines
Quoted 10 lines

3) The data collected and presented to userspace is perfectly uniform governed only by the SPDM specification. There is a clear path to adopt newer versions in the kernel with in-kernel SPDM that instantly with nearly no-code works with all the TSM drivers.
4) This is about half of in-kernel SPDM, the receive buffer reassembly, certificate chain reassembly, x509, padding stripping. Each a complicated topic on its own. We always imagined having in-kernel SPDM, so I view this as effectively free code - contigent on organizing the in-kernel SPDM implementation in a properly modular way.

Quoted 8 lines

5) Without AI this looks actually fairly scary. I can see why people wanted to avoid it with different schemes to have the TSM do some of the work. Yet we planned to do it anyhow for in-kernel SPDM so..
6) With AI this is probably a weeks work I suppose to really expertly polish and make excellent tests.
7) There is clear overlap with Alistair's work, I don't have a sense right now how close some of the parts in that series are to fitting this shape.

Quoted 3 lines

You want the above just to prevent IDE keys from being programmed into the device before accepting the device in the host os, or there is more to it?

Quoted 8 lines

I want a consistent uAPI that is 100% the same for all four implementations. I do not want ARM to show the VCA before measurements in a transcript and AMD to hide the VCA in an AMD specific measurement blob - for example.

I want a uAPI where the user cannot tell the difference between the four backends. When they operate the same PCIe device they all give exactly the same uAPI experiance.

That's great, cannot agree more, but what was that recurring thing about not programming IDE keys before accepting the PF? Thanks,

Quoted 2 lines

Jason

Signature · 1 line
Alexey
maintainer
#73 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Dave Jiang
Quoted 8 lines · Dave Jiang

(6) SPDM_DEVICE_ATTESTATION_INFO_T [ABI 3.3.2.4]: SPDM and TDISP capabilities, TDISP version, the requested chains, and MEASUREMENT. MEASUREMENT is the signed SPDM measurement log, Concatenate(VCA, GET_MEASUREMENTS_REQUEST1, MEASUREMENTS_RESPONSE1, ..., GET_MEASUREMENTS_REQUESTn, MEASUREMENTS_RESPONSEn) [SPDM 10.12.3]. The last MEASUREMENTS carries the signature. The TDX Module keeps the info's digest. The kernel keeps the info as immutable startup data.

I threw all this at AI and it could not answer a question..

If the guest obtains a SPDM_DEVICE_ATTESTATION_INFO_T where does it get the VCA and other transcript information from to validate the device signature? VCA transfer should be defined in the GHCI calls.

AI also complained about inconsistencies in the spec versions I grabbed around if the recollection also updates the GET_DEVICE_ATTESTATION_INFO_HASH. Your message says it does, and that is what I would require, FWIW.

*Every* GET_MEASUREMENT in the guest kernel needs to trace back to some crypto binding that TDX module was in fact the one that issued it on the given device in the current session.

I also think it is a mistake for TDX to hold only one digest over a large report that includes a measurement. That is going to be difficult to use in the guest and requires transfering an unlocked measurement to the guest which I really am really strongly against.

The other arches split the digests to cover SPDM defined objects directly and you can transfer them out of the transcript from GHCI.

Jason

#74 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Jason Gunthorpe

snip

Quoted 9 lines
Quoted 9 lines

The flow really becomes as:

1) Do via RustSPDM:
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup
4) Verify the cert chain of public key used for SPDM session establishment to determine if the key is trusted

Quoted 8 lines
Quoted 4 lines

For this TSM must expose in a trusted way the full cert chain and pub key used for SPDM session establishment or only a full cert chain and guarantee that the pub key on left cert chain was used for session establishment. This is doable for TSM.

There is no signature covering the slots in this case. Userspace tested them blind without knowing they are actually in the device. It is not ideal.

Actually you are right, I re-read SPDM 1.2 now and realized that I forgot that the final key exchange signature *only* covers the chain selected for the authentication and not the whole message exchange (including GET_CERTIFICATES). I have been teaching people secure design of crypto protocols for quite a while in past and usually take it for granted that any final transcript must cover *all* exchanged messages within AKE (this is quite basic security principle that people usually try to follow.. ). Not sure why SPDM 1.2 did it differently. That said, I understand now your points.

Quoted 8 lines
Quoted 4 lines

I am not very familiar with RMM, but if you do things within a single SPDM session (just stopping after you have fetched the certs from all slots and passed it to userspace to select the key/slot), then you dont need to complicate matters by including GET_DIGESTS/CHALLENGE.

The purpose of CHALLENGE is to authenticate the certificates to prevent any MIM from tampering them. It is cheap and closes this hole, I would definately expect it.

Agree now given that GET_CERTIFICATES are not authenticated.

Quoted 26 lines
Quoted 5 lines

TSM in this case just must ensure that SPDM session establishment has happened using the key corresponding to selected slot. TSM can do things like this because it has the final SPDM transcript as result of session establishment and can verify it by itself.

At least in the RMM flow the OS provides the PUBKEY to the RMM so this is never in any doubt. I agree that the TSM implementation has to provide the property that the PUBKEY's signature is checked during KEY_EXCHANGE and the OS must definitively know what PUBKEY was used.

Quoted 9 lines
Quoted 6 lines

Further, if you have a transcript that includes GET_DIGESTS then you don't *have* to select the slot early. The early slot selection mixes the certificate hash into the KEY_EXCHANGE, but if we did GET_DIGESTS then we already have that mixed in. Once you have a signed GET_DIGESTS then you can just request measurements on whatever slot you like without any relay attack.

You lost me on this. If you mean final SPDM transcript in step 3, I dont understand how having GET_DIGESTS helps....

One of the issues with 1.2 is you get a signature over only one slot's certificates during KEY_EXCHANGE. So you can't use the other slots certificates because you don't have any signed evidence to share with the verifier.

Quoted 7 lines

In 1.3 we can do GET_DIGESTS and include it in the transcript that KEY_EXCHANGE signs. The clear purpose of this spec change is to sign all the slots certificates in a way the verifier can see.

The net result is you can now freely use GET_MEASUREMENTS with any slot without any binding issue. Both the slot's leaf key and the certificate chain are signed by KEY_EXCHANGE.

Again, makes sense now in the light that it wasn’t done in SPDM 1.2.

Quoted 7 lines
Quoted 7 lines
Quoted 6 lines

Unfortunately RMM doesn't provide an API for the guest to select the slot during GET_MEASUREMENTs and we don't have any way to pass the certificates from other slots to the guest. So from a guest RMM perspective the slot is fixed, attestation subsystem should report the single fixed slot (maybe not zero) and the other slots are blocked.

Quoted 7 lines
Quoted 4 lines

TDX currently also uses a fixed slot 0 also for step 3, BUT it does return you all cert chains from all slots, so you can actually make a full decision after step 3 whenever you trust the single key using any available cert chain slot.

IMHO this is not useful. Again without signing it is all forgable from the verifier perspective.

Agree.

Best Regards,
Elena

maintainer
#75 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 8 lines · Elena
Quoted 3 lines

There is no signature covering the slots in this case. Userspace tested them blind without knowing they are actually in the device. It is not ideal.

Actually you are right, I re-read SPDM 1.2 now and realized that I forgot that the final key exchange signature *only* covers the chain selected for the authentication and not the whole message exchange (including GET_CERTIFICATES).

Yeah, exactly, I was rather surprised by this too since it is such an modern obvious requirement in crypto design. It got fixed in 1.3 when you opt into multikey, and you can patch it with CHALLENGE on 1.2

So, unless anyone knows otherwise, my feeling is we should treat 1.3 multi-key as the target/goal implementation and make 1.2 look more like it using CHALLENGE. Working to obtain the end goal that a signature covers all certificates userspace sees, and userspace does not see unsigned certificates.

Jason

author maintainer
#76 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Jason Gunthorpe
Quoted 16 lines · Jason Gunthorpe

On Tue, Nov 03, 2026 at 12:01:43PM +0000, Reshetova, Elena wrote:

We will need some multi-step sequence from the uABI perspective:

1) CTLV command START CONNECT:
TSM does:
GET_VERSION / VERSION (VCA: Version)
GET_CAPABILITIES / CAPABILITIES (VCA: Capabilities)
NEGOTIATE_ALGORITHMS / ALGORITHMS (VCA: Algorithms)
GET_DIGESTS / DIGESTS

2) CTLV commands:
query certificates. TSM will issue on demand:
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all chunks)
query VCA items

Quoted 10 lines

3) CTLV commands:
FINISH CONNECT with slot:
TSM for 1.2 does:
CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (clear text)
FINISH / FINISH_RSP (in session, opaque to host)
Or for 1.3 multi-key does:
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (now includes DIGESTS and CERTIFICATE)
FINISH / FINISH_RSP (in session, opaque to host)

Does this split require us to guarantee that no GET_VERSION can be issued in between? Another GET_VERSION in the same requester context between START CONNECT and FINISH CONNECT would reset the responder's negotiated state and transcript, right?

If so, can we avoid this split and always use RustSPDM, with or without multi-key support?

Something like:
Kc: public key used to verify CHALLENGE_AUTH from slot C.
Ks: public key selected for session establishment by user using slot S. C and S may be different slots.

Host / RustSPDM:

    VCA -> GET_DIGESTS -> fetch certificate chains
    CHALLENGE(C) -> retain the messages and CHALLENGE_AUTH signature
    Stop communicating with the device.

Host / userspace:
Examine the discovery results.
Select slot S and validate its certificate chain/key Ks.

Host -> RMM/firmware:
Supply:
- C and Kc, S and Ks
- Algorithms

Fresh GET_VERSION -> CAPABILITIES -> ALGORITHMS
GET_DIGESTS immediately after VCA
GET_CERTIFICATE(C), GET_CERTIFICATE(S)
Check that Kc is the leaf public key in chain C. // Can the slot number change by now ? // Do we need rust spdm challenge auth response here?

Is it possible to validate Kc and Ks in the step above?

-aneesh

#77 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 27 lines
Quoted 11 lines · Jason Gunthorpe

On Tue, Nov 03, 2026 at 12:01:43PM +0000, Reshetova, Elena wrote:

We will need some multi-step sequence from the uABI perspective:

1) CTLV command START CONNECT:
TSM does:
GET_VERSION / VERSION (VCA: Version)
GET_CAPABILITIES / CAPABILITIES (VCA: Capabilities)
NEGOTIATE_ALGORITHMS / ALGORITHMS (VCA: Algorithms)
GET_DIGESTS / DIGESTS

Quoted 3 lines

2) CTLV commands:
query certificates. TSM will issue on demand:
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all

chunks)

Quoted 10 lines

query VCA items

3) CTLV commands:
FINISH CONNECT with slot:
TSM for 1.2 does:
CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (clear text)
FINISH / FINISH_RSP (in session, opaque to host)
Or for 1.3 multi-key does:
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (now includes DIGESTS

Quoted 7 lines

and CERTIFICATE)

Quoted 2 lines

FINISH / FINISH_RSP (in session, opaque to host)

Does this split require us to guarantee that no GET_VERSION can be issued in between?

Yes, in my opinion TSM must enforce it because as you said, GET_VERSION resets everything.

Another GET_VERSION in the same requester context

Quoted 11 lines

between START CONNECT and FINISH CONNECT would reset the responder's negotiated state and transcript, right?

If so, can we avoid this split and always use RustSPDM, with or without multi-key support?

Something like:
Kc: public key used to verify CHALLENGE_AUTH from slot C.
Ks: public key selected for session establishment by user using slot S. C and S may be different slots.

Quoted 4 lines

Host / RustSPDM:

    VCA -> GET_DIGESTS -> fetch certificate chains
    CHALLENGE(C) -> retain the messages and CHALLENGE_AUTH signature
    Stop communicating with the device.

GET_DIGESTS only gets you digests, you still need GET_CERTIFICATES to actually fetch certs.

Quoted 11 lines

Host / userspace:
Examine the discovery results.
Select slot S and validate its certificate chain/key Ks.

Host -> RMM/firmware:
Supply:
- C and Kc, S and Ks
- Algorithms

Fresh GET_VERSION -> CAPABILITIES -> ALGORITHMS

How do you propose to deal with the fact that here TSM may agree with a device on different params than rustSPDM above?

Quoted 4 lines

GET_DIGESTS immediately after VCA
GET_CERTIFICATE(C), GET_CERTIFICATE(S)
Check that Kc is the leaf public key in chain C. // Can the slot number change
by now ?

In addition to VCA influencing the content of the slots, a device could have a FW upgrade in between rustSPDM step and this step and this could change the content of the slots.
But if you fully verify the actual cert chain and key itself you can reject such cases. So your way is also doable with additional checks, but I am worried about the problem to agree on the same VCA.

Best Regards,
Elena

Quoted 7 lines
    // Do we need rust spdm challenge auth response here?

Is it possible to validate Kc and Ks in the step above?

-aneesh

author maintainer
#78 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 32 lines · Elena
Quoted 32 lines
Quoted 11 lines · Jason Gunthorpe

On Tue, Nov 03, 2026 at 12:01:43PM +0000, Reshetova, Elena wrote:

We will need some multi-step sequence from the uABI perspective:

1) CTLV command START CONNECT:
TSM does:
GET_VERSION / VERSION (VCA: Version)
GET_CAPABILITIES / CAPABILITIES (VCA: Capabilities)
NEGOTIATE_ALGORITHMS / ALGORITHMS (VCA: Algorithms)
GET_DIGESTS / DIGESTS

Quoted 3 lines

2) CTLV commands:
query certificates. TSM will issue on demand:
GET_CERTIFICATE / CERTIFICATE (every slot in the slot mask, all

chunks)

Quoted 10 lines

query VCA items

3) CTLV commands:
FINISH CONNECT with slot:
TSM for 1.2 does:
CHALLENGE(slot 0, fresh nonce) / CHALLENGE_AUTH
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (clear text)
FINISH / FINISH_RSP (in session, opaque to host)
Or for 1.3 multi-key does:
KEY_EXCHANGE(slot S) / KEY_EXCHANGE_RSP (now includes DIGESTS

and CERTIFICATE)

Quoted 2 lines

FINISH / FINISH_RSP (in session, opaque to host)

Quoted 6 lines
Quoted 2 lines

Does this split require us to guarantee that no GET_VERSION can be issued in between?

Yes, in my opinion TSM must enforce it because as you said, GET_VERSION resets everything.

How can TSM enforce this? Another subsystem could issue GET_VERSION through the DOE mailbox. IIUC, TSM would only detect a transcript mismatch when KEY_EXCHANGE_RSP validation fails. Is that correct?

Quoted 13 lines
Quoted 13 lines

Another GET_VERSION in the same requester context
between START CONNECT and FINISH CONNECT would reset the responder's negotiated state and transcript, right?

If so, can we avoid this split and always use RustSPDM, with or without multi-key support?

Something like:
Kc: public key used to verify CHALLENGE_AUTH from slot C.
Ks: public key selected for session establishment by user using slot S. C and S may be different slots.

Quoted 8 lines
Quoted 4 lines

Host / RustSPDM:

    VCA -> GET_DIGESTS -> fetch certificate chains
    CHALLENGE(C) -> retain the messages and CHALLENGE_AUTH signature
    Stop communicating with the device.

GET_DIGESTS only gets you digests, you still need GET_CERTIFICATES to actually fetch certs.

yes that is correct.

Quoted 11 lines
Quoted 11 lines

Host / userspace:
Examine the discovery results.
Select slot S and validate its certificate chain/key Ks.

Host -> RMM/firmware:
Supply:
- C and Kc, S and Ks
- Algorithms

Quoted 6 lines
Quoted 1 line
    Fresh GET_VERSION -> CAPABILITIES -> ALGORITHMS

How do you propose to deal with the fact that here TSM may agree with a device on different params than rustSPDM above?

I suppose we would need to pass all of those parameters for the host -> RMM/firmware interaction. I had assumed that the algorithms were the only negotiated parameters needed for SPDM session establishment. Would validating Kc and Ks require passing additional details from the RustSPDM exchange to the firmware?

Quoted 7 lines
Quoted 6 lines

GET_DIGESTS immediately after VCA
GET_CERTIFICATE(C), GET_CERTIFICATE(S)
Check that Kc is the leaf public key in chain C. // Can the slot number change
by now ?

Quoted 9 lines

In addition to VCA influencing the content of the slots, a device could have a FW upgrade in between rustSPDM step and this step and this could change the content of the slots.
But if you fully verify the actual cert chain and key itself you can reject such cases. So your way is also doable with additional checks, but I am worried about the problem to agree on the same VCA.

My main question is whether we should avoid splitting the context and relying on the host not to disrupt the sequence. I wonder whether that would be a poor API design and whether we can avoid it. Is that a goal we should pursue?

-aneesh

maintainer
#79 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 5 lines · Aneesh Kumar K.V
Quoted 5 lines
Quoted 2 lines

Does this split require us to guarantee that no GET_VERSION can be issued in between?

Yes, in my opinion TSM must enforce it because as you said, GET_VERSION resets everything.

+1

Quoted 3 lines

How can TSM enforce this? Another subsystem could issue GET_VERSION through the DOE mailbox. IIUC, TSM would only detect a transcript mismatch when KEY_EXCHANGE_RSP validation fails. Is that correct?

Firstly I think nothing but the attestation subsystem driver should operate the DOE mailbox, certainly not the SPDM flow inside it.

Secondly, yes, the KEY_EXCHANGE should blow up if the session is interfered with in some way, that is one of it's critical security tasks.

Third, the kernel needs to keep track of the SPDM session 'generation' directly and expose it to userspace so there is an unbreakable continuity from START CONNECT to the final ACCEPT.

Quoted 5 lines

I suppose we would need to pass all of those parameters for the host -> RMM/firmware interaction. I had assumed that the algorithms were the only negotiated parameters needed for SPDM session establishment. Would validating Kc and Ks require passing additional details from the RustSPDM exchange to the firmware?

This is just backwards. The TSM should be in control of the session from start to end, and the kernel should be inspecting the plain text messages it sends. We need the TSM to send all the messages we need, like GET_DIGESTS, GET_CERTIFICATES and so on.

Doing two sesssions is just an inherently less secure idea. We should not do this unless we get really really stuck. I prefer we change the TSM interfaces, ie in RMM, to work optimally so we don't need to try to do this.

Nobody has every done any of this before, all the specs are copying each other with incomplete implementations. Nobody should be insisting they can't change.

Quoted 4 lines

My main question is whether we should avoid splitting the context and relying on the host not to disrupt the sequence. I wonder whether that would be a poor API design and whether we can avoid it. Is that a goal we should pursue?

The host can interfere with the TSM's DOE operations, that is part of the threat model and countered by the KEY_EXCHANGE sequence. I don't see any reason to worry about this as a problem, and we should take advantage of it to make the flow work naturally under a single session.

Jason

author maintainer
#80 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Jason Gunthorpe
Quoted 26 lines · Jason Gunthorpe
Quoted 16 lines · Elena
Quoted 16 lines
Quoted 13 lines

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

I am currently doing something similar to the above.

Quoted 8 lines
Quoted 2 lines

https://git.gitlab.arm.com/linux-arm/linux-cca/-
/commit/0657d167aee1ba43dd2765d99b5e3244b83c6ea6

If I understand this correctly, this is not quite the option 1 that Jason proposed above. You dont use GET_DIGESTS and I dont see a binding between cert chain discovered prior to TSM SPDM session and the one during the TSM SPDM session.
Or am I misunderstanding it?

Quoted 5 lines

Right..

I have poked at this alot more and it seems a bit different than I originally was told, especially if we narrow it to RMM only.

Wouldn't comparing the selected certificate chains, together with successful RMM session establishment, be sufficient to bind the selected chain?

IIUC RMM first retrieves the chain before establishing a secure session. We compare that chain with the one retrieved through Rust SPDM, then supply its leaf public key through SET_PUBKEY. RMM subsequently verifies the KEY_EXCHANGE_RSP signature using that key, with the selected certificate-chain hash included in the signed transcript.

The certificate chain comparison alone does not prove possession of the private key, but the subsequent key exchange should provide that proof right? A device returning another device’s certificate chain would therefore pass the comparison but fail session establishment unless it could authenticate using that key.

I haven't read the rest of your email yet. I need to review it and compare the SPDM versions you cited before making any changes.

-aneesh

maintainer
#81 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 30 lines · Aneesh Kumar K.V
Quoted 28 lines · Jason Gunthorpe
Quoted 16 lines · Elena
Quoted 16 lines
Quoted 13 lines

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

I am currently doing something similar to the above.

Quoted 8 lines
Quoted 2 lines

https://git.gitlab.arm.com/linux-arm/linux-cca/-
/commit/0657d167aee1ba43dd2765d99b5e3244b83c6ea6

If I understand this correctly, this is not quite the option 1 that Jason proposed above. You dont use GET_DIGESTS and I dont see a binding between cert chain discovered prior to TSM SPDM session and the one during the TSM SPDM session.
Or am I misunderstanding it?

Right..

Quoted 7 lines
Quoted 3 lines

I have poked at this alot more and it seems a bit different than I originally was told, especially if we narrow it to RMM only.

Wouldn't comparing the selected certificate chains, together with successful RMM session establishment, be sufficient to bind the selected chain?

Only one chain can be checked that way, the chain that was hashed into KEY_EXCHANGE.

The point is to avoid downgrade attacks where you are selecting chains but the "best" chain was hidden by an attacker so you use something weaker.

This is less important for 1.2 since there is only one private key, but it is essential for 1.3 with multi-key and crypto agility that we don't allow an attacker to hide a slot from the negotiation. Eg to hide the strong crypto slot option or something.

Quoted 3 lines

The certificate chain comparison alone does not prove possession of the private key, but the subsequent key exchange should provide that proof right?

Yes, there is only one private key in SPDM 1.2 and the initial KEY_EXCHANGE proves the remote possess it.

Read the later email to Elena first, it is better.

Jason

#82 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Jason Gunthorpe
Quoted 45 lines · Elena
Quoted 5 lines
Quoted 5 lines
Quoted 5 lines

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference

Spec

Quoted 7 lines
Quoted 6 lines
Quoted 1 line

2.1 v0.66, 4.2.20.1).

Yeah, its fine. TDX will have to do what Aneesh proposed for ARM, and use use the Rust SPDM Driver to operate DOE prior to calling TDH.SPDM.CONNECT. This will allow discovering what is in the slots and passing that information to userspace.

Quoted 7 lines

I am somewhat confused by this.

1. You are saying that prior to establishing a
secure SPDM session, you will use unsecure DOE mechanism to discover available cert chains. Here you have to take into account that anything you get at this step is not trusted. A malicious device could send you any cert chains from another legitimate device.

I poked at this with AI for a while.. While nothing about SPDM slots seems particularly clean it can be made to work. It looks like 1.4 improves things meaningfully.

Quoted 3 lines

2. Next, you will choose a cert slot to be used for SPDM authentication and secure session establishment (slot 0 initially in this patch) and allow everything to happen.

Yep

Quoted 4 lines

Now, who guarantees that a cert chain that you saw
at step 1 for the selected slot is the same that is going to be used in actual SPDM connection? Who is verifying/ensuring this? Kernel? Userspace?

Not sure, it would make sense if the kernel can do some of this with rustspdm, but there are alot of details here that I can't guess what is best.

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

Quoted 9 lines

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

We don't issue GET_DIGESTS currently in our SPDM flow, but it could be changed in principle.

Quoted 14 lines

2) Do slot selection and use CHALLENGE to prevent downgrade attacks.
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
CHALLENGE / CHALLENGE_AUTH (using slot 0)
3) Kernel RustSPDM can check that CHALENGE_AUTH is self consistent
2) Supply the certificates to userspace
3) Userspace choose a slot
4) TSM does KEY_EXCHANGE
5) Confirm the certificate of the selected slot signs the transcript from TSM
6) Verifier confirms that the #1 transcript is acceptable

Quoted 1 line

I think this is weaker than #1, but not that much..

It is definitely more complex overall. The only positive part of this option I can see so far is the absence of GET_DIGESTS in the step 4.

Quoted 6 lines

3) Do slot selection without CHALLENGE and then do it again after establishing the session to confirm nothing has changed.

Requires TSM to be able to return slots after opening the session, CCA can't at least.

TDX can return you all slots as a result of SPDM successful session establishment, but I do understand not everyone can.

Quoted 4 lines

It may be we simply don't securely support multi-slot until 1.4 spec support in TSMs. Per 1.2 can be flagged as less secure in the API or something?

We currently don't support multi-key either, so it is in control of both SPDM responder and requestor that such SPDM capability is not selected during initial exchange. If TSM refuses to enable it, it should be ok.
But the overall design and especially userspace ABIs should be imo future proof and we know multi-key is coming.

Best Regards,
Elena.

Quoted 2 lines

Jason

#83 lore raw

RE: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

In reply to Jason Gunthorpe
Quoted 65 lines

-----Original Message-----
From: Jason Gunthorpe <[email protected]>
Sent: Friday, October 30, 2026 3:16 PM
To: Reshetova, Elena <[email protected]>
Cc: Jiang, Dave <[email protected]>; Aneesh Kumar K.V (Arm) <[email protected]>; [email protected]; Jiri Pirko <[email protected]>; Tian, Kevin <[email protected]>; Alexey Kardashevskiy <[email protected]>; Xu Yilun <[email protected]>; Catalin Marinas <[email protected]>; Suzuki K Poulose <[email protected]>; Steven Price <[email protected]>; Kiryl Shutsemau <[email protected]>; Edgecombe, Rick P <[email protected]>; Dave Hansen <[email protected]>; Jonathan Cameron <[email protected]>; Bjorn Helgaas <[email protected]>; Ashish Kalra <[email protected]>; Tom Lendacky <[email protected]>; John Allen <[email protected]>; Will Deacon <[email protected]>; Mark Rutland <[email protected]>; [email protected]
Subject: Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 5 lines · Elena
Quoted 5 lines
Quoted 5 lines

It can't defer the session. The TDX Module runs the whole SPDM session setup inside TDH.SPDM.CONNECT, and the host relays its DOE messages without interpreting them, so there is no defined point to stop after the certificates. The chains come back in the device info only once TDH.SPDM.CONNECT completes (TDX Connect ABI Reference

Spec

Quoted 7 lines
Quoted 6 lines
Quoted 1 line

2.1 v0.66, 4.2.20.1).

Yeah, its fine. TDX will have to do what Aneesh proposed for ARM, and use use the Rust SPDM Driver to operate DOE prior to calling TDH.SPDM.CONNECT. This will allow discovering what is in the slots and passing that information to userspace.

Quoted 7 lines

I am somewhat confused by this.

1. You are saying that prior to establishing a
secure SPDM session, you will use unsecure DOE mechanism to discover available cert chains. Here you have to take into account that anything you get at this step is not trusted. A malicious device could send you any cert chains from another legitimate device.

I poked at this with AI for a while.. While nothing about SPDM slots seems particularly clean it can be made to work. It looks like 1.4 improves things meaningfully.

Quoted 3 lines

2. Next, you will choose a cert slot to be used for SPDM authentication and secure session establishment (slot 0 initially in this patch) and allow everything to happen.

Yep

Quoted 4 lines

Now, who guarantees that a cert chain that you saw
at step 1 for the selected slot is the same that is going to be used in actual SPDM connection? Who is verifying/ensuring this? Kernel? Userspace?

Not sure, it would make sense if the kernel can do some of this with rustspdm, but there are alot of details here that I can't guess what is best.

AI tells me there are a couple of reasonable ways to implement slot selection and avoid downgrade attacks:

Quoted 9 lines

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_DIGESTS / DIGESTS
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
3) TSM module does startup and also issues 'GET_DIGESTS' as part of the signed transcript
4) Confirm that the digests from #1 match the digests from #2 in the TSM transcript.

We can also simplify this further by not using GET_DIEGESTS but assuming TSM can return the cert chain for selected slot back to kernel/userspace in a trusted way. TDX does it by returning the cert chain (actually for all slots if asked, but it is redundant here) on successful SPDM.CONNECT and there is a trusted TDX Module provided digest covering this info.

So, option 1 becomes like:

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
2a) Calculate a digest for the selected slot
3) TSM module does startup
4) Extract cert chain (or only its digest) from info provided by TSM.
Verify that the digest from #2a match the digest calculated based on trusted info provided by TSM.

Opinions?

Best Regards,
Elena.

maintainer
#84 lore raw

Re: [RFC PATCH v1 10/11] virt: coco: arm-cca-host: Publish slot zero and establish streams after acceptance

Quoted 9 lines · Elena

We can also simplify this further by not using GET_DIEGESTS but assuming TSM can return the cert chain for selected slot back to kernel/userspace in a trusted way. TDX does it by returning the cert chain (actually for all slots if asked, but it is redundant here) on successful SPDM.CONNECT and there is a trusted TDX Module provided digest covering this info.

So, option 1 becomes like:

Quoted 8 lines

1) With 1.4 match GET_DIGESTS and MULTI_KEY_CONN_RSP=true
1) Do via RustSPDM:
GET_CERTIFICATE / CERTIFICATE
2) Userspace selects slots
2a) Calculate a digest for the selected slot
3) TSM module does startup
4) Extract cert chain (or only its digest) from info provided by TSM.
Verify that the digest from #2a match the digest calculated based on trusted info provided by TSM.

See my larger email, I don't think this is how SPDM is intended to be used, you should have a GET_DIGESTS that is covered by a signature and included in a transcript..

I see only three options:
1) Use a single slot, so KEY_EXCHANGE includes a hash of the single slot cert chain
2) Use GET_DIGESTS/CHALLENGE on 1.2 followed by KEY_EXCHANGE. Maybe use the same nonce for both signatures so it can't be stale.
3) Use the new multi-key flow on 1.3 which already includes GET_DIGESTS in the transcript hashed by KEY_EXCHANGE

These all achieve the same end goal as what the 1.3 spec did, you end up with a transcript signed by KEY_EXCHANGE that identifies all the legal certificates.

IMHO there is no reason to deviate from this, so the uAPI should be designed around it.

Thus I don't think TDX should fake this with a second GET_CERTIFICATES pass inside the established session and try to skip the signed digests..

Jason

author maintainer
#85 lore raw

RFC PATCH 11/11 virt: coco: arm-cca-guest: Refresh selected evidence against trusted digests

In reply to Aneesh Kumar K.V (Arm)

Refresh either measurements with a 32-byte challenge or the interface report without one. Reject other objects and unsupported flags before issuing native requests.

Verify refreshed bytes against the trusted digest, hash algorithm, certificate slot and lock sequence. Check that the other cached object's sequence is unchanged, then replace only the verified selected cache entry. Keep the previous cache intact on failure and document refresh constraints and generation invalidation.

Cc Catalin Marinas <[email protected]>
Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
---
 drivers/virt/coco/arm-cca-guest/main.c | 134 +++++++++++++++----------
 1 file changed, 83 insertions(+), 51 deletions(-)
drivers/virt/coco/arm-cca-guest/main.c +83 -51
index f50f3e660bbc..d9de5d6c0e37 100644
@@ -174,86 +174,94 @@ static void unregister_cca_tsm_report(void *data)
 
 #ifdef CONFIG_PCI_TSM
 
-static int __maybe_unused
-cca_update_dev_measurements(struct pci_dev *pdev, const u8 *nonce)
+static int cca_refresh_dev_evidence(struct pci_dev *pdev,
+		enum pci_tsm_evidence_type type, const u8 *nonce)
 {
-	int ret;
-	void *measurements;
-	size_t measurements_size;
-	int vdev_id = rsi_vdev_id(pdev);
-	struct pci_tsm_evidence *evidence;
-	struct rsi_vdevice_info *dev_info;
-	struct pci_tsm_evidence_object *obj;
 	struct cca_guest_dsc *dsc = to_cca_guest_dsc(pdev);
+	struct pci_tsm_evidence *evidence = &dsc->pci.base_tsm.evidence;
+	struct pci_tsm_evidence_object *obj = &evidence->obj[type];
+	struct rsi_vdevice_info *dev_info;
+	bool report = type == PCI_TSM_EVIDENCE_TYPE_REPORT;
+	int vdev_id = rsi_vdev_id(pdev);
+	void *data;
+	u8 *digest;
+	u8 expected_hash_algo;
+	size_t size;
+	int object_type;
+	int ret;
 
-	/* Regenerate the measurement from the device */
-	ret = rhi_update_vdev_measurements_cache(pdev, nonce);
-	if (ret) {
-		pci_err(pdev, "failed to update device measurements from device (%d)\n", ret);
-		return ret;
+	if (report) {
+		object_type = RHI_DA_OBJECT_INTERFACE_REPORT;
+		ret = rhi_update_vdev_interface_report_cache(pdev);
+	} else {
+		object_type = RHI_DA_OBJECT_MEASUREMENT;
+		ret = rhi_update_vdev_measurements_cache(pdev, nonce);
 	}
-
-	ret = rhi_read_cached_object(vdev_id, RHI_DA_OBJECT_MEASUREMENT,
-				     &measurements, &measurements_size);
-	if (ret) {
-		pci_err(pdev, "failed to get device measurements from the host (%d)\n", ret);
+	if (ret)
 		return ret;
-	}
 
+	ret = rhi_read_cached_object(vdev_id, object_type, &data, &size);
+	if (ret)
+		return ret;
 	dev_info = kmalloc(sizeof(*dev_info), GFP_KERNEL);
 	if (!dev_info) {
 		ret = -ENOMEM;
-		goto free_measurements;
+		goto free_data;
 	}
-
 	if (rsi_vdev_get_info(vdev_id, virt_to_phys(dev_info))) {
-		pci_err(pdev, "failed to get device digests (%d)\n", ret);
 		ret = -EIO;
 		goto free_dev_info;
 	}
 
-	/* Make sure no unexpected lock/unlock operation happened from guest */
-	if (dsc->dev_info.lock_seq != dev_info->lock_seq) {
-		pci_err(pdev, "Unexpected lock/unlock operation from host (%d)\n", ret);
+	if (evidence->digest_algo == HASH_ALGO_SHA256)
+		expected_hash_algo = RSI_HASH_SHA_256;
+	else
+		expected_hash_algo = RSI_HASH_SHA_512;
+
+	/* Keep every cached object bound to the same locked device. */
+	if (dev_info->lock_seq != dsc->dev_info.lock_seq || dev_info->id_index ||
+	    dev_info->hash_algo != expected_hash_algo) {
 		ret = -EIO;
 		goto free_dev_info;
 	}
 
-	/*
-	 * Verify that the digests of the provided reports match with the
-	 * digests from RMM
-	 */
-	ret = cca_verify_digest(dev_info->hash_algo, measurements,
-				measurements_size, dev_info->meas_digest);
-	if (ret) {
-		pci_err(pdev, "RMM provided digest mismatch (%d)\n", ret);
-		goto free_dev_info;
+	if (report) {
+		if (dev_info->meas_seq != dsc->dev_info.meas_seq) {
+			ret = -EIO;
+			goto free_dev_info;
+		}
+		digest = dev_info->report_digest;
+	} else {
+		if (dev_info->report_seq != dsc->dev_info.report_seq) {
+			ret = -EIO;
+			goto free_dev_info;
+		}
+		digest = dev_info->meas_digest;
 	}
 
-	/* fill evidence details */
-	evidence = &dsc->pci.base_tsm.evidence;
+	ret = cca_verify_digest(dev_info->hash_algo, data, size, digest);
+	if (ret)
+		goto free_dev_info;
 
-	/* Now update the evidence under lock. */
+	/* Publish only after verification; failures preserve the old cache. */
 	down_write(&evidence->lock);
-	evidence->generation = dev_info->meas_seq;
-
-	obj = &evidence->obj[PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS];
-	if (obj->data)
-		kvfree(obj->data);
-	obj->data = measurements;
-	obj->len = measurements_size;
-
-	dsc->dev_info.meas_seq = dev_info->meas_seq;
-	memcpy(dsc->dev_info.meas_digest, dev_info->meas_digest, SHA512_DIGEST_SIZE);
+	kvfree(obj->data);
+	obj->data = data;
+	obj->len = size;
+	memcpy(obj->digest, digest, SHA512_DIGEST_SIZE);
+	if (report)
+		dsc->dev_info.report_seq = dev_info->report_seq;
+	else
+		dsc->dev_info.meas_seq = dev_info->meas_seq;
+	evidence->generation++;
 	up_write(&evidence->lock);
-
 	kfree(dev_info);
 	return 0;
 
 free_dev_info:
 	kfree(dev_info);
-free_measurements:
-	kvfree(measurements);
+free_data:
+	kvfree(data);
 	return ret;
 }
 
@@ -506,6 +514,29 @@ static int cca_tsm_accept(struct pci_dev *pdev)
 	return __cca_tsm_accept(pdev, dsc->dev_info.lock_seq);
 }
 
+static int cca_tsm_refresh_evidence(struct pci_tsm *tsm,
+		enum pci_tsm_evidence_type type, unsigned long flags,
+		void *nonce, size_t nonce_len)
+{
+	if (flags)
+		return -EOPNOTSUPP;
+
+	switch (type) {
+	case PCI_TSM_EVIDENCE_TYPE_MEASUREMENTS:
+		if (nonce_len != 32)
+			return -EINVAL;
+		break;
+	case PCI_TSM_EVIDENCE_TYPE_REPORT:
+		if (nonce_len)
+			return -EINVAL;
+		break;
+	default:
+		return -EOPNOTSUPP;
+	}
+
+	return cca_refresh_dev_evidence(tsm->pdev, type, nonce);
+}
+
 static struct pci_tsm_ops cca_devsec_pci_ops = {
 	.owner = THIS_MODULE,
 	.evidence_types = BIT(PCI_TSM_EVIDENCE_TYPE_CERT0) |
@@ -515,6 +546,7 @@ static struct pci_tsm_ops cca_devsec_pci_ops = {
 	.lock = cca_tsm_lock,
 	.unlock = cca_tsm_unlock,
 	.accept	 = cca_tsm_accept,
+	.refresh_evidence = cca_tsm_refresh_evidence,
 };
 
 static void cca_devsec_tsm_remove(void *tsm_dev)
Signature · 1 line
2.43.0
#86 lore raw
In reply to Aneesh Kumar K.V (Arm)
Quoted 17 lines · Aneesh Kumar K.V (Arm)

This series connects PCI/TSM to the common CTLV attestation interface. Userspace can discover a device's evidence objects, retrieve them, and accept the evidence before the kernel establishes a secure link or moves a guest device into TDISP RUN. It replaces the PCI/TSM netlink evidence interface while retaining connect, disconnect, lock and unlock in sysfs.

This series builds on the CTLV attestation series [1] and Arm CCA device-assignment support. It covers PCI/TSM attestation through certificate slot-zero.

The development tree is available at:

https://gitlab.arm.com/linux-arm/linux-cca.git cca/topics/cca-da-attestation

That branch also includes the follow-up Rust SPDM certificate-slot discovery work, generic attestation fixes, and CTLV tooling fixes.

Quoted 8 lines

[1] https://lore.kernel.org/all/[email protected]

The host flow separates identity collection from session and IDE setup:

sysfs connect

    -> collect and cache the certificate chain
  DEVICE_GET / EVIDENCE_GET
    -> discover available objects and retrieve the chain for verification

Is it possible that these two steps combined to one? They collect, cache the certificates, then hand over the cache to userspace when requested. Why not just use one user interface to collect and hand over the certificates?

And I see a further action is to use kernel native SPDM (rust SPDM) to finish these 2 steps, so they will have nothing to do with TSM any more.

Could the flow be:

1. DEVICE_GET/EVIDENCE_GET

	-> collect and retrieve the certificates via native SPDM
2. sysfs connect
	-> establish the *TSM-based* secure session and IDE streams
3. sysfs disconnect
	-> release the *TSM-based* streams and identity context

This way we don't even touch the existing host side TSM.

Quoted 172 lines

EVIDENCE_ACCEPT

    -> establish the secure session and IDE streams
  sysfs disconnect
    -> release the identity context and any established streams

Both AMD SEV-TIO and Arm CCA host drivers implement this flow using their existing firmware interfaces. AMD pauses the firmware SPDM exchange after collecting the certificate chain, before sending authentication or session establishment requests. CCA creates the PDEV and collects its certificate chain during connect, then completes session and stream setup on acceptance. After both conversions, every link driver must supply an evidence_accept callback; connect collects identity evidence without establishing streams.

This is the slot-zero baseline: the AMD and CCA host drivers expose their slot-zero certificate chains. A follow-up series adds shared SPDM identity discovery and selection among discovered certificate slots. The common interface supports any certificate slot advertised by the driver, and the same evidence-get and evidence-accept commands apply to both series.

For a guest device, sysfs lock collects the initial evidence and creates the CTLV instance. Userspace can optionally refresh supported objects, retrieve the cached evidence, verify it, and issue EVIDENCE_ACCEPT to enter RUN. The CCA guest driver supports refreshing measurements with a 32-byte challenge or an interface report without a challenge. It checks the new bytes against trusted digests and device state before replacing the selected cached object. Refresh is disabled after the device enters RUN.

EVIDENCE_GET reads the cached snapshot, so its challenge attribute becomes optional. EVIDENCE_REFRESH requests new evidence and invalidates previous evidence generations and acceptance. A failed backend refresh also invalidates them and requires recovery.

EVIDENCE_ACCEPT replaces the separate DEVSEC_RUN operation. Clearing acceptance leaves the hardware state unchanged: a guest device can remain in RUN, and host IDE streams remain established. DEVSEC_STATE_GET therefore continues to report acceptance and device state separately. Disconnect and unlock perform teardown.

The common attestation changes let each instance expose the operations and evidence objects its device supports. This allows host links and guest devices to share the PCI attestation implementation while advertising different capabilities. Additional locking and generation helpers let PCI/TSM coordinate state changes, evidence invalidation and notifications with CTLV operations.

The PCI child family adds STATE_GET and EVIDENCE_REFRESH to the inherited attestation operations. An instance appears only after a successful connect or lock and is removed on disconnect or unlock. DEVICE_GET includes the PCI name so userspace can associate an instance with its device.

The first five patches prepare the common attestation interface. The next three define and implement the PCI integration, including its locking helpers. The final three convert AMD and CCA host link setup and add CCA guest evidence refresh. The series builds on the existing CTLV attestation and PCI/TSM device-assignment support.

Notes:
* I used Codex to help implement, review and organize the series.

Cc Jiri Pirko <[email protected]>
Cc Kevin Tian <[email protected]>
Cc Alexey Kardashevskiy <[email protected]>
Cc Xu Yilun <[email protected]>
Cc Catalin Marinas <[email protected]>
Cc Suzuki K Poulose <[email protected]>
Cc Steven Price <[email protected]>
Cc Kiryl Shutsemau <[email protected]>
Cc Rick Edgecombe <[email protected]>
Cc Dave Hansen <[email protected]>
Cc Jonathan Cameron <[email protected]>
Cc Jason Gunthorpe <[email protected]>
Cc Bjorn Helgaas <[email protected]>
Cc Ashish Kalra <[email protected]>
Cc Tom Lendacky <[email protected]>
Cc John Allen <[email protected]>
Cc Will Deacon <[email protected]>
Cc Mark Rutland <[email protected]>

Aneesh Kumar K.V (Arm) (11):
attestation: Restrict inherited operations per instance
attestation: Filter evidence objects per instance
attestation: Enter secure operation when evidence is accepted
attestation: Bind object selection to accepted evidence snapshots
attestation: Make the evidence-get challenge attribute optional PCI/TSM: Describe the PCI attestation control family
attestation: Add attestation helpers for PCI/TSM integration PCI/TSM: Bind CTLV attestation to connected and locked contexts crypto/ccp: Defer SEV-TIO link setup until evidence acceptance
virt: coco: arm-cca-host: Publish slot zero and establish streams
after acceptance
virt: coco: arm-cca-guest: Refresh selected evidence against trusted
digests

 Documentation/ABI/testing/sysfs-bus-pci       |  108 +-
 Documentation/ABI/testing/sysfs-class-tsm     |    9 +-
 .../ctlv/abi/ctlv-attestation-pci.yaml        |   43 +
 Documentation/ctlv/abi/ctlv-attestation.yaml  |   59 +-
 Documentation/ctlv/abi/families.registry.yaml |    1 +
 Documentation/ctlv/families.yaml              |    1 +
 Documentation/ctlv/generated-files.yaml       |   11 +
 .../ctlv/specs/ctlv-attestation-pci.yaml      |   79 +
 .../ctlv/specs/ctlv-attestation.yaml          |   40 +-
 Documentation/driver-api/attestation.rst      |   56 +
 Documentation/driver-api/pci/tsm.rst          |  160 +-
 Documentation/netlink/specs/pci-tsm.yaml      |  151 --
 drivers/attestation/core/core.c               |  123 +-
 drivers/attestation/core/ctlv.c               |  156 +-
 drivers/attestation/core/internal.h           |    3 +
 .../generated/ctlv_attestation_gen.c          |   45 +-
 .../generated/ctlv_attestation_gen.h          |   76 +-
 drivers/attestation/providers/sim.c           |   40 +-
 drivers/attestation/tests/core_kunit.c        |   72 +-
 drivers/crypto/ccp/sev-dev-tio.c              |   17 +-
 drivers/crypto/ccp/sev-dev-tio.h              |    5 +
 drivers/crypto/ccp/sev-dev-tsm.c              |  188 +-
 drivers/pci/Kconfig                           |    3 +
 drivers/pci/tsm/Makefile                      |    6 +-
 drivers/pci/tsm/attestation.c                 |  525 +++++
 drivers/pci/tsm/core.c                        |  431 +++--
 drivers/pci/tsm/ctlv_attestation_pci_gen.c    |  111 ++
 drivers/pci/tsm/ctlv_attestation_pci_gen.h    |  100 +
 drivers/pci/tsm/evidence.c                    |  274 ---
 drivers/pci/tsm/internal.h                    |   26 +
 drivers/pci/tsm/netlink.c                     |   43 -
 drivers/pci/tsm/netlink.h                     |   23 -
 drivers/virt/coco/arm-cca-guest/main.c        |  168 +-
 drivers/virt/coco/arm-cca-guest/rsi-da.c      |   16 +-
 drivers/virt/coco/arm-cca-host/main.c         |  143 +-
 drivers/virt/coco/arm-cca-host/rmi-da.c       |    7 +-
 drivers/virt/coco/arm-cca-host/rmi-da.h       |    2 +
 include/linux/attestation.h                   |   93 +-
 include/linux/pci-tsm.h                       |   86 +-
 include/linux/pci.h                           |    2 +
 include/uapi/linux/ctlv_attestation_gen.h     |   25 +-
 include/uapi/linux/ctlv_attestation_pci_gen.h |   74 +
 include/uapi/linux/ctlv_families_gen.h        |    1 +
 include/uapi/linux/pci-tsm-netlink.h          |  101 -
 samples/devsec/common.c                       |    1 -
 samples/devsec/link_tsm.c                     |   17 +-
 samples/devsec/tsm.c                          |   32 +-
 tools/ctlv/tests/support.py                   |    3 +-
 tools/ctlv/tests/test_outputs.py              |    6 +-
 tools/ctlv/tests/test_semantics.py            |   38 +
 tools/ctlv/tests/test_spec.py                 |    1 +
 .../selftests/attestation/attestation_test.c  |   32 +-
 .../ctlv_attestation_pci_user_gen.c           | 1697 +++++++++++++++++
 .../ctlv_attestation_pci_user_gen.h           |  230 +++
 .../ctlv_attestation_sim_user_gen.c           |  161 +-
 .../ctlv_attestation_tdx_user_gen.c           |  161 +-
 .../attestation/ctlv_attestation_user_gen.c   |  161 +-
 .../attestation/ctlv_attestation_user_gen.h   |   70 +-
 .../attestation/tdx_attestation_test.c        |    9 +-
 59 files changed, 4678 insertions(+), 1644 deletions(-)
 create mode 100644 Documentation/ctlv/abi/ctlv-attestation-pci.yaml
 create mode 100644 Documentation/ctlv/specs/ctlv-attestation-pci.yaml
 delete mode 100644 Documentation/netlink/specs/pci-tsm.yaml
 create mode 100644 drivers/pci/tsm/attestation.c
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.c
 create mode 100644 drivers/pci/tsm/ctlv_attestation_pci_gen.h
 delete mode 100644 drivers/pci/tsm/evidence.c
 create mode 100644 drivers/pci/tsm/internal.h
 delete mode 100644 drivers/pci/tsm/netlink.c
 delete mode 100644 drivers/pci/tsm/netlink.h
 create mode 100644 include/uapi/linux/ctlv_attestation_pci_gen.h
 delete mode 100644 include/uapi/linux/pci-tsm-netlink.h
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.c
 create mode 100644 tools/testing/selftests/attestation/ctlv_attestation_pci_user_gen.h
Signature · 1 line
2.43.0
maintainer
#87 lore raw
Quoted 22 lines · Xu Yilun
Quoted 21 lines · Aneesh Kumar K.V (Arm)

This series connects PCI/TSM to the common CTLV attestation interface. Userspace can discover a device's evidence objects, retrieve them, and accept the evidence before the kernel establishes a secure link or moves a guest device into TDISP RUN. It replaces the PCI/TSM netlink evidence interface while retaining connect, disconnect, lock and unlock in sysfs.

This series builds on the CTLV attestation series [1] and Arm CCA device-assignment support. It covers PCI/TSM attestation through certificate slot-zero.

The development tree is available at:

https://gitlab.arm.com/linux-arm/linux-cca.git cca/topics/cca-da-attestation

That branch also includes the follow-up Rust SPDM certificate-slot discovery work, generic attestation fixes, and CTLV tooling fixes.

[1] https://lore.kernel.org/all/[email protected]

The host flow separates identity collection from session and IDE setup:

Quoted 8 lines
Quoted 4 lines

sysfs connect

    -> collect and cache the certificate chain
  DEVICE_GET / EVIDENCE_GET
    -> discover available objects and retrieve the chain for verification

Is it possible that these two steps combined to one? They collect, cache the certificates, then hand over the cache to userspace when requested. Why not just use one user interface to collect and hand over the certificates?

Any caching needs a careful think.. We should be caching globally in the attestation subsystem things like certificate chains so we can do GET_DIGESTS and then skip the GET_CERTIFICATES if we already have it..

And if we are going to do this rustspdm + TSM stuff the certificate caching should be shared between those components too.

Other stuff is not caching, it is storing immutable data about a single SPDM session. Like the transcript.. This needs to be properly lifetime bound to the session itself through a session generation.

Quoted 10 lines

Could the flow be:

1. DEVICE_GET/EVIDENCE_GET

	-> collect and retrieve the certificates via native SPDM
2. sysfs connect
	-> establish the *TSM-based* secure session and IDE streams
3. sysfs disconnect
	-> release the *TSM-based* streams and identity context

This way we don't even touch the existing host side TSM.

I don't think this is a great idea, ideally we should be seeing the TSM world expand their APIs to support slot discovery properly and securely. Injecting rustspdm to make up those gaps should be seen as a temporary thing. Thus the uAPI presented should be uniform between and old and new TSM..

There should also be some continuity, possibly in the kernel, to bind the digests/certificates from the first phase through to the authenticated phase to avoid downgrade attacks. See my reply to Elena This is much harder to do if the tsm driver goes away in the middle of the process.

Jason