On 12/02/2026 21.43, Zhuoying Cai wrote:
DIAG 320 subcode 2 provides verification-certificates (VCs) that are in the
certificate store. Only X509 certificates in DER format and SHA-256 hash
type are recognized.

The subcode value is denoted by setting the second-left-most bit
of an 8-byte field.

The Verification Certificate Block (VCB) contains the output data
when the operation completes successfully. It includes a common
header followed by zero or more Verification Certificate Entries (VCEs),
depending on the VCB input length and the VC range (from the first VC
index to the last VC index) in the certificate store.

Each VCE contains information about a certificate retrieved from
the S390IPLCertificateStore, such as the certificate name, key type,
key ID length, hash length, and the raw certificate data.
The key ID and hash are extracted from the raw certificate by the crypto API.

Note: SHA2-256 VC hash type is required for retrieving the hash
(fingerprint) of the certificate.

Signed-off-by: Zhuoying Cai <[email protected]>
---
...
  #define VCSSB_NO_VC     4
  #define VCSSB_MIN_LEN   128
  #define VCE_HEADER_LEN  128
+#define VCE_INVALID_LEN 72

Why is 72 an invalid len? Because it's less than VCE_HEADER_LEN ? ... a comment might be helpful here.

...
+static VCEntry *diag_320_build_vce(const S390IPLCertificate *cert, int idx)
+{
+    g_autofree VCEntry *vce = NULL;
+    uint32_t vce_max_size;
+    int rc;
+
+    /*
+     * Each field of the VCE is word-aligned.
+     * Allocate enough space for the largest possible size for this VCE.
+     * As the certificate fields (key-id, hash, data) are parsed, the
+     * VCE's length field will be updated accordingly.
+     */
+    vce_max_size = VCE_HEADER_LEN +
+                   ROUND_UP(CERT_KEY_ID_LEN, 4) +
+                   ROUND_UP(CERT_HASH_LEN, 4) +
+                   ROUND_UP(cert->der_size, 4);
+
+    vce = g_malloc0(vce_max_size);
+    rc = build_vce_header(vce, cert, idx);
+    if (rc) {
+        vce->len = cpu_to_be32(VCE_INVALID_LEN);
+        goto out;
+    }
+
+    vce->len = cpu_to_be32(vce_max_size);
+    rc = build_vce_data(vce, cert);
+    if (rc) {
+        vce->len = cpu_to_be32(VCE_INVALID_LEN);

So errors are apparently encoded via VCE_INVALID_LEN here...

+    }
+
+out:
+    return g_steal_pointer(&vce);
+}
+
+static int handle_diag320_store_vc(S390CPU *cpu, uint64_t addr, uint64_t r1, 
uintptr_t ra,
+                                   S390IPLCertificateStore *cs)
+{
+    g_autofree VCBlock *vcb = NULL;
+    size_t entry_offset;
+    size_t remaining_space;
+    uint32_t vce_len;
+    uint16_t first_vc_index;
+    uint16_t last_vc_index;
+    int cs_start_index;
+    int cs_end_index;
+    uint32_t in_len;
+
+    vcb = g_new0(VCBlock, 1);
+    if (s390_cpu_virt_mem_read(cpu, addr, r1, vcb, sizeof(*vcb))) {
+        s390_cpu_virt_mem_handle_exc(cpu, ra);
+        return -1;
+    }
+
+    in_len = be32_to_cpu(vcb->in_len);
+    first_vc_index = be16_to_cpu(vcb->first_vc_index);
+    last_vc_index = be16_to_cpu(vcb->last_vc_index);
+
+    if (in_len % TARGET_PAGE_SIZE != 0) {
+        return DIAG_320_RC_INVAL_VCB_LEN;
+    }
+
+    if (first_vc_index > last_vc_index) {
+        return DIAG_320_RC_BAD_RANGE;
+    }
+
+    vcb->out_len = VCB_HEADER_LEN;
+
+    /*
+     * DIAG 320 subcode 2 expects to query a certificate store that
+     * maintains an index origin of 1. However, the S390IPLCertificateStore
+     * maintains an index origin of 0. Thus, the indices must be adjusted
+     * for correct access into the cert store. A couple of special cases
+     * must also be accounted for.
+     */
+
+    /* Both indices are 0; return header with no certs */
+    if (first_vc_index == 0 && last_vc_index == 0) {
+        goto out;
+    }
+
+    /* Normalize indices */
+    cs_start_index = (first_vc_index == 0) ? 0 : first_vc_index - 1;
+    cs_end_index = last_vc_index - 1;
+
+    /* Requested range is outside the cert store; return header with no certs 
*/
+    if (cs_start_index >= cs->count || cs_end_index >= cs->count) {
+        goto out;
+    }
+
+    entry_offset = VCB_HEADER_LEN;
+    remaining_space = in_len - VCB_HEADER_LEN;
+
+    for (int i = cs_start_index; i <= cs_end_index; i++) {
+        VCEntry *vce;
+        const S390IPLCertificate *cert = &cs->certs[i];
+
+        vce = diag_320_build_vce(cert, i);
+        vce_len = be32_to_cpu(vce->len);

... but the caller here does not seem to check for VCE_INVALID_LEN ... is it OK to simply ignore this? Again, a comment might be helpful.

 Thomas


+        /*
+         * If there is no more space to store the cert,
+         * set the remaining verification cert count and
+         * break early.
+         */
+        if (remaining_space < vce_len) {
+            vcb->remain_ct = cpu_to_be16(last_vc_index - i);
+            g_free(vce);
+            break;
+        }
+
+        /* Write VCE */
+        if (s390_cpu_virt_mem_write(cpu, addr + entry_offset, r1, vce, 
vce_len)) {
+            s390_cpu_virt_mem_handle_exc(cpu, ra);
+            g_free(vce);
+            return -1;
+        }
+
+        entry_offset += vce_len;
+        vcb->out_len += vce_len;
+        remaining_space -= vce_len;
+        vcb->stored_ct++;
+
+        g_free(vce);
+    }
+    vcb->stored_ct = cpu_to_be16(vcb->stored_ct);
+
+out:
+    vcb->out_len = cpu_to_be32(vcb->out_len);
+
+    if (s390_cpu_virt_mem_write(cpu, addr, r1, vcb, VCB_HEADER_LEN)) {
+        s390_cpu_virt_mem_handle_exc(cpu, ra);
+        return -1;
+    }
+
+    return DIAG_320_RC_OK;
+}
+
  QEMU_BUILD_BUG_MSG(sizeof(VCStorageSizeBlock) != VCSSB_MIN_LEN,
                     "size of VCStorageSizeBlock is wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCBlock) != VCB_HEADER_LEN, "size of VCBlock is 
wrong");
+QEMU_BUILD_BUG_MSG(sizeof(VCEntry) != VCE_HEADER_LEN, "size of VCEntry is 
wrong");
void handle_diag_320(CPUS390XState *env, uint64_t r1, uint64_t r3, uintptr_t ra)
  {
@@ -267,7 +585,8 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, 
uint64_t r3, uintptr_t ra)
           * for now.
           */
          uint32_t ism_word0 = cpu_to_be32(DIAG_320_ISM_QUERY_SUBCODES |
-                                         DIAG_320_ISM_QUERY_VCSI);
+                                         DIAG_320_ISM_QUERY_VCSI |
+                                         DIAG_320_ISM_STORE_VC);
if (s390_cpu_virt_mem_write(cpu, addr, r1, &ism_word0, sizeof(ism_word0))) {
              s390_cpu_virt_mem_handle_exc(cpu, ra);
@@ -293,6 +612,13 @@ void handle_diag_320(CPUS390XState *env, uint64_t r1, 
uint64_t r3, uintptr_t ra)
          }
          env->regs[r1 + 1] = rc;
          break;
+    case DIAG_320_SUBC_STORE_VC:
+        rc = handle_diag320_store_vc(cpu, addr, r1, ra, cs);
+        if (rc == -1) {
+            return;
+        }
+        env->regs[r1 + 1] = rc;
+        break;
      default:
          env->regs[r1 + 1] = DIAG_320_RC_NOT_SUPPORTED;
          break;


Reply via email to