From nobody Sun May 24 19:33:52 2026 Received: from galois.linutronix.de (Galois.linutronix.de [193.142.43.55]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6C32E37C11F; Fri, 22 May 2026 17:26:22 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=193.142.43.55 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779470784; cv=none; b=VLK7tFBomhzrNA2SFe/2NS304wmX1bvLFhUGPTfgArKP78wbCoLeaY37mxJ9WFy/fQSSuQ5QVXx6NSuEq2SBpbPKQt/RC7tOu37WHgNp+vKtm3o8xwb0iXRZ1kWgG5HSG7JtfLtL2jFKcOMlvZEOLP1Ik1FUUtxoMNOwHyiylRA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1779470784; c=relaxed/simple; bh=epQAXVcbpOfzfxjPKEdmvXq3IGl3AB8eauHllInoNVo=; h=Date:From:To:Subject:Cc:In-Reply-To:References:MIME-Version: Message-ID:Content-Type; b=hD8WrfnoXpmClOgPKUCsrcKmxySdg71iqp6sfaeYfBwHCmCF+J3zcQ4feR+/BplALCPYUn7St9lrPUX+9oQz6+pElBD/Rd4PSeGL6aidP54oxkkmpG4g2qjhYZKyLf+68J36eFhASXJRNczE5WpibRaN9/7Zslt6m2i/Y+8xZc0= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de; spf=pass smtp.mailfrom=linutronix.de; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=jhBYcTxW; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b=uUiT5zUs; arc=none smtp.client-ip=193.142.43.55 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=linutronix.de Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=linutronix.de Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="jhBYcTxW"; dkim=permerror (0-bit key) header.d=linutronix.de header.i=@linutronix.de header.b="uUiT5zUs" Date: Fri, 22 May 2026 17:26:19 -0000 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1779470781; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=+a0P0vrcmozn5VMmRnnQcmpXb9oht+99Jq3DBa5tn7M=; b=jhBYcTxWbfBI1s16Yvt2Q6xS138rdpa9RregmN4DpFGnCy/qkjV1gdYMVWyHJc+sAXftbC NF3KiXIWnqE7bxvFyegSfBU+Bb68tZ+w8LLkCA+j4XWKY0+9ltgUKzM/5ISt0RvDQT8FqP U9Jvw1YEzcBAjHxkgk8NYvmM4wPCFabbEoMg6NRvoc/rmeqAcsT6Bau5LJxJw0bNcAhwOd Cd9ylHQc8prt2TDhI4tc9a/9CAV1MOkXdF8QJHJ/Y4erteOM0hmPFaP7SbiLV7g3yR+V5r u7T1ZJmcwN2dLKzsdVquGYnf811rJqBrgtu0OgoFTp36ki88AKzQeTAAYjWPCQ== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1779470781; h=from:from:sender:sender:reply-to:reply-to:subject:subject:date:date: message-id:message-id:to:to:cc:cc:mime-version:mime-version: content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=+a0P0vrcmozn5VMmRnnQcmpXb9oht+99Jq3DBa5tn7M=; b=uUiT5zUsSgm0mzsj0T/vQvjzQbX35kgYTKIFw0BDojZaSk0GEToR/Fw/PktXMYl9PcdGJe fsnz+qBUhrdeDJDA== From: "tip-bot2 for Chao Gao" Sender: tip-bot2@linutronix.de Reply-to: linux-kernel@vger.kernel.org To: linux-tip-commits@vger.kernel.org Subject: [tip: x86/tdx] x86/virt/seamldr: Allocate and populate a module update request Cc: Chao Gao , Dave Hansen , x86@kernel.org, linux-kernel@vger.kernel.org In-Reply-To: <20260520222901.CE09B7B6@davehans-spike.ostc.intel.com> References: <20260520222901.CE09B7B6@davehans-spike.ostc.intel.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Message-ID: <177947077983.711.993010192787251404.tip-bot2@tip-bot2> Robot-ID: Robot-Unsubscribe: Contact to get blacklisted from these emails Precedence: bulk Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable The following commit has been merged into the x86/tdx branch of tip: Commit-ID: 35621312a061ff785a38c209ab05ccd1e4989382 Gitweb: https://git.kernel.org/tip/35621312a061ff785a38c209ab05ccd1e= 4989382 Author: Chao Gao AuthorDate: Wed, 20 May 2026 15:29:01 -07:00 Committer: Dave Hansen CommitterDate: Wed, 20 May 2026 15:37:10 -07:00 x86/virt/seamldr: Allocate and populate a module update request There are two important ABIs here: 'struct tdx_image' - The on-disk and in-memory format for a TDX module update image. 'struct seamldr_params' - The in-memory ABI passed to the TDX module loader. Points to a single 'struct tdx_image' broken up into 4k pages. Userspace supplies the update image in 'struct tdx_image' format. The image consists of a header followed by a sigstruct and the module binary. P-SEAMLDR, however, consumes 'struct seamldr_params' rather than the image directly. Parse the 'struct tdx_image' provided by userspace and populate a matching 'struct seamldr_params'. The 'tdx_image' ABI is versioned. Two public versions exist today: 0x100 and 0x200. This kernel only accepts 0x200. The older 0x100 format is being deprecated and is intentionally not supported here. Future versions of the module might be able to use the same ABIs (user/kernel and kernel/SEAMLDR) but they will not be able to use this kernel code. Reject module images without that specific version. This ensures that the kernel is able to understand the passed-in format. Validate the 'struct tdx_image' header before using it, because the header is consumed solely by the kernel to locate the sigstruct and module within the image. Do not validate the payload itself. The sigstruct and module pages are passed through to P-SEAMLDR, which validates them as part of the update. sigstruct_pages_pa_list currently has only one entry, but it will grow to four pages in the future. Keep it as an array for symmetry with module_pages_pa_list and for extensibility. [ dhansen: normal changelog clarification/munging ] Signed-off-by: Chao Gao Signed-off-by: Dave Hansen Link: https://patch.msgid.link/20260520133909.409394-14-chao.gao@intel.com Link: https://patch.msgid.link/20260520222901.CE09B7B6@davehans-spike.ostc.= intel.com --- arch/x86/virt/vmx/tdx/seamldr.c | 157 ++++++++++++++++++++++++++++++- 1 file changed, 156 insertions(+), 1 deletion(-) diff --git a/arch/x86/virt/vmx/tdx/seamldr.c b/arch/x86/virt/vmx/tdx/seamld= r.c index b2b6a43..bcb1386 100644 --- a/arch/x86/virt/vmx/tdx/seamldr.c +++ b/arch/x86/virt/vmx/tdx/seamldr.c @@ -7,6 +7,8 @@ #define pr_fmt(fmt) "seamldr: " fmt =20 #include +#include +#include #include =20 #include @@ -18,6 +20,35 @@ /* P-SEAMLDR SEAMCALL leaf function */ #define P_SEAMLDR_INFO 0x8000000000000000 =20 +#define SEAMLDR_MAX_NR_MODULE_PAGES 496 +#define SEAMLDR_MAX_NR_SIG_PAGES 1 + +/* + * The seamldr_params "scenario" field specifies the operation mode: + * 0: Install TDX module from scratch (not used by kernel) + * 1: Update existing TDX module to a compatible version + */ +#define SEAMLDR_SCENARIO_UPDATE 1 + +/* + * This is the "SEAMLDR_PARAMS" data structure defined in the + * "SEAM Loader (SEAMLDR) Interface Specification". + * + * It is the in-memory ABI that the kernel passes to the P-SEAMLDR + * to update the TDX module. It breaks the TDX module image up in + * page-size pieces. + */ +struct seamldr_params { + u32 version; + u32 scenario; + u64 sigstruct_pages_pa_list[SEAMLDR_MAX_NR_SIG_PAGES]; + u8 reserved[104]; + u64 module_nr_pages; + u64 module_pages_pa_list[SEAMLDR_MAX_NR_MODULE_PAGES]; +} __packed; + +static_assert(sizeof(struct seamldr_params) =3D=3D 4096); + /* * Serialize P-SEAMLDR calls since the hardware only allows a single CPU to * interact with P-SEAMLDR simultaneously. Use raw version as the calls can @@ -55,6 +86,106 @@ int seamldr_get_info(struct seamldr_info *seamldr_info) } EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host"); =20 +#define TDX_IMAGE_VERSION_2 0x200 + +/* First page of the on-disk module update image: */ +struct tdx_image_header { + u16 version; + u16 checksum; + u8 signature[8]; + u32 sigstruct_nr_pages; + u32 module_nr_pages; + u8 reserved[4076]; +} __packed; + +#define TDX_IMAGE_HEADER_SIZE sizeof(struct tdx_image_header) +static_assert(TDX_IMAGE_HEADER_SIZE =3D=3D 4096); + +/* + * Intel TDX module update ABI structure. aka. "TDX module blob". + * This is the on-disk format that fw_upload lands in a kernel + * buffer. + * + * @payload contains sigstruct pages followed by module pages. + */ +struct tdx_image { + struct tdx_image_header header; + u8 payload[]; +}; + +/* + * Given a vmalloc() allocation, write all of the backing physical + * addresses to pa_list[]. Caller guarantees that the array is big + * enough. + */ +static void populate_pa_list(u64 *pa_list, const u8 *vmalloc_addr, u32 vma= lloc_len_pages) +{ + int i; + + for (i =3D 0; i < vmalloc_len_pages; i++) { + unsigned long offset =3D i * PAGE_SIZE; + unsigned long pfn =3D vmalloc_to_pfn(&vmalloc_addr[offset]); + + pa_list[i] =3D pfn << PAGE_SHIFT; + } +} + +static void populate_seamldr_params(struct seamldr_params *params, + const u8 *sig, u32 sig_nr_pages, + const u8 *mod, u32 mod_nr_pages) +{ + params->version =3D 0; + params->scenario =3D SEAMLDR_SCENARIO_UPDATE; + params->module_nr_pages =3D mod_nr_pages; + + populate_pa_list(params->sigstruct_pages_pa_list, sig, sig_nr_pages); + populate_pa_list(params->module_pages_pa_list, mod, mod_nr_pages); +} + +/* + * @image points to a vmalloc()'d 'struct tdx_image'. Transform + * it into @params which is the P-SEAMLDR ABI format. + */ +static int init_seamldr_params(struct seamldr_params *params, + const struct tdx_image *image, + u32 image_len) +{ + const struct tdx_image_header *header =3D &image->header; + + u32 sigstruct_len =3D header->sigstruct_nr_pages * PAGE_SIZE; + u32 module_len =3D header->module_nr_pages * PAGE_SIZE; + + u8 *header_start =3D (u8 *)header; + u8 *header_end =3D header_start + TDX_IMAGE_HEADER_SIZE; + + u8 *sigstruct_start =3D header_end; + u8 *sigstruct_end =3D sigstruct_start + sigstruct_len; + + u8 *module_start =3D sigstruct_end; + + /* Check the calculated payload size against the image size. */ + if (TDX_IMAGE_HEADER_SIZE + sigstruct_len + module_len !=3D image_len) + return -EINVAL; + + /* Reject unsupported tdx_image ABI versions. */ + if (header->version !=3D TDX_IMAGE_VERSION_2) + return -EINVAL; + + if (header->sigstruct_nr_pages > SEAMLDR_MAX_NR_SIG_PAGES || + header->module_nr_pages > SEAMLDR_MAX_NR_MODULE_PAGES) + return -EINVAL; + + if (memcmp(header->signature, "TDX-BLOB", sizeof(header->signature))) + return -EINVAL; + + if (memchr_inv(header->reserved, 0, sizeof(header->reserved))) + return -EINVAL; + + populate_seamldr_params(params, sigstruct_start, header->sigstruct_nr_pag= es, + module_start, header->module_nr_pages); + return 0; +} + /** * seamldr_install_module - Install a new TDX module. * @data: Pointer to the TDX module image. @@ -64,7 +195,31 @@ EXPORT_SYMBOL_FOR_MODULES(seamldr_get_info, "tdx-host"); */ int seamldr_install_module(const u8 *data, u32 data_len) { + struct seamldr_params *params; + const struct tdx_image *image; + int ret; + + /* + * init_seamldr_params() reads the header early. + * Ensure there is enough data to do at least that. + */ + if (data_len < TDX_IMAGE_HEADER_SIZE) + return -EINVAL; + + image =3D (const struct tdx_image *)data; + + params =3D kzalloc_obj(*params); + if (!params) + return -ENOMEM; + + /* Populate 'params' from 'image'. */ + ret =3D init_seamldr_params(params, image, data_len); + if (ret) + goto out; + /* TODO: Update TDX module here */ - return 0; +out: + kfree(params); + return ret; } EXPORT_SYMBOL_FOR_MODULES(seamldr_install_module, "tdx-host");