Patch Detail
get:
Show a patch.
patch:
Update a patch.
put:
Update a patch.
GET /api/patches/2196089/?format=api
{ "id": 2196089, "url": "http://patchwork.ozlabs.org/api/patches/2196089/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260212204352.1044699-20-zycai@linux.ibm.com/", "project": { "id": 14, "url": "http://patchwork.ozlabs.org/api/projects/14/?format=api", "name": "QEMU Development", "link_name": "qemu-devel", "list_id": "qemu-devel.nongnu.org", "list_email": "qemu-devel@nongnu.org", "web_url": "", "scm_url": "", "webscm_url": "", "list_archive_url": "", "list_archive_url_format": "", "commit_url_format": "" }, "msgid": "<20260212204352.1044699-20-zycai@linux.ibm.com>", "list_archive_url": null, "date": "2026-02-12T20:43:40", "name": "[v8,19/30] pc-bios/s390-ccw: Add signature verification for secure IPL in audit mode", "commit_ref": null, "pull_url": null, "state": "new", "archived": false, "hash": "f8d153dc341c36a1b377f8950f28dffbdab7f57b", "submitter": { "id": 90643, "url": "http://patchwork.ozlabs.org/api/people/90643/?format=api", "name": "Zhuoying Cai", "email": "zycai@linux.ibm.com" }, "delegate": null, "mbox": "http://patchwork.ozlabs.org/project/qemu-devel/patch/20260212204352.1044699-20-zycai@linux.ibm.com/mbox/", "series": [ { "id": 492021, "url": "http://patchwork.ozlabs.org/api/series/492021/?format=api", "web_url": "http://patchwork.ozlabs.org/project/qemu-devel/list/?series=492021", "date": "2026-02-12T20:43:36", "name": "Secure IPL Support for SCSI Scheme of virtio-blk/virtio-scsi Devices", "version": 8, "mbox": "http://patchwork.ozlabs.org/series/492021/mbox/" } ], "comments": "http://patchwork.ozlabs.org/api/patches/2196089/comments/", "check": "pending", "checks": "http://patchwork.ozlabs.org/api/patches/2196089/checks/", "tags": {}, "related": [], "headers": { "Return-Path": "<qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org>", "X-Original-To": "incoming@patchwork.ozlabs.org", "Delivered-To": "patchwork-incoming@legolas.ozlabs.org", "Authentication-Results": [ "legolas.ozlabs.org;\n\tdkim=pass (2048-bit key;\n unprotected) header.d=ibm.com header.i=@ibm.com header.a=rsa-sha256\n header.s=pp1 header.b=JncYJycv;\n\tdkim-atps=neutral", "legolas.ozlabs.org;\n spf=pass (sender SPF authorized) smtp.mailfrom=nongnu.org\n (client-ip=209.51.188.17; helo=lists.gnu.org;\n envelope-from=qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org;\n receiver=patchwork.ozlabs.org)" ], "Received": [ "from lists.gnu.org (lists.gnu.org [209.51.188.17])\n\t(using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits))\n\t(No client certificate requested)\n\tby legolas.ozlabs.org (Postfix) with ESMTPS id 4fBnSF03tDz1xpY\n\tfor <incoming@patchwork.ozlabs.org>; Fri, 13 Feb 2026 07:47:57 +1100 (AEDT)", "from localhost ([::1] helo=lists1p.gnu.org)\n\tby lists.gnu.org with esmtp (Exim 4.90_1)\n\t(envelope-from <qemu-devel-bounces@nongnu.org>)\n\tid 1vqdYJ-0000vs-QS; Thu, 12 Feb 2026 15:44:43 -0500", "from eggs.gnu.org ([2001:470:142:3::10])\n by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <zycai@linux.ibm.com>)\n id 1vqdYI-0000vQ-Lo; Thu, 12 Feb 2026 15:44:42 -0500", "from mx0a-001b2d01.pphosted.com ([148.163.156.1])\n by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256)\n (Exim 4.90_1) (envelope-from <zycai@linux.ibm.com>)\n id 1vqdYF-0007vO-GT; Thu, 12 Feb 2026 15:44:42 -0500", "from pps.filterd (m0353729.ppops.net [127.0.0.1])\n by mx0a-001b2d01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id\n 61CD80In3201530; Thu, 12 Feb 2026 20:44:34 GMT", "from ppma23.wdc07v.mail.ibm.com\n (5d.69.3da9.ip4.static.sl-reverse.com [169.61.105.93])\n by mx0a-001b2d01.pphosted.com (PPS) with ESMTPS id 4c696wg4my-1\n (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT);\n Thu, 12 Feb 2026 20:44:34 +0000 (GMT)", "from pps.filterd (ppma23.wdc07v.mail.ibm.com [127.0.0.1])\n by ppma23.wdc07v.mail.ibm.com (8.18.1.2/8.18.1.2) with ESMTP id\n 61CHZ1oZ012611;\n Thu, 12 Feb 2026 20:44:33 GMT", "from smtprelay06.wdc07v.mail.ibm.com ([172.16.1.73])\n by ppma23.wdc07v.mail.ibm.com (PPS) with ESMTPS id 4c6h7km156-1\n (version=TLSv1.2 cipher=ECDHE-RSA-AES256-GCM-SHA384 bits=256 verify=NOT);\n Thu, 12 Feb 2026 20:44:33 +0000", "from smtpav06.wdc07v.mail.ibm.com (smtpav06.wdc07v.mail.ibm.com\n [10.39.53.233])\n by smtprelay06.wdc07v.mail.ibm.com (8.14.9/8.14.9/NCO v10.0) with ESMTP id\n 61CKiWZh14877360\n (version=TLSv1/SSLv3 cipher=DHE-RSA-AES256-GCM-SHA384 bits=256 verify=OK);\n Thu, 12 Feb 2026 20:44:32 GMT", "from smtpav06.wdc07v.mail.ibm.com (unknown [127.0.0.1])\n by IMSVA (Postfix) with ESMTP id E978758054;\n Thu, 12 Feb 2026 20:44:31 +0000 (GMT)", "from smtpav06.wdc07v.mail.ibm.com (unknown [127.0.0.1])\n by IMSVA (Postfix) with ESMTP id 2759858055;\n Thu, 12 Feb 2026 20:44:30 +0000 (GMT)", "from fedora-workstation.ibmuc.com (unknown [9.61.112.15])\n by smtpav06.wdc07v.mail.ibm.com (Postfix) with ESMTP;\n Thu, 12 Feb 2026 20:44:30 +0000 (GMT)" ], "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed; d=ibm.com; h=cc\n :content-transfer-encoding:date:from:in-reply-to:message-id\n :mime-version:references:subject:to; s=pp1; bh=yPPe0saiwsXOyEBYm\n ImSLPH46cYNMpG3GSNGdDt10yg=; b=JncYJycvoLE7e8ki2+7DWsWBP6qrDY4bh\n rvIe2dCaUkia5nc1nfjoipYTL1tcns5zZLHYUA+X2T+mncdjPYFSf+1sqEYoMDyS\n ev3+XjS9TimYMej2Eik8yJMlfZxZjL8NNA4xz0jhV9kacN5YOLSUv7ZIkIDAP4dM\n Dtoy4k5nO7yPgy0pLVHaZ/f5BHBw0eqICorgeQ9t4Jv/LJgYYWyHO8iSB+NGg4bv\n RAtk1o4fyeE2gmXIt54aXTbR6M2jWuJFv5YRJtGOCusl/2ZF6W8PjgEwNKCuux5S\n C0eMsg9Z0Lxzk7gJNcl69tn9xVLBQ9EQpH/kfLiX+wg1t7TQbC6Kw==", "From": "Zhuoying Cai <zycai@linux.ibm.com>", "To": "thuth@redhat.com, berrange@redhat.com, richard.henderson@linaro.org,\n jrossi@linux.ibm.com, qemu-s390x@nongnu.org, qemu-devel@nongnu.org", "Cc": "david@kernel.org, walling@linux.ibm.com, jjherne@linux.ibm.com,\n pasic@linux.ibm.com, borntraeger@linux.ibm.com, farman@linux.ibm.com,\n mjrosato@linux.ibm.com, iii@linux.ibm.com, eblake@redhat.com,\n armbru@redhat.com, zycai@linux.ibm.com, alifm@linux.ibm.com,\n brueckner@linux.ibm.com", "Subject": "[PATCH v8 19/30] pc-bios/s390-ccw: Add signature verification for\n secure IPL in audit mode", "Date": "Thu, 12 Feb 2026 15:43:40 -0500", "Message-ID": "<20260212204352.1044699-20-zycai@linux.ibm.com>", "X-Mailer": "git-send-email 2.52.0", "In-Reply-To": "<20260212204352.1044699-1-zycai@linux.ibm.com>", "References": "<20260212204352.1044699-1-zycai@linux.ibm.com>", "MIME-Version": "1.0", "Content-Transfer-Encoding": "8bit", "X-TM-AS-GCONF": "00", "X-Authority-Analysis": "v=2.4 cv=YeCwJgRf c=1 sm=1 tr=0 ts=698e3bb2 cx=c_pps\n a=3Bg1Hr4SwmMryq2xdFQyZA==:117 a=3Bg1Hr4SwmMryq2xdFQyZA==:17\n a=HzLeVaNsDn8A:10 a=VkNPw1HP01LnGYTKEx00:22 a=Mpw57Om8IfrbqaoTuvik:22\n a=GgsMoib0sEa3-_RKJdDe:22 a=VnNF1IyMAAAA:8 a=xxmkioofEG7CYTGLI2QA:9", "X-Proofpoint-GUID": "1msbn2vs4WPKfug5iEYYuXrprGjz-2KD", "X-Proofpoint-Spam-Details-Enc": "AW1haW4tMjYwMjEyMDE1NyBTYWx0ZWRfX53yLkZAfzzSQ\n A5sA12xA0A313AJE6WHLorSJ88GzMuZI514bxu6ltmAtdYEkP42jA/9PyIxUNmML4WGcEfUZYIL\n PnUgEQCOSAY0EijltA7m9287vHUSYYuhDnWXXKBV5AxFnLD+R8B06cOQTxoCiAd9GYTRYPVQKes\n bORUIMbCA8MBYUtgbrMHjIjQjbvQzfw+6qWY+eJ+A8Xs7FKBXsuIzQ9Pvg+mzn/HklaVm0SrqnE\n wItpQLElf6Nful+3OolTFAOJaqRj94GMfO3r6j1A72H593dekUt0hpc93HEryg21ETNi7GiRuZJ\n mxkuDMJ4RExeTOXin6sIWUBojXniPl+fubp4TZ505hdIq9TzdHFIt7ATSsTbAXBx18bRRvkH2+g\n ll8qfS1sONFVFUTcpIkSVA/z0ODVxBHjDUrJGwzLN4pqGXZZsRI++mw2t6BBSNl3irS0JHdaFe5\n mV142zeRkdhEqFSaS1A==", "X-Proofpoint-ORIG-GUID": "1msbn2vs4WPKfug5iEYYuXrprGjz-2KD", "X-Proofpoint-Virus-Version": "vendor=baseguard\n engine=ICAP:2.0.293,Aquarius:18.0.1121,Hydra:6.1.51,FMLib:17.12.100.49\n definitions=2026-02-12_05,2026-02-12_03,2025-10-01_01", "X-Proofpoint-Spam-Details": "rule=outbound_notspam policy=outbound score=0\n suspectscore=0 adultscore=0 bulkscore=0 malwarescore=0 phishscore=0\n priorityscore=1501 lowpriorityscore=0 clxscore=1015 impostorscore=0\n spamscore=0 classifier=typeunknown authscore=0 authtc= authcc= route=outbound\n adjust=0 reason=mlx scancount=1 engine=8.22.0-2601150000\n definitions=main-2602120157", "Received-SPF": "pass client-ip=148.163.156.1; envelope-from=zycai@linux.ibm.com;\n helo=mx0a-001b2d01.pphosted.com", "X-Spam_score_int": "-19", "X-Spam_score": "-2.0", "X-Spam_bar": "--", "X-Spam_report": "(-2.0 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1,\n DKIM_VALID=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_NONE=-0.0001,\n RCVD_IN_MSPIKE_H4=0.001, RCVD_IN_MSPIKE_WL=0.001,\n RCVD_IN_VALIDITY_RPBL_BLOCKED=0.001, RCVD_IN_VALIDITY_SAFE_BLOCKED=0.001,\n SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no", "X-Spam_action": "no action", "X-BeenThere": "qemu-devel@nongnu.org", "X-Mailman-Version": "2.1.29", "Precedence": "list", "List-Id": "qemu development <qemu-devel.nongnu.org>", "List-Unsubscribe": "<https://lists.nongnu.org/mailman/options/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=unsubscribe>", "List-Archive": "<https://lists.nongnu.org/archive/html/qemu-devel>", "List-Post": "<mailto:qemu-devel@nongnu.org>", "List-Help": "<mailto:qemu-devel-request@nongnu.org?subject=help>", "List-Subscribe": "<https://lists.nongnu.org/mailman/listinfo/qemu-devel>,\n <mailto:qemu-devel-request@nongnu.org?subject=subscribe>", "Errors-To": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org", "Sender": "qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org" }, "content": "Enable secure IPL in audit mode, which performs signature verification,\nbut any error does not terminate the boot process. Only warnings will be\nlogged to the console instead.\n\nAdd a comp_len variable to store the length of a segment in\nzipl_load_segment. comp_len variable is necessary to store the\ncalculated segment length and is used during signature verification.\nReturn the length on success, or a negative return code on failure.\n\nSecure IPL in audit mode requires at least one certificate provided in\nthe key store along with necessary facilities (Secure IPL Facility,\nCertificate Store Facility and secure IPL extension support).\n\nNote: Secure IPL in audit mode is implemented for the SCSI scheme of\nvirtio-blk/virtio-scsi devices.\n\nSigned-off-by: Zhuoying Cai <zycai@linux.ibm.com>\n---\n docs/system/s390x/secure-ipl.rst | 35 +++\n pc-bios/s390-ccw/Makefile | 3 +-\n pc-bios/s390-ccw/bootmap.c | 36 +++-\n pc-bios/s390-ccw/bootmap.h | 11 +\n pc-bios/s390-ccw/main.c | 6 +\n pc-bios/s390-ccw/s390-ccw.h | 14 ++\n pc-bios/s390-ccw/sclp.c | 43 ++++\n pc-bios/s390-ccw/sclp.h | 6 +\n pc-bios/s390-ccw/secure-ipl.c | 356 +++++++++++++++++++++++++++++++\n pc-bios/s390-ccw/secure-ipl.h | 102 +++++++++\n 10 files changed, 609 insertions(+), 3 deletions(-)\n create mode 100644 pc-bios/s390-ccw/secure-ipl.c\n create mode 100644 pc-bios/s390-ccw/secure-ipl.h", "diff": "diff --git a/docs/system/s390x/secure-ipl.rst b/docs/system/s390x/secure-ipl.rst\nindex 0a02f171b4..3a19b72085 100644\n--- a/docs/system/s390x/secure-ipl.rst\n+++ b/docs/system/s390x/secure-ipl.rst\n@@ -18,3 +18,38 @@ Note: certificate files must have a .pem extension.\n .. code-block:: shell\n \n qemu-system-s390x -machine s390-ccw-virtio,boot-certs.0.path=/.../qemu/certs,boot-certs.1.path=/another/path/cert.pem ...\n+\n+\n+IPL Modes\n+=========\n+Multiple IPL modes are available to differentiate between the various IPL\n+configurations. These modes are mutually exclusive and enabled based on the\n+``boot-certs`` option on the QEMU command line.\n+\n+Normal Mode\n+-----------\n+\n+The absence of certificates will attempt to IPL a guest without secure IPL\n+operations. No checks are performed, and no warnings/errors are reported.\n+This is the default mode.\n+\n+Configuration:\n+\n+.. code-block:: shell\n+\n+ qemu-system-s390x -machine s390-ccw-virtio ...\n+\n+Audit Mode\n+----------\n+\n+When the certificate store is populated with at least one certificate\n+and no additional secure IPL parameters are provided on the command\n+line, then secure IPL will proceed in \"audit mode\". All secure IPL\n+operations will be performed with signature verification errors reported\n+as non-disruptive warnings.\n+\n+Configuration:\n+\n+.. code-block:: shell\n+\n+ qemu-system-s390x -machine s390-ccw-virtio,boot-certs.0.path=/.../qemu/certs,boot-certs.1.path=/another/path/cert.pem ...\ndiff --git a/pc-bios/s390-ccw/Makefile b/pc-bios/s390-ccw/Makefile\nindex a0f24c94a8..603761a857 100644\n--- a/pc-bios/s390-ccw/Makefile\n+++ b/pc-bios/s390-ccw/Makefile\n@@ -34,7 +34,8 @@ QEMU_DGFLAGS = -MMD -MP -MT $@ -MF $(@D)/$(*F).d\n .PHONY : all clean build-all distclean\n \n OBJECTS = start.o main.o bootmap.o jump2ipl.o sclp.o menu.o netmain.o \\\n-\t virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o\n+\t virtio.o virtio-net.o virtio-scsi.o virtio-blkdev.o cio.o dasd-ipl.o \\\n+\t secure-ipl.o\n \n SLOF_DIR := $(SRC_PATH)/../../roms/SLOF\n \ndiff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c\nindex 9a03eab6ed..43a661325f 100644\n--- a/pc-bios/s390-ccw/bootmap.c\n+++ b/pc-bios/s390-ccw/bootmap.c\n@@ -15,6 +15,7 @@\n #include \"bootmap.h\"\n #include \"virtio.h\"\n #include \"bswap.h\"\n+#include \"secure-ipl.h\"\n \n #ifdef DEBUG\n /* #define DEBUG_FALLBACK */\n@@ -617,7 +618,7 @@ static int ipl_eckd(void)\n * Returns: length of the segment on success,\n * negative value on error.\n */\n-static int zipl_load_segment(ComponentEntry *entry, uint64_t address)\n+int zipl_load_segment(ComponentEntry *entry, uint64_t address)\n {\n const int max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr));\n ScsiBlockPtr *bprs = (void *)sec;\n@@ -736,7 +737,19 @@ static int zipl_run(ScsiBlockPtr *pte)\n /* Load image(s) into RAM */\n entry = (ComponentEntry *)(&header[1]);\n \n- rc = zipl_run_normal(&entry, tmp_sec);\n+ switch (boot_mode) {\n+ case ZIPL_BOOT_MODE_SECURE_AUDIT:\n+ rc = zipl_run_secure(&entry, tmp_sec);\n+ break;\n+ case ZIPL_BOOT_MODE_NORMAL:\n+ rc = zipl_run_normal(&entry, tmp_sec);\n+ break;\n+ default:\n+ puts(\"Unknown boot mode\");\n+ rc = -1;\n+ break;\n+ }\n+\n if (rc) {\n return rc;\n }\n@@ -1103,17 +1116,33 @@ static int zipl_load_vscsi(void)\n * IPL starts here\n */\n \n+ZiplBootMode get_boot_mode(uint8_t hdr_flags)\n+{\n+ bool sipl_set = hdr_flags & DIAG308_IPIB_FLAGS_SIPL;\n+ bool iplir_set = hdr_flags & DIAG308_IPIB_FLAGS_IPLIR;\n+\n+ if (!sipl_set && iplir_set) {\n+ return ZIPL_BOOT_MODE_SECURE_AUDIT;\n+ }\n+\n+ return ZIPL_BOOT_MODE_NORMAL;\n+}\n+\n void zipl_load(void)\n {\n VDev *vdev = virtio_get_device();\n \n if (vdev->is_cdrom) {\n+ IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),\n+ \"Secure boot from ISO image is not supported!\");\n ipl_iso_el_torito();\n puts(\"Failed to IPL this ISO image!\");\n return;\n }\n \n if (virtio_get_device_type() == VIRTIO_ID_NET) {\n+ IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),\n+ \"Virtio net boot device does not support secure boot!\");\n netmain();\n puts(\"Failed to IPL from this network!\");\n return;\n@@ -1124,6 +1153,9 @@ void zipl_load(void)\n return;\n }\n \n+ IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),\n+ \"Secure boot with the ECKD scheme is not supported!\");\n+\n switch (virtio_get_device_type()) {\n case VIRTIO_ID_BLOCK:\n zipl_load_vblk();\ndiff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h\nindex 95943441d3..dc2783faa2 100644\n--- a/pc-bios/s390-ccw/bootmap.h\n+++ b/pc-bios/s390-ccw/bootmap.h\n@@ -88,9 +88,18 @@ typedef struct BootMapTable {\n BootMapPointer entry[];\n } __attribute__ ((packed)) BootMapTable;\n \n+#define DER_SIGNATURE_FORMAT 1\n+\n+typedef struct SignatureInformation {\n+ uint8_t format;\n+ uint8_t reserved[3];\n+ uint32_t sig_len;\n+} SignatureInformation;\n+\n typedef union ComponentEntryData {\n uint64_t load_psw;\n uint64_t load_addr;\n+ SignatureInformation sig_info;\n } ComponentEntryData;\n \n typedef struct ComponentEntry {\n@@ -113,6 +122,8 @@ typedef struct ScsiMbr {\n ScsiBlockPtr pt; /* block pointer to program table */\n } __attribute__ ((packed)) ScsiMbr;\n \n+int zipl_load_segment(ComponentEntry *entry, uint64_t address);\n+\n #define ZIPL_MAGIC \"zIPL\"\n #define ZIPL_MAGIC_EBCDIC \"\\xa9\\xc9\\xd7\\xd3\"\n #define IPL1_MAGIC \"\\xc9\\xd7\\xd3\\xf1\" /* == \"IPL1\" in EBCDIC */\ndiff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c\nindex 819f053009..106cdf9dec 100644\n--- a/pc-bios/s390-ccw/main.c\n+++ b/pc-bios/s390-ccw/main.c\n@@ -28,6 +28,7 @@ IplParameterBlock *iplb;\n bool have_iplb;\n static uint16_t cutype;\n LowCore *lowcore; /* Yes, this *is* a pointer to address 0 */\n+ZiplBootMode boot_mode;\n \n #define LOADPARM_PROMPT \"PROMPT \"\n #define LOADPARM_EMPTY \" \"\n@@ -275,6 +276,9 @@ static void ipl_boot_device(void)\n switch (cutype) {\n case CU_TYPE_DASD_3990:\n case CU_TYPE_DASD_2107:\n+ IPL_assert((boot_mode == ZIPL_BOOT_MODE_NORMAL),\n+ \"Passthrough (vfio) CCW device does not support secure boot!\");\n+\n dasd_ipl(blk_schid, cutype);\n break;\n case CU_TYPE_VIRTIO:\n@@ -324,6 +328,8 @@ void main(void)\n probe_boot_device();\n }\n \n+ boot_mode = get_boot_mode(iplb->hdr_flags);\n+\n while (have_iplb) {\n boot_setup();\n if (have_iplb && find_boot_device()) {\ndiff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h\nindex b1dc35cded..1b43817af9 100644\n--- a/pc-bios/s390-ccw/s390-ccw.h\n+++ b/pc-bios/s390-ccw/s390-ccw.h\n@@ -39,6 +39,9 @@ typedef unsigned long long u64;\n #define MIN_NON_ZERO(a, b) ((a) == 0 ? (b) : \\\n ((b) == 0 ? (a) : (MIN(a, b))))\n #endif\n+#ifndef ROUND_UP\n+#define ROUND_UP(n, d) (((n) + (d) - 1) & -(0 ? (n) : (d)))\n+#endif\n \n #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0]))\n \n@@ -64,6 +67,8 @@ void sclp_print(const char *string);\n void sclp_set_write_mask(uint32_t receive_mask, uint32_t send_mask);\n void sclp_setup(void);\n void sclp_get_loadparm_ascii(char *loadparm);\n+bool sclp_is_diag320_on(void);\n+bool sclp_is_sipl_on(void);\n int sclp_read(char *str, size_t count);\n \n /* virtio.c */\n@@ -76,6 +81,15 @@ int virtio_read(unsigned long sector, void *load_addr);\n /* bootmap.c */\n void zipl_load(void);\n \n+typedef enum ZiplBootMode {\n+ ZIPL_BOOT_MODE_NORMAL = 0,\n+ ZIPL_BOOT_MODE_SECURE_AUDIT = 1,\n+} ZiplBootMode;\n+\n+extern ZiplBootMode boot_mode;\n+\n+ZiplBootMode get_boot_mode(uint8_t hdr_flags);\n+\n /* jump2ipl.c */\n void write_reset_psw(uint64_t psw);\n int jump_to_IPL_code(uint64_t address);\ndiff --git a/pc-bios/s390-ccw/sclp.c b/pc-bios/s390-ccw/sclp.c\nindex 4a07de018d..f7514b0245 100644\n--- a/pc-bios/s390-ccw/sclp.c\n+++ b/pc-bios/s390-ccw/sclp.c\n@@ -113,6 +113,49 @@ void sclp_get_loadparm_ascii(char *loadparm)\n }\n }\n \n+static void sclp_get_fac134(uint8_t *fac134)\n+{\n+ ReadInfo *sccb = (void *)_sccb;\n+\n+ memset((char *)_sccb, 0, sizeof(ReadInfo));\n+ sccb->h.length = SCCB_SIZE;\n+ if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {\n+ *fac134 = sccb->fac134;\n+ }\n+}\n+\n+bool sclp_is_diag320_on(void)\n+{\n+ uint8_t fac134 = 0;\n+\n+ sclp_get_fac134(&fac134);\n+ return fac134 & SCCB_FAC134_DIAG320_BIT;\n+}\n+\n+/*\n+ * Get fac_ipl (byte 136 and byte 137 of the SCLP Read Info block)\n+ * for IPL device facilities.\n+ */\n+static void sclp_get_fac_ipl(uint16_t *fac_ipl)\n+{\n+\n+ ReadInfo *sccb = (void *)_sccb;\n+\n+ memset((char *)_sccb, 0, sizeof(ReadInfo));\n+ sccb->h.length = SCCB_SIZE;\n+ if (!sclp_service_call(SCLP_CMDW_READ_SCP_INFO, sccb)) {\n+ *fac_ipl = sccb->fac_ipl;\n+ }\n+}\n+\n+bool sclp_is_sipl_on(void)\n+{\n+ uint16_t fac_ipl = 0;\n+\n+ sclp_get_fac_ipl(&fac_ipl);\n+ return fac_ipl & SCCB_FAC_IPL_SIPL_BIT;\n+}\n+\n int sclp_read(char *str, size_t count)\n {\n ReadEventData *sccb = (void *)_sccb;\ndiff --git a/pc-bios/s390-ccw/sclp.h b/pc-bios/s390-ccw/sclp.h\nindex 64b53cad29..cf147f4634 100644\n--- a/pc-bios/s390-ccw/sclp.h\n+++ b/pc-bios/s390-ccw/sclp.h\n@@ -50,6 +50,8 @@ typedef struct SCCBHeader {\n } __attribute__((packed)) SCCBHeader;\n \n #define SCCB_DATA_LEN (SCCB_SIZE - sizeof(SCCBHeader))\n+#define SCCB_FAC134_DIAG320_BIT 0x4\n+#define SCCB_FAC_IPL_SIPL_BIT 0x4000\n \n typedef struct ReadInfo {\n SCCBHeader h;\n@@ -57,6 +59,10 @@ typedef struct ReadInfo {\n uint8_t rnsize;\n uint8_t reserved[13];\n uint8_t loadparm[LOADPARM_LEN];\n+ uint8_t reserved1[102];\n+ uint8_t fac134;\n+ uint8_t reserved2;\n+ uint16_t fac_ipl;\n } __attribute__((packed)) ReadInfo;\n \n typedef struct SCCB {\ndiff --git a/pc-bios/s390-ccw/secure-ipl.c b/pc-bios/s390-ccw/secure-ipl.c\nnew file mode 100644\nindex 0000000000..1d5c2d40ce\n--- /dev/null\n+++ b/pc-bios/s390-ccw/secure-ipl.c\n@@ -0,0 +1,356 @@\n+/*\n+ * S/390 Secure IPL\n+ *\n+ * Functions to support IPL in secure boot mode (DIAG 320, DIAG 508,\n+ * signature verification, and certificate handling).\n+ *\n+ * For secure IPL overview: docs/system/s390x/secure-ipl.rst\n+ * For secure IPL technical: docs/specs/s390x-secure-ipl.rst\n+ *\n+ * Copyright 2025 IBM Corp.\n+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>\n+ *\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#include <stdlib.h>\n+#include <string.h>\n+#include <stdio.h>\n+#include \"bootmap.h\"\n+#include \"s390-ccw.h\"\n+#include \"secure-ipl.h\"\n+\n+uint8_t vcssb_data[VCSSB_MIN_LEN] __attribute__((__aligned__(PAGE_SIZE)));\n+\n+VCStorageSizeBlock *zipl_secure_get_vcssb(void)\n+{\n+ VCStorageSizeBlock *vcssb;\n+\n+ vcssb = (VCStorageSizeBlock *)vcssb_data;\n+ /* avoid retrieving vcssb multiple times */\n+ if (vcssb->length >= VCSSB_MIN_LEN) {\n+ return vcssb;\n+ }\n+\n+ if (!is_cert_store_facility_supported()) {\n+ puts(\"Certificate Store Facility is not supported by the hypervisor!\");\n+ return NULL;\n+ }\n+\n+ vcssb->length = VCSSB_MIN_LEN;\n+ if (diag320(vcssb, DIAG_320_SUBC_QUERY_VCSI) != DIAG_320_RC_OK) {\n+ return NULL;\n+ }\n+\n+ return vcssb;\n+}\n+\n+static uint32_t get_total_certs_length(void)\n+{\n+ VCStorageSizeBlock *vcssb;\n+\n+ vcssb = zipl_secure_get_vcssb();\n+ if (vcssb == NULL) {\n+ return 0;\n+ }\n+\n+ return vcssb->total_vcb_len - VCB_HEADER_LEN - vcssb->total_vc_ct * VCE_HEADER_LEN;\n+}\n+\n+static uint32_t request_certificate(uint8_t *cert_addr, uint8_t index)\n+{\n+ VCStorageSizeBlock *vcssb;\n+ VCBlock *vcb;\n+ VCEntry *vce;\n+ uint32_t cert_len = 0;\n+ uint32_t max_single_vcb_len;\n+\n+ /* Get Verification Certificate Storage Size block with DIAG320 subcode 1 */\n+ vcssb = zipl_secure_get_vcssb();\n+ if (vcssb == NULL) {\n+ return 0;\n+ }\n+\n+ /*\n+ * Request single entry\n+ * Fill input fields of single-entry VCB\n+ */\n+ max_single_vcb_len = ROUND_UP(vcssb->max_single_vcb_len, PAGE_SIZE);\n+ vcb = malloc(max_single_vcb_len);\n+ vcb->in_len = max_single_vcb_len;\n+ vcb->first_vc_index = index;\n+ vcb->last_vc_index = index;\n+\n+ if (diag320(vcb, DIAG_320_SUBC_STORE_VC) != DIAG_320_RC_OK) {\n+ goto out;\n+ }\n+\n+ if (vcb->out_len == VCB_HEADER_LEN) {\n+ puts(\"No certificate entry\");\n+ goto out;\n+ }\n+ if (vcb->remain_ct != 0) {\n+ puts(\"Not enough memory to store all requested certificates\");\n+ goto out;\n+ }\n+\n+ vce = (VCEntry *)vcb->vce_buf;\n+ if (!(vce->flags & DIAG_320_VCE_FLAGS_VALID)) {\n+ puts(\"Invalid certificate\");\n+ goto out;\n+ }\n+\n+ cert_len = vce->cert_len;\n+ memcpy(cert_addr, (uint8_t *)vce + vce->cert_offset, vce->cert_len);\n+\n+out:\n+ free(vcb);\n+ return cert_len;\n+}\n+\n+static void cert_list_add(IplSignatureCertificateList *cert_list, int cert_index,\n+ uint8_t *cert_addr, uint64_t cert_len)\n+{\n+ if (cert_index > MAX_CERTIFICATES - 1) {\n+ printf(\"Warning: Ignoring cert entry #%d because only %d entries are supported\\n\",\n+ cert_index + 1, MAX_CERTIFICATES);\n+ return;\n+ }\n+\n+ cert_list->cert_entries[cert_index].addr = (uint64_t)cert_addr;\n+ cert_list->cert_entries[cert_index].len = cert_len;\n+ cert_list->ipl_info_header.len += sizeof(cert_list->cert_entries[cert_index]);\n+}\n+\n+static void comp_list_add(IplDeviceComponentList *comp_list, int comp_index,\n+ int cert_index, uint64_t comp_addr,\n+ uint64_t comp_len, uint8_t flags)\n+{\n+ if (comp_index > MAX_CERTIFICATES - 1) {\n+ printf(\"Warning: Ignoring comp entry #%d because only %d entries are supported\\n\",\n+ comp_index + 1, MAX_CERTIFICATES);\n+ return;\n+ }\n+\n+ comp_list->device_entries[comp_index].addr = comp_addr;\n+ comp_list->device_entries[comp_index].len = comp_len;\n+ comp_list->device_entries[comp_index].flags = flags;\n+ comp_list->device_entries[comp_index].cert_index = cert_index;\n+ comp_list->ipl_info_header.len += sizeof(comp_list->device_entries[comp_index]);\n+}\n+\n+static void update_iirb(IplDeviceComponentList *comp_list,\n+ IplSignatureCertificateList *cert_list)\n+{\n+ IplInfoReportBlock *iirb;\n+ IplDeviceComponentList *iirb_comps;\n+ IplSignatureCertificateList *iirb_certs;\n+ uint32_t iirb_hdr_len;\n+ uint32_t comps_len;\n+ uint32_t certs_len;\n+\n+ if (iplb->len % 8 != 0) {\n+ panic(\"IPL parameter block length field value is not multiple of 8 bytes\");\n+ }\n+\n+ iirb_hdr_len = sizeof(IplInfoReportBlockHeader);\n+ comps_len = comp_list->ipl_info_header.len;\n+ certs_len = cert_list->ipl_info_header.len;\n+ if ((comps_len + certs_len + iirb_hdr_len) > sizeof(IplInfoReportBlock)) {\n+ panic(\"Not enough space to hold all components and certificates in IIRB\");\n+ }\n+\n+ /* IIRB immediately follows IPLB */\n+ iirb = &ipl_data.iirb;\n+ iirb->hdr.len = iirb_hdr_len;\n+\n+ /* Copy IPL device component list after IIRB Header */\n+ iirb_comps = (IplDeviceComponentList *) iirb->info_blks;\n+ memcpy(iirb_comps, comp_list, comps_len);\n+\n+ /* Update IIRB length */\n+ iirb->hdr.len += comps_len;\n+\n+ /* Copy IPL sig cert list after IPL device component list */\n+ iirb_certs = (IplSignatureCertificateList *) (iirb->info_blks +\n+ iirb_comps->ipl_info_header.len);\n+ memcpy(iirb_certs, cert_list, certs_len);\n+\n+ /* Update IIRB length */\n+ iirb->hdr.len += certs_len;\n+}\n+\n+static bool secure_ipl_supported(void)\n+{\n+ if (!sclp_is_sipl_on()) {\n+ puts(\"Secure IPL Facility is not supported by the hypervisor!\");\n+ return false;\n+ }\n+\n+ if (!is_signature_verif_supported()) {\n+ puts(\"Secure IPL extensions are not supported by the hypervisor!\");\n+ return false;\n+ }\n+\n+ if (!is_cert_store_facility_supported()) {\n+ puts(\"Certificate Store Facility is not supported by the hypervisor!\");\n+ return false;\n+ }\n+\n+ return true;\n+}\n+\n+static void init_lists(IplDeviceComponentList *comp_list,\n+ IplSignatureCertificateList *cert_list)\n+{\n+ comp_list->ipl_info_header.type = IPL_INFO_BLOCK_TYPE_COMPONENTS;\n+ comp_list->ipl_info_header.len = sizeof(comp_list->ipl_info_header);\n+\n+ cert_list->ipl_info_header.type = IPL_INFO_BLOCK_TYPE_CERTIFICATES;\n+ cert_list->ipl_info_header.len = sizeof(cert_list->ipl_info_header);\n+}\n+\n+static int zipl_load_signature(ComponentEntry *entry, uint64_t sig_sec)\n+{\n+ if (zipl_load_segment(entry, sig_sec) < 0) {\n+ return -1;\n+ }\n+\n+ if (entry->compdat.sig_info.format != DER_SIGNATURE_FORMAT) {\n+ puts(\"Signature is not in DER format\");\n+ return -1;\n+ }\n+\n+ return entry->compdat.sig_info.sig_len;\n+}\n+\n+int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec)\n+{\n+ IplDeviceComponentList comp_list = { 0 };\n+ IplSignatureCertificateList cert_list = { 0 };\n+ ComponentEntry *entry = *entry_ptr;\n+ uint8_t *cert_addr = NULL;\n+ uint64_t *sig = NULL;\n+ int cert_entry_idx = 0;\n+ int comp_entry_idx = 0;\n+ uint64_t comp_addr;\n+ int comp_len;\n+ uint32_t sig_len = 0;\n+ uint64_t cert_len = -1;\n+ uint8_t cert_table_idx = -1;\n+ int cert_index;\n+ uint8_t flags;\n+ bool verified;\n+ /*\n+ * Keep track of which certificate store indices correspond to the\n+ * certificate data entries within the IplSignatureCertificateList to\n+ * prevent allocating space for the same certificate multiple times.\n+ *\n+ * The array index corresponds to the certificate's cert-store index.\n+ *\n+ * The array value corresponds to the certificate's entry within the\n+ * IplSignatureCertificateList (with a value of -1 denoting no entry\n+ * exists for the certificate).\n+ */\n+ int cert_list_table[MAX_CERTIFICATES] = { [0 ... MAX_CERTIFICATES - 1] = -1 };\n+ int signed_count = 0;\n+\n+ if (!secure_ipl_supported()) {\n+ return -1;\n+ }\n+\n+ init_lists(&comp_list, &cert_list);\n+ cert_addr = malloc(get_total_certs_length());\n+ sig = malloc(MAX_SECTOR_SIZE);\n+\n+ while (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {\n+ switch (entry->component_type) {\n+ case ZIPL_COMP_ENTRY_SIGNATURE:\n+ if (sig_len) {\n+ goto out;\n+ }\n+\n+ sig_len = zipl_load_signature(entry, (uint64_t)sig);\n+ if (sig_len < 0) {\n+ goto out;\n+ }\n+ break;\n+ case ZIPL_COMP_ENTRY_LOAD:\n+ comp_addr = entry->compdat.load_addr;\n+ comp_len = zipl_load_segment(entry, comp_addr);\n+ if (comp_len < 0) {\n+ goto out;\n+ }\n+\n+ if (!sig_len) {\n+ break;\n+ }\n+\n+ verified = verify_signature(comp_len, comp_addr, sig_len, (uint64_t)sig,\n+ &cert_len, &cert_table_idx);\n+\n+ /* default cert index and flags for unverified component */\n+ cert_index = -1;\n+ flags = S390_IPL_DEV_COMP_FLAG_SC;\n+\n+ if (verified) {\n+ if (cert_list_table[cert_table_idx] == -1) {\n+ if (!request_certificate(cert_addr, cert_table_idx)) {\n+ puts(\"Could not get certificate\");\n+ goto out;\n+ }\n+\n+ cert_list_table[cert_table_idx] = cert_entry_idx;\n+ cert_list_add(&cert_list, cert_entry_idx, cert_addr, cert_len);\n+\n+ /* increment for the next certificate */\n+ cert_entry_idx++;\n+ cert_addr += cert_len;\n+ }\n+\n+ puts(\"Verified component\");\n+ cert_index = cert_list_table[cert_table_idx];\n+ flags |= S390_IPL_DEV_COMP_FLAG_CSV;\n+ }\n+\n+ comp_list_add(&comp_list, comp_entry_idx, cert_index,\n+ comp_addr, comp_len, flags);\n+\n+ if (!verified) {\n+ zipl_secure_handle(\"Could not verify component\");\n+ }\n+\n+ comp_entry_idx++;\n+ signed_count += 1;\n+ /* After a signature is used another new one can be accepted */\n+ sig_len = 0;\n+ break;\n+ default:\n+ puts(\"Unknown component entry type\");\n+ return -1;\n+ }\n+\n+ entry++;\n+\n+ if ((uint8_t *)(&entry[1]) > tmp_sec + MAX_SECTOR_SIZE) {\n+ puts(\"Wrong entry value\");\n+ return -EINVAL;\n+ }\n+ }\n+\n+ if (signed_count == 0) {\n+ zipl_secure_handle(\"Secure boot is on, but components are not signed\");\n+ }\n+\n+ update_iirb(&comp_list, &cert_list);\n+\n+ *entry_ptr = entry;\n+ free(sig);\n+\n+ return 0;\n+out:\n+ free(cert_addr);\n+ free(sig);\n+\n+ return -1;\n+}\ndiff --git a/pc-bios/s390-ccw/secure-ipl.h b/pc-bios/s390-ccw/secure-ipl.h\nnew file mode 100644\nindex 0000000000..eb5ba0ed47\n--- /dev/null\n+++ b/pc-bios/s390-ccw/secure-ipl.h\n@@ -0,0 +1,102 @@\n+/*\n+ * S/390 Secure IPL\n+ *\n+ * Copyright 2025 IBM Corp.\n+ * Author(s): Zhuoying Cai <zycai@linux.ibm.com>\n+ *\n+ * SPDX-License-Identifier: GPL-2.0-or-later\n+ */\n+\n+#ifndef _PC_BIOS_S390_CCW_SECURE_IPL_H\n+#define _PC_BIOS_S390_CCW_SECURE_IPL_H\n+\n+#include <diag320.h>\n+#include <diag508.h>\n+\n+VCStorageSizeBlock *zipl_secure_get_vcssb(void);\n+int zipl_run_secure(ComponentEntry **entry_ptr, uint8_t *tmp_sec);\n+\n+static inline void zipl_secure_handle(const char *message)\n+{\n+ switch (boot_mode) {\n+ case ZIPL_BOOT_MODE_SECURE_AUDIT:\n+ IPL_check(false, message);\n+ break;\n+ default:\n+ break;\n+ }\n+}\n+\n+static inline uint64_t diag320(void *data, unsigned long subcode)\n+{\n+ register unsigned long addr asm(\"0\") = (unsigned long)data;\n+ register unsigned long rc asm(\"1\") = 0;\n+\n+ asm volatile (\"diag %0,%2,0x320\\n\"\n+ : \"+d\" (addr), \"+d\" (rc)\n+ : \"d\" (subcode)\n+ : \"memory\", \"cc\");\n+ return rc;\n+}\n+\n+static inline bool is_cert_store_facility_supported(void)\n+{\n+ uint32_t d320_ism;\n+\n+ if (!sclp_is_diag320_on()) {\n+ return false;\n+ }\n+\n+ diag320(&d320_ism, DIAG_320_SUBC_QUERY_ISM);\n+ return d320_ism & (DIAG_320_ISM_QUERY_VCSI | DIAG_320_ISM_STORE_VC);\n+}\n+\n+static inline uint64_t _diag508(void *data, unsigned long subcode)\n+{\n+ register unsigned long addr asm(\"0\") = (unsigned long)data;\n+ register unsigned long rc asm(\"1\") = 0;\n+\n+ asm volatile (\"diag %0,%2,0x508\\n\"\n+ : \"+d\" (addr), \"+d\" (rc)\n+ : \"d\" (subcode)\n+ : \"memory\", \"cc\");\n+ return rc;\n+}\n+\n+static inline bool is_signature_verif_supported(void)\n+{\n+ uint64_t d508_subcodes;\n+\n+ d508_subcodes = _diag508(NULL, DIAG_508_SUBC_QUERY_SUBC);\n+ return d508_subcodes & DIAG_508_SUBC_SIG_VERIF;\n+}\n+\n+static inline bool verify_signature(uint64_t comp_len, uint64_t comp_addr,\n+ uint64_t sig_len, uint64_t sig_addr,\n+ uint64_t *cert_len, uint8_t *cert_idx)\n+{\n+ Diag508SigVerifBlock svb;\n+\n+ svb.length = sizeof(Diag508SigVerifBlock);\n+ svb.version = 0;\n+ svb.comp_len = comp_len;\n+ svb.comp_addr = comp_addr;\n+ svb.sig_len = sig_len;\n+ svb.sig_addr = sig_addr;\n+\n+ if (_diag508(&svb, DIAG_508_SUBC_SIG_VERIF) == DIAG_508_RC_OK) {\n+ *cert_len = svb.cert_len;\n+ /*\n+ * DIAG 508 utilizes an index origin of 0 when indexing the cert store.\n+ * The cert_idx will be used for DIAG 320 data structures, which expects\n+ * an index origin of 1. Account for the offset here so it's easier to\n+ * manage later.\n+ */\n+ *cert_idx = svb.cert_store_index + 1;\n+ return true;\n+ }\n+\n+ return false;\n+}\n+\n+#endif /* _PC_BIOS_S390_CCW_SECURE_IPL_H */\n", "prefixes": [ "v8", "19/30" ] }