Re: [PATCH RFC 00/20] attestation: Add a common subsystem and TDX support

From: Dave Jiang

Date: Tue Oct 27 2026 - 14:43:27 EST




On 10/15/26 7:50 AM, Jiri Pirko wrote:
> From: Jiri Pirko <jiri@xxxxxxxxxx>
>
> Linux has several attestation interfaces tied to individual technologies.
> That makes evidence retrieval, measurement registers, device state and
> kernel consumers provider-specific.
>
> This series introduces a common attestation subsystem. Its goal is to
> support platform, TEE and device providers through one model, including
> TPM2, TDX, SEV-SNP, Arm CCA, FIRME, Caliptra and PCI/TSM-backed devices.
> This first series implements the common subsystem and a TDX provider.
> Existing native TDX tsm_mr and tsm_report interfaces remain independent.
>
> Attestation devices use CTLV, a schema-driven character-device TLV UAPI.
> Compared with sysfs or configfs, CTLV executes one validated request
> atomically instead of assembling a transaction through several files.
> Compared with netlink, an opened device fd directly identifies authority
> and lifetime, can be delegated normally, and owns its event queue. CTLV
> also provides:
>
> - generated kernel metadata, UAPI constants and documentation;
> - strict type, bounds, nesting and required-field validation;
> - runtime operation and schema introspection;
> - family inheritance and extension points;
> - fragmented queries with generation checks;
> - per-fd event subscription, overflow reporting and removal notification;
> - large input and output blobs.
>
> The attestation family exposes capabilities, evidence, measurement-register
> topology and operations, an append-only MR journal and journal
> notifications. This first ABI contains the paths exercised by TDX. Extended
> evidence classes, dynamic registers, record payloads, verdict recording and
> device-security transitions follow with providers that need them and are out
> of scope of this initial patchset. The broader direction is illustrated here:
>
> https://github.com/jpirko/linux_mlxsw/commits/wip_attestation_all_1
>
> That branch also contains additional providers (test and sim),
> attestation core extensions, libctlv and ctlvctl. With the tools
> and kernel specifications, an attestation device can be used as follows:
>
> # Discover CTLV devices and their family chains.
> ctlvctl --spec-dir Documentation/ctlv/specs list
>
> # Inspect the root-family lineage and attestation capabilities.
> ctlvctl --spec-dir Documentation/ctlv/specs query ctlv0 families-dump
> ctlvctl --spec-dir Documentation/ctlv/specs query ctlv0 device-get
>
> # List the measurement registers of the attestation device.
> ctlvctl --spec-dir Documentation/ctlv/specs query ctlv0 mr-dump
>
> # Read RTMR0 (MR 1).
> ctlvctl --spec-dir Documentation/ctlv/specs \
> query ctlv0 mr-read mr.id=1
>
> # Extend RTMR0 with 48 zero bytes (SHA-384).
> digest=$(printf '%096d' 0)
> ctlvctl --spec-dir Documentation/ctlv/specs \
> exec ctlv0 mr-extend --json \
> '{"id":1,"value":[{"hash-algo":5,"data":"hex:'"$digest"'"}]}'
>
> # Retrieve the RTMR0 journal.
> ctlvctl --spec-dir Documentation/ctlv/specs \
> query ctlv0 mr-log-get --json '{"selection":[{"id":1}]}'
>
> The 20 patches are organized as follows:
>
> 1-13 add CTLV, its generator and documentation
> 14-17 add the base attestation family and implementation
> 18-20 expose the native TDX service and add the TDX family
>
> Note to reviewers: Please focus on the overall design, UAPI and internal
> attestation-provider API. The internal CTLV and attestation implementations
> remain rough around the edges.
>

< --snip-->

Hi Jiri,

I built a proof of concept on your wip_attestation_all_1 branch with help of LLM
for TDX Connect device attestation to see how it holds up, and wanted to share
what I found.

What I built:

- A ctlv-attestation-spdm family. As you proposed, the sysfs lock creates the
assigned-TDI attestation instance; in my version unlock removes it.
EVIDENCE_GET, EVIDENCE_ACCEPT, DEVSEC_STATE_GET and DEVSEC_RUN all go through
your ops.

- A provider (a 'struct attestation_provider') that sits on top of PCI/TSM, so
any backend can use it. It serves the evidence PCI/TSM already publishes, and
asks the backend for a refresh when EVIDENCE_GET carries a challenge.

I ran it in QEMU with the devsec sample TSM, and in a TDX Connect guest on real
hardware with a real device. Both worked: lock, EVIDENCE_GET, DEVSEC_RUN
refusing a device without a verdict, RUN, and unlock. My tree has out of tree
code for TDX Connect to get things working.

The model works well for device attestation. A few things came up that I think
worth a look.

1. EVIDENCE_ACCEPT and DEVSEC_RUN take any evidence generation since the last
device change (attestation_evidence_validate()). Ranges would narrow that,
but they need measurement registers (attestation_ranges_required()), and a
device has none. So a verdict survives a refresh. In QEMU with the devsec
sample:

EVIDENCE_GET, empty challenge -> evidence generation 1
EVIDENCE_GET, challenge c1 -> generation 2, measurements changed
EVIDENCE_ACCEPT generation 1 -> ok
EVIDENCE_GET, challenge c2 -> generation 3, measurements changed
DEVSEC_RUN generation 1 -> ok, device in RUN

Any process that can issue EVIDENCE_GET can do this between the appraisal and
DEVSEC_RUN, and the device runs on evidence nobody looked at.

Combining EVIDENCE_ACCEPT and DEVSEC_RUN, as you and Aneesh discussed, makes
sense to me, but it doesn't completely resolve this. The combined op would
still take generation 1 and run the device directly.

Only the provider knows a refresh replaced the evidence. It can't tell the
core from inside an op, because attestation_state_changed() takes the lock
ctlv_attestation_op_pre() already holds. How about a helper the provider
calls after a refresh? With it, the old generation gets -ESTALE:

/* core.c, next to attestation_state_changed() */
void attestation_evidence_replaced(struct attestation *att)
{
lockdep_assert_held(&att->lock);
attestation_evidence_accept_clear_locked(att);
attestation_evidence_ranges_clear(att, false);
att->evidence_floor = att->evidence_generation;
att->devsec_version++;
}
EXPORT_SYMBOL_NS_GPL(attestation_evidence_replaced, "ATTESTATION");

/* the provider's evidence_get */
if (request->challenge_length) {
rc = pci_tsm_attest_refresh(pdev, request->challenge,
request->challenge_length);
if (rc)
goto out;
attestation_evidence_replaced(att);
}

2. TDX Connect (maybe others) does not raise an event when a device drops out of
RUN. The provider only finds out when it asks, and on TDX even that answer
can lag. The guest reads the state the TDX Module last saw in a TDISP
response. A function level reset, for example, moves a locked TDI to ERROR,
but the guest keeps reading CONFIG_LOCKED until the TDX Module sees another
TDISP response, such as when the host asks the device for its state.

So the provider learns about a change inside an op, where the same lock keeps
it from calling attestation_state_changed(). DEVSEC_RUN doesn't check the
state itself either in ctlv_attestation_devsec_run_action().

Could the core add a version of attestation_state_changed() that a provider
can call while the op already holds the lock? Or let the provider flag the
change, and have the core send the notification once the op drops the lock?

3. Jason asked on 14/20 whether anyone needs a variable-length challenge. I
don't think devices need a range. A plain read sends an empty challenge, and
an SPDM GET_MEASUREMENTS nonce is "NL = 32 or 0". Your TDX provider always
takes 64 (attestation_tdx_evidence_caps).

A device needs exactly two lengths: 0 for a plain read and 32 for a refresh.
struct attestation_evidence_caps only holds a single min/max range. It's
checked on every request in attestation_evidence_request_decode(), and any
range that allows both 0 and 32 also allows everything in between.

To work around that, my provider declares 0-64 and PCI/TSM rejects any length
other than the backend's nonce size, 32 for both backends I ran. Could the
caps give one exact challenge length instead of a range, plus a flag that
also allows an empty challenge? A device provider would set 32 with the flag,
and your TDX provider 64 without it.

4. EVIDENCE_GET rejects any buffer smaller than max_length
(attestation_evidence_decode_objects()), so userspace has to allocate for the
worst case. Aneesh's provider declares 16M per certificate chain, so every
read needs a 16M buffer. Could EVIDENCE_GET take a smaller buffer and return
the length it needs, or offer a way to ask for the size?

5. evidence.challenge is a required attr of evidence-request, so a plain read
has to send an empty one ("hex:" in Aneesh's example). Could it be optional?

6. tdx_attestation_test's discovery_and_construction_fields expects an
inheritance chain of 4, but the TDX family inherits ctlv-attestation
directly, which gives 3. In a real TDX guest it fails with
"Expected 4 (4) == ctlv_chain_len(self->dev) (3)".