Re: [PATCH v1 13/13] mshv: set up own SynIC registers on a nested root partition

[email protected] Fri, 07 Aug 2026 16:36:06 +0000
Newsgroups org.kernel.vger.linux-hyperv
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Pre-existing issues:
- [Critical] Use-after-free in `mshv_portid_lookup()` due to dereferencing an RCU-protected pointer outside the RCU read-side critical section.
- [High] Use-after-free of `mshv_vp` in `mshv_intercept_isr` and `mshv_scheduler_isr` due to premature `kfree(vp)` in `destroy_partition`.
--

--- Patch [13]: [PATCH v1 13/13] mshv: set up own SynIC registers on a nested root partition ---
commit e9472d7549df2c05d78706a10839e3ca1d79d52c
Author: Wei Hu <[email protected]>

mshv: set up own SynIC registers on a nested root partition
    
Upstream mshv_synic_cpu_init()/_exit() skip programming the SynIC
SIMP/SIEFP/SCONTROL registers when VMBus is active (hv_vmbus_exists()),
assuming VMBus's hv_hyp_synic_enable_regs() already provisioned them.

That assumption is wrong for a *nested* root partition. There, VMBus
programs the *nested* SynIC MSRs (HV_X64_MSR_NESTED_SIMP, ...) via
hv_set_msr()'s nested remap, while mshv_synic reads/writes the
*non-nested* SynIC MSRs (hv_get/set_non_nested_msr). As a result mshv
maps the wrong message page, never receives the SYNIC intercept message
that carries the async hypercall completion, and mshv_async_hvcall_handler
blocks forever -- hanging SEV-SNP guest creation from user space.

Gate the "VMBus owns the SynIC registers" optimization on !hv_nested so
that a nested root partition programs its own non-nested SynIC registers,
restoring correct intercept/async-hypercall delivery. No change for a
non-nested (bare-metal) root.

> diff --git a/drivers/hv/mshv_synic.c b/drivers/hv/mshv_synic.c
> index 7c168e5a740dd..7d714ee5d2267 100644
> --- a/drivers/hv/mshv_synic.c
> +++ b/drivers/hv/mshv_synic.c
[ ... ]
> @@ -593,7 +599,13 @@ static int mshv_synic_cpu_exit(unsigned int cpu)
>  	struct hv_synic_event_ring_page **event_ring_page =
>  		&spages->synic_event_ring_page;
>  	/* VMBus owns SIMP/SIEFP/SCONTROL when it is active */
> -	bool vmbus_active = hv_vmbus_exists();
> +	/*
> +	 * On a nested root partition VMBus programs the *nested* SynIC MSRs
> +	 * (SIMP/SIEFP/SCONTROL) via hv_set_msr()'s nested remap, whereas the
> +	 * code below reads/writes the non-nested SynIC MSRs. So when nested,
> +	 * do not defer SynIC setup to VMBus -- set up our own registers.
> +	 */
> +	bool vmbus_active = hv_vmbus_exists() && !hv_nested;
>  
>  	/* Disable the interrupt */
>  	sint.as_uint64 = hv_get_non_nested_msr(HV_MSR_SINT0 + HV_SYNIC_INTERCEPTION_SINT_INDEX);

[Severity: High]
This isn't a bug introduced by this patch, but I noticed a pre-existing
use-after-free pattern regarding mshv_vp in mshv_intercept_isr() and
mshv_scheduler_isr().

When userspace initiates partition destruction, destroy_partition() frees
virtual processors before removing the partition from the RCU-protected hash
list:

drivers/hv/mshv_root_main.c:destroy_partition() {
...
			kfree(vp);

			partition->pt_vp_array[i] = NULL;
		}
...
}

Concurrently, hypervisor messages can trigger mshv_intercept_isr() which
reads the VP pointer while holding the rcu_read_lock:

drivers/hv/mshv_synic.c:mshv_intercept_isr() {
...
	vp = partition->pt_vp_array[vp_index];
	if (unlikely(!vp)) {
		pr_debug("failed to find VP %u\n", vp_index);
		goto unlock_out;
	}

	kick_vp(vp);
...
}

Is it possible for kick_vp() to write to freed memory if the read happens
just before kfree(vp) in destroy_partition()?


[Severity: Critical]
This is a pre-existing issue, but does mshv_portid_lookup() have a
use-after-free if it drops the RCU lock before dereferencing the protected
pointer?

drivers/hv/mshv_portid_table.c:mshv_portid_lookup() {
...
	rcu_read_lock();
	_info = idr_find(&port_table_idr, port_id);
	rcu_read_unlock();

	if (_info) {
		*info = *_info;
...
}

Concurrently, mshv_portid_free() can remove the port ID, call
synchronize_rcu(), and free the memory:

drivers/hv/mshv_portid_table.c:mshv_portid_free() {
...
	idr_unlock(&port_table_idr);

	synchronize_rcu();
	kfree(info);
...
}

Since rcu_read_unlock() is called prematurely, synchronize_rcu() wouldn't
wait for the dereference.

This is concerning because mshv_doorbell_isr() uses this returned struct to
invoke a callback:

drivers/hv/mshv_synic.c:mshv_doorbell_isr() {
...
		/* Invoke the callback */
		ptinfo.hv_port_doorbell.doorbell_cb(port,
						 ptinfo.hv_port_doorbell.data);
...
}

Could an attacker trigger port freeing to race with the doorbell interrupt
and control the function pointer?

-- 
Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=13