Kernel KVM virtualization development
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Florian Schmidt" <flosch@nutanix.com>
Cc: kvm@vger.kernel.org
Subject: Re: [PATCH] kvm: add EVER_MAPPED ioctl and capability
Date: Fri, 31 Jul 2026 09:32:12 +0000	[thread overview]
Message-ID: <20260731093212.A2DF81F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260731091708.2963414-1-flosch@nutanix.com>

Thank you for your contribution! Sashiko AI review found 6 potential issue(s) to consider:
- [High] The `ever_mapped_bitmap` is dynamically allocated during VM capability enablement but never freed during VM teardown.
- [High] Buffer overflow and KASAN out-of-bounds access due to a byte-aligned allocation size being manipulated by unsigned long bit operations.
- [High] Missing `cond_resched()` in the fallback iteration loop allows a single ioctl to monopolize the CPU, causing soft lockups.
- [High] `KVM_CAP_EVER_MAPPED` enablement succeeds based on the global `tdp_enabled` hardware flag, but silently fails to track memory if the legacy shadow MMU is actively handling the VM.
- [Medium] Unvalidated `reserved` fields in `struct kvm_ever_mapped_log` break future ABI compatibility.
- [Medium] Lockless reading of `kvm->arch.ever_mapped_max_gpa` and `ever_mapped_bitmap` can cause TOCTOU races and spurious errors.
--

--- Patch [1]: [PATCH] kvm: add EVER_MAPPED ioctl and capability ---
commit bb0c368d3b83d1838e5cca827b1430b4b426ca4d
Author: Florian Schmidt <flosch@nutanix.com>
Subject: kvm: add EVER_MAPPED ioctl and capability

This commit introduces a new capability, KVM_CAP_EVER_MAPPED, which allows a
VMM to track which guest pages have ever been mapped using a bitmap. It also
adds an ioctl, KVM_GET_EVER_MAPPED_LOG, to retrieve this tracking information.

> diff --git a/arch/x86/kvm/x86.c b/arch/x86/kvm/x86.c
> index 0626e835e9eb7..89cbcf8399f49 100644
> --- a/arch/x86/kvm/x86.c
> +++ b/arch/x86/kvm/x86.c

[ ... ]

> @@ -2391,6 +2392,9 @@ int kvm_vm_ioctl_check_extension(struct kvm *kvm, long ext)
>  	case KVM_CAP_READONLY_MEM:
>  		r = kvm ? kvm_arch_has_readonly_mem(kvm) : 1;
>  		break;
> +	case KVM_CAP_EVER_MAPPED:
> +		r = (tdp_enabled && IS_ENABLED(CONFIG_X86_64));
> +		break;

[Severity: High]
Does this successfully track memory when the VM is running under the legacy
shadow MMU?

If a VM uses the legacy shadow MMU (for example, with nested L2 guests or the
kvm.tdp_mmu=0 module parameter), the page mappings are handled in mmu.c, which
bypasses the kvm_ever_mapped_set_range() hook added to tdp_mmu.c. Since this
check only verifies the global tdp_enabled hardware flag, could this silently
fail to track memory and cause userspace to observe a clean log when the pages
were actually modified?

[ ... ]

> @@ -4190,6 +4194,36 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
>  		mutex_unlock(&kvm->lock);
>  		break;
>  	}
> +	case KVM_CAP_EVER_MAPPED: {
> +		unsigned long *mapped_bitmap;
> +		unsigned long num_granules;
> +
> +		r = 0;
> +		mutex_lock(&kvm->lock);
> +		if (!tdp_enabled) {
> +			r = -EOPNOTSUPP;
> +		} else if (kvm->arch.ever_mapped_bitmap) {
> +			r = -EEXIST;
> +		} else if (cap->args[0] == 0 ||
> +			   cap->args[0] > (1ULL << kvm_host.maxphyaddr)) {
> +			r = -EINVAL;
> +		} else {
> +			num_granules = DIV_ROUND_UP(cap->args[0], 1ULL << KVM_EVER_MAPPED_SHIFT);
> +			mapped_bitmap =
> +				kvcalloc(BITS_TO_LONGS(num_granules),
> +					 sizeof(long), GFP_KERNEL_ACCOUNT);

[Severity: High]
Is this allocated memory ever freed?

When a VM is destroyed via the kvm_arch_destroy_vm() path, does it clean up
the ever_mapped_bitmap? An unprivileged process could repeatedly create VMs,
enable this capability, and destroy the VMs, potentially exhausting host
memory if the bitmap isn't explicitly freed.

> +			if (!mapped_bitmap)
> +				r = -ENOMEM;
> +			else {
> +				write_lock(&kvm->mmu_lock);
> +				kvm->arch.ever_mapped_bitmap = mapped_bitmap;
> +				kvm->arch.ever_mapped_max_gpa = num_granules << KVM_EVER_MAPPED_SHIFT;
> +				write_unlock(&kvm->mmu_lock);
> +			}
> +		}
> +		mutex_unlock(&kvm->lock);
> +		break;
> +	}

[ ... ]

> @@ -4197,6 +4231,73 @@ int kvm_vm_ioctl_enable_cap(struct kvm *kvm,
>  	return r;
>  }
>  
> +static int kvm_vm_ioctl_get_ever_mapped_log(struct kvm *kvm,
> +					    struct kvm_ever_mapped_log *log)
> +{
> +	u64 max_granules;
> +	unsigned int coarseness;
> +	unsigned long *bitmap;
> +	int r;
> +
> +	if (!kvm->arch.ever_mapped_bitmap)
> +		return -ENOENT;
> +
> +	if (log->flags)
> +		return -EINVAL;

[Severity: Medium]
Should we also validate that the reserved fields in the log structure are
strictly zero here?

If userspace leaves log->reserved uninitialized, stack garbage could be passed
in. Does this prevent safely repurposing these fields in the future without
breaking ABI compatibility with older binaries?

> +
> +	if (log->granule_shift < KVM_EVER_MAPPED_SHIFT || log->granule_shift >= 64)
> +		return -EINVAL;
> +
> +	max_granules = kvm->arch.ever_mapped_max_gpa >> log->granule_shift;

[Severity: Medium]
Could this lockless read of ever_mapped_max_gpa cause a TOCTOU race?

If this is called concurrently with KVM_CAP_EVER_MAPPED enablement, could the
reader observe a non-NULL ever_mapped_bitmap but a stale max_gpa of 0 due to
compiler reordering or missing memory barriers?

If so, max_granules would evaluate to 0 and the ioctl might erroneously fail
with -EINVAL.

> +	if (log->num_granules > max_granules ||
> +	    log->first_granule > max_granules - log->num_granules)
> +		return -EINVAL;
> +
> +	/*
> +	 * For the case where the request uses the same granularity as our internal
> +	 * tracking, and we are byte-aligned (the expected common case), we can
> +	 * just copy_to_user. Otherwise, we have to create a new bitmap to pass.
> +	 *
> +	 * We do not lock against concurrent vCPUs mapping new memory on any of
> +	 * these operations. Bits are set atomically and never cleared, so any race
> +	 * here is indistinguishable from a write happening after this handler
> +	 * finishes but before the caller reads the results.
> +	 */
> +	coarseness = log->granule_shift - KVM_EVER_MAPPED_SHIFT;
> +	if (coarseness == 0 && !(log->first_granule & 7) && !(log->num_granules & 7)) {
> +		if (copy_to_user(log->bitmap,
> +				 (u8 *)kvm->arch.ever_mapped_bitmap + log->first_granule / 8,
> +				 log->num_granules / 8))
> +			return -EFAULT;
> +		return 0;
> +	}
> +
> +	bitmap = kvzalloc(DIV_ROUND_UP(log->num_granules, 8), GFP_KERNEL_ACCOUNT);

[Severity: High]
Does this allocation size safely accommodate the unsigned long bit operations
that follow?

The buffer is allocated with exact byte granularity here using
kvzalloc(DIV_ROUND_UP(log->num_granules, 8), ...), but it is later populated
using __set_bit() which operates on 8-byte (unsigned long) boundaries.

If the requested size is not a multiple of 8 bytes (for example, 9 bytes for
65 granules), could __set_bit() access memory past the requested allocation
boundary and trigger a KASAN out-of-bounds splat?

> +	if (!bitmap)
> +		return -ENOMEM;
> +
> +	for (u64 gran = log->first_granule;
> +	     gran < log->first_granule + log->num_granules;
> +	     gran++) {

[Severity: High]
Can this loop monopolize the CPU and trigger a soft lockup?

If userspace calls this ioctl with unaligned parameters that bypass the
copy_to_user fast path, log->num_granules can be very large (up to 2^31 for a
52-bit physical address space and a 2MB granule).

Does iterating over a potentially massive user-controlled scale without a
cond_resched() in this loop risk keeping the CPU occupied long enough to
trigger the soft lockup watchdog?

> +		unsigned long start = gran << coarseness;
> +		unsigned long end = start + (1UL << coarseness);
> +
> +		if (find_next_bit(kvm->arch.ever_mapped_bitmap, end, start) < end)
> +			__set_bit(gran - log->first_granule, bitmap);
> +	}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260731091708.2963414-1-flosch@nutanix.com?part=1

  reply	other threads:[~2026-07-31  9:32 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-31  9:17 [PATCH] kvm: add EVER_MAPPED ioctl and capability Florian Schmidt
2026-07-31  9:32 ` sashiko-bot [this message]
2026-07-31 12:54 ` Sean Christopherson

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=20260731093212.A2DF81F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=flosch@nutanix.com \
    --cc=kvm@vger.kernel.org \
    --cc=sashiko-reviews@lists.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