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 2/2] Drivers: hv: vmbus: Do not remove the channel when device registration fails
Date: Mon, 02 Nov 2026 10:06:50 +0000	[thread overview]
Message-ID: <sashiko-outbox-193234@kernel.org> (raw)
In-Reply-To: <20261102094719.35760-3-izaiahsun@gmail.com>

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Critical] Drivers: hv: vmbus: channel UAF after device_del() in vmbus_device_register()
- [High] Drivers: hv: vmbus: NULL channel dereference in vmbus_match() on reg failure
--

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

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

This commit clears child_device_obj->channel before dropping the device on
the error paths of vmbus_device_register(), and updates
vmbus_device_release() to only call hv_process_channel_removal() if the
channel pointer is non-NULL, leaving channel cleanup on registration failure
to vmbus_add_channel_work().

> diff --git a/drivers/hv/vmbus_drv.c b/drivers/hv/vmbus_drv.c
> index fc0f16f512669..e8fb0c4c4f5ea 100644
> --- a/drivers/hv/vmbus_drv.c
> +++ b/drivers/hv/vmbus_drv.c

[ ... ]

> @@ -2189,6 +2195,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");
> +		child_device_obj->channel = NULL;
>  		put_device(&child_device_obj->device);
>  		return ret;
>  	}

[Severity: High]
Can clearing child_device_obj->channel here (and in err_dev_unregister below)
cause a NULL pointer dereference in vmbus_match() if a concurrent
driver_attach() is iterating over hv_bus?

Once device_register() -> device_add() -> bus_add_device() adds
child_device_obj->device.p->knode_bus to hv_bus's klist_devices, a concurrent
driver_attach() (for example from __vmbus_driver_register() or userspace
writing to new_id_store() -> vmbus_add_dynid()) can iterate over
klist_devices via bus_for_each_dev() while holding an active klist node
reference on child_device_obj->device.

If device_add() subsequently fails at its DPMError label (or if
vmbus_device_register() fails later and calls device_del() at
err_dev_unregister), bus_remove_device() calls klist_del(&dev->p->knode_bus).
Unlike klist_remove(), klist_del() does not wait for active klist_iter
readers in bus_for_each_dev() to finish before vmbus_device_register() sets
child_device_obj->channel = NULL.

On the concurrent bus_for_each_dev() thread, __driver_attach() calls
driver_match_device() -> vmbus_match() before acquiring device_lock(dev) or
checking dev->p->dead:

drivers/hv/vmbus_drv.c:vmbus_match() {
	...
	/* The hv_sock driver handles all hv_sock offers. */
	if (is_hvsock_channel(hv_dev->channel))
		return drv->hvsock;
	...
}

Unlike the sysfs attribute callbacks in drivers/hv/vmbus_drv.c which check
for !hv_dev->channel, vmbus_match() unconditionally passes hv_dev->channel to
is_hvsock_channel(), dereferencing hv_dev->channel->offermsg when
hv_dev->channel is NULL.

> @@ -2228,7 +2235,10 @@ int vmbus_device_register(struct hv_device *child_device_obj)
>  	kset_unregister(child_device_obj->channels_kset);
>  
>  err_dev_unregister:
> -	device_unregister(&child_device_obj->device);
> +	/* The driver's remove, if one was bound, still needs the channel */
> +	device_del(&child_device_obj->device);
> +	child_device_obj->channel = NULL;
> +	put_device(&child_device_obj->device);
>  	return ret;
>  }

[Severity: Critical]
When vmbus_device_register() fails after device_register() has already
probed and bound a driver, does device_del() followed by freeing newchannel
in vmbus_add_channel_work()'s err_deq_chan path leave dangling channel
pointers when the channel has not been rescinded?

There appear to be two cases where drv->remove() does not sever all
references created during drv->probe() unless the channel was rescinded:

1. For hv_sock channels, hvs_probe() -> hvs_open_connection() in
net/vmw_vsock/hyperv_transport.c sets hvs->chan = chan (or
hvs_new->chan = chan) on the vsock_sock, takes a socket reference via
sock_hold(), and inserts the socket into the vsock connected table and accept
queue. When device_del() invokes vmbus_remove() -> hvs_remove():

net/vmw_vsock/hyperv_transport.c:hvs_remove() {
	struct vmbus_channel *chan = hdev->channel;

	vmbus_close(chan);
}

hvs_remove() only closes the ring buffer because in normal operation it is
only called from hvs_destruct() -> vmbus_hvsock_device_unregister() after
hvs_close_connection() has already run on channel rescind. When
err_dev_unregister runs without a rescind, hvs_close_connection() is never
called, so the live vsock_sock retains hvs->chan pointing to newchannel
after vmbus_add_channel_work() frees it via free_channel(newchannel), leading
to a use-after-free in socket operations or in hvs_destruct() ->
vmbus_hvsock_device_unregister(chan).

2. For multichannel drivers that open sub-channels during probe,
vmbus_process_offer() links offered sub-channels onto newchannel->sc_list
with sc->primary_channel = newchannel and queues vmbus_add_channel_work()
onto handle_sub_chan_wq. When device_del() runs drv->remove() ->
vmbus_close() -> vmbus_disconnect_ring():

drivers/hv/channel.c:vmbus_disconnect_ring() {
	...
	list_for_each_entry_safe(cur_channel, tmp, &channel->sc_list, sc_list) {
		if (cur_channel->rescind)
			wait_for_completion(&cur_channel->rescind_event);

		mutex_lock(&vmbus_connection.channel_mutex);
		if (vmbus_close_internal(cur_channel) == 0) {
			vmbus_free_ring(cur_channel);

			if (cur_channel->rescind)
				hv_process_channel_removal(cur_channel);
		}
		mutex_unlock(&vmbus_connection.channel_mutex);
	}
	...
}

vmbus_disconnect_ring() only removes cur_channel if cur_channel->rescind is
true, leaving non-rescinded sub-channels on sc_list and handle_sub_chan_wq
referencing the freed primary channel.

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

      reply	other threads:[~2026-11-02 10:06 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-02  9:47 [PATCH 0/2] Drivers: hv: vmbus: Fix the error paths of channel setup Yuqiang Sun
2026-11-02  9:47 ` [PATCH 1/2] Drivers: hv: vmbus: Keep the channel when vmbus_add_channel_kobj() fails Yuqiang Sun
2026-11-02  9:47 ` [PATCH 2/2] Drivers: hv: vmbus: Do not remove the channel when device registration fails Yuqiang Sun
2026-11-02 10:06   ` 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-193234@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