Linux-HyperV List
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Yuqiang Sun" <izaiahsun@gmail.com>
Cc: linux-hyperv@vger.kernel.org
Subject: Re: [PATCH v2 2/2] Drivers: hv: vmbus: Do not remove the channel when device registration fails
Date: Tue, 03 Nov 2026 08:37:17 +0000	[thread overview]
Message-ID: <sashiko-outbox-194840@kernel.org> (raw)
In-Reply-To: <20261103080347.90310-3-izaiahsun@gmail.com>

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [Critical] hv: vmbus: dangling channel->device_obj on vmbus_device_register() failure
- [High] hv: vmbus: sub-channel refusal in vmbus_add_channel_kobj() causes deadlock
- [High] hv: vmbus: unlocked channel dereference in vmbus_match() without RCU
- [Medium] hv: vmbus: leaked sysfs state when primary vmbus_add_channel_kobj() fails
--

commit 1c2eec91c8b747f69eceb0c4ce1f5ab28e5429b8
Author: Yuqiang Sun <izaiahsun@gmail.com>

Drivers: hv: vmbus: Do not remove the channel when device registration fails

This commit avoids calling hv_process_channel_removal() in
vmbus_device_release() when device_register() fails by clearing the
device's channel pointer beforehand, and keeps the device registered if
creating the channels kset or primary channel kobject fails.

> diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
> index fc0f16f512669..1b9bc82bebd06 100644
> --- a/drivers/hv/vmbus_drv.c
> +++ b/drivers/hv/vmbus_drv.c
> @@ -840,9 +840,14 @@ static int vmbus_match(struct device *device, const struct device_driver *driver
>  {
>  	const struct hv_driver *drv = drv_to_hv_drv(driver);
>  	struct hv_device *hv_dev = device_to_hv_device(device);
> +	struct vmbus_channel *channel = READ_ONCE(hv_dev->channel);
> +
> +	/* None if vmbus_device_register() failed */
> +	if (!channel)
> +		return 0;
>  
>  	/* The hv_sock driver handles all hv_sock offers. */
> -	if (is_hvsock_channel(hv_dev->channel))
> +	if (is_hvsock_channel(channel))
>  		return drv->hvsock;

[Severity: High]
As reported by Sashiko on v1 [1], can channel be freed before
is_hvsock_channel(channel) dereferences it here?

During device_register() -> device_add(), bus_add_device() links the
device onto the bus klist_devices before later steps in device_add()
(such as dpm_sysfs_add()) can fail. On failure, bus_remove_device() calls
klist_del(), which does not wait for a concurrent __driver_attach() (for
example via vmbus_add_dynid()) that already took a device reference in
bus_for_each_dev() and runs driver_match_device() -> vmbus_match() before
acquiring device_lock(dev).

If vmbus_match() loads hv_dev->channel with READ_ONCE() just before
vmbus_device_register() clears child_device_obj->channel to NULL, the
registering thread returns to vmbus_add_channel_work(), which runs
err_deq_chan and calls free_channel() -> kobject_put() ->
vmbus_chan_release() -> kfree_rcu(channel, rcu).

Because vmbus_match() runs in preemptible process context without
rcu_read_lock(), if the matching thread is preempted after loading
channel, could the RCU grace period complete and free channel before
is_hvsock_channel(channel) reads channel->offermsg.offer.chn_flags?

[ ... ]

> @@ -2103,6 +2114,10 @@ int vmbus_add_channel_kobj(struct hv_device *dev, struct vmbus_channel *channel)
>  	u32 relid = channel->offermsg.child_relid;
>  	int ret;
>  
> +	/* The device has no channels directory if creating it failed */
> +	if (!dev->channels_kset)
> +		return -ENOENT;
> +
>  	/*
>  	 * On failure the kobject keeps its reference: the callers' error
>  	 * paths drop it in free_channel(), which also frees the channel.

[Severity: High]
Does returning -ENOENT when !dev->channels_kset cause sub-channel setup
to deadlock or hang system suspend?

In vmbus_device_register(), device_register() runs before
kset_create_and_add("channels", ...) assigns child_device_obj->channels_kset,
and if kset_create_and_add() fails, the device now stays registered and
bound to its driver with channels_kset == NULL.

During device_register() -> device_add() -> bus_probe_device(), drivers
such as hv_storvsc and hv_netvsc request sub-channels from Hyper-V before
device_register() returns. Because vmbus_process_offer() queues
sub-channel work onto handle_sub_chan_wq concurrently with
handle_primary_chan_wq, vmbus_add_channel_work() for an offered
sub-channel can call vmbus_add_channel_kobj() either before
kset_create_and_add() sets dev->channels_kset or after
kset_create_and_add() fails:

drivers/hv/channel_mgmt.c:vmbus_add_channel_work() {
    ...
		if (vmbus_add_channel_kobj(dev, newchannel))
			goto err_deq_chan;

		if (primary_channel->sc_creation_callback != NULL)
			primary_channel->sc_creation_callback(newchannel);
    ...
}

When vmbus_add_channel_kobj() fails and jumps to err_deq_chan,
sc_creation_callback is skipped. In hv_netvsc, netvsc_subchan_work() ->
rndis_set_subchannel() holds rtnl_lock() and waits uninterruptibly for
netvsc_sc_open() to increment nvdev->open_chn for each requested
sub-channel:

drivers/net/hyperv/rndis_filter.c:rndis_set_subchannel() {
    ...
	/* wait for all sub channels to open */
	wait_event(nvdev->subchan_open,
		   atomic_read(&nvdev->open_chn) == nvdev->num_chn);
}

In addition, vmbus_process_offer() increments
vmbus_connection.nr_chan_close_on_suspend for every sub-channel and
allocates a target CPU in init_vp_index(), but err_deq_chan in
vmbus_add_channel_work() frees newchannel without decrementing
nr_chan_close_on_suspend or calling hv_clear_allocated_cpu(). Wouldn't
this leave rndis_set_subchannel() deadlocked holding rtnl_lock(), and
cause vmbus_bus_suspend() to hang forever in
wait_for_completion(&vmbus_connection.ready_for_suspend_event)?

> @@ -2189,6 +2204,7 @@ int vmbus_device_register(struct hv_device *child_device_obj)
>  	ret = device_register(&child_device_obj->device);
>  	if (ret) {
>  		pr_err("Unable to register child device\n");
> +		WRITE_ONCE(child_device_obj->channel, NULL);
>  		put_device(&child_device_obj->device);
>  		return ret;
>  	}

[Severity: Critical]
When device_register() fails here and put_device() frees
child_device_obj via vmbus_device_release(), does newchannel->device_obj
remain a dangling pointer to the freed struct hv_device?

In vmbus_add_channel_work(), newchannel->device_obj is assigned before
calling vmbus_device_register(newchannel->device_obj), and on failure it
jumps to err_deq_chan without clearing newchannel->device_obj:

drivers/hv/channel_mgmt.c:vmbus_add_channel_work() {
    ...
	ret = vmbus_device_register(newchannel->device_obj);

	if (ret != 0) {
		pr_err("unable to add child device object (relid %d)\n",
			newchannel->offermsg.child_relid);
		goto err_deq_chan;
	}
    ...
err_deq_chan:
	mutex_lock(&vmbus_connection.channel_mutex);
    ...
	WRITE_ONCE(newchannel->probe_done, true);
}

If the host rescinds the channel before err_deq_chan unmaps the relid,
vmbus_onoffer_rescind() looks up the channel via relid2channel(), waits
for channel->probe_done to become true, and then checks
channel->device_obj:

drivers/hv/channel_mgmt.c:vmbus_onoffer_rescind() {
    ...
	if (channel->device_obj) {
		if (channel->chn_rescind_callback) {
			channel->chn_rescind_callback(channel);
            ...
			return;
		}
		dev = get_device(&channel->device_obj->device);
		if (dev) {
			vmbus_device_unregister(channel->device_obj);
			put_device(dev);
		}
	}
}

Once err_deq_chan sets probe_done = true and frees newchannel, wouldn't
vmbus_onoffer_rescind() dereference the already-freed
channel->device_obj (and access channel while err_deq_chan is freeing
it)?

> @@ -2205,31 +2221,17 @@ int vmbus_device_register(struct hv_device *child_device_obj)
>  	 * here. The race can't result in a kernel failure, but the user space
>  	 * program may get an error in accessing "channels" or its
>  	 * subdirectories. See also comments with vmbus_add_dynid() about a
> -	 * related race condition.
> +	 * related race condition. For the same reason, the device stays
> +	 * registered if its channel cannot be shown in sysfs.
>  	 */
>  	child_device_obj->channels_kset = kset_create_and_add("channels",
>  							      NULL, kobj);
> -	if (!child_device_obj->channels_kset) {
> -		ret = -ENOMEM;
> -		goto err_dev_unregister;
> -	}
> -
> -	ret = vmbus_add_channel_kobj(child_device_obj,
> -				     child_device_obj->channel);
> -	if (ret) {
> +	if (!child_device_obj->channels_kset ||
> +	    vmbus_add_channel_kobj(child_device_obj, child_device_obj->channel))
>  		pr_err("Unable to register primary channel\n");

[Severity: Medium]
What happens to child_device_obj->channel->kobj and
child_device_obj->channels_kset if kset_create_and_add() succeeds,
kobject_add() inside vmbus_add_channel_kobj() succeeds, and then
sysfs_create_group() fails?

In vmbus_add_channel_kobj(), if sysfs_create_group() fails, kobj is not
removed from sysfs because the function relies on the caller's error
path to clean up the channel:

drivers/hv/vmbus_drv.c:vmbus_add_channel_kobj() {
    ...
	kobj->kset = dev->channels_kset;
	ret = kobject_add(kobj, NULL, "%u", relid);
	if (ret)
		return ret;

	ret = sysfs_create_group(kobj, &vmbus_chan_group);

	if (ret) {
		/*
		 * The calling functions' error handling paths will cleanup the
		 * empty channel directory.
		 */
		dev_err(device, "Unable to set up channel sysfs files\n");
		return ret;
	}
    ...
}

Because vmbus_device_register() no longer unwinds on
vmbus_add_channel_kobj() failure, doesn't this leave an empty
/sys/bus/vmbus/devices/<dev>/channels/<relid> directory in sysfs with
kobj->state_in_sysfs == 1 (causing vmbus_remove_channel_attr_group() to
later call sysfs_remove_group() on a group that failed creation), while
also leaving child_device_obj->channels_kset non-NULL so sub-channels
are not refused as described in the commit message?

[1] https://lore.kernel.org/r/20261102094719.35760-3-izaiahsun@gmail.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261103080347.90310-1-izaiahsun@gmail.com?part=2

      reply	other threads:[~2026-11-03  8:37 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-03  8:03 [PATCH v2 0/2] Drivers: hv: vmbus: Fix the error paths of channel setup Yuqiang Sun
2026-11-03  8:03 ` [PATCH v2 1/2] Drivers: hv: vmbus: Keep the channel when vmbus_add_channel_kobj() fails Yuqiang Sun
2026-11-03  8:03 ` [PATCH v2 2/2] Drivers: hv: vmbus: Do not remove the channel when device registration fails Yuqiang Sun
2026-11-03  8:37   ` sashiko-bot [this message]

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=sashiko-outbox-194840@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=izaiahsun@gmail.com \
    --cc=linux-hyperv@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