kvm.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] target/i386/sev: add the support to query the attestation report
@ 2020-12-04 21:31 Brijesh Singh
  2020-12-10 16:13 ` James Bottomley
                   ` (3 more replies)
  0 siblings, 4 replies; 6+ messages in thread
From: Brijesh Singh @ 2020-12-04 21:31 UTC (permalink / raw)
  To: qemu-devel
  Cc: Brijesh Singh, James Bottomley, Tom Lendacky, Eric Blake,
	Paolo Bonzini, kvm

The SEV FW >= 0.23 added a new command that can be used to query the
attestation report containing the SHA-256 digest of the guest memory
and VMSA encrypted with the LAUNCH_UPDATE and sign it with the PEK.

Note, we already have a command (LAUNCH_MEASURE) that can be used to
query the SHA-256 digest of the guest memory encrypted through the
LAUNCH_UPDATE. The main difference between previous and this command
is that the report is signed with the PEK and unlike the LAUNCH_MEASURE
command the ATTESATION_REPORT command can be called while the guest
is running.

Add a QMP interface "query-sev-attestation-report" that can be used
to get the report encoded in base64.

Cc: James Bottomley <jejb@linux.ibm.com>
Cc: Tom Lendacky <Thomas.Lendacky@amd.com>
Cc: Eric Blake <eblake@redhat.com>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Cc: kvm@vger.kernel.org
Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
---
 linux-headers/linux/kvm.h |  8 ++++++
 qapi/misc-target.json     | 38 +++++++++++++++++++++++++++
 target/i386/monitor.c     |  6 +++++
 target/i386/sev-stub.c    |  7 +++++
 target/i386/sev.c         | 54 +++++++++++++++++++++++++++++++++++++++
 target/i386/sev_i386.h    |  2 ++
 6 files changed, 115 insertions(+)

diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
index 56ce14ad20..6d0f8101ba 100644
--- a/linux-headers/linux/kvm.h
+++ b/linux-headers/linux/kvm.h
@@ -1585,6 +1585,8 @@ enum sev_cmd_id {
 	KVM_SEV_DBG_ENCRYPT,
 	/* Guest certificates commands */
 	KVM_SEV_CERT_EXPORT,
+	/* Attestation report */
+	KVM_SEV_GET_ATTESTATION_REPORT,
 
 	KVM_SEV_NR_MAX,
 };
@@ -1637,6 +1639,12 @@ struct kvm_sev_dbg {
 	__u32 len;
 };
 
+struct kvm_sev_attestation_report {
+	__u8 mnonce[16];
+	__u64 uaddr;
+	__u32 len;
+};
+
 #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1 << 0)
 #define KVM_DEV_ASSIGN_PCI_2_3		(1 << 1)
 #define KVM_DEV_ASSIGN_MASK_INTX	(1 << 2)
diff --git a/qapi/misc-target.json b/qapi/misc-target.json
index 1e561fa97b..ec6565e6ef 100644
--- a/qapi/misc-target.json
+++ b/qapi/misc-target.json
@@ -267,3 +267,41 @@
 ##
 { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
   'if': 'defined(TARGET_ARM)' }
+
+
+##
+# @SevAttestationReport:
+#
+# The struct describes attestation report for a Secure Encrypted Virtualization
+# feature.
+#
+# @data:  guest attestation report (base64 encoded)
+#
+#
+# Since: 5.2
+##
+{ 'struct': 'SevAttestationReport',
+  'data': { 'data': 'str'},
+  'if': 'defined(TARGET_I386)' }
+
+##
+# @query-sev-attestation-report:
+#
+# This command is used to get the SEV attestation report, and is supported on AMD
+# X86 platforms only.
+#
+# @mnonce: a random 16 bytes of data (it will be included in report)
+#
+# Returns: SevAttestationReport objects.
+#
+# Since: 5.2
+#
+# Example:
+#
+# -> { "execute" : "query-sev-attestation-report", "arguments": { "mnonce": "aaaaaaa" } }
+# <- { "return" : { "data": "aaaaaaaabbbddddd"} }
+#
+##
+{ 'command': 'query-sev-attestation-report', 'data': { 'mnonce': 'str' },
+  'returns': 'SevAttestationReport',
+  'if': 'defined(TARGET_I386)' }
diff --git a/target/i386/monitor.c b/target/i386/monitor.c
index 9f9e1c42f4..a4b65f330c 100644
--- a/target/i386/monitor.c
+++ b/target/i386/monitor.c
@@ -729,3 +729,9 @@ SevCapability *qmp_query_sev_capabilities(Error **errp)
 {
     return sev_get_capabilities(errp);
 }
+
+SevAttestationReport *
+qmp_query_sev_attestation_report(const char *mnonce, Error **errp)
+{
+    return sev_get_attestation_report(mnonce, errp);
+}
diff --git a/target/i386/sev-stub.c b/target/i386/sev-stub.c
index 88e3f39a1e..66d16f53d8 100644
--- a/target/i386/sev-stub.c
+++ b/target/i386/sev-stub.c
@@ -49,3 +49,10 @@ SevCapability *sev_get_capabilities(Error **errp)
     error_setg(errp, "SEV is not available in this QEMU");
     return NULL;
 }
+
+SevAttestationReport *
+sev_get_attestation_report(const char *mnonce, Error **errp)
+{
+    error_setg(errp, "SEV is not available in this QEMU");
+    return NULL;
+}
diff --git a/target/i386/sev.c b/target/i386/sev.c
index 93c4d60b82..28958fb71b 100644
--- a/target/i386/sev.c
+++ b/target/i386/sev.c
@@ -68,6 +68,7 @@ struct SevGuestState {
 
 #define DEFAULT_GUEST_POLICY    0x1 /* disable debug */
 #define DEFAULT_SEV_DEVICE      "/dev/sev"
+#define DEFAULT_ATTESATION_REPORT_BUF_SIZE      4096
 
 static SevGuestState *sev_guest;
 static Error *sev_mig_blocker;
@@ -490,6 +491,59 @@ out:
     return cap;
 }
 
+SevAttestationReport *
+sev_get_attestation_report(const char *mnonce, Error **errp)
+{
+    struct kvm_sev_attestation_report input = {};
+    SevGuestState *sev = sev_guest;
+    SevAttestationReport *report;
+    guchar *data;
+    int err = 0, ret;
+
+    if (!sev_enabled()) {
+        error_setg(errp, "SEV is not enabled");
+        return NULL;
+    }
+
+    /* Verify that user provided random data length */
+    if (strlen(mnonce) != sizeof(input.mnonce)) {
+        error_setg(errp, "Expected mnonce data len %ld got %ld",
+                sizeof(input.mnonce), strlen(mnonce));
+        return NULL;
+    }
+
+    /* Query the report length */
+    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
+            &input, &err);
+    if (ret < 0) {
+        if (err != SEV_RET_INVALID_LEN) {
+            error_setg(errp, "failed to query the attestation report length "
+                    "ret=%d fw_err=%d (%s)", ret, err, fw_error_to_str(err));
+            return NULL;
+        }
+    }
+
+    data = g_malloc(input.len);
+    input.uaddr = (unsigned long)data;
+    memcpy(input.mnonce, mnonce, sizeof(input.mnonce));
+
+    /* Query the report */
+    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
+            &input, &err);
+    if (ret) {
+        error_setg_errno(errp, errno, "Failed to get attestation report"
+                " ret=%d fw_err=%d (%s)", ret, err, fw_error_to_str(err));
+        goto e_free_data;
+    }
+
+    report = g_new0(SevAttestationReport, 1);
+    report->data = g_base64_encode(data, input.len);
+
+e_free_data:
+    g_free(data);
+    return report;
+}
+
 static int
 sev_read_file_base64(const char *filename, guchar **data, gsize *len)
 {
diff --git a/target/i386/sev_i386.h b/target/i386/sev_i386.h
index 4db6960f60..e2d0774708 100644
--- a/target/i386/sev_i386.h
+++ b/target/i386/sev_i386.h
@@ -35,5 +35,7 @@ extern uint32_t sev_get_cbit_position(void);
 extern uint32_t sev_get_reduced_phys_bits(void);
 extern char *sev_get_launch_measurement(void);
 extern SevCapability *sev_get_capabilities(Error **errp);
+extern SevAttestationReport *
+sev_get_attestation_report(const char *mnonce, Error **errp);
 
 #endif
-- 
2.17.1


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] target/i386/sev: add the support to query the attestation report
  2020-12-04 21:31 [PATCH] target/i386/sev: add the support to query the attestation report Brijesh Singh
@ 2020-12-10 16:13 ` James Bottomley
  2020-12-11 13:23   ` Brijesh Singh
  2020-12-13 20:28 ` Dov Murik
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 6+ messages in thread
From: James Bottomley @ 2020-12-10 16:13 UTC (permalink / raw)
  To: Brijesh Singh, qemu-devel; +Cc: Tom Lendacky, Eric Blake, Paolo Bonzini, kvm

On Fri, 2020-12-04 at 15:31 -0600, Brijesh Singh wrote:
> The SEV FW >= 0.23 added a new command that can be used to query the
> attestation report containing the SHA-256 digest of the guest memory
> and VMSA encrypted with the LAUNCH_UPDATE and sign it with the PEK.
> 
> Note, we already have a command (LAUNCH_MEASURE) that can be used to
> query the SHA-256 digest of the guest memory encrypted through the
> LAUNCH_UPDATE. The main difference between previous and this command
> is that the report is signed with the PEK and unlike the
> LAUNCH_MEASURE
> command the ATTESATION_REPORT command can be called while the guest
> is running.
> 
> Add a QMP interface "query-sev-attestation-report" that can be used
> to get the report encoded in base64.
> 
> Cc: James Bottomley <jejb@linux.ibm.com>
> Cc: Tom Lendacky <Thomas.Lendacky@amd.com>
> Cc: Eric Blake <eblake@redhat.com>
> Cc: Paolo Bonzini <pbonzini@redhat.com>
> Cc: kvm@vger.kernel.org
> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
> ---
>  linux-headers/linux/kvm.h |  8 ++++++
>  qapi/misc-target.json     | 38 +++++++++++++++++++++++++++
>  target/i386/monitor.c     |  6 +++++
>  target/i386/sev-stub.c    |  7 +++++
>  target/i386/sev.c         | 54
> +++++++++++++++++++++++++++++++++++++++
>  target/i386/sev_i386.h    |  2 ++
>  6 files changed, 115 insertions(+)
> 
> diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
> index 56ce14ad20..6d0f8101ba 100644
> --- a/linux-headers/linux/kvm.h
> +++ b/linux-headers/linux/kvm.h
> @@ -1585,6 +1585,8 @@ enum sev_cmd_id {
>  	KVM_SEV_DBG_ENCRYPT,
>  	/* Guest certificates commands */
>  	KVM_SEV_CERT_EXPORT,
> +	/* Attestation report */
> +	KVM_SEV_GET_ATTESTATION_REPORT,
>  
>  	KVM_SEV_NR_MAX,
>  };
> @@ -1637,6 +1639,12 @@ struct kvm_sev_dbg {
>  	__u32 len;
>  };
>  
> +struct kvm_sev_attestation_report {
> +	__u8 mnonce[16];
> +	__u64 uaddr;
> +	__u32 len;
> +};
> +
>  #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1 << 0)
>  #define KVM_DEV_ASSIGN_PCI_2_3		(1 << 1)
>  #define KVM_DEV_ASSIGN_MASK_INTX	(1 << 2)
> diff --git a/qapi/misc-target.json b/qapi/misc-target.json
> index 1e561fa97b..ec6565e6ef 100644
> --- a/qapi/misc-target.json
> +++ b/qapi/misc-target.json
> @@ -267,3 +267,41 @@
>  ##
>  { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
>    'if': 'defined(TARGET_ARM)' }
> +
> +
> +##
> +# @SevAttestationReport:
> +#
> +# The struct describes attestation report for a Secure Encrypted
> Virtualization
> +# feature.
> +#
> +# @data:  guest attestation report (base64 encoded)
> +#
> +#
> +# Since: 5.2
> +##
> +{ 'struct': 'SevAttestationReport',
> +  'data': { 'data': 'str'},
> +  'if': 'defined(TARGET_I386)' }
> +
> +##
> +# @query-sev-attestation-report:
> +#
> +# This command is used to get the SEV attestation report, and is
> supported on AMD
> +# X86 platforms only.
> +#
> +# @mnonce: a random 16 bytes of data (it will be included in report)
> +#
> +# Returns: SevAttestationReport objects.
> +#
> +# Since: 5.2
> +#
> +# Example:
> +#
> +# -> { "execute" : "query-sev-attestation-report", "arguments": {
> "mnonce": "aaaaaaa" } }
> +# <- { "return" : { "data": "aaaaaaaabbbddddd"} }

It would be nice here, rather than returning a binary blob to break it
up into the actual returned components like query-sev does.

> +##
> +{ 'command': 'query-sev-attestation-report', 'data': { 'mnonce':
> 'str' },
> +  'returns': 'SevAttestationReport',
> +  'if': 'defined(TARGET_I386)' }
[...]
> diff --git a/target/i386/sev.c b/target/i386/sev.c
> index 93c4d60b82..28958fb71b 100644
> --- a/target/i386/sev.c
> +++ b/target/i386/sev.c
> @@ -68,6 +68,7 @@ struct SevGuestState {
>  
>  #define DEFAULT_GUEST_POLICY    0x1 /* disable debug */
>  #define DEFAULT_SEV_DEVICE      "/dev/sev"
> +#define DEFAULT_ATTESATION_REPORT_BUF_SIZE      4096
>  
>  static SevGuestState *sev_guest;
>  static Error *sev_mig_blocker;
> @@ -490,6 +491,59 @@ out:
>      return cap;
>  }
>  
> +SevAttestationReport *
> +sev_get_attestation_report(const char *mnonce, Error **errp)
> +{
> +    struct kvm_sev_attestation_report input = {};
> +    SevGuestState *sev = sev_guest;
> +    SevAttestationReport *report;
> +    guchar *data;
> +    int err = 0, ret;
> +
> +    if (!sev_enabled()) {
> +        error_setg(errp, "SEV is not enabled");
> +        return NULL;
> +    }
> +
> +    /* Verify that user provided random data length */

There should be a g_base64_decode here, shouldn't there, so we can pass
an arbitrary 16 byte binary blob.

> +    if (strlen(mnonce) != sizeof(input.mnonce)) {

So this if would check the decoded length.

> +        error_setg(errp, "Expected mnonce data len %ld got %ld",
> +                sizeof(input.mnonce), strlen(mnonce));
> +        return NULL;
> +    }
> +
> +    /* Query the report length */
> +    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
> +            &input, &err);
> +    if (ret < 0) {
> +        if (err != SEV_RET_INVALID_LEN) {
> +            error_setg(errp, "failed to query the attestation report
> length "
> +                    "ret=%d fw_err=%d (%s)", ret, err,
> fw_error_to_str(err));
> +            return NULL;
> +        }
> +    }
> +
> +    data = g_malloc(input.len);
> +    input.uaddr = (unsigned long)data;
> +    memcpy(input.mnonce, mnonce, sizeof(input.mnonce));
> +
> +    /* Query the report */
> +    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
> +            &input, &err);
> +    if (ret) {
> +        error_setg_errno(errp, errno, "Failed to get attestation
> report"
> +                " ret=%d fw_err=%d (%s)", ret, err,
> fw_error_to_str(err));

report should be set to NULL here to avoid returning uninitialized data
from the goto.

> +        goto e_free_data;
> +    }
> +
> +    report = g_new0(SevAttestationReport, 1);
> +    report->data = g_base64_encode(data, input.len);
> +
> +e_free_data:
> +    g_free(data);
> +    return report;
> +}
> +
>  static int
>  sev_read_file_base64(const char *filename, guchar **data, gsize
> *len)

James



^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] target/i386/sev: add the support to query the attestation report
  2020-12-10 16:13 ` James Bottomley
@ 2020-12-11 13:23   ` Brijesh Singh
  0 siblings, 0 replies; 6+ messages in thread
From: Brijesh Singh @ 2020-12-11 13:23 UTC (permalink / raw)
  To: jejb, qemu-devel
  Cc: brijesh.singh, Tom Lendacky, Eric Blake, Paolo Bonzini, kvm


On 12/10/20 10:13 AM, James Bottomley wrote:
> On Fri, 2020-12-04 at 15:31 -0600, Brijesh Singh wrote:
>> The SEV FW >= 0.23 added a new command that can be used to query the
>> attestation report containing the SHA-256 digest of the guest memory
>> and VMSA encrypted with the LAUNCH_UPDATE and sign it with the PEK.
>>
>> Note, we already have a command (LAUNCH_MEASURE) that can be used to
>> query the SHA-256 digest of the guest memory encrypted through the
>> LAUNCH_UPDATE. The main difference between previous and this command
>> is that the report is signed with the PEK and unlike the
>> LAUNCH_MEASURE
>> command the ATTESATION_REPORT command can be called while the guest
>> is running.
>>
>> Add a QMP interface "query-sev-attestation-report" that can be used
>> to get the report encoded in base64.
>>
>> Cc: James Bottomley <jejb@linux.ibm.com>
>> Cc: Tom Lendacky <Thomas.Lendacky@amd.com>
>> Cc: Eric Blake <eblake@redhat.com>
>> Cc: Paolo Bonzini <pbonzini@redhat.com>
>> Cc: kvm@vger.kernel.org
>> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
>> ---
>>  linux-headers/linux/kvm.h |  8 ++++++
>>  qapi/misc-target.json     | 38 +++++++++++++++++++++++++++
>>  target/i386/monitor.c     |  6 +++++
>>  target/i386/sev-stub.c    |  7 +++++
>>  target/i386/sev.c         | 54
>> +++++++++++++++++++++++++++++++++++++++
>>  target/i386/sev_i386.h    |  2 ++
>>  6 files changed, 115 insertions(+)
>>
>> diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
>> index 56ce14ad20..6d0f8101ba 100644
>> --- a/linux-headers/linux/kvm.h
>> +++ b/linux-headers/linux/kvm.h
>> @@ -1585,6 +1585,8 @@ enum sev_cmd_id {
>>  	KVM_SEV_DBG_ENCRYPT,
>>  	/* Guest certificates commands */
>>  	KVM_SEV_CERT_EXPORT,
>> +	/* Attestation report */
>> +	KVM_SEV_GET_ATTESTATION_REPORT,
>>  
>>  	KVM_SEV_NR_MAX,
>>  };
>> @@ -1637,6 +1639,12 @@ struct kvm_sev_dbg {
>>  	__u32 len;
>>  };
>>  
>> +struct kvm_sev_attestation_report {
>> +	__u8 mnonce[16];
>> +	__u64 uaddr;
>> +	__u32 len;
>> +};
>> +
>>  #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1 << 0)
>>  #define KVM_DEV_ASSIGN_PCI_2_3		(1 << 1)
>>  #define KVM_DEV_ASSIGN_MASK_INTX	(1 << 2)
>> diff --git a/qapi/misc-target.json b/qapi/misc-target.json
>> index 1e561fa97b..ec6565e6ef 100644
>> --- a/qapi/misc-target.json
>> +++ b/qapi/misc-target.json
>> @@ -267,3 +267,41 @@
>>  ##
>>  { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
>>    'if': 'defined(TARGET_ARM)' }
>> +
>> +
>> +##
>> +# @SevAttestationReport:
>> +#
>> +# The struct describes attestation report for a Secure Encrypted
>> Virtualization
>> +# feature.
>> +#
>> +# @data:  guest attestation report (base64 encoded)
>> +#
>> +#
>> +# Since: 5.2
>> +##
>> +{ 'struct': 'SevAttestationReport',
>> +  'data': { 'data': 'str'},
>> +  'if': 'defined(TARGET_I386)' }
>> +
>> +##
>> +# @query-sev-attestation-report:
>> +#
>> +# This command is used to get the SEV attestation report, and is
>> supported on AMD
>> +# X86 platforms only.
>> +#
>> +# @mnonce: a random 16 bytes of data (it will be included in report)
>> +#
>> +# Returns: SevAttestationReport objects.
>> +#
>> +# Since: 5.2
>> +#
>> +# Example:
>> +#
>> +# -> { "execute" : "query-sev-attestation-report", "arguments": {
>> "mnonce": "aaaaaaa" } }
>> +# <- { "return" : { "data": "aaaaaaaabbbddddd"} }
> It would be nice here, rather than returning a binary blob to break it
> up into the actual returned components like query-sev does.

In past, I have seen that the fields defined in blobs have changed based
on the API versions. So, I tried to stay away from expanding the blob
unless its absolutely required. I would  prefer to stick to that approach.


>
>> +##
>> +{ 'command': 'query-sev-attestation-report', 'data': { 'mnonce':
>> 'str' },
>> +  'returns': 'SevAttestationReport',
>> +  'if': 'defined(TARGET_I386)' }
> [...]
>> diff --git a/target/i386/sev.c b/target/i386/sev.c
>> index 93c4d60b82..28958fb71b 100644
>> --- a/target/i386/sev.c
>> +++ b/target/i386/sev.c
>> @@ -68,6 +68,7 @@ struct SevGuestState {
>>  
>>  #define DEFAULT_GUEST_POLICY    0x1 /* disable debug */
>>  #define DEFAULT_SEV_DEVICE      "/dev/sev"
>> +#define DEFAULT_ATTESATION_REPORT_BUF_SIZE      4096
>>  
>>  static SevGuestState *sev_guest;
>>  static Error *sev_mig_blocker;
>> @@ -490,6 +491,59 @@ out:
>>      return cap;
>>  }
>>  
>> +SevAttestationReport *
>> +sev_get_attestation_report(const char *mnonce, Error **errp)
>> +{
>> +    struct kvm_sev_attestation_report input = {};
>> +    SevGuestState *sev = sev_guest;
>> +    SevAttestationReport *report;
>> +    guchar *data;
>> +    int err = 0, ret;
>> +
>> +    if (!sev_enabled()) {
>> +        error_setg(errp, "SEV is not enabled");
>> +        return NULL;
>> +    }
>> +
>> +    /* Verify that user provided random data length */
> There should be a g_base64_decode here, shouldn't there, so we can pass
> an arbitrary 16 byte binary blob.


Agreed, I will make this field base64 in v2.


>
>> +    if (strlen(mnonce) != sizeof(input.mnonce)) {
> So this if would check the decoded length.
>
>> +        error_setg(errp, "Expected mnonce data len %ld got %ld",
>> +                sizeof(input.mnonce), strlen(mnonce));
>> +        return NULL;
>> +    }
>> +
>> +    /* Query the report length */
>> +    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
>> +            &input, &err);
>> +    if (ret < 0) {
>> +        if (err != SEV_RET_INVALID_LEN) {
>> +            error_setg(errp, "failed to query the attestation report
>> length "
>> +                    "ret=%d fw_err=%d (%s)", ret, err,
>> fw_error_to_str(err));
>> +            return NULL;
>> +        }
>> +    }
>> +
>> +    data = g_malloc(input.len);
>> +    input.uaddr = (unsigned long)data;
>> +    memcpy(input.mnonce, mnonce, sizeof(input.mnonce));
>> +
>> +    /* Query the report */
>> +    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
>> +            &input, &err);
>> +    if (ret) {
>> +        error_setg_errno(errp, errno, "Failed to get attestation
>> report"
>> +                " ret=%d fw_err=%d (%s)", ret, err,
>> fw_error_to_str(err));
> report should be set to NULL here to avoid returning uninitialized data
> from the goto.


Noted. thanks

>
>> +        goto e_free_data;
>> +    }
>> +
>> +    report = g_new0(SevAttestationReport, 1);
>> +    report->data = g_base64_encode(data, input.len);
>> +
>> +e_free_data:
>> +    g_free(data);
>> +    return report;
>> +}
>> +
>>  static int
>>  sev_read_file_base64(const char *filename, guchar **data, gsize
>> *len)
> James
>
>

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] target/i386/sev: add the support to query the attestation report
  2020-12-04 21:31 [PATCH] target/i386/sev: add the support to query the attestation report Brijesh Singh
  2020-12-10 16:13 ` James Bottomley
@ 2020-12-13 20:28 ` Dov Murik
  2021-01-14 18:34 ` Dr. David Alan Gilbert
  2021-01-19 16:45 ` Eric Blake
  3 siblings, 0 replies; 6+ messages in thread
From: Dov Murik @ 2020-12-13 20:28 UTC (permalink / raw)
  To: Brijesh Singh, qemu-devel
  Cc: James Bottomley, Tom Lendacky, Eric Blake, Paolo Bonzini, kvm



On 04/12/2020 23:31, Brijesh Singh wrote:
> The SEV FW >= 0.23 added a new command that can be used to query the
> attestation report containing the SHA-256 digest of the guest memory
> and VMSA encrypted with the LAUNCH_UPDATE and sign it with the PEK.
> 
> Note, we already have a command (LAUNCH_MEASURE) that can be used to
> query the SHA-256 digest of the guest memory encrypted through the
> LAUNCH_UPDATE. The main difference between previous and this command
> is that the report is signed with the PEK and unlike the LAUNCH_MEASURE
> command the ATTESATION_REPORT command can be called while the guest
> is running.
> 
> Add a QMP interface "query-sev-attestation-report" that can be used
> to get the report encoded in base64.
> 
> Cc: James Bottomley <jejb@linux.ibm.com>
> Cc: Tom Lendacky <Thomas.Lendacky@amd.com>
> Cc: Eric Blake <eblake@redhat.com>
> Cc: Paolo Bonzini <pbonzini@redhat.com>
> Cc: kvm@vger.kernel.org
> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
> ---
>   linux-headers/linux/kvm.h |  8 ++++++
>   qapi/misc-target.json     | 38 +++++++++++++++++++++++++++
>   target/i386/monitor.c     |  6 +++++
>   target/i386/sev-stub.c    |  7 +++++
>   target/i386/sev.c         | 54 +++++++++++++++++++++++++++++++++++++++
>   target/i386/sev_i386.h    |  2 ++
>   6 files changed, 115 insertions(+)
> 
> diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
> index 56ce14ad20..6d0f8101ba 100644
> --- a/linux-headers/linux/kvm.h
> +++ b/linux-headers/linux/kvm.h
> @@ -1585,6 +1585,8 @@ enum sev_cmd_id {
>   	KVM_SEV_DBG_ENCRYPT,
>   	/* Guest certificates commands */
>   	KVM_SEV_CERT_EXPORT,
> +	/* Attestation report */
> +	KVM_SEV_GET_ATTESTATION_REPORT,
> 
>   	KVM_SEV_NR_MAX,
>   };
> @@ -1637,6 +1639,12 @@ struct kvm_sev_dbg {
>   	__u32 len;
>   };
> 
> +struct kvm_sev_attestation_report {
> +	__u8 mnonce[16];
> +	__u64 uaddr;
> +	__u32 len;
> +};
> +
>   #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1 << 0)
>   #define KVM_DEV_ASSIGN_PCI_2_3		(1 << 1)
>   #define KVM_DEV_ASSIGN_MASK_INTX	(1 << 2)
> diff --git a/qapi/misc-target.json b/qapi/misc-target.json
> index 1e561fa97b..ec6565e6ef 100644
> --- a/qapi/misc-target.json
> +++ b/qapi/misc-target.json
> @@ -267,3 +267,41 @@
>   ##
>   { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
>     'if': 'defined(TARGET_ARM)' }
> +
> +
> +##
> +# @SevAttestationReport:
> +#
> +# The struct describes attestation report for a Secure Encrypted Virtualization
> +# feature.
> +#
> +# @data:  guest attestation report (base64 encoded)
> +#
> +#
> +# Since: 5.2
> +##
> +{ 'struct': 'SevAttestationReport',
> +  'data': { 'data': 'str'},
> +  'if': 'defined(TARGET_I386)' }
> +
> +##
> +# @query-sev-attestation-report:
> +#
> +# This command is used to get the SEV attestation report, and is supported on AMD
> +# X86 platforms only.
> +#
> +# @mnonce: a random 16 bytes of data (it will be included in report)
> +#
> +# Returns: SevAttestationReport objects.
> +#
> +# Since: 5.2
> +#
> +# Example:
> +#
> +# -> { "execute" : "query-sev-attestation-report", "arguments": { "mnonce": "aaaaaaa" } }
> +# <- { "return" : { "data": "aaaaaaaabbbddddd"} }
> +#
> +##
> +{ 'command': 'query-sev-attestation-report', 'data': { 'mnonce': 'str' },
> +  'returns': 'SevAttestationReport',
> +  'if': 'defined(TARGET_I386)' }
> diff --git a/target/i386/monitor.c b/target/i386/monitor.c
> index 9f9e1c42f4..a4b65f330c 100644
> --- a/target/i386/monitor.c
> +++ b/target/i386/monitor.c
> @@ -729,3 +729,9 @@ SevCapability *qmp_query_sev_capabilities(Error **errp)
>   {
>       return sev_get_capabilities(errp);
>   }
> +
> +SevAttestationReport *
> +qmp_query_sev_attestation_report(const char *mnonce, Error **errp)
> +{
> +    return sev_get_attestation_report(mnonce, errp);
> +}
> diff --git a/target/i386/sev-stub.c b/target/i386/sev-stub.c
> index 88e3f39a1e..66d16f53d8 100644
> --- a/target/i386/sev-stub.c
> +++ b/target/i386/sev-stub.c
> @@ -49,3 +49,10 @@ SevCapability *sev_get_capabilities(Error **errp)
>       error_setg(errp, "SEV is not available in this QEMU");
>       return NULL;
>   }
> +
> +SevAttestationReport *
> +sev_get_attestation_report(const char *mnonce, Error **errp)
> +{
> +    error_setg(errp, "SEV is not available in this QEMU");
> +    return NULL;
> +}
> diff --git a/target/i386/sev.c b/target/i386/sev.c
> index 93c4d60b82..28958fb71b 100644
> --- a/target/i386/sev.c
> +++ b/target/i386/sev.c
> @@ -68,6 +68,7 @@ struct SevGuestState {
> 
>   #define DEFAULT_GUEST_POLICY    0x1 /* disable debug */
>   #define DEFAULT_SEV_DEVICE      "/dev/sev"
> +#define DEFAULT_ATTESATION_REPORT_BUF_SIZE      4096

This is unused.


> 
>   static SevGuestState *sev_guest;
>   static Error *sev_mig_blocker;
> @@ -490,6 +491,59 @@ out:
>       return cap;
>   }
> 
> +SevAttestationReport *
> +sev_get_attestation_report(const char *mnonce, Error **errp)
> +{
> +    struct kvm_sev_attestation_report input = {};
> +    SevGuestState *sev = sev_guest;
> +    SevAttestationReport *report;
> +    guchar *data;
> +    int err = 0, ret;
> +
> +    if (!sev_enabled()) {
> +        error_setg(errp, "SEV is not enabled");
> +        return NULL;
> +    }
> +
> +    /* Verify that user provided random data length */
> +    if (strlen(mnonce) != sizeof(input.mnonce)) {
> +        error_setg(errp, "Expected mnonce data len %ld got %ld",
> +                sizeof(input.mnonce), strlen(mnonce));
> +        return NULL;
> +    }
> +
> +    /* Query the report length */
> +    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
> +            &input, &err);
> +    if (ret < 0) {
> +        if (err != SEV_RET_INVALID_LEN) {
> +            error_setg(errp, "failed to query the attestation report length "
> +                    "ret=%d fw_err=%d (%s)", ret, err, fw_error_to_str(err));
> +            return NULL;
> +        }
> +    }
> +
> +    data = g_malloc(input.len);
> +    input.uaddr = (unsigned long)data;
> +    memcpy(input.mnonce, mnonce, sizeof(input.mnonce));
> +
> +    /* Query the report */
> +    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
> +            &input, &err);
> +    if (ret) {
> +        error_setg_errno(errp, errno, "Failed to get attestation report"
> +                " ret=%d fw_err=%d (%s)", ret, err, fw_error_to_str(err));
> +        goto e_free_data;

James already noted the missing `report = NULL` here. Another option is 
to avoid the goto entirely and use qemu's g_autofree attribute for 
'report'; then you can simply `return NULL` here.  I guess it's a matter 
of style (elsewhere in sev.c there are `goto err`, so maybe it's best to 
keep the same style).



> +    }
> +
> +    report = g_new0(SevAttestationReport, 1);
> +    report->data = g_base64_encode(data, input.len);

In most other SEV commands in sev.c there's a trace-event with the 
details of the command (for example, in sev_launch_get_measure it just 
prints the result).  I think adding such a trace here with the nonce and 
the report->data would be helpful for debugging.

-Dov


> +
> +e_free_data:
> +    g_free(data);
> +    return report;
> +}
> +
>   static int
>   sev_read_file_base64(const char *filename, guchar **data, gsize *len)
>   {
> diff --git a/target/i386/sev_i386.h b/target/i386/sev_i386.h
> index 4db6960f60..e2d0774708 100644
> --- a/target/i386/sev_i386.h
> +++ b/target/i386/sev_i386.h
> @@ -35,5 +35,7 @@ extern uint32_t sev_get_cbit_position(void);
>   extern uint32_t sev_get_reduced_phys_bits(void);
>   extern char *sev_get_launch_measurement(void);
>   extern SevCapability *sev_get_capabilities(Error **errp);
> +extern SevAttestationReport *
> +sev_get_attestation_report(const char *mnonce, Error **errp);
> 
>   #endif
> 


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] target/i386/sev: add the support to query the attestation report
  2020-12-04 21:31 [PATCH] target/i386/sev: add the support to query the attestation report Brijesh Singh
  2020-12-10 16:13 ` James Bottomley
  2020-12-13 20:28 ` Dov Murik
@ 2021-01-14 18:34 ` Dr. David Alan Gilbert
  2021-01-19 16:45 ` Eric Blake
  3 siblings, 0 replies; 6+ messages in thread
From: Dr. David Alan Gilbert @ 2021-01-14 18:34 UTC (permalink / raw)
  To: Brijesh Singh, dgibson
  Cc: qemu-devel, Tom Lendacky, kvm, James Bottomley, Paolo Bonzini

* Brijesh Singh (brijesh.singh@amd.com) wrote:
> The SEV FW >= 0.23 added a new command that can be used to query the
> attestation report containing the SHA-256 digest of the guest memory
> and VMSA encrypted with the LAUNCH_UPDATE and sign it with the PEK.
> 
> Note, we already have a command (LAUNCH_MEASURE) that can be used to
> query the SHA-256 digest of the guest memory encrypted through the
> LAUNCH_UPDATE. The main difference between previous and this command
> is that the report is signed with the PEK and unlike the LAUNCH_MEASURE
> command the ATTESATION_REPORT command can be called while the guest
> is running.
> 
> Add a QMP interface "query-sev-attestation-report" that can be used
> to get the report encoded in base64.
> 
> Cc: James Bottomley <jejb@linux.ibm.com>
> Cc: Tom Lendacky <Thomas.Lendacky@amd.com>
> Cc: Eric Blake <eblake@redhat.com>
> Cc: Paolo Bonzini <pbonzini@redhat.com>
> Cc: kvm@vger.kernel.org
> Signed-off-by: Brijesh Singh <brijesh.singh@amd.com>
> ---
>  linux-headers/linux/kvm.h |  8 ++++++
>  qapi/misc-target.json     | 38 +++++++++++++++++++++++++++
>  target/i386/monitor.c     |  6 +++++
>  target/i386/sev-stub.c    |  7 +++++
>  target/i386/sev.c         | 54 +++++++++++++++++++++++++++++++++++++++
>  target/i386/sev_i386.h    |  2 ++
>  6 files changed, 115 insertions(+)
> 
> diff --git a/linux-headers/linux/kvm.h b/linux-headers/linux/kvm.h
> index 56ce14ad20..6d0f8101ba 100644
> --- a/linux-headers/linux/kvm.h
> +++ b/linux-headers/linux/kvm.h
> @@ -1585,6 +1585,8 @@ enum sev_cmd_id {
>  	KVM_SEV_DBG_ENCRYPT,
>  	/* Guest certificates commands */
>  	KVM_SEV_CERT_EXPORT,
> +	/* Attestation report */
> +	KVM_SEV_GET_ATTESTATION_REPORT,
>  
>  	KVM_SEV_NR_MAX,
>  };
> @@ -1637,6 +1639,12 @@ struct kvm_sev_dbg {
>  	__u32 len;
>  };
>  
> +struct kvm_sev_attestation_report {
> +	__u8 mnonce[16];
> +	__u64 uaddr;
> +	__u32 len;
> +};
> +
>  #define KVM_DEV_ASSIGN_ENABLE_IOMMU	(1 << 0)
>  #define KVM_DEV_ASSIGN_PCI_2_3		(1 << 1)
>  #define KVM_DEV_ASSIGN_MASK_INTX	(1 << 2)
> diff --git a/qapi/misc-target.json b/qapi/misc-target.json
> index 1e561fa97b..ec6565e6ef 100644
> --- a/qapi/misc-target.json
> +++ b/qapi/misc-target.json
> @@ -267,3 +267,41 @@
>  ##
>  { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
>    'if': 'defined(TARGET_ARM)' }
> +
> +
> +##
> +# @SevAttestationReport:
> +#
> +# The struct describes attestation report for a Secure Encrypted Virtualization
> +# feature.
> +#
> +# @data:  guest attestation report (base64 encoded)
> +#
> +#
> +# Since: 5.2
> +##
> +{ 'struct': 'SevAttestationReport',
> +  'data': { 'data': 'str'},
> +  'if': 'defined(TARGET_I386)' }
> +
> +##
> +# @query-sev-attestation-report:
> +#
> +# This command is used to get the SEV attestation report, and is supported on AMD
> +# X86 platforms only.
> +#
> +# @mnonce: a random 16 bytes of data (it will be included in report)
> +#
> +# Returns: SevAttestationReport objects.
> +#
> +# Since: 5.2
> +#
> +# Example:
> +#
> +# -> { "execute" : "query-sev-attestation-report", "arguments": { "mnonce": "aaaaaaa" } }
> +# <- { "return" : { "data": "aaaaaaaabbbddddd"} }
> +#
> +##

Can you please make this more generic, I'm thinking of something like:

  query-attestation-report
that returns an AttstationReport, which has a number of optional
components, of which you start off with one optional 'sev' component.

That way we can get other vendors to add to that same command rather
than inventing one extra command per vendor.

Dave

> +{ 'command': 'query-sev-attestation-report', 'data': { 'mnonce': 'str' },
> +  'returns': 'SevAttestationReport',
> +  'if': 'defined(TARGET_I386)' }
> diff --git a/target/i386/monitor.c b/target/i386/monitor.c
> index 9f9e1c42f4..a4b65f330c 100644
> --- a/target/i386/monitor.c
> +++ b/target/i386/monitor.c
> @@ -729,3 +729,9 @@ SevCapability *qmp_query_sev_capabilities(Error **errp)
>  {
>      return sev_get_capabilities(errp);
>  }
> +
> +SevAttestationReport *
> +qmp_query_sev_attestation_report(const char *mnonce, Error **errp)
> +{
> +    return sev_get_attestation_report(mnonce, errp);
> +}
> diff --git a/target/i386/sev-stub.c b/target/i386/sev-stub.c
> index 88e3f39a1e..66d16f53d8 100644
> --- a/target/i386/sev-stub.c
> +++ b/target/i386/sev-stub.c
> @@ -49,3 +49,10 @@ SevCapability *sev_get_capabilities(Error **errp)
>      error_setg(errp, "SEV is not available in this QEMU");
>      return NULL;
>  }
> +
> +SevAttestationReport *
> +sev_get_attestation_report(const char *mnonce, Error **errp)
> +{
> +    error_setg(errp, "SEV is not available in this QEMU");
> +    return NULL;
> +}
> diff --git a/target/i386/sev.c b/target/i386/sev.c
> index 93c4d60b82..28958fb71b 100644
> --- a/target/i386/sev.c
> +++ b/target/i386/sev.c
> @@ -68,6 +68,7 @@ struct SevGuestState {
>  
>  #define DEFAULT_GUEST_POLICY    0x1 /* disable debug */
>  #define DEFAULT_SEV_DEVICE      "/dev/sev"
> +#define DEFAULT_ATTESATION_REPORT_BUF_SIZE      4096
>  
>  static SevGuestState *sev_guest;
>  static Error *sev_mig_blocker;
> @@ -490,6 +491,59 @@ out:
>      return cap;
>  }
>  
> +SevAttestationReport *
> +sev_get_attestation_report(const char *mnonce, Error **errp)
> +{
> +    struct kvm_sev_attestation_report input = {};
> +    SevGuestState *sev = sev_guest;
> +    SevAttestationReport *report;
> +    guchar *data;
> +    int err = 0, ret;
> +
> +    if (!sev_enabled()) {
> +        error_setg(errp, "SEV is not enabled");
> +        return NULL;
> +    }
> +
> +    /* Verify that user provided random data length */
> +    if (strlen(mnonce) != sizeof(input.mnonce)) {
> +        error_setg(errp, "Expected mnonce data len %ld got %ld",
> +                sizeof(input.mnonce), strlen(mnonce));
> +        return NULL;
> +    }
> +
> +    /* Query the report length */
> +    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
> +            &input, &err);
> +    if (ret < 0) {
> +        if (err != SEV_RET_INVALID_LEN) {
> +            error_setg(errp, "failed to query the attestation report length "
> +                    "ret=%d fw_err=%d (%s)", ret, err, fw_error_to_str(err));
> +            return NULL;
> +        }
> +    }
> +
> +    data = g_malloc(input.len);
> +    input.uaddr = (unsigned long)data;
> +    memcpy(input.mnonce, mnonce, sizeof(input.mnonce));
> +
> +    /* Query the report */
> +    ret = sev_ioctl(sev->sev_fd, KVM_SEV_GET_ATTESTATION_REPORT,
> +            &input, &err);
> +    if (ret) {
> +        error_setg_errno(errp, errno, "Failed to get attestation report"
> +                " ret=%d fw_err=%d (%s)", ret, err, fw_error_to_str(err));
> +        goto e_free_data;
> +    }
> +
> +    report = g_new0(SevAttestationReport, 1);
> +    report->data = g_base64_encode(data, input.len);
> +
> +e_free_data:
> +    g_free(data);
> +    return report;
> +}
> +
>  static int
>  sev_read_file_base64(const char *filename, guchar **data, gsize *len)
>  {
> diff --git a/target/i386/sev_i386.h b/target/i386/sev_i386.h
> index 4db6960f60..e2d0774708 100644
> --- a/target/i386/sev_i386.h
> +++ b/target/i386/sev_i386.h
> @@ -35,5 +35,7 @@ extern uint32_t sev_get_cbit_position(void);
>  extern uint32_t sev_get_reduced_phys_bits(void);
>  extern char *sev_get_launch_measurement(void);
>  extern SevCapability *sev_get_capabilities(Error **errp);
> +extern SevAttestationReport *
> +sev_get_attestation_report(const char *mnonce, Error **errp);
>  
>  #endif
> -- 
> 2.17.1
> 
> 
-- 
Dr. David Alan Gilbert / dgilbert@redhat.com / Manchester, UK


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: [PATCH] target/i386/sev: add the support to query the attestation report
  2020-12-04 21:31 [PATCH] target/i386/sev: add the support to query the attestation report Brijesh Singh
                   ` (2 preceding siblings ...)
  2021-01-14 18:34 ` Dr. David Alan Gilbert
@ 2021-01-19 16:45 ` Eric Blake
  3 siblings, 0 replies; 6+ messages in thread
From: Eric Blake @ 2021-01-19 16:45 UTC (permalink / raw)
  To: Brijesh Singh, qemu-devel
  Cc: James Bottomley, Tom Lendacky, Paolo Bonzini, kvm

On 12/4/20 3:31 PM, Brijesh Singh wrote:
> The SEV FW >= 0.23 added a new command that can be used to query the
> attestation report containing the SHA-256 digest of the guest memory
> and VMSA encrypted with the LAUNCH_UPDATE and sign it with the PEK.
> 
> Note, we already have a command (LAUNCH_MEASURE) that can be used to
> query the SHA-256 digest of the guest memory encrypted through the
> LAUNCH_UPDATE. The main difference between previous and this command
> is that the report is signed with the PEK and unlike the LAUNCH_MEASURE
> command the ATTESATION_REPORT command can be called while the guest
> is running.
> 
> Add a QMP interface "query-sev-attestation-report" that can be used
> to get the report encoded in base64.
> 

> +++ b/qapi/misc-target.json
> @@ -267,3 +267,41 @@
>  ##
>  { 'command': 'query-gic-capabilities', 'returns': ['GICCapability'],
>    'if': 'defined(TARGET_ARM)' }
> +
> +
> +##
> +# @SevAttestationReport:
> +#
> +# The struct describes attestation report for a Secure Encrypted Virtualization
> +# feature.
> +#
> +# @data:  guest attestation report (base64 encoded)
> +#
> +#
> +# Since: 5.2

You've missed the 5.2 release; this should be since 6.0.

> +##
> +{ 'struct': 'SevAttestationReport',
> +  'data': { 'data': 'str'},
> +  'if': 'defined(TARGET_I386)' }
> +
> +##
> +# @query-sev-attestation-report:
> +#
> +# This command is used to get the SEV attestation report, and is supported on AMD
> +# X86 platforms only.
> +#
> +# @mnonce: a random 16 bytes of data (it will be included in report)

This says 16 bytes,...

> +#
> +# Returns: SevAttestationReport objects.
> +#
> +# Since: 5.2

Likewise.

> +#
> +# Example:
> +#
> +# -> { "execute" : "query-sev-attestation-report", "arguments": { "mnonce": "aaaaaaa" } }

...but this example does not use 16 bytes.  That's confusing.

> +# <- { "return" : { "data": "aaaaaaaabbbddddd"} }
> +#
> +##
> +{ 'command': 'query-sev-attestation-report', 'data': { 'mnonce': 'str' },
> +  'returns': 'SevAttestationReport',
> +  'if': 'defined(TARGET_I386)' }
> diff --git a/target/i386/monitor.c b/target/i386/monitor.c


-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3226
Virtualization:  qemu.org | libvirt.org


^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2021-01-19 18:28 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-12-04 21:31 [PATCH] target/i386/sev: add the support to query the attestation report Brijesh Singh
2020-12-10 16:13 ` James Bottomley
2020-12-11 13:23   ` Brijesh Singh
2020-12-13 20:28 ` Dov Murik
2021-01-14 18:34 ` Dr. David Alan Gilbert
2021-01-19 16:45 ` Eric Blake

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).