Linux-ARM-Kernel Archive on lore.kernel.org
 help / color / mirror / Atom feed
From: Sebastian Ene <sebastianene@google.com>
To: catalin.marinas@arm.com, fuad.tabba@linux.dev,
	joey.gouly@arm.com,  mark.rutland@arm.com, maz@kernel.org,
	oupton@kernel.org, rananta@google.com,  Sascha.Bischoff@arm.com,
	suzuki.poulose@arm.com, will@kernel.org
Cc: kvmarm@lists.linux.dev, android-kvm@google.com,
	bgrzesik@google.com,  linux-arm-kernel@lists.infradead.org,
	linux-kernel@vger.kernel.org,  nathan@kernel.org,
	perlarsen@google.com, sebastianene@google.com,
	 seiden@linux.ibm.com, smostafa@google.com, tglx@kernel.org,
	 vdonnefort@google.com, vladimir.murzin@arm.com,
	yuzenghui@huawei.com,  zenghui.yu@linux.dev
Subject: [PATCH v2 07/13] KVM: arm64: Restrict host access to the private ITS tables
Date: Fri,  7 Aug 2026 16:43:17 +0000	[thread overview]
Message-ID: <20260807164322.2970811-9-sebastianene@google.com> (raw)
In-Reply-To: <20260807164322.2970811-2-sebastianene@google.com>

Make the last level of the tables(DeviceTable, Collection and vPE)
inaccessible to the host by donating them to the hypervisor.
This prevents a compromised host from patching an entry with an
address that it wants to write to and then using an ITS command to
write over the memory content from that address.

When tables are configured with indirect layout, shadow the first
layer by copying it to a separate table, update the gic ITS host
driver to use the copy instead of the original table and share the copy
between the host and the hypervisor. Make the original layer
innaccessible to the host by donating the table memory from the host to
the hypervisor.
This ensures that the pKVM ITS emulation mediates the
configuration written by the driver in the first layer of the table and
sanitizes the entries before writing to the original table programmed
in hardware. The update phase of the original table from the copy will
be done when commands are sent to the ITS.

Signed-off-by: Sebastian Ene <sebastianene@google.com>
---
 arch/arm64/kvm/hyp/nvhe/its_emulate.c | 161 ++++++++++++++++++++++++++
 1 file changed, 161 insertions(+)

diff --git a/arch/arm64/kvm/hyp/nvhe/its_emulate.c b/arch/arm64/kvm/hyp/nvhe/its_emulate.c
index e943ab972aa5..1ce2f9d8fcf9 100644
--- a/arch/arm64/kvm/hyp/nvhe/its_emulate.c
+++ b/arch/arm64/kvm/hyp/nvhe/its_emulate.c
@@ -237,6 +237,20 @@ static int pkvm_setup_its_shadow_cmdq(struct its_host_state *host_state)
 	return ret;
 }
 
+static void pkvm_teardown_its_shadow_cmdq(struct its_host_state *host_state)
+{
+	u64 i, start_pfn, num_pages = host_state->cmdq_len >> PAGE_SHIFT;
+
+	start_pfn = hyp_virt_to_pfn(host_state->cmd_host_copy);
+	hyp_unpin_shared_mem(host_state->cmd_host_copy,
+			     host_state->cmd_host_copy + host_state->cmdq_len);
+
+	for (i = 0; i < num_pages; i++)
+		WARN_ON(__pkvm_host_unshare_hyp(start_pfn + i));
+
+	WARN_ON(__pkvm_hyp_donate_host(hyp_virt_to_pfn(host_state->cmd_original), num_pages));
+}
+
 static struct pkvm_protected_reg *get_region(phys_addr_t dev_addr)
 {
 	int i;
@@ -249,6 +263,147 @@ static struct pkvm_protected_reg *get_region(phys_addr_t dev_addr)
 	return NULL;
 }
 
+static void pkvm_unshare_shadow_table(void *shadow, u64 nr_pages)
+{
+	u64 i, start_pfn = hyp_virt_to_pfn(shadow);
+
+	hyp_unpin_shared_mem(shadow, shadow + (nr_pages << PAGE_SHIFT));
+
+	for (i = 0; i < nr_pages; i++)
+		WARN_ON(__pkvm_host_unshare_hyp(start_pfn + i));
+}
+
+static int pkvm_host_unmap_last_level(void *shadow, size_t num_pages, u32 psz)
+{
+	phys_addr_t table_addr;
+	u64 *table = shadow;
+	int i, end;
+	int ret;
+
+	end = (num_pages << PAGE_SHIFT) / sizeof(*table);
+	for (i = 0; i < end; i++) {
+		if (!(table[i] & GITS_BASER_VALID))
+			continue;
+
+		table_addr = table[i] & PHYS_MASK;
+		ret = __pkvm_host_donate_hyp(hyp_phys_to_pfn(table_addr), psz >> PAGE_SHIFT);
+		if (ret)
+			goto err_donate;
+	}
+
+	return 0;
+err_donate:
+	for (i = i - 1; i >= 0; i--) {
+		if (!(table[i] & GITS_BASER_VALID))
+			continue;
+
+		table_addr = table[i] & PHYS_MASK;
+		__pkvm_hyp_donate_host(hyp_phys_to_pfn(table_addr), psz >> PAGE_SHIFT);
+	}
+	return ret;
+}
+
+static int pkvm_share_shadow_table(void *shadow, u64 nr_pages)
+{
+	u64 i, ret, start_pfn = hyp_virt_to_pfn(shadow);
+
+	for (i = 0; i < nr_pages; i++) {
+		ret = __pkvm_host_share_hyp(start_pfn + i);
+		if (ret)
+			goto unshare;
+	}
+
+	ret = hyp_pin_shared_mem(shadow, shadow + (nr_pages << PAGE_SHIFT));
+	if (ret)
+		goto unshare;
+
+	return ret;
+unshare:
+	while (i--)
+		__pkvm_host_unshare_hyp(start_pfn + i);
+	return ret;
+}
+
+static void pkvm_host_map_last_level(void *shadow, size_t num_pages, u32 psz)
+{
+	u64 *table = shadow;
+	int i, end = (num_pages << PAGE_SHIFT) / sizeof(*table);
+	phys_addr_t table_addr;
+
+	for (i = 0; i < end; i++) {
+		if (!(table[i] & GITS_BASER_VALID))
+			continue;
+
+		table_addr = table[i] & PHYS_MASK;
+		WARN_ON(__pkvm_hyp_donate_host(hyp_phys_to_pfn(table_addr), psz >> PAGE_SHIFT));
+	}
+}
+
+static int pkvm_setup_its_shadow_baser(struct its_host_state *host_state)
+{
+	u64 baser_val, num_pages;
+	void *original_table, *snapshot_table;
+	int ret;
+	int i;
+
+	for (i = 0; i < GITS_BASER_NR_REGS; i++) {
+		baser_val = host_state->tables[i].val;
+		if (!(baser_val & GITS_BASER_VALID))
+			continue;
+
+		original_table = kern_hyp_va(host_state->tables[i].base);
+		num_pages = (1 << host_state->tables[i].order);
+
+		ret = __pkvm_host_donate_hyp(hyp_virt_to_pfn(original_table), num_pages);
+		if (ret)
+			goto err_donate;
+
+		if (baser_val & GITS_BASER_INDIRECT) {
+			if (!host_state->tables[i].base_snapshot) {
+				ret = -EINVAL;
+				goto err_with_donation;
+			}
+
+			snapshot_table = kern_hyp_va(host_state->tables[i].base_snapshot);
+			ret = pkvm_share_shadow_table(snapshot_table, num_pages);
+			if (ret)
+				goto err_with_donation;
+
+			ret = pkvm_host_unmap_last_level(original_table, num_pages,
+							 host_state->tables[i].psz);
+			if (ret)
+				goto err_with_share;
+		}
+	}
+
+	return 0;
+err_with_share:
+	pkvm_unshare_shadow_table(snapshot_table, num_pages);
+err_with_donation:
+	__pkvm_hyp_donate_host(hyp_virt_to_pfn(original_table), num_pages);
+err_donate:
+	for (i = i - 1; i >= 0; i--) {
+		baser_val = host_state->tables[i].val;
+		if (!(baser_val & GITS_BASER_VALID))
+			continue;
+
+		original_table = kern_hyp_va(host_state->tables[i].base);
+		num_pages = (1 << host_state->tables[i].order);
+
+		if (baser_val & GITS_BASER_INDIRECT) {
+			snapshot_table = kern_hyp_va(host_state->tables[i].base_snapshot);
+			pkvm_unshare_shadow_table(snapshot_table, num_pages);
+
+			pkvm_host_map_last_level(original_table, num_pages,
+						 host_state->tables[i].psz);
+		}
+
+		WARN_ON(__pkvm_hyp_donate_host(hyp_virt_to_pfn(original_table), num_pages));
+	}
+
+	return ret;
+}
+
 DEFINE_HYP_SPINLOCK(its_setup_lock);
 
 int pkvm_its_emulate_setup(phys_addr_t dev_addr, struct its_host_state *host_state, void *priv,
@@ -294,6 +449,10 @@ int pkvm_its_emulate_setup(phys_addr_t dev_addr, struct its_host_state *host_sta
 	if (ret)
 		goto err_with_host_state;
 
+	ret = pkvm_setup_its_shadow_baser(host_state);
+	if (ret)
+		goto err_with_shadow_cmdq;
+
 	hyp_spin_lock_init(&priv_state->its_lock);
 
 	priv_state->host_state = host_state;
@@ -312,6 +471,8 @@ int pkvm_its_emulate_setup(phys_addr_t dev_addr, struct its_host_state *host_sta
 	hyp_spin_unlock(&its_setup_lock);
 
 	return 0;
+err_with_shadow_cmdq:
+	pkvm_teardown_its_shadow_cmdq(host_state);
 err_with_host_state:
 	WARN_ON(__pkvm_hyp_donate_host(hyp_virt_to_pfn(host_state), 1));
 err_with_priv:
-- 
2.55.0.654.g21b8a5bc05-goog



  parent reply	other threads:[~2026-08-07 16:44 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-08-07 16:43 [PATCH v2 00/13] KVM: ITS hardening for pKVM Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 01/13] KVM: arm64: Donate MMIO to the hypervisor Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 02/13] KVM: arm64: Track host-unmapped MMIO regions in a static array Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 03/13] KVM: arm64: Support host MMIO trap handlers for unmapped devices Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 04/13] KVM: Parse the device tree and register the ITS region with pKVM Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 05/13] irqchip/gic-v3-its: Add support for the ITS emulation setup Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 06/13] KVM: arm64: Shadow the ITS command queue and setup emulation Sebastian Ene
2026-08-07 16:43 ` Sebastian Ene [this message]
2026-08-07 16:43 ` [PATCH v2 08/13] KVM: arm64: Trap & emulate the ITS MAPD command Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 09/13] KVM: arm64: Trap & emulate the ITS MAPC command Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 10/13] KVM: arm64: Restrict host updates to GITS_CTLR Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 11/13] KVM: arm64: Prevent the host from specifying a different command queue Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 12/13] KVM: arm64: Prevent the host from programming new GITS_BASER tables Sebastian Ene
2026-08-07 16:43 ` [PATCH v2 13/13] KVM: arm64: Implement HVC interface for ITS emulation setup Sebastian Ene

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260807164322.2970811-9-sebastianene@google.com \
    --to=sebastianene@google.com \
    --cc=Sascha.Bischoff@arm.com \
    --cc=android-kvm@google.com \
    --cc=bgrzesik@google.com \
    --cc=catalin.marinas@arm.com \
    --cc=fuad.tabba@linux.dev \
    --cc=joey.gouly@arm.com \
    --cc=kvmarm@lists.linux.dev \
    --cc=linux-arm-kernel@lists.infradead.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=mark.rutland@arm.com \
    --cc=maz@kernel.org \
    --cc=nathan@kernel.org \
    --cc=oupton@kernel.org \
    --cc=perlarsen@google.com \
    --cc=rananta@google.com \
    --cc=seiden@linux.ibm.com \
    --cc=smostafa@google.com \
    --cc=suzuki.poulose@arm.com \
    --cc=tglx@kernel.org \
    --cc=vdonnefort@google.com \
    --cc=vladimir.murzin@arm.com \
    --cc=will@kernel.org \
    --cc=yuzenghui@huawei.com \
    --cc=zenghui.yu@linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox