All of lore.kernel.org
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Sebastian Ene" <sebastianene@google.com>
Cc: kvmarm@lists.linux.dev, "Marc Zyngier" <maz@kernel.org>,
	"Oliver Upton" <oupton@kernel.org>
Subject: Re: [PATCH v2 08/13] KVM: arm64: Trap & emulate the ITS MAPD command
Date: Fri, 07 Aug 2026 16:57:24 +0000	[thread overview]
Message-ID: <20260807165724.952CD1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260807164322.2970811-10-sebastianene@google.com>

Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] The rollback logic in check_table_update is dead code and allows host-driven hypervisor memory compromise.
- [Critical] get_num_itt_pages calculates ITT boundaries incorrectly, resulting in underpinning of unaligned ITT tail pages.
- [Critical] check_table_update does not enforce physical address alignment for Level 2 tables, allowing hardware ITS memory corruption.
- [High] track_pfn_remove skips clearing the tracking entry on early return, permanently leaking dte_entry slots.
- [High] Partial failure in process_its_mapd aborts command execution without rolling back EL2 metadata donations.
- [High] track_pfn_add leaks hyp_page refcounts when mapping duplicate commands, permanently stranding host memory.
- [High] Architecturally legal MAPD unmap commands fail with -EINVAL, permanently stalling the emulated ITS command queue.
- [Medium] Re-mapping an existing device to a new ITT leaks the old ITT pages.
--

--- Patch [8]: [PATCH v2 08/13] KVM: arm64: Trap & emulate the ITS MAPD command ---
commit d27a7f8a2aeffcb0dd24c8e126274036ef6c275e
Author: Sebastian Ene <sebastianene@google.com>

KVM: arm64: Trap & emulate the ITS MAPD command

Parse the MAPD command and extract the ITT address to sanitize it. When
the command has the valid bit set, share and pin the memory that holds
the ITT table with the hypervisor to prevent it from being given to
someone else (eg. a VM). Use the pinning as a mechanism to get a grip
to the page and to prevent other users of the pKVM API from sharing or
donating the page for something else.

> diff --git a/arch/arm64/kvm/hyp/nvhe/its_emulate.c b/arch/arm64/kvm/hyp/nvhe/its_emulate.c
> index 1ce2f9d8fcf9c..071a08d3602d7 100644
> --- a/arch/arm64/kvm/hyp/nvhe/its_emulate.c
> +++ b/arch/arm64/kvm/hyp/nvhe/its_emulate.c
[ ... ]
> @@ -110,11 +118,236 @@ static int submit_single_cmd(struct its_priv_state *its, bool retry)
>  	return 0;
>  }
>  
> +static int get_num_itt_pages(struct its_priv_state *its, u8 num_bits)
> +{
> +	u64 gits_typer, nr_ites;
> +	size_t sz;
> +
> +	gits_typer = readq_relaxed(its->base + GITS_TYPER);
> +	if (num_bits > FIELD_GET(GITS_TYPER_IDBITS, gits_typer))
> +		return -EINVAL;
> +
> +	nr_ites = BIT_ULL(num_bits + 1);
> +	sz = nr_ites * (FIELD_GET(GITS_TYPER_ITT_ENTRY_SIZE, gits_typer) + 1);
> +	sz = max(sz, ITS_ITT_ALIGN) + ITS_ITT_ALIGN - 1;
> +
> +	return PAGE_ALIGN(sz) >> PAGE_SHIFT;
> +}

[Severity: Critical]
Does this calculation miss the intra-page offset of the itt_addr? If a host
provides an ITS-aligned but not page-aligned address, sz alone doesn't
account for the page boundary crossing. This could result in under-pinning
the tail page and allowing it to be mapped to a guest while the hardware ITS
writes to it.

[ ... ]
> +static int check_table_update(struct its_priv_state *its, u32 device_id, u64 type, bool rollback)
> +{
> +	struct its_baser *table = get_table_from_snapshot(its->host_state, type);
> +	size_t lvl2_entry_sz, lvl1_table_sz, num_lvl2_entries, num_lvl1_entries;
> +	u64 *snapshot_table, *original_table;
> +	u64 prev_entry, new_entry;
> +	u32 new_entry_index;
> +	int ret;
> +
[ ... ]
> +	snapshot_table = kern_hyp_va(table->base_snapshot);
> +	original_table = kern_hyp_va(table->base);
> +
> +	/*
> +	 * Look at the host table copy and if the entry hasn't changed the valid
> +	 * bit compared to the original table used by the hardwre, don't update anything.
> +	 */
> +	new_entry = snapshot_table[new_entry_index];
> +	prev_entry = original_table[new_entry_index];
> +	if (!((new_entry ^ prev_entry) & GITS_BASER_VALID))
> +		return 0;

[Severity: Critical]
If a command fails and triggers a rollback, will this early return bypass
the rollback logic? Because the forward pass already updated original_table,
new_entry and prev_entry will match here. This leaves a window for the host
to manipulate snapshot_table and maliciously donate arbitrary hypervisor
pages.

> +
> +	/*
> +	 * The host can play nasty tricks with read-modify-write after a
> +	 * rollback is triggered but we still hold on to the original tables
> +	 * which are hyp managed and we don't give back any other page to the
> +	 * host.
> +	 */
> +	if (rollback)
> +		new_entry = new_entry ^ GITS_BASER_VALID;
> +
> +	if (new_entry & GITS_BASER_VALID)
> +		ret = __pkvm_host_donate_hyp(hyp_phys_to_pfn(new_entry & PHYS_MASK),
> +					     table->psz >> PAGE_SHIFT);

[Severity: Critical]
Does this pass the physical address to the hypervisor without enforcing the
architectural page size alignment (GITS_BASER.Page_Size)? If the host
provides an unaligned physical address, the hypervisor will donate based on
the unaligned PFN, but the hardware ITS masks the lower bits, potentially
accessing memory outside the donated range.

> +	else
> +		ret = __pkvm_hyp_donate_host(hyp_phys_to_pfn(prev_entry & PHYS_MASK),
> +					     table->psz >> PAGE_SHIFT);
> +	if (ret)
> +		return ret;
> +
> +	original_table[new_entry_index] = new_entry;
> +	return 0;
> +}
> +
> +static int track_pfn_add(struct its_priv_state *its, u32 device_id, u64 pfn)
> +{
> +	void *virt = hyp_phys_to_virt(hyp_pfn_to_phys(pfn));
> +	struct dte_entry *entries = &its->tracked_entries[0];
> +	bool pfn_shared = false;
> +	int ret;
> +	int i;
> +
> +	for (i = 0; i < its->num_tracked_entries; i++) {
> +		if (entries[i].itt_pfn == pfn) {
> +			if (entries[i].device_id != device_id) {
> +				pfn_shared = true;
> +				break;
> +			} else {
> +				return hyp_pin_shared_mem(virt, virt + PAGE_SIZE);
> +			}

[Severity: High]
If the host issues multiple MAPD commands with Valid=1 for the same
device_id and ITT, can this leak hyp_page refcounts? It increments the pin
count for a duplicate mapping without adding a new tracking entry. When
unmapped, track_pfn_remove unpins only once and clears the tracking entry,
which would permanently strand the extra pins.

> +		}
> +	}
[ ... ]
> +static int track_pfn_remove(struct its_priv_state *its, u32 device_id, u64 pfn)
> +{
> +	void *virt = hyp_phys_to_virt(hyp_pfn_to_phys(pfn));
> +	struct dte_entry *entries = &its->tracked_entries[0];
> +	int ret;
> +	int i;
> +
> +	for (i = 0; i < its->num_tracked_entries; i++) {
> +		if (entries[i].itt_pfn != pfn || entries[i].device_id != device_id)
> +			continue;
> +
> +		/* To decrement the refcount, first try to unshare it */
> +		ret = __pkvm_host_unshare_hyp(pfn);
> +		if (ret == -EBUSY) {
> +			hyp_unpin_shared_mem(virt, virt + PAGE_SIZE);
> +			ret = __pkvm_host_unshare_hyp(pfn);
> +			if (ret == -EBUSY)
> +				return 0;

[Severity: High]
Does this early return skip clearing the tracking entry? If an ITT page is
shared by multiple devices and the second unshare returns -EBUSY, bypassing
the memset below will permanently leak the dte_entry slot for this device,
eventually leading to -ENOSPC.

> +
> +			WARN_ON(ret);
> +		}
> +
> +		memset(&entries[i], 0, sizeof(struct dte_entry));
> +		its->empty_entry = i;
> +		return 0;
> +	}
> +
> +	return -EINVAL;
> +}
[ ... ]
> +static int process_its_mapd(struct its_priv_state *its, struct its_cmd_block *cmd, bool rollback)
> +{
> +	phys_addr_t itt_addr = cmd->raw_cmd[2] & GENMASK(51, 8);
> +	bool remove = !(cmd->raw_cmd[2] & BIT(63));
> +	u8 size = cmd->raw_cmd[1] & GENMASK(4, 0);
> +	u32 device_id = cmd->raw_cmd[0] >> 32;
> +	int num_pages, ret;
> +	u64 itt_pfn;
> +
> +	if (rollback)
> +		remove = !remove;
> +
> +	itt_pfn = hyp_phys_to_pfn(itt_addr);
> +	num_pages = get_num_itt_pages(its, size);

[Severity: High]
The GICv3 specification dictates that when a MAPD command has Valid=0 (an
unmap), the ITT_addr and Size fields are IGNORED. If a driver provides zeros
for these ignored fields during an unmap, will extracting them here cause
track_pfn_remove to fail with -EINVAL, permanently stalling the emulated ITS
command queue?

> +	if (num_pages < 0)
> +		return num_pages;
> +
> +	ret = check_table_update(its, device_id, GITS_BASER_TYPE_DEVICE, rollback);
> +	if (ret)
> +		return ret;
> +
> +	return track_pfn(its, device_id, itt_pfn, num_pages, remove);
> +}

[Severity: High]
If track_pfn() fails here (for example, if the tracking array is full), the
error is returned directly. Will this leave the mutations and donations
performed by check_table_update() permanently applied without rolling them
back or executing the command in hardware?

[Severity: Medium]
When a MAPD command is issued with Valid=1 for a device_id that is already
mapped, but with a new ITT_addr, does this leak the old ITT pages? The new
PFNs are tracked, but there doesn't appear to be logic to look up and remove
the pre-existing mapping for this device_id.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260807164322.2970811-2-sebastianene@google.com?part=8

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

Thread overview: 27+ 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:58   ` sashiko-bot
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 17:00   ` sashiko-bot
2026-08-07 16:43 ` [PATCH v2 03/13] KVM: arm64: Support host MMIO trap handlers for unmapped devices Sebastian Ene
2026-08-07 17:03   ` sashiko-bot
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 17:09   ` sashiko-bot
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 17:00   ` sashiko-bot
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:57   ` sashiko-bot
2026-08-07 16:43 ` [PATCH v2 07/13] KVM: arm64: Restrict host access to the private ITS tables Sebastian Ene
2026-08-07 17:02   ` sashiko-bot
2026-08-07 16:43 ` [PATCH v2 08/13] KVM: arm64: Trap & emulate the ITS MAPD command Sebastian Ene
2026-08-07 16:57   ` sashiko-bot [this message]
2026-08-07 16:43 ` [PATCH v2 09/13] KVM: arm64: Trap & emulate the ITS MAPC command Sebastian Ene
2026-08-07 17:04   ` sashiko-bot
2026-08-07 16:43 ` [PATCH v2 10/13] KVM: arm64: Restrict host updates to GITS_CTLR Sebastian Ene
2026-08-07 17:02   ` sashiko-bot
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 17:28   ` sashiko-bot
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 17:11   ` sashiko-bot
2026-08-07 16:43 ` [PATCH v2 13/13] KVM: arm64: Implement HVC interface for ITS emulation setup Sebastian Ene
2026-08-07 17:17   ` sashiko-bot

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=20260807165724.952CD1F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=kvmarm@lists.linux.dev \
    --cc=maz@kernel.org \
    --cc=oupton@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    --cc=sebastianene@google.com \
    /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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.