* [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support
@ 2026-11-13 16:08 Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files Jiri Pirko
` (8 more replies)
0 siblings, 9 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Linux has several attestation interfaces, each tied to one technology.
Extending a measurement takes device-specific flows for TPM, CCA, TDX
and SEV, none of them discoverable, and requesting a signed quote takes
another set, SPDM included. A new root of trust has no logical home
short of squeezing it into tsm_report and tsm_mr: tsm_report covers
reports alone, the MRs went to tsm_mr, and SPDM fits neither.
This series adds a common attestation subsystem. Its goal is to support
platform, TEE and device roots of trust through one model, including
TPM2, TDX, SEV-SNP, Arm CCA, FIRME, Caliptra and PCI/TSM-backed
devices, so that a program extends a register or asks for evidence
without knowing the hardware behind it. This first series implements
the common subsystem and a TDX provider. The native TDX interfaces,
/dev/tdx_guest, tsm_report and tsm_mr, stay as they are.
RFC v1 [1] posted this and CTLV as one patchset. For convenience, v2
splits it in two, and this series applies on top of the CTLV series,
posted alongside as
[PATCH RFC v2 00/15] ctlv: Add a schema-driven TLV uAPI for character devices
Each attestation device has a node, /dev/attestation/attestationN,
that speaks CTLV: a request is checked against the family's schema and
executed at once, rather than assembled through several files. The
base family, ctlv-attestation, has what every provider shares, so a
tool written against it works with any provider. TDX's family,
ctlv-attestation-tdx, inherits it and adds no ops. It fills the base
family's extension points: device-get's info with the TD's MRCONFIGID,
MROWNER and MROWNERCONFIG, and evidence-vendor-format with the two TDX
Quote formats.
The base family's ops, as ctlvgen --signatures prints them, with the
purpose of each:
# What the device supports, fixed for its life, and its family's fields.
device-get() ->
{ caps: { evidence?: { objects[16]: { id, class, formats[4]?,
vendor-formats[4]?, max-length,
challenge-lengths[8]?: { length?,
min-length?,
max-length? } } },
mr?: { flags } }, info: device-info }
# Evidence: each object named, written to a blob the caller supplies,
# a signed one made by the call over the challenge.
evidence-get(challenge?, objects[16]: { id, data: out blob }) ->
{ results[16]: { format?, vendor-format?, length }, mr-generation? }
# Describe every MR, or the one an ID names.
mr-get(id?) -> { items[]: { id, mr-desc } }
# One MR's values, a digest per bank, and the MR generation they
# reflect.
mr-read(id) -> { mr-generation, mr-values[hash-algo]: mr-value }
# Extend banks of one MR by the caller's digests.
mr-extend(id, mr-values[hash-algo]: mr-value) -> { mr-generation }
An mr-desc is an MR's name, its banks, what it allows and its register
index, and an mr-value one bank's hash algorithm and digest. The
userspace ABI gives each op's request, reply, errors and access.
On TDX, evidence-get returns the Quote over a 64-byte challenge,
labelled a legacy or a DICE one, and, where ACPI describes it, the
CCEL event log. The MRs are MRTD, which cannot be extended, and RTMR0
to RTMR3, all SHA-384. With ctlvctl from the CTLV series and
CTLV_SPEC_DIR pointing at Documentation/ctlv/specs, the simulator's
output, rewritten for a TD, its lengths left out:
# The device and its family chain.
$ ctlvctl list
attestation/attestation0 ctlv/ctlv-attestation/ctlv-attestation-tdx
# What it supports.
$ ctlvctl exec attestation/attestation0 device-get
# device-get, 1 fragment, generation 0
device-get caps
device-get caps.evidence
device-get caps.evidence.objects
device-get caps.evidence.objects.id 0
device-get caps.evidence.objects.class signed
device-get caps.evidence.objects.vendor-formats quote
device-get caps.evidence.objects.vendor-formats quote-dice
device-get caps.evidence.objects.max-length 131048
device-get caps.evidence.objects.challenge-lengths
device-get caps.evidence.objects.challenge-lengths.length 64
device-get caps.evidence.objects
device-get caps.evidence.objects.id 1
device-get caps.evidence.objects.class event-log
device-get caps.evidence.objects.formats ccel
device-get caps.evidence.objects.max-length ...
device-get caps.mr
device-get caps.mr.flags extend
device-get info
device-info.mrconfigid hex:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
device-info.mrowner hex:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
device-info.mrownerconfig hex:000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
# A Quote over a 64-byte nonce, written to quote.bin.
$ nonce=$(openssl rand -hex 64)
$ ctlvctl exec attestation/attestation0 evidence-get --json \
'{"challenge":"hex:'"$nonce"'",
"objects":[{"id":0,"data":{"file":"quote.bin"}}]}'
# evidence-get, 1 fragment, generation 0
mr-generation 0
evidence-get results
evidence-get results.vendor-format quote
evidence-get results.length ...
# Every MR.
$ ctlvctl exec attestation/attestation0 mr-get
# mr-get, 1 fragment, generation 0
mr-get items
mr-get items.id 0
mr-desc.name mrtd
mr-desc.supported-hash-algorithms HASH_ALGO_SHA384
mr-desc.caps 0
mr-get items
mr-get items.id 1
mr-desc.name rtmr0
mr-desc.supported-hash-algorithms HASH_ALGO_SHA384
mr-desc.register-index 0
mr-desc.caps extend
...
# The value of RTMR0, MR 1.
$ ctlvctl exec attestation/attestation0 mr-read id=1
# mr-read, 1 fragment, generation 0
mr-generation 0
mr-values
mr-value.hash-algo HASH_ALGO_SHA384
mr-value.data hex:b57a300eac0c9b8593bafbadee812f8915456913c28010dab20d700446169bdd56e1af87ce94d66c8fd2d456b3e42826
# Extend RTMR3, MR 4, by a file's SHA-384 digest.
$ digest=$(sha384sum app.img | cut -d' ' -f1)
$ ctlvctl exec attestation/attestation0 mr-extend --json \
'{"id":4,"mr-values":[{"hash-algo":"HASH_ALGO_SHA384",
"data":"hex:'"$digest"'"}]}'
# mr-extend, 1 fragment, generation 0
mr-generation 1
This series is the first part. The branch [2] has the whole work,
building on it: CTLV's events, the MR journal with IMA as its first
user, memfd and dma-buf evidence blobs, an mr-extend that hashes the
caller's data, evidence acceptance and device security, a simulation
provider, KUnit tests and selftests.
The series adds 3480 lines:
attestation core, C 1340
generated files 770
TDX provider, C 470
docs 462
family specifications 206
ABI snapshots 170
registry, build 62
Changes since RFC v1, each patch listing its own:
- Each evidence object has an ID, a class, signed or event-log, and
the formats it may have: from evidence-format for a layout a
standard defines, from evidence-vendor-format, whose values the
device's family gives, for a vendor's. A signed object lists the
challenge lengths it takes.
- evidence-get names objects by ID, may omit the challenge, and takes
output blobs of any length, failing with the length one needs.
- TDX labels each Quote, returns the CCEL log, and leaves the
TDREPORT to /dev/tdx_guest.
- The MR journal and its notifications are left out, until IMA uses
the subsystem.
- mr-extend may extend some of an MR's banks, and every MR can be
read.
- mr-get, a query, describes every MR or the one its optional id
names, and mr-dump is gone.
- Attestation has its own class and /dev/attestation/ nodes, and the
TDX provider is part of tdx-guest instead of a module of its own.
- The specs are written as op trees and records, with a comment on
every item, and a userspace ABI document describes the family.
The patches:
1 the base family's specification and generated files
2 the userspace ABI
3-5 the provider and instance core, the MRs, and the CTLV ops
6 how to write a provider
7 tdx-guest's native attestation service, for the provider
8-9 the TDX family's specification and implementation
Note to reviewers: the uAPI, in 1 and 2, and the provider API, in 3
and 6, matter most at this stage.
[1] https://lore.kernel.org/all/20261015145053.3664509-1-jiri@resnulli.us/
[2] https://github.com/jpirko/linux_mlxsw/commits/wip_attestation_all_2
Jiri Pirko (9):
attestation: Add the base family specification and generated files
Documentation: attestation: Add userspace ABI
attestation: Prepare provider and instance core
attestation: Prepare measurement registers
attestation: Add CTLV operation handling
Documentation: attestation: Describe how to write a provider
tdx-guest: Expose the native attestation service API
attestation: Add the TDX family specification and generated files
attestation: Add the TDX family implementation
.../ctlv/abi/ctlv-attestation-tdx.yaml | 21 +
Documentation/ctlv/abi/ctlv-attestation.yaml | 147 +++++
Documentation/ctlv/abi/families.registry.yaml | 2 +
Documentation/ctlv/families.yaml | 2 +
Documentation/ctlv/generated-files.yaml | 14 +
.../ctlv/specs/ctlv-attestation-tdx.yaml | 25 +
.../ctlv/specs/ctlv-attestation.yaml | 181 +++++++
Documentation/driver-api/attestation.rst | 99 ++++
Documentation/driver-api/index.rst | 1 +
.../userspace-api/attestation-tdx.rst | 59 +++
Documentation/userspace-api/attestation.rst | 294 ++++++++++
Documentation/userspace-api/index.rst | 1 +
Documentation/virt/coco/tdx-guest.rst | 8 +
MAINTAINERS | 13 +
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/attestation/Kconfig | 16 +
drivers/attestation/Makefile | 5 +
drivers/attestation/core/core.c | 500 ++++++++++++++++++
drivers/attestation/core/ctlv.c | 499 +++++++++++++++++
drivers/attestation/core/internal.h | 34 ++
drivers/attestation/core/mr.c | 64 +++
.../generated/ctlv_attestation_gen.c | 179 +++++++
.../generated/ctlv_attestation_gen.h | 204 +++++++
drivers/virt/coco/tdx-guest/Kconfig | 4 +-
drivers/virt/coco/tdx-guest/Makefile | 6 +
drivers/virt/coco/tdx-guest/attestation.c | 313 +++++++++++
.../coco/tdx-guest/ctlv_attestation_tdx_gen.c | 65 +++
.../coco/tdx-guest/ctlv_attestation_tdx_gen.h | 44 ++
drivers/virt/coco/tdx-guest/internal.h | 48 ++
.../coco/tdx-guest/{tdx-guest.c => main.c} | 137 ++++-
include/linux/attestation.h | 243 +++++++++
include/uapi/ctlv/ctlv_attestation_gen.h | 234 ++++++++
include/uapi/ctlv/ctlv_attestation_tdx_gen.h | 42 ++
include/uapi/ctlv/ctlv_families_gen.h | 2 +
35 files changed, 3480 insertions(+), 29 deletions(-)
create mode 100644 Documentation/ctlv/abi/ctlv-attestation-tdx.yaml
create mode 100644 Documentation/ctlv/abi/ctlv-attestation.yaml
create mode 100644 Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
create mode 100644 Documentation/ctlv/specs/ctlv-attestation.yaml
create mode 100644 Documentation/driver-api/attestation.rst
create mode 100644 Documentation/userspace-api/attestation-tdx.rst
create mode 100644 Documentation/userspace-api/attestation.rst
create mode 100644 drivers/attestation/Kconfig
create mode 100644 drivers/attestation/Makefile
create mode 100644 drivers/attestation/core/core.c
create mode 100644 drivers/attestation/core/ctlv.c
create mode 100644 drivers/attestation/core/internal.h
create mode 100644 drivers/attestation/core/mr.c
create mode 100644 drivers/attestation/generated/ctlv_attestation_gen.c
create mode 100644 drivers/attestation/generated/ctlv_attestation_gen.h
create mode 100644 drivers/virt/coco/tdx-guest/attestation.c
create mode 100644 drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
create mode 100644 drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
create mode 100644 drivers/virt/coco/tdx-guest/internal.h
rename drivers/virt/coco/tdx-guest/{tdx-guest.c => main.c} (79%)
create mode 100644 include/linux/attestation.h
create mode 100644 include/uapi/ctlv/ctlv_attestation_gen.h
create mode 100644 include/uapi/ctlv/ctlv_attestation_tdx_gen.h
--
2.56.0
^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 2/9] Documentation: attestation: Add userspace ABI Jiri Pirko
` (7 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Define the base attestation family. Reserve its family ID and add the
ABI snapshot, registry entry and generated kernel and UAPI files.
The ABI is intentionally specification-first. The generated descriptor
is linked after the provider and operation handlers arrive.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- dropped the MR log ops and the sets, groups and attrs only they use
- dropped the family's priv lifecycle hooks
- made the hash algorithm attrs an enum of <linux/hash_info.h>
- replaced the blob class and representation with an evidence class,
signed or event-log
- dropped max-total-output-length
- renamed stubs to extension points
- commented the values, operations and fields
- wrote the spec as op trees and records
- reported each signed object's challenge lengths, in bytes, as a list
of up to 8 lengths and ranges
- let evidence-get omit the challenge
- took output blobs of any length
- merged mr-dump into mr-get, a query whose id is an optional filter
- listed an MR's banks in the provider's order, each once
- let mr-extend extend some of an MR's banks
- dropped the MR read caps, as every MR can be read
- numbered every enum member and flag bit
- moved the generated UAPI headers to include/uapi/ctlv/
- documented every item of the spec
- put each field's doc above its generated helpers
- said that every evidence-get produces new evidence
- replaced the format strings with the evidence-format enum and the
evidence-vendor-format enum extension point
- named an evidence-get object by its ID
- said message instead of wire
- bounded an MR's banks by hash-algo, not a number
---
Documentation/ctlv/abi/ctlv-attestation.yaml | 147 +++++++++++
Documentation/ctlv/abi/families.registry.yaml | 1 +
Documentation/ctlv/families.yaml | 1 +
Documentation/ctlv/generated-files.yaml | 7 +
.../ctlv/specs/ctlv-attestation.yaml | 181 ++++++++++++++
MAINTAINERS | 9 +
.../generated/ctlv_attestation_gen.c | 179 ++++++++++++++
.../generated/ctlv_attestation_gen.h | 204 +++++++++++++++
include/uapi/ctlv/ctlv_attestation_gen.h | 234 ++++++++++++++++++
include/uapi/ctlv/ctlv_families_gen.h | 1 +
10 files changed, 964 insertions(+)
create mode 100644 Documentation/ctlv/abi/ctlv-attestation.yaml
create mode 100644 Documentation/ctlv/specs/ctlv-attestation.yaml
create mode 100644 drivers/attestation/generated/ctlv_attestation_gen.c
create mode 100644 drivers/attestation/generated/ctlv_attestation_gen.h
create mode 100644 include/uapi/ctlv/ctlv_attestation_gen.h
diff --git a/Documentation/ctlv/abi/ctlv-attestation.yaml b/Documentation/ctlv/abi/ctlv-attestation.yaml
new file mode 100644
index 000000000000..16b7be1fbc70
--- /dev/null
+++ b/Documentation/ctlv/abi/ctlv-attestation.yaml
@@ -0,0 +1,147 @@
+# 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 family, from
+# Documentation/ctlv/specs/ctlv-attestation.yaml, written by
+# "make -C tools/ctlv regen".
+#
+# Updating the file is how a UAPI change gets reviewed; an update that
+# removes or renumbers anything, narrows a bound, or makes a member required
+# is an ABI break, whatever the file says afterwards.
+family: {name: ctlv-attestation, id: 1, inherits: ctlv}
+enums:
+ evidence-class: {signed: 1, event-log: 2}
+ evidence-format: {ccel: 1}
+ hash-algo: {$header: linux/hash_info.h, $enum: hash_algo,
+ $end: HASH_ALGO__LAST}
+flags:
+ mr-device-caps: {extend: 0}
+ mr-caps: {extend: 0}
+records:
+ mr-desc:
+ id: 0x00010000
+ fields:
+ name: {number: 1, type: string, min-length: 1, max-length: 128}
+ supported-hash-algorithms: {number: 2, enum: hash-algo,
+ max-occurrences: hash-algo}
+ caps: {number: 3, flags: mr-caps}
+ register-index: {number: 4, type: uint, max: u32-max, optional: true}
+ mr-value:
+ id: 0x00020000
+ fields:
+ hash-algo: {number: 1, enum: hash-algo}
+ data: {number: 2, type: binary, min-length: 16, max-length: 64}
+ mr-generation:
+ id: 0x00030000
+ fields:
+ mr-generation: {number: 1, type: uint, max: u32-max}
+ mr-values:
+ id: 0x00040000
+ fields:
+ mr-values: {number: 1, record: mr-value, max-occurrences: hash-algo}
+ device-get request:
+ id: 0x80010000
+ device-get reply:
+ id: 0xc0010000
+ fields:
+ caps: {number: 1, record: device-get caps}
+ info: {number: 2, extension: device-info}
+ device-get caps:
+ id: 0xc0011000
+ fields:
+ evidence: {number: 1, record: device-get caps.evidence, optional: true}
+ mr: {number: 2, record: device-get caps.mr, optional: true}
+ device-get caps.evidence:
+ id: 0xc0011100
+ fields:
+ objects: {number: 1, record: device-get caps.evidence.objects,
+ max-occurrences: 16}
+ device-get caps.evidence.objects:
+ id: 0xc0011110
+ fields:
+ id: {number: 1, type: uint, max: 15}
+ class: {number: 2, enum: evidence-class}
+ formats: {number: 3, enum: evidence-format, max-occurrences: 4,
+ optional: true}
+ vendor-formats: {number: 4, enum: evidence-vendor-format,
+ max-occurrences: 4, optional: true}
+ max-length: {number: 5, type: uint}
+ challenge-lengths:
+ {number: 6, record: device-get caps.evidence.objects.challenge-lengths,
+ max-occurrences: 8, optional: true}
+ device-get caps.evidence.objects.challenge-lengths:
+ id: 0xc0011111
+ fields:
+ length: {number: 1, type: uint, min: 1, max: 4K, unit: bytes,
+ optional: true}
+ min-length: {number: 2, type: uint, min: 1, max: 4K, unit: bytes,
+ optional: true}
+ max-length: {number: 3, type: uint, min: 1, max: 4K, unit: bytes,
+ optional: true}
+ device-get caps.mr:
+ id: 0xc0011200
+ fields:
+ flags: {number: 1, flags: mr-device-caps}
+ evidence-get request:
+ id: 0x80020000
+ fields:
+ challenge: {number: 1, type: binary, min-length: 1, max-length: 4K,
+ optional: true}
+ objects: {number: 2, record: evidence-get objects, max-occurrences: 16}
+ evidence-get objects:
+ id: 0x80021000
+ fields:
+ id: {number: 1, type: uint, max: 15}
+ data: {number: 2, blob: output, backings: [va], max-length: u32-max}
+ evidence-get reply:
+ id: 0xc0020000
+ fields:
+ results: {number: 1, record: evidence-get results, max-occurrences: 16}
+ members: ['record mr-generation?']
+ evidence-get results:
+ id: 0xc0021000
+ fields:
+ format: {number: 1, enum: evidence-format, optional: true}
+ vendor-format: {number: 2, enum: evidence-vendor-format, optional: true}
+ length: {number: 3, type: uint}
+ mr-get request:
+ id: 0x80030000
+ fields:
+ id: {number: 1, type: uint, max: u32-max, optional: true}
+ mr-get reply:
+ id: 0xc0030000
+ fields:
+ items: {number: 1, record: mr-get items, max-occurrences: unlimited}
+ mr-get items:
+ id: 0xc0031000
+ fields:
+ id: {number: 1, type: uint, max: u32-max}
+ members: [$include mr-desc]
+ mr-read request:
+ id: 0x80040000
+ fields:
+ id: {number: 1, type: uint, max: u32-max}
+ mr-read reply:
+ id: 0xc0040000
+ members: [record mr-generation, record mr-values]
+ mr-extend request:
+ id: 0x80050000
+ fields:
+ id: {number: 1, type: uint, max: u32-max}
+ members: [record mr-values]
+ mr-extend reply:
+ id: 0xc0050000
+ members: [record mr-generation]
+extensions: {device-info: 1}
+enum-extensions: {evidence-vendor-format: 1}
+ops:
+ device-get: {id: 1, type: action, fd-access: read,
+ request: device-get request, reply: device-get reply}
+ evidence-get: {id: 2, type: action, availability: dynamic, optional: true,
+ request: evidence-get request, reply: evidence-get reply}
+ mr-get: {id: 3, type: query, fd-access: read, optional: true,
+ request: mr-get request, reply: mr-get reply}
+ mr-read: {id: 4, type: query, fd-access: read, optional: true,
+ request: mr-read request, reply: mr-read reply}
+ mr-extend: {id: 5, type: action, optional: true, request: mr-extend request,
+ reply: mr-extend reply}
diff --git a/Documentation/ctlv/abi/families.registry.yaml b/Documentation/ctlv/abi/families.registry.yaml
index 5845e7457771..7a5c6af29565 100644
--- a/Documentation/ctlv/abi/families.registry.yaml
+++ b/Documentation/ctlv/abi/families.registry.yaml
@@ -5,3 +5,4 @@
# Written by "make -C tools/ctlv regen".
families:
- ctlv
+ - ctlv-attestation
diff --git a/Documentation/ctlv/families.yaml b/Documentation/ctlv/families.yaml
index 4d219d7af74a..dbb0243b873d 100644
--- a/Documentation/ctlv/families.yaml
+++ b/Documentation/ctlv/families.yaml
@@ -15,3 +15,4 @@
families:
- ctlv
+ - ctlv-attestation
diff --git a/Documentation/ctlv/generated-files.yaml b/Documentation/ctlv/generated-files.yaml
index d4766b176ceb..00a56634805d 100644
--- a/Documentation/ctlv/generated-files.yaml
+++ b/Documentation/ctlv/generated-files.yaml
@@ -16,3 +16,10 @@ families:
kernel-header: include/linux/ctlv_gen.h
kernel-source: lib/ctlv/ctlv_gen.c
user-source: tools/ctlv/libctlv/ctlv_user_gen.c
+
+ - name: ctlv-attestation
+ source: Documentation/ctlv/specs/ctlv-attestation.yaml
+ outputs:
+ uapi-header: include/uapi/ctlv/ctlv_attestation_gen.h
+ kernel-header: drivers/attestation/generated/ctlv_attestation_gen.h
+ kernel-source: drivers/attestation/generated/ctlv_attestation_gen.c
diff --git a/Documentation/ctlv/specs/ctlv-attestation.yaml b/Documentation/ctlv/specs/ctlv-attestation.yaml
new file mode 100644
index 000000000000..78b2dad6318a
--- /dev/null
+++ b/Documentation/ctlv/specs/ctlv-attestation.yaml
@@ -0,0 +1,181 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+# Attestation devices: evidence and measurement registers (MRs). The device's
+# family inherits this one, fills device-info and gives evidence-vendor-format
+# the formats its vendor defines.
+family: ctlv-attestation
+inherits: ctlv
+
+enums:
+ # What an evidence object is.
+ evidence-class:
+ # Made by evidence-get over its challenge, and signed by the root of
+ # trust.
+ - signed
+ # The platform's log of what firmware and the boot chain measured into
+ # the MRs. Later extends, through mr-extend or otherwise, are not in it.
+ - event-log
+ # A layout of an evidence object that a standard defines.
+ evidence-format:
+ # The log area of an ACPI CCEL table, a TCG crypto-agile event log.
+ - ccel
+ # A layout of an evidence object that one vendor defines, given by the
+ # device's family.
+ evidence-vendor-format: extension
+ # The kernel's hash algorithms.
+ hash-algo:
+ $header: linux/hash_info.h
+ $enum: hash_algo
+ $end: HASH_ALGO__LAST
+
+flags:
+ # What a device offers for its MRs.
+ mr-device-caps:
+ # mr-extend is provided.
+ - extend
+ # What one MR allows.
+ mr-caps:
+ # The MR may be extended.
+ - extend
+
+records:
+ # One MR, as mr-get describes it.
+ mr-desc:
+ # The platform's name for the MR, such as rtmr0.
+ name: string(1..128)
+ # Its banks, each once, in the provider's order.
+ supported-hash-algorithms[hash-algo]: hash-algo
+ # What it allows.
+ caps: mr-caps
+ # The hardware's number for the MR, where it has one: 0 for rtmr0.
+ register-index?: uint(..u32-max)
+
+ # One bank of an MR.
+ mr-value:
+ # The bank's algorithm.
+ hash-algo: hash-algo
+ # The digest, as long as hash-algo's.
+ data: binary(16..64)
+
+ # The device's MR generation, which each extend bumps.
+ mr-generation: uint(..u32-max)
+
+ # Values for banks of the MR, each bank once.
+ mr-values[hash-algo]: record mr-value
+
+ops:
+ # What the device can do, and its family's fields.
+ device-get:
+ type: action
+ fd-access: read
+ reply:
+ # What the device supports, fixed for its life.
+ caps:
+ # Present when the device produces evidence.
+ evidence?:
+ # One per object evidence-get can produce.
+ objects[16]:
+ # The device's handle for the object in requests.
+ id: uint(..15)
+ # What the object is.
+ class: evidence-class
+ # The layouts a standard defines that the object may have. At
+ # least one of formats and vendor-formats is present.
+ formats[4]?: evidence-format
+ # The layouts its vendor defines that the object may have.
+ vendor-formats[4]?: evidence-vendor-format
+ # The longest the object gets; a blob this long always holds it.
+ max-length: uint
+ # A signed object's challenge lengths: each entry is one length,
+ # or a range from min-length to max-length.
+ challenge-lengths[8]?:
+ # A single length.
+ length?: uint(1..4K, bytes)
+ # The shortest of a range.
+ min-length?: uint(1..4K, bytes)
+ # The longest of a range.
+ max-length?: uint(1..4K, bytes)
+ # Present when the device has MRs.
+ mr?:
+ # The MR operations the device provides.
+ flags: mr-device-caps
+ # The device's family's fields.
+ info: extension device-info
+
+ # Evidence objects, written into the caller's blobs. A signed object is made
+ # by the call, over the challenge, and never reused; the others are read as
+ # they stand at the call.
+ evidence-get:
+ type: action
+ fd-access: read-write
+ availability: dynamic
+ optional: true
+ request:
+ # Bound into each signed object named: pass a verifier's nonce, or a hash
+ # binding one to your context, never data a peer chose. Present exactly
+ # when a signed object is named, and of a length each of them takes.
+ challenge?: binary(1..4K)
+ # The objects to produce, each named once.
+ objects[16]:
+ # The object, by the ID device-get lists.
+ id: uint(..15)
+ # Where the object is written. One too short for it fails the op with
+ # EMSGSIZE, and error-info's required-length says how long it has to
+ # be.
+ data: blob(out, va)
+ reply:
+ # One per object, in request order.
+ results[16]:
+ # The layout it came in, where a standard defines it. Exactly one of
+ # format and vendor-format is present.
+ format?: evidence-format
+ # The layout it came in, where its vendor defines it.
+ vendor-format?: evidence-vendor-format
+ # The bytes written to its blob.
+ length: uint
+ # Present when the evidence depends on the MRs.
+ mr-generation?: record
+
+ # The MRs' IDs and descriptions, which do not change; mr-read returns
+ # values, which change with every extension and are read from the hardware.
+ mr-get:
+ type: query
+ fd-access: read
+ optional: true
+ suggested-reply-length: 4K
+ request:
+ # Only this MR; every MR without it.
+ id?: uint(..u32-max)
+ reply:
+ # One per MR, ordered by ID.
+ items[]:
+ # The device's handle for the MR in requests.
+ id: uint(..u32-max)
+ $include: mr-desc
+
+ # One MR's values, and the MR generation they belong to.
+ mr-read:
+ type: query
+ fd-access: read
+ optional: true
+ suggested-reply-length: 4K
+ request:
+ # The MR to read, by the ID mr-get lists.
+ id: uint(..u32-max)
+ reply:
+ mr-generation: record
+ mr-values: record
+
+ # Extends banks of the MR by digests, as TPM2_PCR_Extend.
+ mr-extend:
+ type: action
+ fd-access: read-write
+ optional: true
+ request:
+ # The MR to extend, by the ID mr-get lists.
+ id: uint(..u32-max)
+ # For the banks to extend, each once, in any order.
+ mr-values: record
+ reply:
+ mr-generation: record
diff --git a/MAINTAINERS b/MAINTAINERS
index b16bedbbe0b6..fa450924b921 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4433,6 +4433,15 @@ F: rust/kernel/sync/atomic.rs
F: rust/kernel/sync/atomic/
F: rust/kernel/sync/refcount.rs
+ATTESTATION SUBSYSTEM
+M: Jiri Pirko <jiri@nvidia.com>
+L: linux-security-module@vger.kernel.org
+S: Supported
+F: Documentation/ctlv/abi/ctlv-attestation*
+F: Documentation/ctlv/specs/ctlv-attestation*
+F: drivers/attestation/
+F: include/uapi/ctlv/ctlv_attestation*
+
ATTO EXPRESSSAS SAS/SATA RAID SCSI DRIVER
M: Bradley Grove <linuxdrivers@attotech.com>
L: linux-scsi@vger.kernel.org
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.c b/drivers/attestation/generated/ctlv_attestation_gen.c
new file mode 100644
index 000000000000..e1c759d6fd8a
--- /dev/null
+++ b/drivers/attestation/generated/ctlv_attestation_gen.c
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN kernel source */
+
+#define _CTLV_GENERATED_METADATA_ONLY
+#include <linux/array_size.h>
+#include <linux/bits.h>
+#include <linux/build_bug.h>
+#include <linux/ctlv_family.h>
+#include <linux/export.h>
+#include <linux/limits.h>
+#include <linux/sizes.h>
+
+#include <uapi/ctlv/ctlv_gen.h>
+#include <uapi/ctlv/ctlv_attestation_gen.h>
+
+#include "ctlv_attestation_gen.h"
+
+static const struct ctlv_node_schema device_get_request_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(0, 0, 0),
+};
+
+static const struct ctlv_node_schema device_get_reply_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_CAPS, 1, 3, 2, 0),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_INFO, 1, 0, 0, 0),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_CAPS_EVIDENCE, 1, 5, 1, 1),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_CAPS_MR, 1, 6, 1, 1),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_EVIDENCE_OBJECTS, 16, 7, 6, 3),
+ CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_T_DEVICE_GET_MR_FLAGS, 1,
+ CTLV_ATTESTATION_MR_DEVICE_CAPS_MASK),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_ID, 1, 0, 15),
+ CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CLASS, 1, 2),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_MAX_LENGTH, 1, 0, U64_MAX),
+ CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_FORMATS, 4, 1),
+ CTLV_NODE_SCHEMA_ENUM_EXT(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_VENDOR_FORMATS, 4,
+ CTLV_FAMILY_ATTESTATION,
+ CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CHALLENGE_LENGTHS, 8, 13, 3,
+ 0),
+ CTLV_NODE_SCHEMA_UINT_UNIT(CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH, 1, 1,
+ 4096, CTLV_UNIT_BYTES, 0),
+ CTLV_NODE_SCHEMA_UINT_UNIT(CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH, 1, 1,
+ 4096, CTLV_UNIT_BYTES, 0),
+ CTLV_NODE_SCHEMA_UINT_UNIT(CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH, 1, 1,
+ 4096, CTLV_UNIT_BYTES, 0),
+};
+
+/* Extension points selected by this tree. */
+static const struct ctlv_ext_schema device_get_reply_ext_schemas[] = {
+ CTLV_EXT_SCHEMA(CTLV_FAMILY_ATTESTATION, CTLV_ATTESTATION_EXT_DEVICE_INFO, 2),
+};
+
+static const struct ctlv_node_schema evidence_get_request_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 2, 1),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS, 16, 3, 2, 2),
+ CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_T_EVIDENCE_GET_CHALLENGE, 1, 1, SZ_4K),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_ID, 1, 0, 15),
+ CTLV_NODE_SCHEMA_BLOB(CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_DATA, 1, U32_MAX, 1,
+ BIT(CTLV_BLOB_TYPE_VA),
+ CTLV_BLOB_DIRECTION_OUTPUT),
+};
+
+static const struct ctlv_node_schema evidence_get_reply_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 2, 1),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS, 16, 3, 3, 1),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GENERATION, 1, 0, U32_MAX),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_LENGTH, 1, 0, U64_MAX),
+ CTLV_NODE_SCHEMA_ENUM(CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_FORMAT, 1, 1),
+ CTLV_NODE_SCHEMA_ENUM_EXT(CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_VENDOR_FORMAT, 1,
+ CTLV_FAMILY_ATTESTATION,
+ CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT),
+};
+
+static const struct ctlv_node_schema mr_get_request_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 1, 0),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GET_ID, 1, 0, U32_MAX),
+};
+
+static const struct ctlv_node_schema mr_get_reply_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_MR_GET_ITEMS, CTLV_NODE_SCHEMA_UNLIMITED, 2, 5,
+ 4),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GET_ITEMS_ID, 1, 0, U32_MAX),
+ CTLV_NODE_SCHEMA_STRING(CTLV_ATTESTATION_T_MR_DESC_NAME, 1, 1, 128),
+ CTLV_NODE_SCHEMA_ENUM_RANGE(CTLV_ATTESTATION_T_MR_DESC_SUPPORTED_HASH_ALGORITHMS, 23,
+ HASH_ALGO_MD4, HASH_ALGO__LAST - 1),
+ CTLV_NODE_SCHEMA_FLAGS(CTLV_ATTESTATION_T_MR_DESC_CAPS, 1, CTLV_ATTESTATION_MR_CAPS_MASK),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_DESC_REGISTER_INDEX, 1, 0, U32_MAX),
+};
+
+static const struct ctlv_node_schema mr_read_request_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_READ_ID, 1, 0, U32_MAX),
+};
+
+static const struct ctlv_node_schema mr_read_reply_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GENERATION, 1, 0, U32_MAX),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_MR_VALUES, 23, 3, 2, 2),
+ CTLV_NODE_SCHEMA_ENUM_RANGE(CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO, 1, HASH_ALGO_MD4,
+ HASH_ALGO__LAST - 1),
+ CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_T_MR_VALUE_DATA, 1, 16, 64),
+};
+
+static const struct ctlv_node_schema mr_extend_request_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 2, 2),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_EXTEND_ID, 1, 0, U32_MAX),
+ CTLV_NODE_SCHEMA_RECORD(CTLV_ATTESTATION_T_MR_VALUES, 23, 3, 2, 2),
+ CTLV_NODE_SCHEMA_ENUM_RANGE(CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO, 1, HASH_ALGO_MD4,
+ HASH_ALGO__LAST - 1),
+ CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_T_MR_VALUE_DATA, 1, 16, 64),
+};
+
+static const struct ctlv_node_schema mr_extend_reply_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 1, 1),
+ CTLV_NODE_SCHEMA_UINT(CTLV_ATTESTATION_T_MR_GENERATION, 1, 0, U32_MAX),
+};
+
+/* 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, 15848,
+ CTLV_SCHEMA_TREE(device_get_request),
+ CTLV_SCHEMA_TREE_EXTS(device_get_reply)),
+ CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_EVIDENCE_GET, CTLV_OP_FD_ACCESS_READ_WRITE,
+ CTLV_OP_AVAILABILITY_DYNAMIC, 6176, 1448,
+ CTLV_SCHEMA_TREE(evidence_get_request),
+ CTLV_SCHEMA_TREE(evidence_get_reply), .optional = true),
+ CTLV_OP_SCHEMA_QUERY(CTLV_ATTESTATION_OP_MR_GET, CTLV_OP_FD_ACCESS_READ,
+ CTLV_OP_AVAILABILITY_FIXED, 40, 184, 4096,
+ CTLV_SCHEMA_TREE(mr_get_request), CTLV_SCHEMA_TREE(mr_get_reply),
+ .optional = true),
+ CTLV_OP_SCHEMA_QUERY(CTLV_ATTESTATION_OP_MR_READ, CTLV_OP_FD_ACCESS_READ,
+ CTLV_OP_AVAILABILITY_FIXED, 40, 112, 4096,
+ CTLV_SCHEMA_TREE(mr_read_request), CTLV_SCHEMA_TREE(mr_read_reply),
+ .optional = true),
+ CTLV_OP_SCHEMA_ACTION(CTLV_ATTESTATION_OP_MR_EXTEND, CTLV_OP_FD_ACCESS_READ_WRITE,
+ CTLV_OP_AVAILABILITY_FIXED, 2800, 40,
+ CTLV_SCHEMA_TREE(mr_extend_request),
+ CTLV_SCHEMA_TREE(mr_extend_reply), .optional = true),
+};
+
+/* The family ID is its position in the central registry. */
+const struct ctlv_family_schema ctlv_attestation_schema = {
+ .name = "ctlv-attestation",
+ .family_id = 1,
+ .ops = ctlv_attestation_op_schemas,
+ .ops_count = ARRAY_SIZE(ctlv_attestation_op_schemas),
+};
+
+/* Maximum uevent cost for this family chain. */
+static_assert(107 <= CTLV_UEVENT_MAX_LEN);
+static_assert(4 <= CTLV_UEVENT_MAX_VARS);
+
+/* Guards for generated maxima. */
+static_assert(15848 <= CTLV_MAX_INLINE_MESSAGE_LEN);
+static_assert(4 <= CTLV_MAX_DECLARED_RECORD_DEPTH);
+
+/* Local action and query bindings in declaration order. */
+static const struct ctlv_op ctlv_attestation_ops[] = {
+ CTLV_OP_ACTION(ctlv_attestation_device_get),
+ CTLV_OP_ACTION(ctlv_attestation_evidence_get),
+ CTLV_OP_QUERY(ctlv_attestation_mr_get),
+ CTLV_OP_QUERY(ctlv_attestation_mr_read),
+ CTLV_OP_ACTION(ctlv_attestation_mr_extend),
+};
+
+/* Family descriptor used by ctlv_init(). */
+const struct ctlv_family ctlv_attestation_family = {
+ .schema = &ctlv_attestation_schema,
+ .parent = &ctlv_family,
+ .ops = ctlv_attestation_ops,
+ .ops_count = ARRAY_SIZE(ctlv_attestation_ops),
+};
+EXPORT_SYMBOL_NS_GPL(ctlv_attestation_family, "CTLV");
diff --git a/drivers/attestation/generated/ctlv_attestation_gen.h b/drivers/attestation/generated/ctlv_attestation_gen.h
new file mode 100644
index 000000000000..5ff54385b42e
--- /dev/null
+++ b/drivers/attestation/generated/ctlv_attestation_gen.h
@@ -0,0 +1,204 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN kernel header */
+
+/* Kernel metadata and family API of the ctlv-attestation CTLV family. */
+
+#ifndef _LINUX_CTLV_ATTESTATION_GEN_H
+#define _LINUX_CTLV_ATTESTATION_GEN_H
+
+#include <linux/ctlv_family.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+
+#include <uapi/ctlv/ctlv_attestation_gen.h>
+
+/* Extension points, as a descendant's fill names one. */
+#define CTLV_ATTESTATION_EXT_DEVICE_INFO 1
+
+/* Enum extension points, numbered apart from the others. */
+#define CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT 1
+
+/* Ops this family declares, in declaration order. */
+extern const struct ctlv_op_schema ctlv_attestation_op_schemas[];
+
+extern const struct ctlv_family_schema ctlv_attestation_schema;
+
+/* Implemented by this family, one per action and query it declares. */
+int ctlv_attestation_device_get_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request,
+ struct ctlv_cursor *reply);
+int ctlv_attestation_evidence_get_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request,
+ struct ctlv_cursor *reply);
+int ctlv_attestation_mr_get_query(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request, struct ctlv_cursor *reply);
+int ctlv_attestation_mr_read_query(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request, struct ctlv_cursor *reply);
+int ctlv_attestation_mr_extend_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request, struct ctlv_cursor *reply);
+
+extern const struct ctlv_family ctlv_attestation_family;
+
+#ifndef _CTLV_GENERATED_METADATA_ONLY
+
+/* Optional publication and dynamic availability helpers. */
+CTLV_OP_PROVIDE(ctlv_attestation_evidence_get, CTLV_ATTESTATION_OP_EVIDENCE_GET)
+CTLV_OP_ENABLE(ctlv_attestation_evidence_get, CTLV_ATTESTATION_OP_EVIDENCE_GET)
+CTLV_OP_DISABLE(ctlv_attestation_evidence_get, CTLV_ATTESTATION_OP_EVIDENCE_GET)
+CTLV_OP_PROVIDE(ctlv_attestation_mr_get, CTLV_ATTESTATION_OP_MR_GET)
+CTLV_OP_PROVIDE(ctlv_attestation_mr_read, CTLV_ATTESTATION_OP_MR_READ)
+CTLV_OP_PROVIDE(ctlv_attestation_mr_extend, CTLV_ATTESTATION_OP_MR_EXTEND)
+
+/* Reading a validated message. A blob has a resolver instead of a getter. */
+/* The objects to produce, each named once. */
+CTLV_IN_REF(ctlv_attestation_evidence_get_objects, CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS,
+ CTLV_SCHEMA_RECORD)
+#define ctlv_attestation_evidence_get_objects_for_each(_cursor, _record) \
+ CTLV_IN_FOR_EACH(ctlv_attestation_evidence_get_objects, _cursor, _record)
+/* The object, by the ID device-get lists. */
+CTLV_IN_REF(ctlv_attestation_evidence_get_objects_id, CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_ID,
+ CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_evidence_get_objects_id)
+CTLV_IN_U64(ctlv_attestation_evidence_get_objects_id)
+/*
+ * Where the object is written. One too short for it fails the op with EMSGSIZE, and error-info's
+ * required-length says how long it has to be.
+ */
+CTLV_IN_REF(ctlv_attestation_evidence_get_objects_data,
+ CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_DATA, CTLV_SCHEMA_BLOB)
+CTLV_IN_HAS(ctlv_attestation_evidence_get_objects_data)
+CTLV_IN_BLOB(ctlv_attestation_evidence_get_objects_data)
+/*
+ * Bound into each signed object named: pass a verifier's nonce, or a hash binding one to your
+ * context, never data a peer chose. Present exactly when a signed object is named, and of a length
+ * each of them takes.
+ */
+CTLV_IN_REF(ctlv_attestation_evidence_get_challenge, CTLV_ATTESTATION_T_EVIDENCE_GET_CHALLENGE,
+ CTLV_SCHEMA_BINARY)
+CTLV_IN_HAS(ctlv_attestation_evidence_get_challenge)
+CTLV_IN_BINARY(ctlv_attestation_evidence_get_challenge)
+/* Only this MR; every MR without it. */
+CTLV_IN_REF(ctlv_attestation_mr_get_id, CTLV_ATTESTATION_T_MR_GET_ID, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_mr_get_id)
+CTLV_IN_U64(ctlv_attestation_mr_get_id)
+/* The MR to read, by the ID mr-get lists. */
+CTLV_IN_REF(ctlv_attestation_mr_read_id, CTLV_ATTESTATION_T_MR_READ_ID, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_mr_read_id)
+CTLV_IN_U64(ctlv_attestation_mr_read_id)
+/* The MR to extend, by the ID mr-get lists. */
+CTLV_IN_REF(ctlv_attestation_mr_extend_id, CTLV_ATTESTATION_T_MR_EXTEND_ID, CTLV_SCHEMA_UINT)
+CTLV_IN_HAS(ctlv_attestation_mr_extend_id)
+CTLV_IN_U64(ctlv_attestation_mr_extend_id)
+/* Values for banks of the MR, each bank once. */
+CTLV_IN_REF(ctlv_attestation_mr_values, CTLV_ATTESTATION_T_MR_VALUES, CTLV_SCHEMA_RECORD)
+#define ctlv_attestation_mr_values_for_each(_cursor, _record) \
+ CTLV_IN_FOR_EACH(ctlv_attestation_mr_values, _cursor, _record)
+/* The bank's algorithm. */
+CTLV_IN_REF(ctlv_attestation_mr_value_hash_algo, CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO,
+ CTLV_SCHEMA_ENUM)
+CTLV_IN_HAS(ctlv_attestation_mr_value_hash_algo)
+CTLV_IN_U64(ctlv_attestation_mr_value_hash_algo)
+/* The digest, as long as hash-algo's. */
+CTLV_IN_REF(ctlv_attestation_mr_value_data, CTLV_ATTESTATION_T_MR_VALUE_DATA, CTLV_SCHEMA_BINARY)
+CTLV_IN_HAS(ctlv_attestation_mr_value_data)
+CTLV_IN_BINARY(ctlv_attestation_mr_value_data)
+
+/* Writing a reply. An append is atomic; a record ends with ctlv_record_end(). */
+/* What the device supports, fixed for its life. */
+CTLV_RECORD_START(ctlv_attestation_device_get_caps, CTLV_ATTESTATION_T_DEVICE_GET_CAPS,
+ CTLV_SCHEMA_RECORD)
+/* Present when the device produces evidence. */
+CTLV_RECORD_START(ctlv_attestation_device_get_caps_evidence,
+ CTLV_ATTESTATION_T_DEVICE_GET_CAPS_EVIDENCE, CTLV_SCHEMA_RECORD)
+/* One per object evidence-get can produce. */
+CTLV_RECORD_START(ctlv_attestation_device_get_evidence_objects,
+ CTLV_ATTESTATION_T_DEVICE_GET_EVIDENCE_OBJECTS, CTLV_SCHEMA_RECORD)
+/* The device's handle for the object in requests. */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_id, CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_ID,
+ CTLV_SCHEMA_UINT)
+/* What the object is. */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_class, CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CLASS,
+ CTLV_SCHEMA_ENUM)
+/* The longest the object gets; a blob this long always holds it. */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_max_length,
+ CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_MAX_LENGTH, CTLV_SCHEMA_UINT)
+/*
+ * The layouts a standard defines that the object may have. At least one of formats and
+ * vendor-formats is present.
+ */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_formats,
+ CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_FORMATS, CTLV_SCHEMA_ENUM)
+/* The layouts its vendor defines that the object may have. */
+CTLV_PUT_U64(ctlv_attestation_device_get_objects_vendor_formats,
+ CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_VENDOR_FORMATS, CTLV_SCHEMA_ENUM)
+/*
+ * A signed object's challenge lengths: each entry is one length, or a range from min-length to
+ * max-length.
+ */
+CTLV_RECORD_START(ctlv_attestation_device_get_objects_challenge_lengths,
+ CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CHALLENGE_LENGTHS, CTLV_SCHEMA_RECORD)
+/* A single length. */
+CTLV_PUT_U64(ctlv_attestation_device_get_challenge_lengths_length,
+ CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH, CTLV_SCHEMA_UINT)
+/* The shortest of a range. */
+CTLV_PUT_U64(ctlv_attestation_device_get_challenge_lengths_min_length,
+ CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH, CTLV_SCHEMA_UINT)
+/* The longest of a range. */
+CTLV_PUT_U64(ctlv_attestation_device_get_challenge_lengths_max_length,
+ CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH, CTLV_SCHEMA_UINT)
+/* Present when the device has MRs. */
+CTLV_RECORD_START(ctlv_attestation_device_get_caps_mr, CTLV_ATTESTATION_T_DEVICE_GET_CAPS_MR,
+ CTLV_SCHEMA_RECORD)
+/* The MR operations the device provides. */
+CTLV_PUT_U64(ctlv_attestation_device_get_mr_flags, CTLV_ATTESTATION_T_DEVICE_GET_MR_FLAGS,
+ CTLV_SCHEMA_FLAGS)
+/* The device's family's fields. */
+CTLV_RECORD_START(ctlv_attestation_device_get_info, CTLV_ATTESTATION_T_DEVICE_GET_INFO,
+ CTLV_SCHEMA_RECORD)
+/* One per object, in request order. */
+CTLV_RECORD_START(ctlv_attestation_evidence_get_results, CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS,
+ CTLV_SCHEMA_RECORD)
+/* The bytes written to its blob. */
+CTLV_PUT_U64(ctlv_attestation_evidence_get_results_length,
+ CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_LENGTH, CTLV_SCHEMA_UINT)
+/*
+ * The layout it came in, where a standard defines it. Exactly one of format and vendor-format is
+ * present.
+ */
+CTLV_PUT_U64(ctlv_attestation_evidence_get_results_format,
+ CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_FORMAT, CTLV_SCHEMA_ENUM)
+/* The layout it came in, where its vendor defines it. */
+CTLV_PUT_U64(ctlv_attestation_evidence_get_results_vendor_format,
+ CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_VENDOR_FORMAT, CTLV_SCHEMA_ENUM)
+/* The device's MR generation, which each extend bumps. */
+CTLV_PUT_U64(ctlv_attestation_mr_generation, CTLV_ATTESTATION_T_MR_GENERATION, CTLV_SCHEMA_UINT)
+/* One per MR, ordered by ID. */
+CTLV_RECORD_START(ctlv_attestation_mr_get_items, CTLV_ATTESTATION_T_MR_GET_ITEMS,
+ CTLV_SCHEMA_RECORD)
+/* The device's handle for the MR in requests. */
+CTLV_PUT_U64(ctlv_attestation_mr_get_items_id, CTLV_ATTESTATION_T_MR_GET_ITEMS_ID, CTLV_SCHEMA_UINT)
+/* The platform's name for the MR, such as rtmr0. */
+CTLV_PUT_STRING(ctlv_attestation_mr_desc_name, CTLV_ATTESTATION_T_MR_DESC_NAME, CTLV_SCHEMA_STRING)
+/* Its banks, each once, in the provider's order. */
+CTLV_PUT_U64(ctlv_attestation_mr_desc_supported_hash_algorithms,
+ CTLV_ATTESTATION_T_MR_DESC_SUPPORTED_HASH_ALGORITHMS, CTLV_SCHEMA_ENUM)
+/* What it allows. */
+CTLV_PUT_U64(ctlv_attestation_mr_desc_caps, CTLV_ATTESTATION_T_MR_DESC_CAPS, CTLV_SCHEMA_FLAGS)
+/* The hardware's number for the MR, where it has one: 0 for rtmr0. */
+CTLV_PUT_U64(ctlv_attestation_mr_desc_register_index, CTLV_ATTESTATION_T_MR_DESC_REGISTER_INDEX,
+ CTLV_SCHEMA_UINT)
+/* Values for banks of the MR, each bank once. */
+CTLV_RECORD_START(ctlv_attestation_mr_values, CTLV_ATTESTATION_T_MR_VALUES, CTLV_SCHEMA_RECORD)
+/* The bank's algorithm. */
+CTLV_PUT_U64(ctlv_attestation_mr_value_hash_algo, CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO,
+ CTLV_SCHEMA_ENUM)
+/* The digest, as long as hash-algo's. */
+CTLV_PUT_BINARY(ctlv_attestation_mr_value_data, CTLV_ATTESTATION_T_MR_VALUE_DATA,
+ CTLV_SCHEMA_BINARY)
+#endif /* _CTLV_GENERATED_METADATA_ONLY */
+
+#endif /* _LINUX_CTLV_ATTESTATION_GEN_H */
diff --git a/include/uapi/ctlv/ctlv_attestation_gen.h b/include/uapi/ctlv/ctlv_attestation_gen.h
new file mode 100644
index 000000000000..f4a1cfe02bb2
--- /dev/null
+++ b/include/uapi/ctlv/ctlv_attestation_gen.h
@@ -0,0 +1,234 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN uapi header */
+
+/*
+ * Constants of the ctlv-attestation CTLV family. Types and op IDs are complete; the message format
+ * carrying them is in <ctlv/ctlv.h>.
+ *
+ * The inherited ctlv header is included here, so one include covers the whole effective schema.
+ */
+
+#ifndef _UAPI_CTLV_CTLV_ATTESTATION_GEN_H
+#define _UAPI_CTLV_CTLV_ATTESTATION_GEN_H
+
+#include <ctlv/ctlv.h>
+#include <ctlv/ctlv_families_gen.h>
+#include <ctlv/ctlv_gen.h>
+#include <linux/const.h>
+#include <linux/hash_info.h>
+
+/* enum evidence-class */
+enum _ctlv_attestation_evidence_class {
+ /* Made by evidence-get over its challenge, and signed by the root of trust. */
+ CTLV_ATTESTATION_EVIDENCE_CLASS_SIGNED = 1,
+ /*
+ * The platform's log of what firmware and the boot chain measured into the MRs. Later
+ * extends, through mr-extend or otherwise, are not in it.
+ */
+ CTLV_ATTESTATION_EVIDENCE_CLASS_EVENT_LOG = 2,
+};
+
+/* enum evidence-format */
+enum _ctlv_attestation_evidence_format {
+ /* The log area of an ACPI CCEL table, a TCG crypto-agile event log. */
+ CTLV_ATTESTATION_EVIDENCE_FORMAT_CCEL = 1,
+};
+
+/* flags mr-device-caps */
+enum _ctlv_attestation_mr_device_caps_bit {
+ CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND_BIT = 0,
+};
+
+/* mr-extend is provided. */
+#define CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND _BITULL(CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND_BIT)
+#define CTLV_ATTESTATION_MR_DEVICE_CAPS_MASK CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND
+
+/* flags mr-caps */
+enum _ctlv_attestation_mr_caps_bit {
+ CTLV_ATTESTATION_MR_CAPS_EXTEND_BIT = 0,
+};
+
+/* The MR may be extended. */
+#define CTLV_ATTESTATION_MR_CAPS_EXTEND _BITULL(CTLV_ATTESTATION_MR_CAPS_EXTEND_BIT)
+#define CTLV_ATTESTATION_MR_CAPS_MASK CTLV_ATTESTATION_MR_CAPS_EXTEND
+
+/* mr-desc */
+/* The platform's name for the MR, such as rtmr0. */
+#define CTLV_ATTESTATION_T_MR_DESC_NAME \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00010000, 1)
+/* Its banks, each once, in the provider's order. */
+#define CTLV_ATTESTATION_T_MR_DESC_SUPPORTED_HASH_ALGORITHMS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00010000, 2)
+/* What it allows. */
+#define CTLV_ATTESTATION_T_MR_DESC_CAPS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00010000, 3)
+/* The hardware's number for the MR, where it has one: 0 for rtmr0. */
+#define CTLV_ATTESTATION_T_MR_DESC_REGISTER_INDEX \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00010000, 4)
+
+/* mr-value */
+/* The bank's algorithm. */
+#define CTLV_ATTESTATION_T_MR_VALUE_HASH_ALGO \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00020000, 1)
+/* The digest, as long as hash-algo's. */
+#define CTLV_ATTESTATION_T_MR_VALUE_DATA \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00020000, 2)
+
+/* mr-generation */
+/* The device's MR generation, which each extend bumps. */
+#define CTLV_ATTESTATION_T_MR_GENERATION \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00030000, 1)
+
+/* mr-values */
+/* Values for banks of the MR, each bank once. */
+#define CTLV_ATTESTATION_T_MR_VALUES \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x00040000, 1)
+
+/* device-get reply */
+/* What the device supports, fixed for its life. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CAPS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0010000, 1)
+/* The device's family's fields. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_INFO \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0010000, 2)
+
+/* device-get caps */
+/* Present when the device produces evidence. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CAPS_EVIDENCE \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011000, 1)
+/* Present when the device has MRs. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CAPS_MR \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011000, 2)
+
+/* device-get caps.evidence */
+/* One per object evidence-get can produce. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_EVIDENCE_OBJECTS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011100, 1)
+
+/* device-get caps.evidence.objects */
+/* The device's handle for the object in requests. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_ID \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 1)
+/* What the object is. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CLASS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 2)
+/*
+ * The layouts a standard defines that the object may have. At least one of formats and
+ * vendor-formats is present.
+ */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_FORMATS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 3)
+/* The layouts its vendor defines that the object may have. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_VENDOR_FORMATS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 4)
+/* The longest the object gets; a blob this long always holds it. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_MAX_LENGTH \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 5)
+/*
+ * A signed object's challenge lengths: each entry is one length, or a range from min-length to
+ * max-length.
+ */
+#define CTLV_ATTESTATION_T_DEVICE_GET_OBJECTS_CHALLENGE_LENGTHS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011110, 6)
+
+/* device-get caps.evidence.objects.challenge-lengths */
+/* A single length. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011111, 1)
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH_UNIT CTLV_UNIT_BYTES
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_LENGTH_SCALE 0
+/* The shortest of a range. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011111, 2)
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH_UNIT CTLV_UNIT_BYTES
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MIN_LENGTH_SCALE 0
+/* The longest of a range. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011111, 3)
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH_UNIT CTLV_UNIT_BYTES
+#define CTLV_ATTESTATION_T_DEVICE_GET_CHALLENGE_LENGTHS_MAX_LENGTH_SCALE 0
+
+/* device-get caps.mr */
+/* The MR operations the device provides. */
+#define CTLV_ATTESTATION_T_DEVICE_GET_MR_FLAGS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0011200, 1)
+
+/* evidence-get request */
+/*
+ * Bound into each signed object named: pass a verifier's nonce, or a hash binding one to your
+ * context, never data a peer chose. Present exactly when a signed object is named, and of a length
+ * each of them takes.
+ */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_CHALLENGE \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80020000, 1)
+/* The objects to produce, each named once. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80020000, 2)
+
+/* evidence-get objects */
+/* The object, by the ID device-get lists. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_ID \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80021000, 1)
+/*
+ * Where the object is written. One too short for it fails the op with EMSGSIZE, and error-info's
+ * required-length says how long it has to be.
+ */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_OBJECTS_DATA \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80021000, 2)
+
+/* evidence-get reply */
+/* One per object, in request order. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0020000, 1)
+
+/* evidence-get results */
+/*
+ * The layout it came in, where a standard defines it. Exactly one of format and vendor-format is
+ * present.
+ */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_FORMAT \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0021000, 1)
+/* The layout it came in, where its vendor defines it. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_VENDOR_FORMAT \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0021000, 2)
+/* The bytes written to its blob. */
+#define CTLV_ATTESTATION_T_EVIDENCE_GET_RESULTS_LENGTH \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0021000, 3)
+
+/* mr-get request */
+/* Only this MR; every MR without it. */
+#define CTLV_ATTESTATION_T_MR_GET_ID \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80030000, 1)
+
+/* mr-get reply */
+/* One per MR, ordered by ID. */
+#define CTLV_ATTESTATION_T_MR_GET_ITEMS \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0030000, 1)
+
+/* mr-get items */
+/* The device's handle for the MR in requests. */
+#define CTLV_ATTESTATION_T_MR_GET_ITEMS_ID \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0xc0031000, 1)
+
+/* mr-read request */
+/* The MR to read, by the ID mr-get lists. */
+#define CTLV_ATTESTATION_T_MR_READ_ID \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80040000, 1)
+
+/* mr-extend request */
+/* The MR to extend, by the ID mr-get lists. */
+#define CTLV_ATTESTATION_T_MR_EXTEND_ID \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION, 0x80050000, 1)
+
+/* ops */
+#define CTLV_ATTESTATION_OP_DEVICE_GET CTLV_OP(CTLV_FAMILY_ATTESTATION, 1)
+#define CTLV_ATTESTATION_OP_EVIDENCE_GET CTLV_OP(CTLV_FAMILY_ATTESTATION, 2)
+#define CTLV_ATTESTATION_OP_MR_GET CTLV_OP(CTLV_FAMILY_ATTESTATION, 3)
+#define CTLV_ATTESTATION_OP_MR_READ CTLV_OP(CTLV_FAMILY_ATTESTATION, 4)
+#define CTLV_ATTESTATION_OP_MR_EXTEND CTLV_OP(CTLV_FAMILY_ATTESTATION, 5)
+
+#endif /* _UAPI_CTLV_CTLV_ATTESTATION_GEN_H */
diff --git a/include/uapi/ctlv/ctlv_families_gen.h b/include/uapi/ctlv/ctlv_families_gen.h
index 83f1719c89aa..83f6a257ddf2 100644
--- a/include/uapi/ctlv/ctlv_families_gen.h
+++ b/include/uapi/ctlv/ctlv_families_gen.h
@@ -12,6 +12,7 @@
enum _ctlv_family {
CTLV_FAMILY_ROOT = 0,
+ CTLV_FAMILY_ATTESTATION = 1,
};
#endif /* _UAPI_CTLV_CTLV_FAMILIES_GEN_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC v2 2/9] Documentation: attestation: Add userspace ABI
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 3/9] attestation: Prepare provider and instance core Jiri Pirko
` (6 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Describe the attestation family for userspace: its operations and their
errors, the evidence objects and the measurement registers.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
Documentation/userspace-api/attestation.rst | 283 ++++++++++++++++++++
Documentation/userspace-api/index.rst | 1 +
MAINTAINERS | 1 +
3 files changed, 285 insertions(+)
create mode 100644 Documentation/userspace-api/attestation.rst
diff --git a/Documentation/userspace-api/attestation.rst b/Documentation/userspace-api/attestation.rst
new file mode 100644
index 000000000000..3c610acc3382
--- /dev/null
+++ b/Documentation/userspace-api/attestation.rst
@@ -0,0 +1,283 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+=========================
+Attestation userspace ABI
+=========================
+
+A confidential guest proves what it runs to a remote verifier with evidence: a
+statement of its measurements, signed by its root of trust and carrying a
+challenge the verifier chose, and the logs behind those measurements. An
+attestation provider gives its device a node, ``/dev/attestation/attestationN``.
+Through it a program asks for evidence, and reads and extends the measurement
+registers (MRs) the evidence reports.
+
+The node speaks CTLV, described in :doc:`ctlv`, as a descendant of the
+``ctlv-attestation`` family, one family per provider. Every operation below
+belongs to ``ctlv-attestation``, so a tool written against it works with any
+provider. The specification is
+``Documentation/ctlv/specs/ctlv-attestation.yaml`` and the constants are in
+``include/uapi/ctlv/ctlv_attestation_gen.h``.
+
+====================== ====== ========== ================================
+Operation Type Access Purpose
+====================== ====== ========== ================================
+device-get action read what the device supports
+evidence-get action read-write evidence objects
+mr-get query read describe the MRs
+mr-read query read one MR's values
+mr-extend action read-write extend one MR
+====================== ====== ========== ================================
+
+``device-get`` is always provided. Every other operation is optional, and a
+device provides those its provider implements, which ``op-get`` lists. The
+errors below are the family's own. Those of the transport, such as ``EACCES``
+for a descriptor without the access an operation needs, are in :doc:`ctlv`,
+and once a device is gone or has stopped, every operation fails with
+``ENODEV``. A provider's own failure returns its errno, such as ``EIO``.
+
+Generations
+===========
+
+A counter tells a program whether what it holds is still current. It is 32
+bits wide. An operation that would advance it past its maximum fails with
+``EOVERFLOW`` and stops the device.
+
+``mr-generation``
+ The state of the MRs. It advances with every extension, and a reply that
+ depends on MR state carries the generation it reflects.
+
+Evidence
+========
+
+``device-get``
+--------------
+
+Describes the device: what it supports and the provider's own information. It
+takes no request. The reply carries:
+
+``caps``
+ A record for each part the device has:
+
+ ``caps.evidence``
+ With ``evidence-get``: one of ``objects`` for each object the device
+ produces, up to 16, as `Objects`_ describes.
+
+ ``caps.mr``
+ With MRs: ``flags``, which has ``extend`` where ``mr-extend`` is
+ provided. ``mr-read`` is always provided with MRs.
+
+``info``
+ The device's family's fields, its ``device-info``.
+
+``evidence-get``
+----------------
+
+Writes each object the request names into a blob the caller supplies. A
+signed object is made by the call, over the challenge, and never reused. The
+others, such as an event log, are read as they stand at the call. One call so
+returns signed evidence with the log needed to check it. The request carries:
+
+``challenge``
+ The challenge, described below. Present exactly when a signed object is
+ named.
+
+``objects``
+ 1 to 16 objects, each naming one of the ``caps.evidence.objects`` of
+ ``device-get`` by its ``id`` and giving ``data``, the output blob it is
+ written to, sized as described below.
+
+The reply carries:
+
+``results``
+ One per object, in request order, each giving the object's format in
+ exactly one of ``format`` and ``vendor-format``, and its ``length``, the
+ bytes written to its blob.
+
+``mr-generation``
+ Where an object binds MRs, the MR generation the evidence reflects.
+
+``EINVAL``
+ A signed object is named without a challenge, a challenge is given
+ without one, or its length is one a signed object named does not take.
+ Two objects are the same.
+
+``EMSGSIZE``
+ An object's blob is too short for it. The error information's
+ ``required-length`` says how long it has to be.
+
+``ENOENT``
+ An object's ``id`` is not one ``device-get`` lists.
+
+``EPROTO``
+ The root of trust returned an object in a format the device does not
+ list for it.
+
+Access is read-write. Availability is dynamic: a device may switch the
+operation off for a time, and meanwhile it fails with ``EOPNOTSUPP``.
+
+The challenge
+~~~~~~~~~~~~~
+
+A signed object binds the challenge. Each signed object lists its
+``challenge-lengths``, up to 8, each one ``length`` or a range from
+``min-length`` to ``max-length``, and the subsystem keeps them within 1 to 4K.
+The challenge has to be of a length every signed object named lists.
+
+Pass a nonce from the verifier, or a hash that binds such a nonce to the
+caller's own context, such as a key the evidence is to vouch for. Never pass
+data a peer chose. Any process that can run ``evidence-get`` can have the
+device sign what it passes, and one that passes a peer's bytes makes the
+device a signing oracle for that peer.
+
+Objects
+~~~~~~~
+
+Each of ``caps.evidence.objects`` describes one object:
+
+``id``
+ 0 to 15, which a request names it by.
+
+``class``
+ What the object is, as ``evidence-class`` below says.
+
+``formats`` and ``vendor-formats``
+ The formats it may have.
+
+``max-length``
+ The longest it gets, however many objects a call asks for.
+
+``challenge-lengths``
+ For a signed object, the challenge lengths it takes.
+
+``evidence-class`` says what an object is:
+
+``signed``
+ Evidence the root of trust signs over the challenge. It carries its own
+ signature.
+
+``event-log``
+ The platform's log of what firmware and the boot chain measured into the
+ MRs, for a verifier to replay. Later extends, through ``mr-extend`` or
+ otherwise, are not in it.
+
+A blob of the object's ``max-length`` always holds it. A shorter one saves
+memory where objects run shorter than their bound, as certificate chains do:
+where the object does not fit, ``evidence-get`` fails with ``EMSGSIZE`` and
+returns no evidence, and ``required-length`` in the error information is the
+length to retry with. The retry collects the evidence anew, and may pass the
+same challenge.
+
+Two enums name the formats:
+
+``evidence-format``
+ ``ctlv-attestation``'s, for a layout a standard defines, such as the
+ CCEL, so every family that produces it uses the same value.
+
+``evidence-vendor-format``
+ For a layout one vendor defines, whose values the device's family gives
+ through a CTLV enum extension point.
+
+The ABI check of ``regen`` covers both enums. An object lists the formats it
+may have, from either enum, in ``formats`` and ``vendor-formats``, and the
+reply names the one written in exactly one of ``format`` and
+``vendor-format``. A new layout is a new value.
+
+Measurement registers
+=====================
+
+Extending an MR folds a digest into its value, so the value commits to every
+extension in order. An MR has a value for each hash algorithm it supports, a
+bank. An MR's ``id`` is the device's handle for it, and ``mr-desc`` describes
+it:
+
+``name``
+ The platform's name for the MR, such as ``rtmr0``.
+
+``supported-hash-algorithms``
+ One for each bank, in the provider's order.
+
+``register-index``
+ The hardware's number for the MR, where it has one.
+
+``caps``
+ ``extend`` where the MR may be extended. A provider still refuses an
+ extend the platform forbids at the time, as with ``EPERM`` for a TPM PCR
+ the current locality may not extend.
+
+Every MR can be read, though a read can still fail, as when the caller lacks
+authorization or the platform's state forbids it.
+
+The name, banks, caps and register index describe an MR, and do not change.
+The values change with every extension, so the two are read apart.
+``mr-get`` describes every MR, which is how a program learns the IDs, or the
+one an ID names. It is answered from what the device keeps, while
+``mr-read``, which returns one MR's values, asks the hardware. ``mr-read`` and
+``mr-extend`` take only the ID.
+
+A hash algorithm is a constant of ``enum hash_algo`` in
+``include/uapi/linux/hash_info.h``, such as ``HASH_ALGO_SHA384``.
+
+Nothing reserves an MR, and the kernel does not arbitrate between its
+writers. Any program with a read-write descriptor can extend it, and so can
+the register's own interface, as for a TPM's PCRs. A program that replays an
+MR from its own records has to make sure, through the system's configuration,
+that it is the only writer; ``mr-generation`` counts only the extensions made
+through the device.
+
+``mr-get``
+----------
+
+Describes every MR, in fragments ordered by ID, or only the MR whose ``id``
+the request carries. Each is one of ``items``, with its ``id`` and its
+``mr-desc``, as `Measurement registers`_ lists it. The query's generation is
+the MR generation, so a continuation fails with ``ESTALE`` once any MR has
+changed.
+
+``ENOENT``
+ No MR has that ID.
+
+Access is read.
+
+``mr-read``
+-----------
+
+Returns the current values of the MR whose ``id`` the request carries. The
+reply carries:
+
+``mr-generation``
+ The MR generation of the values.
+
+``mr-values``
+ One for each bank, in ``mr-get``'s order, with its ``hash-algo`` and
+ ``data``.
+
+``ENOENT``
+ No MR has that ID.
+
+Access is read.
+
+``mr-extend``
+-------------
+
+Extends the MR whose ``id`` the request carries. The request carries one of
+``mr-values`` for each bank of the MR to extend, in any order, each a
+``hash-algo`` and a ``data`` of exactly that algorithm's digest size. The
+banks left out keep their values. The device hashes nothing for the caller.
+
+The reply carries ``mr-generation``, the MR generation after the extension.
+
+``ENOENT``
+ No MR has that ID.
+
+``EOPNOTSUPP``
+ The MR cannot be extended, or the provider cannot extend only some of its
+ banks.
+
+``EPERM``
+ The platform forbids extending the MR at the time.
+
+``EINVAL``
+ The request carries no ``mr-values``, a value is for a bank the MR does not
+ have or repeats one, or a digest has the wrong length.
+
+Access is read-write.
diff --git a/Documentation/userspace-api/index.rst b/Documentation/userspace-api/index.rst
index d1b53c4e16eb..61051cd28fd1 100644
--- a/Documentation/userspace-api/index.rst
+++ b/Documentation/userspace-api/index.rst
@@ -36,6 +36,7 @@ Security-related interfaces
mfd_noexec
spec_ctrl
tee
+ attestation
check_exec
Devices and I/O
diff --git a/MAINTAINERS b/MAINTAINERS
index fa450924b921..0c1778e676ed 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4439,6 +4439,7 @@ L: linux-security-module@vger.kernel.org
S: Supported
F: Documentation/ctlv/abi/ctlv-attestation*
F: Documentation/ctlv/specs/ctlv-attestation*
+F: Documentation/userspace-api/attestation*.rst
F: drivers/attestation/
F: include/uapi/ctlv/ctlv_attestation*
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC v2 3/9] attestation: Prepare provider and instance core
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 2/9] Documentation: attestation: Add userspace ABI Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 4/9] attestation: Prepare measurement registers Jiri Pirko
` (5 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Add provider validation, instance registration and lifecycle management
for the attestation family. Each instance is a device of the attestation
class, with a node under /dev/attestation/, and embeds its CTLV
instance and node.
The provider descriptor is one immutable evidence and MR contract; MR
state follows in the next patch. The core object can be compile-checked
here. CONFIG_ATTESTATION remains unwired until the CTLV handlers land.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- gave attestation its own device, class and /dev/attestation/ nodes,
each a struct ctlv_device
- dropped the goto from attestation_registry_insert()
- noted that instance indexes are never reused
- selected CRYPTO_HASH_INFO
- selected CTLV instead of depending on it
- kept each guard() on one line
- dropped the journal from the Kconfig help
- dropped read(), poll() and the event teardown
- refused hash algorithms the hash-algo enum does not list
- gave each evidence object a class and the formats it may have
- dropped max_total_output_length
- took each signed object's challenge lengths as a list, ascending
and disjoint
- left the object count and challenge length limits to CTLV
- added attestation_evidence_write(), for blobs of any length
- let a request omit the challenge
- separated the steps of the longer functions
- took an MR's algorithms in any order, each once
- let mr_extend get the digests of some of an MR's banks
- required mr_read of a provider with MRs, as every MR can be read
- said that an MR's extend cap is fixed
- took the UAPI headers from include/uapi/ctlv/
- named evidence objects by ID, and had the provider give each
result's format
---
MAINTAINERS | 1 +
drivers/attestation/Kconfig | 12 +
drivers/attestation/Makefile | 4 +
drivers/attestation/core/core.c | 429 ++++++++++++++++++++++++++++
drivers/attestation/core/internal.h | 21 ++
include/linux/attestation.h | 240 ++++++++++++++++
6 files changed, 707 insertions(+)
create mode 100644 drivers/attestation/Kconfig
create mode 100644 drivers/attestation/Makefile
create mode 100644 drivers/attestation/core/core.c
create mode 100644 drivers/attestation/core/internal.h
create mode 100644 include/linux/attestation.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 0c1778e676ed..e57fc8bb9c1d 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4441,6 +4441,7 @@ F: Documentation/ctlv/abi/ctlv-attestation*
F: Documentation/ctlv/specs/ctlv-attestation*
F: Documentation/userspace-api/attestation*.rst
F: drivers/attestation/
+F: include/linux/attestation*
F: include/uapi/ctlv/ctlv_attestation*
ATTO EXPRESSSAS SAS/SATA RAID SCSI DRIVER
diff --git a/drivers/attestation/Kconfig b/drivers/attestation/Kconfig
new file mode 100644
index 000000000000..a6bfb17dd480
--- /dev/null
+++ b/drivers/attestation/Kconfig
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0-only
+menuconfig ATTESTATION
+ tristate "Attestation"
+ select CRYPTO_HASH_INFO
+ select CTLV
+ help
+ Provides a CTLV family for evidence and measurement registers.
+
+ Providers register one instance for a physical or logical device and
+ translate the common operations to their native service.
+
+ If you are unsure, say N.
diff --git a/drivers/attestation/Makefile b/drivers/attestation/Makefile
new file mode 100644
index 000000000000..8a75558a7013
--- /dev/null
+++ b/drivers/attestation/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0
+obj-$(CONFIG_ATTESTATION) += attestation.o
+
+attestation-y := core/core.o
diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
new file mode 100644
index 000000000000..587a0a9616b6
--- /dev/null
+++ b/drivers/attestation/core/core.c
@@ -0,0 +1,429 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <crypto/hash_info.h>
+#include <linux/bitmap.h>
+#include <linux/cleanup.h>
+#include <linux/ctlv.h>
+#include <linux/ctlv_device.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/limits.h>
+#include <linux/module.h>
+#include <linux/overflow.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "internal.h"
+
+#define ATTESTATION_MAX_DEVICES 4096
+
+static DEFINE_XARRAY(attestation_instances);
+
+static DEFINE_MUTEX(attestation_registry_lock);
+/* Never reused, so a stale index cannot name a newer instance. */
+static unsigned long attestation_next_index;
+
+/* A reply carries only what hash-algo in ctlv-attestation.yaml lists. */
+static bool attestation_algorithm_valid(enum hash_algo algorithm)
+{
+ return algorithm >= 0 && algorithm <= HASH_ALGO_SHA3_512 && hash_digest_size[algorithm] &&
+ hash_digest_size[algorithm] <= HASH_MAX_DIGESTSIZE;
+}
+
+static int attestation_mr_props_validate(const struct attestation_provider *provider,
+ const struct attestation_mr_props *props)
+{
+ DECLARE_BITMAP(seen, HASH_ALGO__LAST) = {};
+ size_t index;
+
+ if (!props || !props->name || !*props->name || strlen(props->name) > 128 ||
+ !props->algorithms || !props->algorithms_count || props->algorithms_count > 23)
+ return -EINVAL;
+ for (index = 0; index < props->algorithms_count; index++) {
+ enum hash_algo algorithm = props->algorithms[index];
+
+ if (!attestation_algorithm_valid(algorithm) ||
+ __test_and_set_bit(algorithm, seen))
+ return -EINVAL;
+ }
+ if (props->extendable && !provider->mr_extend)
+ return -EINVAL;
+ return 0;
+}
+
+/* What device-get and evidence-get carry, as ctlv-attestation.yaml bounds it. */
+#define ATTESTATION_EVIDENCE_OBJECTS_MAX 16
+#define ATTESTATION_EVIDENCE_FORMATS_MAX 4
+#define ATTESTATION_CHALLENGE_LENGTHS_MAX 8
+#define ATTESTATION_CHALLENGE_MAX 4096
+/* Moves with each value appended to evidence-format. */
+#define ATTESTATION_EVIDENCE_FORMAT_LAST CTLV_ATTESTATION_EVIDENCE_FORMAT_CCEL
+
+static bool
+attestation_challenge_lengths_valid(const struct attestation_evidence_supported_object *object)
+{
+ size_t index;
+
+ if (object->evidence_class != ATTESTATION_EVIDENCE_SIGNED)
+ return !object->challenge_lengths_count;
+ if (!object->challenge_lengths || !object->challenge_lengths_count ||
+ object->challenge_lengths_count > ATTESTATION_CHALLENGE_LENGTHS_MAX)
+ return false;
+ for (index = 0; index < object->challenge_lengths_count; index++) {
+ const struct attestation_evidence_challenge_length *length =
+ &object->challenge_lengths[index];
+
+ if (!length->min || length->min > length->max ||
+ length->max > ATTESTATION_CHALLENGE_MAX ||
+ (index && object->challenge_lengths[index - 1].max >= length->min))
+ return false;
+ }
+ return true;
+}
+
+static bool
+attestation_formats_valid(const struct attestation_evidence_supported_object *object,
+ u32 vendor_max)
+{
+ size_t counts[2] = {}, index, prior;
+
+ if (!object->formats || !object->formats_count)
+ return false;
+ for (index = 0; index < object->formats_count; index++) {
+ const struct attestation_evidence_format *format = &object->formats[index];
+ u32 max = format->vendor ? vendor_max : ATTESTATION_EVIDENCE_FORMAT_LAST;
+
+ if (!format->value || format->value > max ||
+ ++counts[format->vendor] > ATTESTATION_EVIDENCE_FORMATS_MAX)
+ return false;
+ for (prior = 0; prior < index; prior++)
+ if (object->formats[prior].vendor == format->vendor &&
+ object->formats[prior].value == format->value)
+ return false;
+ }
+ return true;
+}
+
+static int attestation_evidence_caps_validate(const struct attestation_provider *provider,
+ const struct ctlv_family *family)
+{
+ const struct attestation_evidence_caps *caps = provider->evidence_caps;
+ size_t index;
+ u32 vendor_max;
+
+ if (!caps)
+ return provider->evidence_get ? -EINVAL : 0;
+ if (!provider->evidence_get || !caps->objects || !caps->objects_count ||
+ caps->objects_count > ATTESTATION_EVIDENCE_OBJECTS_MAX)
+ return -EINVAL;
+
+ vendor_max = ctlv_enum_ext_max(family, CTLV_FAMILY_ATTESTATION,
+ CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT);
+ for (index = 0; index < caps->objects_count; index++) {
+ const struct attestation_evidence_supported_object *object = &caps->objects[index];
+
+ if (object->evidence_class < ATTESTATION_EVIDENCE_SIGNED ||
+ object->evidence_class > ATTESTATION_EVIDENCE_EVENT_LOG ||
+ !attestation_formats_valid(object, vendor_max) ||
+ !attestation_challenge_lengths_valid(object) ||
+ !object->max_length || object->max_length > UINT_MAX)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int attestation_provider_validate(const struct attestation_provider *provider,
+ const struct ctlv_family *family)
+{
+ size_t index;
+ int ret;
+
+ if (!provider)
+ return -EINVAL;
+ ret = attestation_evidence_caps_validate(provider, family);
+ if (ret)
+ return ret;
+ if (!!provider->mrs != !!provider->mrs_count)
+ return -EINVAL;
+ if (provider->mrs_count && !provider->mr_read)
+ return -EINVAL;
+ if ((provider->mr_read || provider->mr_extend) && !provider->mrs_count)
+ return -EINVAL;
+ for (index = 0; index < provider->mrs_count; index++) {
+ ret = attestation_mr_props_validate(provider, &provider->mrs[index]);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static bool attestation_parent_exists(struct device *parent)
+{
+ struct attestation_dev *att;
+ unsigned long index;
+
+ xa_for_each(&attestation_instances, index, att)
+ if (att->node.dev.parent == parent)
+ return true;
+ return false;
+}
+
+static int attestation_registry_insert(struct attestation_dev *att)
+{
+ unsigned long index;
+ void *old;
+ int ret;
+
+ guard(mutex)(&attestation_registry_lock);
+ if (attestation_next_index > UINT_MAX)
+ return -ENOSPC;
+ index = attestation_next_index;
+ ret = xa_reserve(&attestation_instances, index, GFP_KERNEL);
+ if (ret)
+ return ret;
+
+ xa_lock(&attestation_instances);
+ if (attestation_parent_exists(att->node.dev.parent)) {
+ __xa_erase(&attestation_instances, index);
+ ret = -EEXIST;
+ } else {
+ old = __xa_store(&attestation_instances, index, att, GFP_NOWAIT);
+ ret = xa_err(old);
+ }
+ if (!ret) {
+ att->index = index;
+ attestation_next_index++;
+ }
+ xa_unlock(&attestation_instances);
+ return ret;
+}
+
+static void attestation_registry_remove(struct attestation_dev *att)
+{
+ xa_erase(&attestation_instances, att->index);
+}
+
+bool attestation_registered(struct attestation_dev *att)
+{
+ bool registered;
+
+ lockdep_assert_held(&att->lock);
+ xa_lock(&attestation_instances);
+ registered = xa_get_mark(&attestation_instances, att->index, ATTESTATION_REGISTERED);
+ xa_unlock(&attestation_instances);
+ return registered;
+}
+
+void attestation_stop(struct attestation_dev *att)
+{
+ lockdep_assert_held(&att->lock);
+ xa_lock(&attestation_instances);
+ __xa_clear_mark(&attestation_instances, att->index, ATTESTATION_REGISTERED);
+ xa_unlock(&attestation_instances);
+}
+
+/**
+ * attestation_evidence_write - Write an evidence object to its blob.
+ * @object: the requested object
+ * @data: the object's bytes
+ * @length: their length, at most the object's max_length
+ *
+ * A blob too short for the object fails the operation, and the caller is told
+ * how long it has to be.
+ *
+ * Return: 0, -EMSGSIZE where the blob is too short, or another negative error.
+ */
+int attestation_evidence_write(const struct attestation_evidence_object_request *object,
+ const void *data, size_t length)
+{
+ if (length > object->supported_object->max_length)
+ return -E2BIG;
+ if (length > ctlv_blob_len(object->output))
+ return ctlv_blob_too_short(object->output, length);
+ return ctlv_blob_write(object->output, 0, data, length);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_evidence_write, "ATTESTATION");
+
+void attestation_provide_ops(struct attestation_dev *att)
+{
+ const struct attestation_provider *provider = att->provider;
+ struct ctlv *ctlv = &att->ctlv;
+
+ if (provider->evidence_caps) {
+ ctlv_attestation_evidence_get_provide(ctlv);
+ ctlv_attestation_evidence_get_enable(ctlv);
+ }
+}
+
+/* Runs from the release, so it must not reach the provider's module. */
+static void attestation_state_free(struct attestation_dev *att)
+{
+ mutex_destroy(&att->lock);
+}
+
+static int attestation_op_pre(struct ctlv *ctlv, struct ctlv_op_ctx *ctx)
+{
+ struct attestation_dev *att = attestation_from_ctlv(ctlv);
+
+ mutex_lock(&att->lock);
+ if (!attestation_registered(att)) {
+ mutex_unlock(&att->lock);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void attestation_op_post(struct ctlv *ctlv, struct ctlv_op_ctx *ctx)
+{
+ mutex_unlock(&attestation_from_ctlv(ctlv)->lock);
+}
+
+static const struct ctlv_hooks attestation_ctlv_hooks = {
+ .pre = attestation_op_pre,
+ .post = attestation_op_post,
+};
+
+/* Kernel callers find a device by its mark, and are drained once it is gone. */
+static void attestation_withdraw(struct attestation_dev *att)
+{
+ xa_lock(&attestation_instances);
+ __xa_clear_mark(&attestation_instances, att->index, ATTESTATION_REGISTERED);
+ xa_unlock(&attestation_instances);
+}
+
+static char *attestation_devnode(const struct device *dev, umode_t *mode)
+{
+ return kasprintf(GFP_KERNEL, "attestation/%s", dev_name(dev));
+}
+
+static void attestation_dev_release(struct device *dev)
+{
+ struct attestation_dev *att = container_of(dev, struct attestation_dev, node.dev);
+
+ attestation_state_free(att);
+ ctlv_fini(&att->ctlv);
+ ctlv_device_fini(&att->node);
+ kfree(att);
+}
+
+/*
+ * The core registers the class, so an open holds the core module and not the
+ * provider's, which may unload while readers remain.
+ */
+static struct ctlv_device_class attestation_class = {
+ .class = {
+ .name = "attestation",
+ .dev_release = attestation_dev_release,
+ .devnode = attestation_devnode,
+ },
+ .max_devices = ATTESTATION_MAX_DEVICES,
+};
+
+struct attestation_dev *_attestation_alloc(struct device *parent,
+ const struct attestation_provider *provider,
+ const struct ctlv_family *family, size_t size,
+ size_t offset)
+{
+ struct attestation_dev *att;
+ int ret;
+
+ if (!parent || WARN_ON_ONCE(offset || size < sizeof(*att)) ||
+ !ctlv_family_inherits(family, &ctlv_attestation_family))
+ return ERR_PTR(-EINVAL);
+ ret = attestation_provider_validate(provider, family);
+ if (ret)
+ return ERR_PTR(ret);
+
+ att = kzalloc(size, GFP_KERNEL);
+ if (!att)
+ return ERR_PTR(-ENOMEM);
+ att->provider = provider;
+ mutex_init(&att->lock);
+
+ /* From here on, the release frees whatever was set up. */
+ ret = ctlv_device_init(&att->node, &attestation_class, parent, &att->ctlv);
+ if (!ret)
+ ret = ctlv_init(&att->ctlv, &att->node.dev, family, &attestation_ctlv_hooks);
+ if (ret) {
+ put_device(&att->node.dev);
+ return ERR_PTR(ret);
+ }
+ attestation_provide_ops(att);
+ return att;
+}
+EXPORT_SYMBOL_NS_GPL(_attestation_alloc, "ATTESTATION");
+
+/**
+ * attestation_register - Make an allocated device reachable.
+ * @att: device from attestation_alloc()
+ *
+ * Return: 0, or a negative errno with @att still to be put.
+ */
+int attestation_register(struct attestation_dev *att)
+{
+ int ret;
+
+ ret = attestation_registry_insert(att);
+ if (ret)
+ return ret;
+ ctlv_publish(&att->ctlv);
+ xa_lock(&attestation_instances);
+ __xa_set_mark(&attestation_instances, att->index, ATTESTATION_REGISTERED);
+ xa_unlock(&attestation_instances);
+
+ ret = ctlv_device_add(&att->node);
+ if (ret)
+ goto err_withdraw;
+ return 0;
+
+err_withdraw:
+ attestation_withdraw(att);
+ ctlv_unpublish(&att->ctlv);
+ attestation_registry_remove(att);
+ return ret;
+}
+EXPORT_SYMBOL_NS_GPL(attestation_register, "ATTESTATION");
+
+/**
+ * attestation_unregister - Cut a registered device off from every caller.
+ * @att: registered device
+ *
+ * No provider callback runs once this returns, though open files may still
+ * hold @att.
+ */
+void attestation_unregister(struct attestation_dev *att)
+{
+ /* No new opens, and no node to open. */
+ ctlv_device_del(&att->node);
+ attestation_withdraw(att);
+ ctlv_unpublish(&att->ctlv);
+ attestation_registry_remove(att);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_unregister, "ATTESTATION");
+
+/**
+ * attestation_put - Drop the reference attestation_alloc() returned.
+ * @att: device
+ */
+void attestation_put(struct attestation_dev *att)
+{
+ put_device(&att->node.dev);
+}
+EXPORT_SYMBOL_NS_GPL(attestation_put, "ATTESTATION");
+
+static int __init attestation_module_init(void)
+{
+ return ctlv_device_class_register(&attestation_class);
+}
+
+static void __exit attestation_module_exit(void)
+{
+ WARN_ON_ONCE(!xa_empty(&attestation_instances));
+ xa_destroy(&attestation_instances);
+ ctlv_device_class_unregister(&attestation_class);
+}
+
+module_init(attestation_module_init);
+module_exit(attestation_module_exit);
+MODULE_DESCRIPTION("Attestation core");
+MODULE_LICENSE("GPL");
+MODULE_IMPORT_NS("CTLV");
diff --git a/drivers/attestation/core/internal.h b/drivers/attestation/core/internal.h
new file mode 100644
index 000000000000..f42bc3cd9a35
--- /dev/null
+++ b/drivers/attestation/core/internal.h
@@ -0,0 +1,21 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _ATTESTATION_INTERNAL_H
+#define _ATTESTATION_INTERNAL_H
+
+#include <linux/attestation.h>
+#include <linux/ctlv.h>
+#include <linux/mutex.h>
+#include <linux/xarray.h>
+
+#include "../generated/ctlv_attestation_gen.h"
+
+#define ATTESTATION_REGISTERED XA_MARK_1
+
+bool attestation_registered(struct attestation_dev *att);
+void attestation_stop(struct attestation_dev *att);
+
+int attestation_provider_validate(const struct attestation_provider *provider,
+ const struct ctlv_family *family);
+void attestation_provide_ops(struct attestation_dev *att);
+
+#endif /* _ATTESTATION_INTERNAL_H */
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
new file mode 100644
index 000000000000..29bb776bfb41
--- /dev/null
+++ b/include/linux/attestation.h
@@ -0,0 +1,240 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+#ifndef _LINUX_ATTESTATION_H
+#define _LINUX_ATTESTATION_H
+
+#include <crypto/hash.h>
+#include <linux/build_bug.h>
+#include <linux/ctlv.h>
+#include <linux/ctlv_device.h>
+#include <linux/device.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+
+#include <uapi/ctlv/ctlv_attestation_gen.h>
+#include <uapi/linux/hash_info.h>
+
+struct attestation_dev;
+struct ctlv_blob;
+struct ctlv_family;
+
+enum attestation_evidence_class {
+ ATTESTATION_EVIDENCE_SIGNED = CTLV_ATTESTATION_EVIDENCE_CLASS_SIGNED,
+ ATTESTATION_EVIDENCE_EVENT_LOG = CTLV_ATTESTATION_EVIDENCE_CLASS_EVENT_LOG,
+};
+
+/**
+ * struct attestation_digest - One MR digest.
+ * @algorithm: hash algorithm
+ * @data: digest bytes
+ */
+struct attestation_digest {
+ enum hash_algo algorithm;
+ u8 data[HASH_MAX_DIGESTSIZE];
+};
+
+/**
+ * struct attestation_digest_set - Digests for an MR's banks, in the MR's order.
+ * @values_count: number of digests
+ * @values: digest array
+ */
+struct attestation_digest_set {
+ size_t values_count;
+ struct attestation_digest *values;
+};
+
+/**
+ * struct attestation_mr_props - Immutable MR properties.
+ * @id: provider-unique MR ID
+ * @name: MR name
+ * @algorithms: bank hash algorithms, each once, in the order replies list them
+ * @algorithms_count: number of algorithms
+ * @has_register_index: whether @register_index is valid
+ * @register_index: native register index
+ * @extendable: MR may be extended. Fixed: the provider instead refuses an
+ * extend the platform forbids at the time, e.g. -EPERM for a TPM locality.
+ */
+struct attestation_mr_props {
+ unsigned int id;
+ const char *name;
+ const enum hash_algo *algorithms;
+ size_t algorithms_count;
+ bool has_register_index;
+ unsigned int register_index;
+ bool extendable;
+};
+
+/**
+ * struct attestation_evidence_format - The layout of an evidence object.
+ * @vendor: @value is an evidence-vendor-format value the device's family
+ * gives, not an evidence-format one
+ * @value: the value, such as CTLV_ATTESTATION_EVIDENCE_FORMAT_CCEL
+ */
+struct attestation_evidence_format {
+ bool vendor;
+ u32 value;
+};
+
+/**
+ * struct attestation_evidence_challenge_length - Challenge lengths taken.
+ * @min: shortest challenge bytes, at least 1
+ * @max: longest challenge bytes, @min for one exact length, at most 4096
+ */
+struct attestation_evidence_challenge_length {
+ size_t min;
+ size_t max;
+};
+
+/**
+ * struct attestation_evidence_supported_object - One evidence object.
+ * @evidence_class: what the object is
+ * @formats: the layouts it may have, at most four of each kind
+ * @formats_count: number of formats
+ * @challenge_lengths: a signed object's challenge lengths, ascending and
+ * disjoint; none for any other object
+ * @challenge_lengths_count: number of challenge lengths, at most 8
+ * @max_length: longest the object gets, in bytes
+ * @binds_mrs: the object covers MR values, as they are or by a digest, so
+ * the reply carries mr-generation
+ *
+ * The object's index in the provider's objects is its ID.
+ */
+struct attestation_evidence_supported_object {
+ enum attestation_evidence_class evidence_class;
+ const struct attestation_evidence_format *formats;
+ size_t formats_count;
+ const struct attestation_evidence_challenge_length *challenge_lengths;
+ size_t challenge_lengths_count;
+ size_t max_length;
+ bool binds_mrs;
+};
+
+/**
+ * struct attestation_evidence_caps - Immutable evidence capabilities.
+ * @objects: supported evidence objects, at most 16
+ * @objects_count: number of objects
+ */
+struct attestation_evidence_caps {
+ const struct attestation_evidence_supported_object *objects;
+ size_t objects_count;
+};
+
+/**
+ * struct attestation_evidence_object_request - One resolved output object.
+ * @supported_object: the object requested
+ * @output: resolved output blob
+ */
+struct attestation_evidence_object_request {
+ const struct attestation_evidence_supported_object *supported_object;
+ struct ctlv_blob *output;
+};
+
+/**
+ * struct attestation_evidence_request - Validated evidence request.
+ * @challenge: challenge bytes, of a length every signed object requested
+ * takes, or NULL where the request names no signed object
+ * @challenge_length: challenge length, 0 without a challenge
+ * @objects: requested output objects
+ * @objects_count: number of objects
+ */
+struct attestation_evidence_request {
+ const u8 *challenge;
+ size_t challenge_length;
+ const struct attestation_evidence_object_request *objects;
+ size_t objects_count;
+};
+
+/**
+ * struct attestation_evidence_object_result - What a provider made of one object.
+ * @format: the layout it came in, one of the object's formats
+ * @length: bytes written
+ */
+struct attestation_evidence_object_result {
+ struct attestation_evidence_format format;
+ size_t length;
+};
+
+/**
+ * struct attestation_evidence_result - Provider evidence result.
+ * @objects: one for every requested object, in request order
+ */
+struct attestation_evidence_result {
+ struct attestation_evidence_object_result *objects;
+};
+
+/**
+ * struct attestation_provider - Immutable provider operations and properties.
+ * @evidence_get: produce requested evidence, each object written with
+ * attestation_evidence_write() and its result filled in
+ * @mr_read: read one MR; every MR can be read, so required with @mrs
+ * @mr_extend: extend the banks of one MR it gets digests for, leaving the
+ * others, or fail with -EOPNOTSUPP before changing any if it cannot
+ * @evidence_caps: evidence capabilities, or NULL
+ * @mrs: fixed MR properties
+ * @mrs_count: number of fixed MRs
+ */
+struct attestation_provider {
+ int (*evidence_get)(struct attestation_dev *att,
+ const struct attestation_evidence_request *request,
+ struct attestation_evidence_result *result);
+ int (*mr_read)(struct attestation_dev *att, unsigned int mr_id,
+ struct attestation_digest_set *values);
+ int (*mr_extend)(struct attestation_dev *att, unsigned int mr_id,
+ const struct attestation_digest_set *values);
+ const struct attestation_evidence_caps *evidence_caps;
+ const struct attestation_mr_props *mrs;
+ size_t mrs_count;
+};
+
+/**
+ * struct attestation_dev - One attestation device.
+ * @node: the device's node, whose release frees the driver's struct embedding
+ * this
+ *
+ * A driver embeds this first in its own struct, allocated by
+ * attestation_alloc(), and reaches that struct with container_of().
+ */
+struct attestation_dev {
+ struct ctlv_device node;
+ /* private: */
+ struct ctlv ctlv;
+ const struct attestation_provider *provider;
+ unsigned int index;
+
+ /* Mutable instance state. */
+ struct mutex lock;
+
+};
+
+/* What a CTLV callback of a family inheriting ctlv-attestation was handed. */
+static inline struct attestation_dev *attestation_from_ctlv(struct ctlv *ctlv)
+{
+ return container_of(ctlv, struct attestation_dev, ctlv);
+}
+
+struct attestation_dev *_attestation_alloc(struct device *parent,
+ const struct attestation_provider *provider,
+ const struct ctlv_family *family, size_t size,
+ size_t offset);
+
+/**
+ * attestation_alloc - Allocate a driver struct embedding an attestation device.
+ * @parent: the device attested
+ * @provider: provider operations and properties, which must outlive the device
+ * @family: the driver's CTLV family, which inherits ctlv-attestation
+ * @type: the driver's struct
+ * @member: its struct attestation_dev, the first member
+ *
+ * Return: the driver's struct, released by attestation_put(), or an error
+ * pointer.
+ */
+#define attestation_alloc(parent, provider, family, type, member) \
+ ((type *)_attestation_alloc(parent, provider, family, sizeof(type), \
+ offsetof(type, member)))
+
+int attestation_register(struct attestation_dev *att);
+void attestation_unregister(struct attestation_dev *att);
+void attestation_put(struct attestation_dev *att);
+
+int attestation_evidence_write(const struct attestation_evidence_object_request *object,
+ const void *data, size_t length);
+#endif /* _LINUX_ATTESTATION_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC v2 4/9] attestation: Prepare measurement registers
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
` (2 preceding siblings ...)
2026-11-13 16:08 ` [PATCH RFC v2 3/9] attestation: Prepare provider and instance core Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 5/9] attestation: Add CTLV operation handling Jiri Pirko
` (4 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Add the fixed MRs from the provider descriptor, their lookup and MR
extension, which advances an instance-wide MR generation.
The MR objects can be compile-checked here. CONFIG_ATTESTATION remains
unwired until the CTLV operation handlers complete the module.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- kept the MR state in struct attestation_dev, which providers embed
- dropped the MR journal and its notifications
- counted MR changes in a plain MR generation
- retitled from "Prepare measurement register journal"
- used the helpers generated for the rewritten spec
- let an extend carry values for some of an MR's banks
- always provided mr-read with MRs
---
drivers/attestation/Makefile | 2 +-
drivers/attestation/core/core.c | 71 +++++++++++++++++++++++++++++
drivers/attestation/core/internal.h | 13 ++++++
drivers/attestation/core/mr.c | 64 ++++++++++++++++++++++++++
include/linux/attestation.h | 5 +-
5 files changed, 153 insertions(+), 2 deletions(-)
create mode 100644 drivers/attestation/core/mr.c
diff --git a/drivers/attestation/Makefile b/drivers/attestation/Makefile
index 8a75558a7013..28036586f5d8 100644
--- a/drivers/attestation/Makefile
+++ b/drivers/attestation/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ATTESTATION) += attestation.o
-attestation-y := core/core.o
+attestation-y := core/core.o core/mr.o
diff --git a/drivers/attestation/core/core.c b/drivers/attestation/core/core.c
index 587a0a9616b6..24abe0923306 100644
--- a/drivers/attestation/core/core.c
+++ b/drivers/attestation/core/core.c
@@ -213,6 +213,12 @@ bool attestation_registered(struct attestation_dev *att)
return registered;
}
+void attestation_mr_changed(struct attestation_dev *att)
+{
+ lockdep_assert_held(&att->lock);
+ att->mr_generation++;
+}
+
void attestation_stop(struct attestation_dev *att)
{
lockdep_assert_held(&att->lock);
@@ -221,6 +227,38 @@ void attestation_stop(struct attestation_dev *att)
xa_unlock(&attestation_instances);
}
+static struct attestation_mr *attestation_mr_alloc(struct attestation_dev *att,
+ const struct attestation_mr_props *props)
+{
+ struct attestation_mr *mr;
+ int ret;
+
+ ret = attestation_mr_props_validate(att->provider, props);
+ if (ret)
+ return ERR_PTR(ret);
+ if (xa_load(&att->mrs, props->id))
+ return ERR_PTR(-EEXIST);
+ mr = kzalloc_obj(*mr, GFP_KERNEL);
+ if (!mr)
+ return ERR_PTR(-ENOMEM);
+ mr->props = props;
+ ret = xa_insert(&att->mrs, props->id, mr, GFP_KERNEL);
+ if (ret) {
+ kfree(mr);
+ return ERR_PTR(ret);
+ }
+ return mr;
+}
+
+struct attestation_mr *attestation_mr_lookup_live(struct attestation_dev *att, unsigned int mr_id)
+{
+ struct attestation_mr *mr;
+
+ lockdep_assert_held(&att->lock);
+ mr = xa_load(&att->mrs, mr_id);
+ return mr ? mr : ERR_PTR(-ENOENT);
+}
+
/**
* attestation_evidence_write - Write an evidence object to its blob.
* @object: the requested object
@@ -252,11 +290,41 @@ void attestation_provide_ops(struct attestation_dev *att)
ctlv_attestation_evidence_get_provide(ctlv);
ctlv_attestation_evidence_get_enable(ctlv);
}
+
+ if (provider->mrs_count) {
+ ctlv_attestation_mr_get_provide(ctlv);
+ ctlv_attestation_mr_read_provide(ctlv);
+ if (provider->mr_extend)
+ ctlv_attestation_mr_extend_provide(ctlv);
+ }
+}
+
+static int attestation_mrs_create(struct attestation_dev *att)
+{
+ const struct attestation_provider *provider = att->provider;
+ size_t index;
+
+ for (index = 0; index < provider->mrs_count; index++) {
+ struct attestation_mr *mr;
+
+ mr = attestation_mr_alloc(att, &provider->mrs[index]);
+ if (IS_ERR(mr))
+ return PTR_ERR(mr);
+ }
+ return 0;
}
/* Runs from the release, so it must not reach the provider's module. */
static void attestation_state_free(struct attestation_dev *att)
{
+ struct attestation_mr *mr;
+ unsigned long index;
+
+ xa_for_each(&att->mrs, index, mr) {
+ xa_erase(&att->mrs, index);
+ kfree(mr);
+ }
+ xa_destroy(&att->mrs);
mutex_destroy(&att->lock);
}
@@ -338,11 +406,14 @@ struct attestation_dev *_attestation_alloc(struct device *parent,
return ERR_PTR(-ENOMEM);
att->provider = provider;
mutex_init(&att->lock);
+ xa_init(&att->mrs);
/* From here on, the release frees whatever was set up. */
ret = ctlv_device_init(&att->node, &attestation_class, parent, &att->ctlv);
if (!ret)
ret = ctlv_init(&att->ctlv, &att->node.dev, family, &attestation_ctlv_hooks);
+ if (!ret)
+ ret = attestation_mrs_create(att);
if (ret) {
put_device(&att->node.dev);
return ERR_PTR(ret);
diff --git a/drivers/attestation/core/internal.h b/drivers/attestation/core/internal.h
index f42bc3cd9a35..19c2ade5e6db 100644
--- a/drivers/attestation/core/internal.h
+++ b/drivers/attestation/core/internal.h
@@ -11,11 +11,24 @@
#define ATTESTATION_REGISTERED XA_MARK_1
+struct attestation_mr {
+ const struct attestation_mr_props *props;
+};
+
bool attestation_registered(struct attestation_dev *att);
void attestation_stop(struct attestation_dev *att);
+void attestation_mr_changed(struct attestation_dev *att);
int attestation_provider_validate(const struct attestation_provider *provider,
const struct ctlv_family *family);
void attestation_provide_ops(struct attestation_dev *att);
+struct attestation_mr *attestation_mr_lookup_live(struct attestation_dev *att, unsigned int mr_id);
+int attestation_mr_values_validate(const struct attestation_mr_props *props,
+ const struct attestation_digest_set *values, bool all_banks);
+int attestation_mr_extend_locked(struct attestation_dev *att, struct attestation_mr *mr,
+ const struct attestation_digest_set *values);
+
+int attestation_mr_props_encode(struct ctlv_cursor *reply, const struct attestation_mr *mr);
+
#endif /* _ATTESTATION_INTERNAL_H */
diff --git a/drivers/attestation/core/mr.c b/drivers/attestation/core/mr.c
new file mode 100644
index 000000000000..44a8c06df0c6
--- /dev/null
+++ b/drivers/attestation/core/mr.c
@@ -0,0 +1,64 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <linux/device.h>
+#include <linux/errno.h>
+#include <linux/limits.h>
+
+#include "internal.h"
+
+/* @values follow the banks' order, so each bank comes once at most. */
+int attestation_mr_values_validate(const struct attestation_mr_props *props,
+ const struct attestation_digest_set *values, bool all_banks)
+{
+ size_t bank = 0, index;
+
+ if (!values || !values->values || !values->values_count ||
+ (all_banks && values->values_count != props->algorithms_count))
+ return -EINVAL;
+ for (index = 0; index < values->values_count; index++, bank++) {
+ while (bank < props->algorithms_count &&
+ props->algorithms[bank] != values->values[index].algorithm)
+ bank++;
+ if (bank == props->algorithms_count)
+ return -EINVAL;
+ }
+ return 0;
+}
+
+int attestation_mr_extend_locked(struct attestation_dev *att, struct attestation_mr *mr,
+ const struct attestation_digest_set *values)
+{
+ int ret;
+
+ lockdep_assert_held(&att->lock);
+ if (att->mr_generation == UINT_MAX) {
+ dev_warn(att->node.dev.parent,
+ "attestation instance %u stopped: mr generation exhausted\n",
+ att->index);
+ attestation_stop(att);
+ return -EOVERFLOW;
+ }
+ ret = att->provider->mr_extend(att, mr->props->id, values);
+ if (ret)
+ return ret;
+ attestation_mr_changed(att);
+ return 0;
+}
+
+int attestation_mr_props_encode(struct ctlv_cursor *reply, const struct attestation_mr *mr)
+{
+ u64 caps = 0;
+ size_t index;
+ bool ok = true;
+
+ ok &= ctlv_attestation_mr_get_items_id_put(reply, mr->props->id);
+ ok &= ctlv_attestation_mr_desc_name_put(reply, mr->props->name);
+ for (index = 0; index < mr->props->algorithms_count; index++)
+ ok &= ctlv_attestation_mr_desc_supported_hash_algorithms_put(
+ reply, mr->props->algorithms[index]);
+ if (mr->props->has_register_index)
+ ok &= ctlv_attestation_mr_desc_register_index_put(reply, mr->props->register_index);
+ if (mr->props->extendable)
+ caps |= CTLV_ATTESTATION_MR_CAPS_EXTEND;
+ ok &= ctlv_attestation_mr_desc_caps_put(reply, caps);
+ return ok ? 0 : -EMSGSIZE;
+}
diff --git a/include/linux/attestation.h b/include/linux/attestation.h
index 29bb776bfb41..7745938092c7 100644
--- a/include/linux/attestation.h
+++ b/include/linux/attestation.h
@@ -9,6 +9,7 @@
#include <linux/device.h>
#include <linux/mutex.h>
#include <linux/types.h>
+#include <linux/xarray.h>
#include <uapi/ctlv/ctlv_attestation_gen.h>
#include <uapi/linux/hash_info.h>
@@ -200,9 +201,11 @@ struct attestation_dev {
const struct attestation_provider *provider;
unsigned int index;
- /* Mutable instance state. */
+ /* Mutable instance and MR state. */
struct mutex lock;
+ unsigned int mr_generation;
+ struct xarray mrs;
};
/* What a CTLV callback of a family inheriting ctlv-attestation was handed. */
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC v2 5/9] attestation: Add CTLV operation handling
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
` (3 preceding siblings ...)
2026-11-13 16:08 ` [PATCH RFC v2 4/9] attestation: Prepare measurement registers Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 6/9] Documentation: attestation: Describe how to write a provider Jiri Pirko
` (3 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Translate CTLV evidence and MR operations to the provider core and wire
the attestation family into the kernel build.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- handed the op handlers the CTLV instance
- put resolved blobs on evidence decode errors
- continued an MR dump past an MR that does not fit
- dropped the MR log query
- dropped the total output length check and its E2BIG
- reported each evidence object's class and formats
- reported each signed object's challenge lengths as a list, and took
a challenge of a length every signed object named lists
- left the MR ID and hash algorithm bounds to CTLV
- used the helpers generated for the rewritten spec
- let evidence-get omit the challenge
- took output blobs of any length
- separated the steps of the longer functions
- let mr-extend extend some of an MR's banks, in any order
- replied to mr-read in the provider's bank order
- dropped the MR read caps, as every MR can be read
- said record instead of nest, the root being one
- took evidence-get's objects by ID, and replied with each one's
format and length
---
drivers/Kconfig | 2 +
drivers/Makefile | 1 +
drivers/attestation/Makefile | 3 +-
drivers/attestation/core/ctlv.c | 499 ++++++++++++++++++++++++++++++++
4 files changed, 504 insertions(+), 1 deletion(-)
create mode 100644 drivers/attestation/core/ctlv.c
diff --git a/drivers/Kconfig b/drivers/Kconfig
index f2bed2ddeb66..5458886b0110 100644
--- a/drivers/Kconfig
+++ b/drivers/Kconfig
@@ -25,6 +25,8 @@ source "drivers/cache/Kconfig"
source "drivers/connector/Kconfig"
+source "drivers/attestation/Kconfig"
+
source "drivers/firmware/Kconfig"
source "drivers/fwctl/Kconfig"
diff --git a/drivers/Makefile b/drivers/Makefile
index 0841ea851847..597db8c49da9 100644
--- a/drivers/Makefile
+++ b/drivers/Makefile
@@ -135,6 +135,7 @@ obj-$(CONFIG_MEMSTICK) += memstick/
obj-$(CONFIG_INFINIBAND) += infiniband/
obj-y += firmware/
obj-$(CONFIG_FWCTL) += fwctl/
+obj-$(CONFIG_ATTESTATION) += attestation/
obj-$(CONFIG_CRYPTO) += crypto/
obj-$(CONFIG_SUPERH) += sh/
obj-y += clocksource/
diff --git a/drivers/attestation/Makefile b/drivers/attestation/Makefile
index 28036586f5d8..e4bff7633d18 100644
--- a/drivers/attestation/Makefile
+++ b/drivers/attestation/Makefile
@@ -1,4 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ATTESTATION) += attestation.o
-attestation-y := core/core.o core/mr.o
+attestation-y := core/core.o core/ctlv.o core/mr.o \
+ generated/ctlv_attestation_gen.o
diff --git a/drivers/attestation/core/ctlv.c b/drivers/attestation/core/ctlv.c
new file mode 100644
index 000000000000..5299649a6066
--- /dev/null
+++ b/drivers/attestation/core/ctlv.c
@@ -0,0 +1,499 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <crypto/hash_info.h>
+#include <linux/bitmap.h>
+#include <linux/cleanup.h>
+#include <linux/err.h>
+#include <linux/limits.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "internal.h"
+
+static bool attestation_challenge_length_put(struct ctlv_cursor *object, size_t min, size_t max)
+{
+ struct ctlv_cursor entry;
+ bool ok;
+
+ if (!ctlv_attestation_device_get_objects_challenge_lengths_start(object, &entry))
+ return false;
+ if (min == max) {
+ ok = ctlv_attestation_device_get_challenge_lengths_length_put(&entry, min);
+ } else {
+ ok = ctlv_attestation_device_get_challenge_lengths_min_length_put(&entry, min);
+ ok &= ctlv_attestation_device_get_challenge_lengths_max_length_put(&entry, max);
+ }
+ ctlv_record_end(&entry);
+ return ok;
+}
+
+static bool attestation_evidence_object_put(struct ctlv_cursor *object, size_t id,
+ const struct attestation_evidence_supported_object *cap)
+{
+ size_t index;
+ bool ok;
+
+ ok = ctlv_attestation_device_get_objects_id_put(object, id);
+ ok &= ctlv_attestation_device_get_objects_class_put(object, cap->evidence_class);
+ for (index = 0; index < cap->formats_count; index++) {
+ u32 value = cap->formats[index].value;
+
+ if (cap->formats[index].vendor)
+ ok &= ctlv_attestation_device_get_objects_vendor_formats_put(object, value);
+ else
+ ok &= ctlv_attestation_device_get_objects_formats_put(object, value);
+ }
+ ok &= ctlv_attestation_device_get_objects_max_length_put(object, cap->max_length);
+ for (index = 0; index < cap->challenge_lengths_count; index++)
+ ok &= attestation_challenge_length_put(object, cap->challenge_lengths[index].min,
+ cap->challenge_lengths[index].max);
+ return ok;
+}
+
+static int attestation_device_caps_put(struct attestation_dev *att, struct ctlv_cursor *reply)
+{
+ const struct attestation_provider *provider = att->provider;
+ struct ctlv_cursor caps_record, group, object;
+ bool ok = true;
+ size_t index;
+ u64 flags;
+
+ if (!ctlv_attestation_device_get_caps_start(reply, &caps_record))
+ return -EMSGSIZE;
+
+ if (provider->evidence_caps) {
+ const struct attestation_evidence_caps *caps = provider->evidence_caps;
+
+ if (!ctlv_attestation_device_get_caps_evidence_start(&caps_record, &group))
+ return -EMSGSIZE;
+
+ for (index = 0; index < caps->objects_count; index++) {
+ if (!ctlv_attestation_device_get_evidence_objects_start(&group, &object)) {
+ ok = false;
+ break;
+ }
+ ok &= attestation_evidence_object_put(&object, index,
+ &caps->objects[index]);
+ ctlv_record_end(&object);
+ }
+ ctlv_record_end(&group);
+ }
+
+ if (provider->mrs_count) {
+ if (!ctlv_attestation_device_get_caps_mr_start(&caps_record, &group))
+ return -EMSGSIZE;
+
+ flags = 0;
+ if (provider->mr_extend)
+ flags |= CTLV_ATTESTATION_MR_DEVICE_CAPS_EXTEND;
+ ok &= ctlv_attestation_device_get_mr_flags_put(&group, flags);
+ ctlv_record_end(&group);
+ }
+ ctlv_record_end(&caps_record);
+ return ok ? 0 : -EMSGSIZE;
+}
+
+int ctlv_attestation_device_get_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+ struct attestation_dev *att = attestation_from_ctlv(ctlv);
+ struct ctlv_cursor info;
+ int ret;
+
+ ret = attestation_device_caps_put(att, reply);
+ if (ret)
+ return ret;
+ if (!ctlv_attestation_device_get_info_start(reply, &info))
+ return -EMSGSIZE;
+ ret = ctlv_ext_fill(ctx, request, &info);
+ ctlv_record_end(&info);
+ return ret;
+}
+
+static void attestation_evidence_request_free(struct attestation_evidence_object_request *objects,
+ size_t count)
+{
+ while (count) {
+ count--;
+ if (objects[count].output)
+ ctlv_blob_put(objects[count].output);
+ }
+ kfree(objects);
+}
+
+static int
+attestation_evidence_decode_objects(struct attestation_dev *att, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request,
+ struct attestation_evidence_request *decoded,
+ struct attestation_evidence_object_request **objects_out)
+{
+ const struct attestation_evidence_caps *caps = att->provider->evidence_caps;
+ struct attestation_evidence_object_request *objects;
+ struct ctlv_cursor object;
+ size_t count = 0, index = 0, previous;
+ int ret;
+
+ ctlv_attestation_evidence_get_objects_for_each(&object, request) count++;
+ objects = kcalloc(count, sizeof(*objects), GFP_KERNEL);
+ if (!objects)
+ return -ENOMEM;
+
+ ctlv_attestation_evidence_get_objects_for_each(&object, request)
+ {
+ const struct attestation_evidence_supported_object *supported;
+ u64 id;
+
+ id = ctlv_attestation_evidence_get_objects_id_get(&object);
+ if (id >= caps->objects_count) {
+ ret = -ENOENT;
+ goto err_free;
+ }
+ supported = &caps->objects[id];
+ for (previous = 0; previous < index; previous++)
+ if (objects[previous].supported_object == supported) {
+ ret = -EINVAL;
+ goto err_free;
+ }
+
+ objects[index].supported_object = supported;
+ objects[index].output =
+ ctlv_attestation_evidence_get_objects_data_resolve(ctx, &object);
+ if (IS_ERR(objects[index].output)) {
+ ret = PTR_ERR(objects[index].output);
+ objects[index].output = NULL;
+ goto err_free;
+ }
+ index++;
+ }
+
+ decoded->objects = objects;
+ decoded->objects_count = count;
+ *objects_out = objects;
+ return 0;
+
+err_free:
+ attestation_evidence_request_free(objects, count);
+ return ret;
+}
+
+static bool
+attestation_challenge_length_supported(const struct attestation_evidence_supported_object *object,
+ size_t length)
+{
+ size_t index;
+
+ for (index = 0; index < object->challenge_lengths_count; index++) {
+ if (length >= object->challenge_lengths[index].min &&
+ length <= object->challenge_lengths[index].max)
+ return true;
+ }
+ return false;
+}
+
+/* A challenge goes with signed objects only, and has to suit each. */
+static int attestation_evidence_challenge_check(const struct attestation_evidence_request *decoded)
+{
+ bool signed_named = false;
+ size_t index;
+
+ for (index = 0; index < decoded->objects_count; index++) {
+ const struct attestation_evidence_supported_object *object =
+ decoded->objects[index].supported_object;
+
+ if (object->evidence_class != ATTESTATION_EVIDENCE_SIGNED)
+ continue;
+ signed_named = true;
+ if (!decoded->challenge ||
+ !attestation_challenge_length_supported(object, decoded->challenge_length))
+ return -EINVAL;
+ }
+ return !signed_named && decoded->challenge ? -EINVAL : 0;
+}
+
+static int attestation_evidence_request_decode(struct attestation_dev *att, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request,
+ struct attestation_evidence_request *decoded,
+ struct attestation_evidence_object_request **objects)
+{
+ u32 challenge_length;
+ int ret;
+
+ if (ctlv_attestation_evidence_get_challenge_present(request)) {
+ decoded->challenge =
+ ctlv_attestation_evidence_get_challenge_get(request, &challenge_length);
+ decoded->challenge_length = challenge_length;
+ }
+
+ ret = attestation_evidence_decode_objects(att, ctx, request, decoded, objects);
+ if (ret)
+ return ret;
+ return attestation_evidence_challenge_check(decoded);
+}
+
+static bool attestation_format_in(const struct attestation_evidence_supported_object *object,
+ const struct attestation_evidence_format *format)
+{
+ size_t index;
+
+ for (index = 0; index < object->formats_count; index++)
+ if (object->formats[index].vendor == format->vendor &&
+ object->formats[index].value == format->value)
+ return true;
+ return false;
+}
+
+static bool
+attestation_evidence_result_put(struct ctlv_cursor *reply,
+ const struct attestation_evidence_supported_object *object,
+ const struct attestation_evidence_object_result *produced)
+{
+ u32 value = produced->format.value;
+ struct ctlv_cursor entry;
+ bool ok;
+
+ if (!ctlv_attestation_evidence_get_results_start(reply, &entry))
+ return false;
+ if (produced->format.vendor)
+ ok = ctlv_attestation_evidence_get_results_vendor_format_put(&entry, value);
+ else
+ ok = ctlv_attestation_evidence_get_results_format_put(&entry, value);
+ ok &= ctlv_attestation_evidence_get_results_length_put(&entry, produced->length);
+ ctlv_record_end(&entry);
+ return ok;
+}
+
+int ctlv_attestation_evidence_get_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request,
+ struct ctlv_cursor *reply)
+{
+ struct attestation_evidence_object_request *objects = NULL;
+ struct attestation_evidence_request decoded = {};
+ struct attestation_evidence_result result = {};
+ struct attestation_dev *att = attestation_from_ctlv(ctlv);
+ bool mr_dependent = false;
+ size_t index;
+ int put_ret;
+ int ret;
+
+ ret = attestation_evidence_request_decode(att, ctx, request, &decoded, &objects);
+ if (ret)
+ goto out;
+
+ result.objects = kcalloc(decoded.objects_count, sizeof(*result.objects), GFP_KERNEL);
+ if (!result.objects) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ for (index = 0; index < decoded.objects_count; index++)
+ mr_dependent |= decoded.objects[index].supported_object->binds_mrs;
+ ret = att->provider->evidence_get(att, &decoded, &result);
+ if (ret)
+ goto out;
+
+ for (index = 0; index < decoded.objects_count; index++) {
+ const struct attestation_evidence_object_result *produced = &result.objects[index];
+
+ if (produced->length > decoded.objects[index].supported_object->max_length ||
+ produced->length > ctlv_blob_len(decoded.objects[index].output)) {
+ ret = -E2BIG;
+ goto out;
+ }
+ if (!attestation_format_in(decoded.objects[index].supported_object,
+ &produced->format)) {
+ ret = -EPROTO;
+ goto out;
+ }
+ }
+
+ for (index = 0; index < decoded.objects_count; index++) {
+ put_ret = ctlv_blob_put(decoded.objects[index].output);
+ objects[index].output = NULL;
+ if (!ret)
+ ret = put_ret;
+ }
+ if (ret)
+ goto out;
+ if (mr_dependent && !ctlv_attestation_mr_generation_put(reply, att->mr_generation)) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+ for (index = 0; index < decoded.objects_count; index++)
+ if (!attestation_evidence_result_put(reply, decoded.objects[index].supported_object,
+ &result.objects[index])) {
+ ret = -EMSGSIZE;
+ goto out;
+ }
+out:
+ kfree(result.objects);
+ attestation_evidence_request_free(objects, decoded.objects_count);
+ return ret;
+}
+
+int ctlv_attestation_mr_get_query(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+ struct attestation_dev *att = attestation_from_ctlv(ctlv);
+ struct attestation_mr *mr;
+ struct ctlv_cursor inner;
+ unsigned long id, first = 0, last = UINT_MAX;
+ u64 cursor = ctlv_op_query_cursor(ctx);
+ unsigned int generation = att->mr_generation;
+ bool progress = false;
+ int ret;
+
+ if (cursor && ctlv_op_query_generation(ctx) != generation)
+ return -ESTALE;
+ if (ctlv_attestation_mr_get_id_present(request)) {
+ first = ctlv_attestation_mr_get_id_get(request);
+ mr = attestation_mr_lookup_live(att, first);
+ if (IS_ERR(mr))
+ return PTR_ERR(mr);
+ last = first;
+ } else if (cursor) {
+ first = cursor - 1;
+ }
+
+ xa_for_each_range(&att->mrs, id, mr, first, last) {
+ if (!ctlv_attestation_mr_get_items_start(reply, &inner))
+ break;
+ ret = attestation_mr_props_encode(&inner, mr);
+ if (ret == -EMSGSIZE) {
+ ctlv_record_cancel(&inner);
+ break;
+ }
+ ctlv_record_end(&inner);
+ if (ret)
+ return ret;
+ progress = true;
+ }
+
+ if (!mr)
+ return ctlv_op_query_done(ctx, generation);
+ if (!progress)
+ return -EMSGSIZE;
+ return ctlv_op_query_more(ctx, generation, id + 1);
+}
+
+static int attestation_mr_values_put(struct ctlv_cursor *reply,
+ const struct attestation_digest_set *values)
+{
+ struct ctlv_cursor inner;
+ size_t index;
+ bool ok;
+
+ for (index = 0; index < values->values_count; index++) {
+ const struct attestation_digest *value = &values->values[index];
+ enum hash_algo algorithm = value->algorithm;
+ size_t length = hash_digest_size[algorithm];
+
+ if (!ctlv_attestation_mr_values_start(reply, &inner))
+ return -EMSGSIZE;
+ ok = ctlv_attestation_mr_value_hash_algo_put(&inner, algorithm);
+ ok &= ctlv_attestation_mr_value_data_put(&inner, value->data, length);
+ ctlv_record_end(&inner);
+ if (!ok)
+ return -EMSGSIZE;
+ }
+ return 0;
+}
+
+int ctlv_attestation_mr_read_query(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+ struct attestation_digest_set values = {};
+ struct attestation_dev *att = attestation_from_ctlv(ctlv);
+ struct attestation_mr *mr;
+ u64 id;
+ int ret;
+
+ if (ctlv_op_query_cursor(ctx) &&
+ ctlv_op_query_generation(ctx) != att->mr_generation)
+ return -ESTALE;
+ id = ctlv_attestation_mr_read_id_get(request);
+ mr = attestation_mr_lookup_live(att, id);
+ if (IS_ERR(mr))
+ return PTR_ERR(mr);
+
+ values.values_count = mr->props->algorithms_count;
+ values.values = kcalloc(values.values_count, sizeof(*values.values), GFP_KERNEL);
+ if (!values.values)
+ return -ENOMEM;
+ for (id = 0; id < values.values_count; id++)
+ values.values[id].algorithm = mr->props->algorithms[id];
+ ret = att->provider->mr_read(att, mr->props->id, &values);
+ if (!ret)
+ ret = attestation_mr_values_validate(mr->props, &values, true);
+ if (!ret && !ctlv_attestation_mr_generation_put(reply, att->mr_generation))
+ ret = -EMSGSIZE;
+ if (!ret)
+ ret = attestation_mr_values_put(reply, &values);
+ kfree(values.values);
+ if (ret)
+ return ret;
+ return ctlv_op_query_done(ctx, att->mr_generation);
+}
+
+static int attestation_mr_extend_decode(const struct ctlv_cursor *request,
+ const struct attestation_mr *mr,
+ struct attestation_digest_set *values)
+{
+ size_t banks = mr->props->algorithms_count, bank;
+ DECLARE_BITMAP(seen, HASH_ALGO__LAST) = {};
+ struct ctlv_cursor item;
+
+ values->values = kcalloc(banks, sizeof(*values->values), GFP_KERNEL);
+ if (!values->values)
+ return -ENOMEM;
+ ctlv_attestation_mr_values_for_each(&item, request)
+ {
+ u64 algorithm = ctlv_attestation_mr_value_hash_algo_get(&item);
+ struct attestation_digest *value;
+ const void *data;
+ u32 length;
+
+ for (bank = 0; bank < banks; bank++)
+ if (mr->props->algorithms[bank] == algorithm)
+ break;
+ data = ctlv_attestation_mr_value_data_get(&item, &length);
+ if (bank == banks || __test_and_set_bit(bank, seen) ||
+ length != hash_digest_size[algorithm])
+ return -EINVAL;
+ value = &values->values[bank];
+ value->algorithm = algorithm;
+ memcpy(value->data, data, length);
+ }
+ /* Close up the banks left out. */
+ for_each_set_bit(bank, seen, banks)
+ values->values[values->values_count++] = values->values[bank];
+ return 0;
+}
+
+int ctlv_attestation_mr_extend_action(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request, struct ctlv_cursor *reply)
+{
+ struct attestation_digest_set values = {};
+ struct attestation_dev *att = attestation_from_ctlv(ctlv);
+ struct attestation_mr *mr;
+ u64 id;
+ int ret;
+
+ id = ctlv_attestation_mr_extend_id_get(request);
+ mr = attestation_mr_lookup_live(att, id);
+ if (IS_ERR(mr))
+ return PTR_ERR(mr);
+
+ if (!mr->props->extendable)
+ return -EOPNOTSUPP;
+ ret = attestation_mr_extend_decode(request, mr, &values);
+ if (ret)
+ goto out_values;
+ ret = attestation_mr_values_validate(mr->props, &values, false);
+ if (ret)
+ goto out_values;
+ ret = attestation_mr_extend_locked(att, mr, &values);
+ if (!ret && !ctlv_attestation_mr_generation_put(reply, att->mr_generation))
+ ret = -EMSGSIZE;
+out_values:
+ kfree(values.values);
+ return ret;
+}
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC v2 6/9] Documentation: attestation: Describe how to write a provider
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
` (4 preceding siblings ...)
2026-11-13 16:08 ` [PATCH RFC v2 5/9] attestation: Add CTLV operation handling Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 7/9] tdx-guest: Expose the native attestation service API Jiri Pirko
` (2 subsequent siblings)
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Describe how a provider registers with the attestation core, how other
writers share its MRs and how it is torn down, with the kernel-doc of
the provider API.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- split out of the provider and instance core patch
- dropped the journal from the overview
- said how other writers extend the same MRs
- added the kernel-doc to the driver API page
---
Documentation/driver-api/attestation.rst | 99 +++++++++++++++++++++
Documentation/driver-api/index.rst | 1 +
Documentation/userspace-api/attestation.rst | 3 +-
MAINTAINERS | 1 +
4 files changed, 103 insertions(+), 1 deletion(-)
create mode 100644 Documentation/driver-api/attestation.rst
diff --git a/Documentation/driver-api/attestation.rst b/Documentation/driver-api/attestation.rst
new file mode 100644
index 000000000000..4137a856aea9
--- /dev/null
+++ b/Documentation/driver-api/attestation.rst
@@ -0,0 +1,99 @@
+.. SPDX-License-Identifier: GPL-2.0-only
+
+===========
+Attestation
+===========
+
+The attestation subsystem gives each provider device a node of the
+``attestation`` class, ``/dev/attestation/attestationN``, which answers as a
+descendant of the ``ctlv-attestation`` family, one family per provider. A
+program tells providers apart by the last entry of ``family-get`` on the
+open device, and generic tools use the ``ctlv-attestation`` operations on any
+of them. The subsystem owns common evidence retrieval and
+measurement-register topology. Providers retain ownership of native hardware
+access and immutable capability descriptions.
+
+Registering a provider
+======================
+
+A provider embeds ``struct attestation_dev`` first in its own structure and
+allocates both with ``attestation_alloc()``, passing its
+``struct attestation_provider`` and its family descriptor, which inherits
+``ctlv-attestation``:
+
+.. code-block:: c
+
+ struct foo {
+ struct attestation_dev att;
+ struct foo_hw *hw;
+ };
+
+ foo = attestation_alloc(parent, &foo_provider,
+ &ctlv_attestation_foo_family, struct foo, att);
+ if (IS_ERR(foo))
+ return PTR_ERR(foo);
+ foo->hw = hw;
+ ret = attestation_register(&foo->att);
+ if (ret) {
+ attestation_put(&foo->att);
+ return ret;
+ }
+
+A provider callback gets the ``struct attestation_dev`` and reaches the
+provider's structure with ``container_of()``. A CTLV callback of the
+provider's own family gets the CTLV instance, which
+``attestation_from_ctlv()`` turns into the device. No provider callback runs
+before ``attestation_register()``, so the provider finishes its own setup in
+between. The descriptor, evidence capabilities, fixed MR properties, strings
+and arrays remain immutable until the last ``attestation_put()``.
+
+The subsystem embeds the CTLV instance, and its hooks run every operation,
+those of the device's family included, under one instance mutex. That
+serializes an operation and its native callback.
+
+Every MR can be read, so a provider with MRs implements ``mr_read``.
+Fixed MRs are listed in the provider descriptor. Their property storage
+remains valid until the last ``attestation_put()``.
+
+The evidence capabilities list the objects ``evidence-get`` produces, each
+object's ID being its index. An object gives its class and the formats it
+may have, ``ctlv-attestation``'s ``evidence-format`` or its family's
+``evidence-vendor-format``, and a signed object its challenge lengths.
+``attestation_alloc()`` refuses a format the family does not give.
+
+``evidence_get`` writes each object with ``attestation_evidence_write()``,
+which fails the operation with ``-EMSGSIZE`` where the caller's blob is too
+short, and tells the caller how long it has to be. It fills in each object's
+result: the format written and its length. A format the object does not list
+fails the call with ``-EPROTO``.
+
+Other writers
+=============
+
+This interface is not the only way to extend a measurement register, and it
+does not arbitrate between the ways there are. Every program holding a
+read-write descriptor of the device can extend a register through
+``mr-extend``, and a register with a native interface of its own can be
+extended through that too, as a TPM's PCRs are by programs using
+``/dev/tpm0`` or ``/dev/tpmrm0`` and by IMA. Nothing reserves a register for
+one writer. A register two writers extend holds the extensions of both, in
+the order they arrived, and ``mr-read`` and the evidence report it as it is.
+A program that replays a register from its own records has to be its only
+writer, by agreement with whatever else runs on the system.
+
+Teardown
+========
+
+``attestation_unregister()`` first removes the node, so nothing opens the
+device again. It then withdraws the device from the kernel registry, refusing
+the operations of files still open. Only then does it unpublish the CTLV
+instance, which drains the operations in flight. No provider callback runs
+once it returns, so the provider may release its native state before
+``attestation_put()``. Open files keep the structure until they close.
+
+Kernel API
+==========
+
+.. kernel-doc:: include/linux/attestation.h
+.. kernel-doc:: drivers/attestation/core/core.c
+ :export:
diff --git a/Documentation/driver-api/index.rst b/Documentation/driver-api/index.rst
index 0c5b9d2165d8..625355c504ef 100644
--- a/Documentation/driver-api/index.rst
+++ b/Documentation/driver-api/index.rst
@@ -38,6 +38,7 @@ of interest to most developers working on device drivers.
early-userspace/index
connector
ctlv
+ attestation
device-io
devfreq
dma-buf
diff --git a/Documentation/userspace-api/attestation.rst b/Documentation/userspace-api/attestation.rst
index 3c610acc3382..16646d8b60d7 100644
--- a/Documentation/userspace-api/attestation.rst
+++ b/Documentation/userspace-api/attestation.rst
@@ -16,7 +16,8 @@ The node speaks CTLV, described in :doc:`ctlv`, as a descendant of the
belongs to ``ctlv-attestation``, so a tool written against it works with any
provider. The specification is
``Documentation/ctlv/specs/ctlv-attestation.yaml`` and the constants are in
-``include/uapi/ctlv/ctlv_attestation_gen.h``.
+``include/uapi/ctlv/ctlv_attestation_gen.h``. Writing a provider is
+:doc:`/driver-api/attestation`.
====================== ====== ========== ================================
Operation Type Access Purpose
diff --git a/MAINTAINERS b/MAINTAINERS
index e57fc8bb9c1d..172b20d5fdd1 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4439,6 +4439,7 @@ L: linux-security-module@vger.kernel.org
S: Supported
F: Documentation/ctlv/abi/ctlv-attestation*
F: Documentation/ctlv/specs/ctlv-attestation*
+F: Documentation/driver-api/attestation.rst
F: Documentation/userspace-api/attestation*.rst
F: drivers/attestation/
F: include/linux/attestation*
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC v2 7/9] tdx-guest: Expose the native attestation service API
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
` (5 preceding siblings ...)
2026-11-13 16:08 ` [PATCH RFC v2 6/9] Documentation: attestation: Describe how to write a provider Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 8/9] attestation: Add the TDX family specification and generated files Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 9/9] attestation: Add the TDX family implementation Jiri Pirko
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Factor the Quote and measurement-register operations of the misc, TSM
report and TSM MR interfaces into helpers declared in a driver-internal
header, for an attestation provider built into the driver to share.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- kept the helpers inside tdx-guest, in place of a client API and its
header
- dropped the MAINTAINERS lines, which N: tdx already covers
- sized the TSM report outblob to its Quote
- dropped an unreachable return in tdx_do_report()
- dropped the stale kernel client section from tdx-guest.rst
- numbered every enum member
---
drivers/virt/coco/tdx-guest/internal.h | 30 ++++++
drivers/virt/coco/tdx-guest/tdx-guest.c | 130 +++++++++++++++++++-----
2 files changed, 132 insertions(+), 28 deletions(-)
create mode 100644 drivers/virt/coco/tdx-guest/internal.h
diff --git a/drivers/virt/coco/tdx-guest/internal.h b/drivers/virt/coco/tdx-guest/internal.h
new file mode 100644
index 000000000000..d57a8c385aff
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/internal.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+#ifndef _TDX_GUEST_INTERNAL_H
+#define _TDX_GUEST_INTERNAL_H
+
+#include <crypto/sha2.h>
+#include <linux/types.h>
+
+#define TDX_GUEST_QUOTE_MAX_LEN ((128 * 1024) - 24)
+
+struct tdx_guest_info {
+ u8 mrconfigid[SHA384_DIGEST_SIZE];
+ u8 mrowner[SHA384_DIGEST_SIZE];
+ u8 mrownerconfig[SHA384_DIGEST_SIZE];
+};
+
+enum tdx_guest_mr {
+ TDX_GUEST_MR_MRTD = 0,
+ TDX_GUEST_MR_RTMR0 = 1,
+ TDX_GUEST_MR_RTMR1 = 2,
+ TDX_GUEST_MR_RTMR2 = 3,
+ TDX_GUEST_MR_RTMR3 = 4,
+};
+
+/* @reportdata is TDX_REPORTDATA_LEN bytes, and @out holds TDX_GUEST_QUOTE_MAX_LEN. */
+int tdx_guest_quote_get(const u8 *reportdata, void *out, size_t *out_length);
+int tdx_guest_mr_read(enum tdx_guest_mr mr, u8 value[SHA384_DIGEST_SIZE]);
+int tdx_guest_rtmr_extend(unsigned int rtmr,
+ const u8 value[SHA384_DIGEST_SIZE]);
+
+#endif /* _TDX_GUEST_INTERNAL_H */
diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/tdx-guest.c
index 11d741da3b35..c49454ec0b18 100644
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/tdx-guest.c
@@ -25,8 +25,11 @@
#include <asm/cpu_device_id.h>
#include <asm/tdx.h>
+#include "internal.h"
+
/* TDREPORT buffer */
static u8 *tdx_report_buf;
+static struct tdx_guest_info tdx_guest_info;
/* Lock to serialize TDG.MR.REPORT and TDG.MR.RTMR.EXTEND TDCALLs */
static DEFINE_MUTEX(mr_lock);
@@ -49,27 +52,32 @@ enum {
TDREPORT_servtd_hash = TDREPORT_rtmr3 + SHA384_DIGEST_SIZE,
};
-static int tdx_do_report(sockptr_t data, sockptr_t tdreport)
+static int __tdx_do_report(sockptr_t data, sockptr_t tdreport)
{
- scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
- u8 *reportdata = tdx_report_buf + TDREPORT_reportdata;
- int ret;
+ u8 *reportdata = tdx_report_buf + TDREPORT_reportdata;
+ int ret;
- if (!sockptr_is_null(data) &&
- copy_from_sockptr(reportdata, data, TDX_REPORTDATA_LEN))
- return -EFAULT;
+ lockdep_assert_held(&mr_lock);
+ if (!sockptr_is_null(data) &&
+ copy_from_sockptr(reportdata, data, TDX_REPORTDATA_LEN))
+ return -EFAULT;
- ret = tdx_mcall_get_report0(reportdata, tdx_report_buf);
- if (WARN_ONCE(ret, "tdx_mcall_get_report0() failed: %d", ret))
- return ret;
+ ret = tdx_mcall_get_report0(reportdata, tdx_report_buf);
+ if (WARN_ONCE(ret, "tdx_mcall_get_report0() failed: %d", ret))
+ return ret;
- if (!sockptr_is_null(tdreport) &&
- copy_to_sockptr(tdreport, tdx_report_buf, TDX_REPORT_LEN))
- return -EFAULT;
- }
+ if (!sockptr_is_null(tdreport) &&
+ copy_to_sockptr(tdreport, tdx_report_buf, TDX_REPORT_LEN))
+ return -EFAULT;
return 0;
}
+static int tdx_do_report(sockptr_t data, sockptr_t tdreport)
+{
+ scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock)
+ return __tdx_do_report(data, tdreport);
+}
+
static int tdx_do_extend(u8 mr_ind, const u8 *data)
{
scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
@@ -90,6 +98,34 @@ static int tdx_do_extend(u8 mr_ind, const u8 *data)
return 0;
}
+int tdx_guest_mr_read(enum tdx_guest_mr mr, u8 value[SHA384_DIGEST_SIZE])
+{
+ static const size_t offsets[] = {
+ [TDX_GUEST_MR_MRTD] = TDREPORT_mrtd,
+ [TDX_GUEST_MR_RTMR0] = TDREPORT_rtmr0,
+ [TDX_GUEST_MR_RTMR1] = TDREPORT_rtmr1,
+ [TDX_GUEST_MR_RTMR2] = TDREPORT_rtmr2,
+ [TDX_GUEST_MR_RTMR3] = TDREPORT_rtmr3,
+ };
+ int ret;
+
+ scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
+ ret = __tdx_do_report(KERNEL_SOCKPTR(NULL),
+ KERNEL_SOCKPTR(NULL));
+ if (ret)
+ return ret;
+ memcpy(value, tdx_report_buf + offsets[mr],
+ SHA384_DIGEST_SIZE);
+ }
+ return 0;
+}
+
+int tdx_guest_rtmr_extend(unsigned int rtmr,
+ const u8 value[SHA384_DIGEST_SIZE])
+{
+ return tdx_do_extend(rtmr, value);
+}
+
#define TDX_MR_(r) .mr_value = (void *)TDREPORT_##r, TSM_MR_(r, SHA384)
static struct tsm_measurement_register tdx_mrs[] = {
{ TDX_MR_(rtmr0) | TSM_MR_F_RTMR },
@@ -135,6 +171,12 @@ static const struct attribute_group *tdx_mr_init(void)
rc = tdx_mr_refresh(&tdx_measurements);
if (rc)
return ERR_PTR(rc);
+ memcpy(tdx_guest_info.mrconfigid,
+ buf + TDREPORT_mrconfigid, SHA384_DIGEST_SIZE);
+ memcpy(tdx_guest_info.mrowner,
+ buf + TDREPORT_mrowner, SHA384_DIGEST_SIZE);
+ memcpy(tdx_guest_info.mrownerconfig,
+ buf + TDREPORT_mrownerconfig, SHA384_DIGEST_SIZE);
/*
* @mr_value was initialized with the offset only, while the base
@@ -189,6 +231,9 @@ struct tdx_quote_buf {
u8 data[];
};
+static_assert(TDX_GUEST_QUOTE_MAX_LEN ==
+ TDX_DEFAULT_QUOTE_SIZE - sizeof(struct tdx_quote_buf));
+
static struct tdx_quote_buf *quote_buf;
/* Lock to streamline quote requests */
@@ -285,15 +330,18 @@ static int wait_for_quote_completion(struct tdx_quote_buf *buf, u32 timeout)
return (i == timeout) ? -ETIMEDOUT : 0;
}
-static int tdx_report_new_locked(struct tsm_report *report)
+/* Leaves the Quote in quote_buf; copy it out before dropping quote_lock. */
+static int __tdx_guest_quote_get(const u8 *reportdata, size_t length,
+ size_t *out_length)
{
- u8 *buf;
- struct tsm_report_desc *desc = &report->desc;
size_t quote_buf_size = get_quote_buf_size();
u32 out_len;
int ret;
u64 err;
+ lockdep_assert_held("e_lock);
+ if (!reportdata || length != TDX_REPORTDATA_LEN)
+ return -EINVAL;
/*
* If the previous request is timedout or interrupted, and the
* Quote buf status is still in GET_QUOTE_IN_FLIGHT (owned by
@@ -302,16 +350,13 @@ static int tdx_report_new_locked(struct tsm_report *report)
if (quote_buf->status == GET_QUOTE_IN_FLIGHT)
return -EBUSY;
- if (desc->inblob_len != TDX_REPORTDATA_LEN)
- return -EINVAL;
-
memset(quote_buf, 0, quote_buf_size);
/* Update Quote buffer header */
quote_buf->version = GET_QUOTE_CMD_VER;
quote_buf->in_len = TDX_REPORT_LEN;
- ret = tdx_do_report(KERNEL_SOCKPTR(desc->inblob),
+ ret = tdx_do_report(KERNEL_SOCKPTR((void *)reportdata),
KERNEL_SOCKPTR(quote_buf->data));
if (ret)
return ret;
@@ -337,13 +382,7 @@ static int tdx_report_new_locked(struct tsm_report *report)
if (struct_size(quote_buf, data, out_len) > quote_buf_size)
return -EFBIG;
-
- buf = kvmemdup(quote_buf->data, out_len, GFP_KERNEL);
- if (!buf)
- return -ENOMEM;
-
- report->outblob = buf;
- report->outblob_len = out_len;
+ *out_length = out_len;
/*
* TODO: parse the PEM-formatted cert chain out of the quote buffer when
@@ -353,6 +392,41 @@ static int tdx_report_new_locked(struct tsm_report *report)
return ret;
}
+int tdx_guest_quote_get(const u8 *reportdata, void *out, size_t *out_length)
+{
+ int ret;
+
+ scoped_cond_guard(mutex_intr, return -EINTR, "e_lock) {
+ ret = __tdx_guest_quote_get(reportdata, TDX_REPORTDATA_LEN,
+ out_length);
+ if (ret)
+ return ret;
+ /* The TDX module may allow a Quote longer than @out. */
+ if (*out_length > TDX_GUEST_QUOTE_MAX_LEN)
+ return -EFBIG;
+ memcpy(out, quote_buf->data, *out_length);
+ }
+ return 0;
+}
+
+static int tdx_report_new_locked(struct tsm_report *report)
+{
+ struct tsm_report_desc *desc = &report->desc;
+ size_t length;
+ void *buf;
+ int ret;
+
+ ret = __tdx_guest_quote_get(desc->inblob, desc->inblob_len, &length);
+ if (ret)
+ return ret;
+ buf = kvmemdup(quote_buf->data, length, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+ report->outblob = buf;
+ report->outblob_len = length;
+ return 0;
+}
+
static int tdx_report_new(struct tsm_report *report, void *unused)
{
scoped_cond_guard(mutex_intr, return -EINTR, "e_lock)
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC v2 8/9] attestation: Add the TDX family specification and generated files
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
` (6 preceding siblings ...)
2026-11-13 16:08 ` [PATCH RFC v2 7/9] tdx-guest: Expose the native attestation service API Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 9/9] attestation: Add the TDX family implementation Jiri Pirko
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Define the TDX attestation family and reserve family ID 2. Add its ABI
snapshot, registry entry and generated kernel and UAPI files.
The ABI is intentionally specification-first. The generated descriptor
is linked with the provider implementation in the following patch.
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- generated the kernel files into drivers/virt/coco/tdx-guest/
- dropped the family's priv lifecycle hook
- generated an extension fill in place of a stub fill
- wrote the spec as op trees and records
- numbered every enum member
- moved the generated UAPI headers to include/uapi/ctlv/
- documented every item of the spec
- put each field's doc above its generated helpers
- gave the legacy and DICE Quote formats as evidence-vendor-format
values
- said message instead of wire
---
.../ctlv/abi/ctlv-attestation-tdx.yaml | 21 ++++++
Documentation/ctlv/abi/families.registry.yaml | 1 +
Documentation/ctlv/families.yaml | 1 +
Documentation/ctlv/generated-files.yaml | 7 ++
.../ctlv/specs/ctlv-attestation-tdx.yaml | 25 +++++++
MAINTAINERS | 1 +
.../coco/tdx-guest/ctlv_attestation_tdx_gen.c | 65 +++++++++++++++++++
.../coco/tdx-guest/ctlv_attestation_tdx_gen.h | 44 +++++++++++++
include/uapi/ctlv/ctlv_attestation_tdx_gen.h | 42 ++++++++++++
include/uapi/ctlv/ctlv_families_gen.h | 1 +
10 files changed, 208 insertions(+)
create mode 100644 Documentation/ctlv/abi/ctlv-attestation-tdx.yaml
create mode 100644 Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
create mode 100644 drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
create mode 100644 drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
create mode 100644 include/uapi/ctlv/ctlv_attestation_tdx_gen.h
diff --git a/Documentation/ctlv/abi/ctlv-attestation-tdx.yaml b/Documentation/ctlv/abi/ctlv-attestation-tdx.yaml
new file mode 100644
index 000000000000..f8dc3c085c71
--- /dev/null
+++ b/Documentation/ctlv/abi/ctlv-attestation-tdx.yaml
@@ -0,0 +1,21 @@
+# 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-tdx family, from
+# Documentation/ctlv/specs/ctlv-attestation-tdx.yaml, written by
+# "make -C tools/ctlv regen".
+#
+# Updating the file is how a UAPI change gets reviewed; an update that
+# removes or renumbers anything, narrows a bound, or makes a member required
+# is an ABI break, whatever the file says afterwards.
+family: {name: ctlv-attestation-tdx, id: 2, inherits: ctlv-attestation}
+enums:
+ evidence-vendor-format: {quote: 1, quote-dice: 2}
+records:
+ device-info:
+ id: 0x00010000
+ fields:
+ mrconfigid: {number: 1, type: binary, length: 48}
+ mrowner: {number: 2, type: binary, length: 48}
+ mrownerconfig: {number: 3, type: binary, length: 48}
+fills: {device-info: device-info}
diff --git a/Documentation/ctlv/abi/families.registry.yaml b/Documentation/ctlv/abi/families.registry.yaml
index 7a5c6af29565..0f55cc4f2e35 100644
--- a/Documentation/ctlv/abi/families.registry.yaml
+++ b/Documentation/ctlv/abi/families.registry.yaml
@@ -6,3 +6,4 @@
families:
- ctlv
- ctlv-attestation
+ - ctlv-attestation-tdx
diff --git a/Documentation/ctlv/families.yaml b/Documentation/ctlv/families.yaml
index dbb0243b873d..27bafae3ca66 100644
--- a/Documentation/ctlv/families.yaml
+++ b/Documentation/ctlv/families.yaml
@@ -16,3 +16,4 @@
families:
- ctlv
- ctlv-attestation
+ - ctlv-attestation-tdx
diff --git a/Documentation/ctlv/generated-files.yaml b/Documentation/ctlv/generated-files.yaml
index 00a56634805d..e78997f13334 100644
--- a/Documentation/ctlv/generated-files.yaml
+++ b/Documentation/ctlv/generated-files.yaml
@@ -23,3 +23,10 @@ families:
uapi-header: include/uapi/ctlv/ctlv_attestation_gen.h
kernel-header: drivers/attestation/generated/ctlv_attestation_gen.h
kernel-source: drivers/attestation/generated/ctlv_attestation_gen.c
+
+ - name: ctlv-attestation-tdx
+ source: Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
+ outputs:
+ uapi-header: include/uapi/ctlv/ctlv_attestation_tdx_gen.h
+ kernel-header: drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
+ kernel-source: drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
diff --git a/Documentation/ctlv/specs/ctlv-attestation-tdx.yaml b/Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
new file mode 100644
index 000000000000..0093015b4991
--- /dev/null
+++ b/Documentation/ctlv/specs/ctlv-attestation-tdx.yaml
@@ -0,0 +1,25 @@
+# SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+%YAML 1.2
+---
+# The attestation device of an Intel TDX guest: Quotes of its TDREPORT, the
+# CCEL event log, MRTD and RTMR0 to RTMR3.
+family: ctlv-attestation-tdx
+inherits: ctlv-attestation
+
+enums:
+ # Fills ctlv-attestation's evidence-vendor-format.
+ evidence-vendor-format:
+ # An Intel TDX DCAP Quote, from the host's quoting enclave.
+ - quote
+ # A DICE-based TDX Quote, an EAT in a CBOR Web Token.
+ - quote-dice
+
+records:
+ # Fills ctlv-attestation's device-info.
+ device-info:
+ # MRCONFIGID of the TD, the ID of its non-owner configuration.
+ mrconfigid: binary(48)
+ # MROWNER of the TD, the ID of its owner.
+ mrowner: binary(48)
+ # MROWNERCONFIG of the TD, the ID of its owner's configuration.
+ mrownerconfig: binary(48)
diff --git a/MAINTAINERS b/MAINTAINERS
index 172b20d5fdd1..0734efa1a78f 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -4442,6 +4442,7 @@ F: Documentation/ctlv/specs/ctlv-attestation*
F: Documentation/driver-api/attestation.rst
F: Documentation/userspace-api/attestation*.rst
F: drivers/attestation/
+F: drivers/virt/coco/tdx-guest/*attestation*
F: include/linux/attestation*
F: include/uapi/ctlv/ctlv_attestation*
diff --git a/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c b/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
new file mode 100644
index 000000000000..8795febbdeeb
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.c
@@ -0,0 +1,65 @@
+// SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause)
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation-tdx.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN kernel source */
+
+#define _CTLV_GENERATED_METADATA_ONLY
+#include <linux/array_size.h>
+#include <linux/build_bug.h>
+#include <linux/ctlv_family.h>
+#include <linux/export.h>
+
+#include <uapi/ctlv/ctlv_gen.h>
+#include <uapi/ctlv/ctlv_attestation_tdx_gen.h>
+
+#include "ctlv_attestation_tdx_gen.h"
+
+static const struct ctlv_node_schema device_info_fill_node_schemas[] = {
+ CTLV_NODE_SCHEMA_ROOT(1, 3, 3),
+ CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MRCONFIGID, 1, 48, 48),
+ CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNER, 1, 48, 48),
+ CTLV_NODE_SCHEMA_BINARY(CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNERCONFIG, 1, 48, 48),
+};
+
+/* The extension points this family fills. */
+static const struct ctlv_ext_fill_schema ctlv_attestation_tdx_ext_fill_schemas[] = {
+ CTLV_EXT_FILL_SCHEMA(CTLV_FAMILY_ATTESTATION, CTLV_ATTESTATION_EXT_DEVICE_INFO,
+ device_info),
+};
+
+/* The enum extension points this family fills. */
+static const struct ctlv_enum_fill_schema ctlv_attestation_tdx_enum_fill_schemas[] = {
+ CTLV_ENUM_FILL_SCHEMA(CTLV_FAMILY_ATTESTATION,
+ CTLV_ATTESTATION_ENUM_EXT_EVIDENCE_VENDOR_FORMAT, 2),
+};
+
+/* The family ID is its position in the central registry. */
+const struct ctlv_family_schema ctlv_attestation_tdx_schema = {
+ .name = "ctlv-attestation-tdx",
+ .family_id = 2,
+ .ext_fill_schemas = ctlv_attestation_tdx_ext_fill_schemas,
+ .ext_fill_schemas_count = ARRAY_SIZE(ctlv_attestation_tdx_ext_fill_schemas),
+ .enum_fill_schemas = ctlv_attestation_tdx_enum_fill_schemas,
+ .enum_fill_schemas_count = ARRAY_SIZE(ctlv_attestation_tdx_enum_fill_schemas),
+};
+
+/* Maximum uevent cost for this family chain. */
+static_assert(150 <= CTLV_UEVENT_MAX_LEN);
+static_assert(5 <= CTLV_UEVENT_MAX_VARS);
+
+/* Extension-point callbacks. */
+static const struct ctlv_ext_ops ctlv_attestation_tdx_ext_ops[] = {
+ CTLV_EXT_OPS(CTLV_FAMILY_ATTESTATION, CTLV_ATTESTATION_EXT_DEVICE_INFO,
+ ctlv_attestation_tdx_device_info),
+};
+
+/* Family descriptor used by ctlv_init(). */
+const struct ctlv_family ctlv_attestation_tdx_family = {
+ .schema = &ctlv_attestation_tdx_schema,
+ .parent = &ctlv_attestation_family,
+ .ext_ops = ctlv_attestation_tdx_ext_ops,
+ .ext_ops_count = ARRAY_SIZE(ctlv_attestation_tdx_ext_ops),
+};
+EXPORT_SYMBOL_NS_GPL(ctlv_attestation_tdx_family, "CTLV");
diff --git a/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h b/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
new file mode 100644
index 000000000000..99c3cbc41a39
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/ctlv_attestation_tdx_gen.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation-tdx.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN kernel header */
+
+/* Kernel metadata and family API of the ctlv-attestation-tdx CTLV family. */
+
+#ifndef _LINUX_CTLV_ATTESTATION_TDX_GEN_H
+#define _LINUX_CTLV_ATTESTATION_TDX_GEN_H
+
+#include <linux/ctlv_family.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+
+#include <uapi/ctlv/ctlv_attestation_tdx_gen.h>
+
+#include "ctlv_attestation_gen.h"
+
+extern const struct ctlv_family_schema ctlv_attestation_tdx_schema;
+
+/* device-info of ctlv-attestation: fill callback. */
+int ctlv_attestation_tdx_device_info_fill(struct ctlv *ctlv, struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request,
+ struct ctlv_cursor *record);
+
+extern const struct ctlv_family ctlv_attestation_tdx_family;
+
+#ifndef _CTLV_GENERATED_METADATA_ONLY
+
+/* Writing a reply. An append is atomic; a record ends with ctlv_record_end(). */
+/* MRCONFIGID of the TD, the ID of its non-owner configuration. */
+CTLV_PUT_BINARY(ctlv_attestation_tdx_device_info_mrconfigid,
+ CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MRCONFIGID, CTLV_SCHEMA_BINARY)
+/* MROWNER of the TD, the ID of its owner. */
+CTLV_PUT_BINARY(ctlv_attestation_tdx_device_info_mrowner,
+ CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNER, CTLV_SCHEMA_BINARY)
+/* MROWNERCONFIG of the TD, the ID of its owner's configuration. */
+CTLV_PUT_BINARY(ctlv_attestation_tdx_device_info_mrownerconfig,
+ CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNERCONFIG, CTLV_SCHEMA_BINARY)
+#endif /* _CTLV_GENERATED_METADATA_ONLY */
+
+#endif /* _LINUX_CTLV_ATTESTATION_TDX_GEN_H */
diff --git a/include/uapi/ctlv/ctlv_attestation_tdx_gen.h b/include/uapi/ctlv/ctlv_attestation_tdx_gen.h
new file mode 100644
index 000000000000..77339ca50ca5
--- /dev/null
+++ b/include/uapi/ctlv/ctlv_attestation_tdx_gen.h
@@ -0,0 +1,42 @@
+/* SPDX-License-Identifier: ((GPL-2.0 WITH Linux-syscall-note) OR BSD-3-Clause) */
+/*
+ * Generated from Documentation/ctlv/specs/ctlv-attestation-tdx.yaml by
+ * tools/ctlv/ctlv-regen.sh. Do not edit.
+ */
+/* CTLV-GEN uapi header */
+
+/*
+ * Constants of the ctlv-attestation-tdx CTLV family. Types and op IDs are complete; the message
+ * format carrying them is in <ctlv/ctlv.h>.
+ *
+ * The inherited ctlv-attestation header is included here, so one include covers the whole effective
+ * schema.
+ */
+
+#ifndef _UAPI_CTLV_CTLV_ATTESTATION_TDX_GEN_H
+#define _UAPI_CTLV_CTLV_ATTESTATION_TDX_GEN_H
+
+#include <ctlv/ctlv.h>
+#include <ctlv/ctlv_attestation_gen.h>
+#include <ctlv/ctlv_families_gen.h>
+
+/* enum evidence-vendor-format, an extension point of ctlv-attestation */
+enum _ctlv_attestation_tdx_evidence_vendor_format {
+ /* An Intel TDX DCAP Quote, from the host's quoting enclave. */
+ CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE = 1,
+ /* A DICE-based TDX Quote, an EAT in a CBOR Web Token. */
+ CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE_DICE = 2,
+};
+
+/* device-info */
+/* MRCONFIGID of the TD, the ID of its non-owner configuration. */
+#define CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MRCONFIGID \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION_TDX, 0x00010000, 1)
+/* MROWNER of the TD, the ID of its owner. */
+#define CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNER \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION_TDX, 0x00010000, 2)
+/* MROWNERCONFIG of the TD, the ID of its owner's configuration. */
+#define CTLV_ATTESTATION_TDX_T_DEVICE_INFO_MROWNERCONFIG \
+ CTLV_TYPE(CTLV_FAMILY_ATTESTATION_TDX, 0x00010000, 3)
+
+#endif /* _UAPI_CTLV_CTLV_ATTESTATION_TDX_GEN_H */
diff --git a/include/uapi/ctlv/ctlv_families_gen.h b/include/uapi/ctlv/ctlv_families_gen.h
index 83f6a257ddf2..849671f8681d 100644
--- a/include/uapi/ctlv/ctlv_families_gen.h
+++ b/include/uapi/ctlv/ctlv_families_gen.h
@@ -13,6 +13,7 @@
enum _ctlv_family {
CTLV_FAMILY_ROOT = 0,
CTLV_FAMILY_ATTESTATION = 1,
+ CTLV_FAMILY_ATTESTATION_TDX = 2,
};
#endif /* _UAPI_CTLV_CTLV_FAMILIES_GEN_H */
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
* [PATCH RFC v2 9/9] attestation: Add the TDX family implementation
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
` (7 preceding siblings ...)
2026-11-13 16:08 ` [PATCH RFC v2 8/9] attestation: Add the TDX family specification and generated files Jiri Pirko
@ 2026-11-13 16:08 ` Jiri Pirko
8 siblings, 0 replies; 10+ messages in thread
From: Jiri Pirko @ 2026-11-13 16:08 UTC (permalink / raw)
To: linux-coco
Cc: linux-kernel, linux-api, x86, kvm, linux-doc, jgg, david.quigley,
lukas, ksinyuk, rodrigo.vivi, cedric.xing, sameo, drdeeglaze,
James.Bottomley, aik, jpiotrowski, qinkun, pgonda, jic23,
alistair.francis, ankita, suzuki.poulose, kas, rick.p.edgecombe,
elena.reshetova, dave.hansen, sathyanarayanan.kuppuswamy,
peter.fang, jarkko, zohar, yeoreum.yun, aneesh.kumar, dave.jiang,
nik.borisov, sergii.dmytruk
From: Jiri Pirko <jiri@nvidia.com>
Build the TDX attestation provider into tdx-guest when attestation is
enabled. Expose the Quote, MRTD and RTMR operations through the same
helpers as the native interfaces, which stay as they are, and label
each Quote as a legacy or a DICE one by its first bytes [1]. Where
ACPI describes a CCEL table, also return the firmware's event log as
evidence, for a verifier replaying the RTMRs.
[1] https://lore.kernel.org/all/avNfZ0lLl-OrkCHs@intel.com/
Assisted-by: Cursor: GPT-5.6 Sol
Assisted-by: Cursor: Claude Opus 5.5
Signed-off-by: Jiri Pirko <jiri@nvidia.com>
---
RFC->RFCv2:
- built the provider into tdx-guest in place of the attestation-tdx
module
- embedded struct attestation_dev in the provider's state
- accounted the evidence buffer to its caller's memcg
- dropped the MR-journal boundary claim from the TDX documentation
- returned the CCEL event log as an event-log evidence object
- dropped the total output length
- documented the provider as part of tdx-guest
- reported the 64-byte challenge length as a list entry
- used the helpers generated for the rewritten spec
- wrote the evidence objects with attestation_evidence_write()
- dropped the RTMR read caps, as every MR can be read
- said record instead of nest, the root being one
- took the UAPI headers from include/uapi/ctlv/
- dropped the TDREPORT evidence object, which /dev/tdx_guest keeps,
and labelled each Quote as a legacy or a DICE one
- described the provider for userspace in a document of its own
---
.../userspace-api/attestation-tdx.rst | 59 ++++
Documentation/userspace-api/attestation.rst | 10 +
Documentation/virt/coco/tdx-guest.rst | 8 +
drivers/attestation/Kconfig | 4 +
drivers/virt/coco/tdx-guest/Kconfig | 4 +-
drivers/virt/coco/tdx-guest/Makefile | 6 +
drivers/virt/coco/tdx-guest/attestation.c | 313 ++++++++++++++++++
drivers/virt/coco/tdx-guest/internal.h | 18 +
.../coco/tdx-guest/{tdx-guest.c => main.c} | 7 +
9 files changed, 428 insertions(+), 1 deletion(-)
create mode 100644 Documentation/userspace-api/attestation-tdx.rst
create mode 100644 drivers/virt/coco/tdx-guest/attestation.c
rename drivers/virt/coco/tdx-guest/{tdx-guest.c => main.c} (98%)
diff --git a/Documentation/userspace-api/attestation-tdx.rst b/Documentation/userspace-api/attestation-tdx.rst
new file mode 100644
index 000000000000..ca0ed209143b
--- /dev/null
+++ b/Documentation/userspace-api/attestation-tdx.rst
@@ -0,0 +1,59 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+===============
+TDX attestation
+===============
+
+With ``CONFIG_ATTESTATION``, the TDX guest driver registers an attestation
+device of the ``ctlv-attestation-tdx`` family, whose specification is
+``Documentation/ctlv/specs/ctlv-attestation-tdx.yaml``. Its ``device-info``,
+the ``info`` of ``device-get``, carries ``mrconfigid``, ``mrowner`` and
+``mrownerconfig``, 48 bytes each.
+
+Measurement registers
+=====================
+
+== ===== ============== ======= ======
+ID Name Register index Bank Extend
+== ===== ============== ======= ======
+0 mrtd none SHA-384 no
+1 rtmr0 0 SHA-384 yes
+2 rtmr1 1 SHA-384 yes
+3 rtmr2 2 SHA-384 yes
+4 rtmr3 3 SHA-384 yes
+== ===== ============== ======= ======
+
+Each ``mr-read`` takes a TDREPORT.
+
+The TSM-MR files,
+``/sys/devices/virtual/misc/tdx_guest/measurements/rtmr[0123]:sha384``, extend
+the same RTMRs as ``mr-extend``. Firmware and the boot loader usually extend
+RTMR0 to RTMR2 before the kernel starts, which leaves RTMR3 to the running
+system.
+
+Evidence
+========
+
+== ======== ========= ================= ========= ========
+ID Object Class Formats Challenge Binds
+== ======== ========= ================= ========= ========
+0 Quote signed quote, quote-dice 64 bytes every MR
+1 CCEL log event-log ccel none no MR
+== ======== ========= ================= ========= ========
+
+The challenge goes into the REPORTDATA the Quote carries. The host's quoting
+service returns a DCAP Quote or a DICE-based one, and the device names it
+``quote`` by its header's version and TEE type, or ``quote-dice`` by the CBOR
+magic it starts with. Both are ``ctlv-attestation-tdx``'s vendor formats. A
+Quote of neither fails ``evidence-get`` with ``EPROTO``.
+
+The log is there where the firmware describes its CC event log with an ACPI
+CCEL table, and is the whole log area. It does not change after boot, and one
+call returns it with the Quote.
+
+The TDREPORT is MAC'd for the platform, not signed, so it is no object;
+``TDX_CMD_GET_REPORT0`` of ``/dev/tdx_guest`` returns it.
+
+A guest has one TDX device, which a udev rule can give a stable name::
+
+ ENV{CTLV_FAMILY}=="ctlv-attestation-tdx", SYMLINK+="attestation/tdx"
diff --git a/Documentation/userspace-api/attestation.rst b/Documentation/userspace-api/attestation.rst
index 16646d8b60d7..32ecaf5cba35 100644
--- a/Documentation/userspace-api/attestation.rst
+++ b/Documentation/userspace-api/attestation.rst
@@ -282,3 +282,13 @@ The reply carries ``mr-generation``, the MR generation after the extension.
have or repeats one, or a digest has the wrong length.
Access is read-write.
+
+Providers
+=========
+
+What each provider's family adds, and what its device produces:
+
+.. toctree::
+ :maxdepth: 1
+
+ attestation-tdx
diff --git a/Documentation/virt/coco/tdx-guest.rst b/Documentation/virt/coco/tdx-guest.rst
index 46e316db6bb4..16afc8e5a6ef 100644
--- a/Documentation/virt/coco/tdx-guest.rst
+++ b/Documentation/virt/coco/tdx-guest.rst
@@ -50,3 +50,11 @@ TDX reference material is collected here:
https://www.intel.com/content/www/us/en/developer/articles/technical/intel-trust-domain-extensions.html
The driver is based on TDX module specification v1.0 and TDX GHCI specification v1.0.
+
+Attestation device
+==================
+
+With ``CONFIG_ATTESTATION``, the driver also registers an attestation device,
+described in Documentation/userspace-api/attestation-tdx.rst. It shares the
+TDREPORT, Quote and RTMR paths, and their locks, with the misc ioctl, the
+TSM-report configfs interface and the TSM-MR sysfs attributes.
diff --git a/drivers/attestation/Kconfig b/drivers/attestation/Kconfig
index a6bfb17dd480..4307b28e679e 100644
--- a/drivers/attestation/Kconfig
+++ b/drivers/attestation/Kconfig
@@ -10,3 +10,7 @@ menuconfig ATTESTATION
translate the common operations to their native service.
If you are unsure, say N.
+
+if ATTESTATION
+
+endif # ATTESTATION
diff --git a/drivers/virt/coco/tdx-guest/Kconfig b/drivers/virt/coco/tdx-guest/Kconfig
index dbbdc14383b1..aa6687979a6f 100644
--- a/drivers/virt/coco/tdx-guest/Kconfig
+++ b/drivers/virt/coco/tdx-guest/Kconfig
@@ -1,12 +1,14 @@
config TDX_GUEST_DRIVER
tristate "TDX Guest driver"
depends on INTEL_TDX_GUEST
+ depends on ATTESTATION || !ATTESTATION
select TSM_REPORTS
select TSM_MEASUREMENTS
help
The driver provides userspace interface to communicate with
the TDX module to request the TDX guest details like attestation
- report.
+ report. With ATTESTATION, it also registers a TDX attestation
+ device.
To compile this driver as module, choose M here. The module will
be called tdx-guest.
diff --git a/drivers/virt/coco/tdx-guest/Makefile b/drivers/virt/coco/tdx-guest/Makefile
index 775cb463f9c8..027cc16e1df3 100644
--- a/drivers/virt/coco/tdx-guest/Makefile
+++ b/drivers/virt/coco/tdx-guest/Makefile
@@ -1,2 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TDX_GUEST_DRIVER) += tdx-guest.o
+
+tdx-guest-y := main.o
+tdx-guest-$(CONFIG_ATTESTATION) += attestation.o ctlv_attestation_tdx_gen.o
+
+# The generated family header includes its parent's.
+ccflags-y += -I $(srctree)/drivers/attestation/generated
diff --git a/drivers/virt/coco/tdx-guest/attestation.c b/drivers/virt/coco/tdx-guest/attestation.c
new file mode 100644
index 000000000000..1bb2e7b66928
--- /dev/null
+++ b/drivers/virt/coco/tdx-guest/attestation.c
@@ -0,0 +1,313 @@
+// SPDX-License-Identifier: GPL-2.0-only
+#include <crypto/sha2.h>
+#include <linux/acpi.h>
+#include <linux/attestation.h>
+#include <linux/ctlv.h>
+#include <linux/err.h>
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/unaligned.h>
+
+#include <uapi/linux/tdx-guest.h>
+
+#include "ctlv_attestation_tdx_gen.h"
+#include "internal.h"
+
+struct tdx_attestation {
+ struct attestation_dev att;
+ const struct tdx_guest_info *info;
+ void __iomem *log;
+ size_t log_length;
+};
+
+static struct tdx_attestation *tdx_att;
+
+static struct tdx_attestation *to_tdx_attestation(struct attestation_dev *att)
+{
+ return container_of(att, struct tdx_attestation, att);
+}
+
+static const enum hash_algo tdx_attestation_algorithms[] = {
+ HASH_ALGO_SHA384,
+};
+
+static const struct attestation_mr_props tdx_attestation_mrs[] = {
+ {
+ .id = 0,
+ .name = "mrtd",
+ .algorithms = tdx_attestation_algorithms,
+ .algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+ },
+ {
+ .id = 1,
+ .name = "rtmr0",
+ .algorithms = tdx_attestation_algorithms,
+ .algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+ .has_register_index = true,
+ .register_index = 0,
+ .extendable = true,
+ },
+ {
+ .id = 2,
+ .name = "rtmr1",
+ .algorithms = tdx_attestation_algorithms,
+ .algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+ .has_register_index = true,
+ .register_index = 1,
+ .extendable = true,
+ },
+ {
+ .id = 3,
+ .name = "rtmr2",
+ .algorithms = tdx_attestation_algorithms,
+ .algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+ .has_register_index = true,
+ .register_index = 2,
+ .extendable = true,
+ },
+ {
+ .id = 4,
+ .name = "rtmr3",
+ .algorithms = tdx_attestation_algorithms,
+ .algorithms_count = ARRAY_SIZE(tdx_attestation_algorithms),
+ .has_register_index = true,
+ .register_index = 3,
+ .extendable = true,
+ },
+};
+
+#define TDX_QUOTE_HEADER_LEN 48
+#define TDX_QUOTE_TEE_TYPE_TDX 0x81
+
+static const struct attestation_evidence_format tdx_attestation_quote_formats[] = {
+ { .vendor = true, .value = CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE },
+ { .vendor = true, .value = CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE_DICE },
+};
+
+static const struct attestation_evidence_format tdx_attestation_log_formats[] = {
+ { .value = CTLV_ATTESTATION_EVIDENCE_FORMAT_CCEL },
+};
+
+static const struct attestation_evidence_challenge_length
+tdx_attestation_challenge_lengths[] = {
+ { .min = TDX_REPORTDATA_LEN, .max = TDX_REPORTDATA_LEN },
+};
+
+/* The log comes last, so a guest without a CCEL table can leave it out. */
+static struct attestation_evidence_supported_object
+tdx_attestation_evidence_objects[] = {
+ {
+ .evidence_class = ATTESTATION_EVIDENCE_SIGNED,
+ .formats = tdx_attestation_quote_formats,
+ .formats_count = ARRAY_SIZE(tdx_attestation_quote_formats),
+ .challenge_lengths = tdx_attestation_challenge_lengths,
+ .challenge_lengths_count =
+ ARRAY_SIZE(tdx_attestation_challenge_lengths),
+ .max_length = TDX_GUEST_QUOTE_MAX_LEN,
+ .binds_mrs = true,
+ },
+ {
+ .evidence_class = ATTESTATION_EVIDENCE_EVENT_LOG,
+ .formats = tdx_attestation_log_formats,
+ .formats_count = ARRAY_SIZE(tdx_attestation_log_formats),
+ },
+};
+
+/* The object count and the total length are set at init. */
+static struct attestation_evidence_caps tdx_attestation_evidence_caps = {
+ .objects = tdx_attestation_evidence_objects,
+};
+
+/*
+ * A DCAP Quote's header starts with its version, 4 or 5, and TEE type. A
+ * DICE-based Quote, a CBOR Web Token, starts with the CBOR magic, d9 d9 f7.
+ */
+static int tdx_attestation_quote_label(const u8 *quote, size_t length,
+ struct attestation_evidence_format *format)
+{
+ format->vendor = true;
+ if (length >= TDX_QUOTE_HEADER_LEN &&
+ (get_unaligned_le16(quote) == 4 || get_unaligned_le16(quote) == 5) &&
+ get_unaligned_le32(quote + 4) == TDX_QUOTE_TEE_TYPE_TDX) {
+ format->value = CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE;
+ return 0;
+ }
+ if (length >= 3 && !memcmp(quote, "\xd9\xd9\xf7", 3)) {
+ format->value = CTLV_ATTESTATION_TDX_EVIDENCE_VENDOR_FORMAT_QUOTE_DICE;
+ return 0;
+ }
+ return -EPROTO;
+}
+
+static void __iomem *tdx_attestation_log_map(size_t *length)
+{
+ struct acpi_table_header *header;
+ struct acpi_table_ccel *ccel;
+ void __iomem *log = NULL;
+
+ if (ACPI_FAILURE(acpi_get_table(ACPI_SIG_CCEL, 0, &header)))
+ return NULL;
+ ccel = container_of(header, struct acpi_table_ccel, header);
+ if (header->length >= sizeof(*ccel) && ccel->log_area_start_address &&
+ ccel->log_area_minimum_length &&
+ ccel->log_area_minimum_length <= UINT_MAX)
+ log = acpi_os_map_iomem(ccel->log_area_start_address,
+ ccel->log_area_minimum_length);
+ if (log)
+ *length = ccel->log_area_minimum_length;
+ acpi_put_table(header);
+ return log;
+}
+
+static int
+tdx_attestation_evidence_get(struct attestation_dev *att,
+ const struct attestation_evidence_request *request,
+ struct attestation_evidence_result *result)
+{
+ struct tdx_attestation *tdx = to_tdx_attestation(att);
+ size_t index, length;
+ void *buffer;
+ int ret = 0;
+
+ for (index = 0; !ret && index < request->objects_count; index++) {
+ const struct attestation_evidence_object_request *object =
+ &request->objects[index];
+ struct attestation_evidence_object_result *produced =
+ &result->objects[index];
+
+ buffer = kvmalloc(object->supported_object->max_length,
+ GFP_KERNEL_ACCOUNT);
+ if (!buffer)
+ return -ENOMEM;
+
+ if (object->supported_object == &tdx_attestation_evidence_objects[0]) {
+ ret = tdx_guest_quote_get(request->challenge, buffer,
+ &length);
+ if (!ret)
+ ret = tdx_attestation_quote_label(buffer, length,
+ &produced->format);
+ } else {
+ memcpy_fromio(buffer, tdx->log, tdx->log_length);
+ length = tdx->log_length;
+ produced->format = tdx_attestation_log_formats[0];
+ }
+
+ if (!ret)
+ ret = attestation_evidence_write(object, buffer, length);
+ if (!ret)
+ produced->length = length;
+ kvfree(buffer);
+ }
+ return ret;
+}
+
+static int tdx_attestation_mr_read(struct attestation_dev *att, unsigned int mr_id,
+ struct attestation_digest_set *values)
+{
+ static const enum tdx_guest_mr native_mrs[] = {
+ TDX_GUEST_MR_MRTD,
+ TDX_GUEST_MR_RTMR0,
+ TDX_GUEST_MR_RTMR1,
+ TDX_GUEST_MR_RTMR2,
+ TDX_GUEST_MR_RTMR3,
+ };
+
+ if (mr_id >= ARRAY_SIZE(native_mrs) ||
+ values->values_count != 1 ||
+ values->values[0].algorithm != HASH_ALGO_SHA384)
+ return -EINVAL;
+ return tdx_guest_mr_read(native_mrs[mr_id], values->values[0].data);
+}
+
+static int
+tdx_attestation_mr_extend(struct attestation_dev *att, unsigned int mr_id,
+ const struct attestation_digest_set *values)
+{
+ if (mr_id < 1 || mr_id > 4)
+ return -EOPNOTSUPP;
+ if (values->values_count != 1 ||
+ values->values[0].algorithm != HASH_ALGO_SHA384)
+ return -EINVAL;
+ return tdx_guest_rtmr_extend(mr_id - 1, values->values[0].data);
+}
+
+static const struct attestation_provider tdx_attestation_provider = {
+ .evidence_get = tdx_attestation_evidence_get,
+ .mr_read = tdx_attestation_mr_read,
+ .mr_extend = tdx_attestation_mr_extend,
+ .evidence_caps = &tdx_attestation_evidence_caps,
+ .mrs = tdx_attestation_mrs,
+ .mrs_count = ARRAY_SIZE(tdx_attestation_mrs),
+};
+
+int ctlv_attestation_tdx_device_info_fill(struct ctlv *ctlv,
+ struct ctlv_op_ctx *ctx,
+ const struct ctlv_cursor *request,
+ struct ctlv_cursor *record)
+{
+ const struct tdx_guest_info *info =
+ to_tdx_attestation(attestation_from_ctlv(ctlv))->info;
+ bool ok;
+
+ ok = ctlv_attestation_tdx_device_info_mrconfigid_put(record, info->mrconfigid,
+ sizeof(info->mrconfigid));
+ ok &= ctlv_attestation_tdx_device_info_mrowner_put(record, info->mrowner,
+ sizeof(info->mrowner));
+ ok &= ctlv_attestation_tdx_device_info_mrownerconfig_put(record,
+ info->mrownerconfig,
+ sizeof(info->mrownerconfig));
+ return ok ? 0 : -EMSGSIZE;
+}
+
+int tdx_attestation_register(struct device *parent,
+ const struct tdx_guest_info *info)
+{
+ struct attestation_evidence_caps *caps = &tdx_attestation_evidence_caps;
+ struct tdx_attestation *tdx;
+ size_t log_length = 0;
+ void __iomem *log;
+ int ret;
+
+ log = tdx_attestation_log_map(&log_length);
+ tdx_attestation_evidence_objects[1].max_length = log_length;
+ caps->objects_count = ARRAY_SIZE(tdx_attestation_evidence_objects);
+ if (!log)
+ caps->objects_count--;
+
+ tdx = attestation_alloc(parent, &tdx_attestation_provider,
+ &ctlv_attestation_tdx_family, struct tdx_attestation, att);
+ if (IS_ERR(tdx)) {
+ ret = PTR_ERR(tdx);
+ goto err_unmap;
+ }
+ tdx->info = info;
+ tdx->log = log;
+ tdx->log_length = log_length;
+ ret = attestation_register(&tdx->att);
+ if (ret) {
+ attestation_put(&tdx->att);
+ goto err_unmap;
+ }
+ tdx_att = tdx;
+ return 0;
+
+err_unmap:
+ if (log)
+ acpi_os_unmap_iomem(log, log_length);
+ return ret;
+}
+
+void tdx_attestation_unregister(void)
+{
+ if (!tdx_att)
+ return;
+ attestation_unregister(&tdx_att->att);
+ if (tdx_att->log)
+ acpi_os_unmap_iomem(tdx_att->log, tdx_att->log_length);
+ attestation_put(&tdx_att->att);
+ tdx_att = NULL;
+}
+
+MODULE_IMPORT_NS("ATTESTATION");
+MODULE_IMPORT_NS("CTLV");
diff --git a/drivers/virt/coco/tdx-guest/internal.h b/drivers/virt/coco/tdx-guest/internal.h
index d57a8c385aff..fc10e68d231a 100644
--- a/drivers/virt/coco/tdx-guest/internal.h
+++ b/drivers/virt/coco/tdx-guest/internal.h
@@ -5,6 +5,8 @@
#include <crypto/sha2.h>
#include <linux/types.h>
+struct device;
+
#define TDX_GUEST_QUOTE_MAX_LEN ((128 * 1024) - 24)
struct tdx_guest_info {
@@ -27,4 +29,20 @@ int tdx_guest_mr_read(enum tdx_guest_mr mr, u8 value[SHA384_DIGEST_SIZE]);
int tdx_guest_rtmr_extend(unsigned int rtmr,
const u8 value[SHA384_DIGEST_SIZE]);
+#if IS_ENABLED(CONFIG_ATTESTATION)
+int tdx_attestation_register(struct device *parent,
+ const struct tdx_guest_info *info);
+void tdx_attestation_unregister(void);
+#else
+static inline int tdx_attestation_register(struct device *parent,
+ const struct tdx_guest_info *info)
+{
+ return 0;
+}
+
+static inline void tdx_attestation_unregister(void)
+{
+}
+#endif
+
#endif /* _TDX_GUEST_INTERNAL_H */
diff --git a/drivers/virt/coco/tdx-guest/tdx-guest.c b/drivers/virt/coco/tdx-guest/main.c
similarity index 98%
rename from drivers/virt/coco/tdx-guest/tdx-guest.c
rename to drivers/virt/coco/tdx-guest/main.c
index c49454ec0b18..bcda631a0232 100644
--- a/drivers/virt/coco/tdx-guest/tdx-guest.c
+++ b/drivers/virt/coco/tdx-guest/main.c
@@ -522,6 +522,12 @@ static int __init tdx_guest_init(void)
if (ret)
goto free_quote;
+ /* The other interfaces stay up without the attestation device. */
+ ret = tdx_attestation_register(tdx_misc_dev.this_device,
+ &tdx_guest_info);
+ if (ret)
+ pr_warn("attestation device registration failed: %d\n", ret);
+
return 0;
free_quote:
@@ -537,6 +543,7 @@ module_init(tdx_guest_init);
static void __exit tdx_guest_exit(void)
{
+ tdx_attestation_unregister();
tsm_report_unregister(&tdx_tsm_ops);
free_quote_buf(quote_buf);
misc_deregister(&tdx_misc_dev);
--
2.56.0
^ permalink raw reply related [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-11-13 16:09 UTC | newest]
Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-11-13 16:08 [PATCH RFC v2 0/9] attestation: Add a common subsystem and TDX support Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 1/9] attestation: Add the base family specification and generated files Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 2/9] Documentation: attestation: Add userspace ABI Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 3/9] attestation: Prepare provider and instance core Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 4/9] attestation: Prepare measurement registers Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 5/9] attestation: Add CTLV operation handling Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 6/9] Documentation: attestation: Describe how to write a provider Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 7/9] tdx-guest: Expose the native attestation service API Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 8/9] attestation: Add the TDX family specification and generated files Jiri Pirko
2026-11-13 16:08 ` [PATCH RFC v2 9/9] attestation: Add the TDX family implementation Jiri Pirko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox