Linux-HyperV List
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Emerson Busson" <emersonbusson@gmail.com>
Cc: linux-hyperv@vger.kernel.org
Subject: Re: [PATCH v2 12/14] hv: netvsc: allocate RNDIS request descriptors with kvzalloc_obj()
Date: Thu, 08 Oct 2026 19:09:36 +0000	[thread overview]
Message-ID: <sashiko-outbox-164497@kernel.org> (raw)
In-Reply-To: <20261007190752.336426-13-emersonbusson@gmail.com>

Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
- [Critical] hv_netvsc: Double-free and UAF on duplicate RNDIS response
- [Medium] selftests: rndis_request_size_premise_test fails unconditionally on architectures with >4KB pages
- [Medium] netvsc: netvsc_dma_map() leaks earlier DMA mappings when dma_map_single() fails
- [Medium] hyperv: netvsc: DMA mapping leak in RNDIS_MSG_HALT control message
- [Low] kconfig: HYPERV_NET_KUNIT_TEST cannot be enabled when driver is built as a module
--

commit 91156bed3a78e0ac932d5c6bf8e50ef88aa52fa1
Author: Emerson Busson <emersonbusson@gmail.com>

hv: netvsc: allocate RNDIS request descriptors with kvzalloc_obj()

This commit changes the allocation of RNDIS control requests from
kzalloc_obj() to kvzalloc_obj() to avoid order-2 page allocation failures
under buddy fragmentation. It resolves the vmalloc-backed descriptor into
an array of page buffers for the DMA layout and includes KUnit test cases
to cover the allocation and layout requirements.

> diff --git a/drivers/net/hyperv/Kconfig b/drivers/net/hyperv/Kconfig
> index 982964c1a9fb9..226728fd77d71 100644
> --- a/drivers/net/hyperv/Kconfig
> +++ b/drivers/net/hyperv/Kconfig
> @@ -6,3 +6,15 @@ config HYPERV_NET
>  	select NLS
>  	help
>  	  Select this option to enable the Hyper-V virtual network driver.
> +
> +config HYPERV_NET_KUNIT_TEST
> +	bool "Build Hyper-V netvsc KUnit tests"
> +	depends on HYPERV_NET && KUNIT

[Severity: Low]
Will this test be unselectable for modular builds?

Because HYPERV_NET_KUNIT_TEST is a bool depending on the tristate
HYPERV_NET, if HYPERV_NET is built as a module (=m), this boolean symbol
will be clamped to n.

Should this use depends on HYPERV_NET != n && KUNIT instead?

> +	default KUNIT_ALL_TESTS
> +	help
> +	  Build the netvsc KUnit test suites into the hv_netvsc object.

[ ... ]

> diff --git a/drivers/net/hyperv/rndis_filter.c b/drivers/net/hyperv/rndis_filter.c
> index 9b6c44979b4ea..1ce40baa2d239 100644
> --- a/drivers/net/hyperv/rndis_filter.c
> +++ b/drivers/net/hyperv/rndis_filter.c

[ ... ]

> @@ -115,8 +102,8 @@ static struct rndis_request *get_rndis_request(struct rndis_device *dev,
>  	return request;
>  }
>  
> -static void put_rndis_request(struct rndis_device *dev,
> -			    struct rndis_request *req)
> +void put_rndis_request(struct rndis_device *dev,
> +		       struct rndis_request *req)
>  {
>  	unsigned long flags;
>  
> @@ -124,7 +111,8 @@ static void put_rndis_request(struct rndis_device *dev,
>  	list_del(&req->list_ent);
>  	spin_unlock_irqrestore(&dev->request_lock, flags);
>  
> -	kfree(req);
> +	/* Paired with the kvzalloc_obj() in get_rndis_request(). */
> +	kvfree(req);
>  }

[Severity: Medium]
Does freeing the request here permanently leak its DMA mapping when the
device is halted?

When rndis_filter_halt_device() sends an RNDIS_MSG_HALT request, it gets
mapped via netvsc_dma_map(). Because it expects no response,
rndis_filter_receive_response() is never executed to unmap it. 

Additionally, the TX completion path in netvsc_send_tx_complete() skips
unmapping for control messages since skb is NULL.

When rndis_filter_halt_device() subsequently calls put_rndis_request(),
the request and its req->pkt.dma_range array are freed, but
dma_unmap_single() is never called, leaking the SWIOTLB mappings.

[Severity: Critical]
Can a duplicate response from the host cause a double-free and
use-after-free here?

When rndis_filter_receive_response() processes a response, it looks up the
request in dev->req_list but does not remove it:

rndis_filter_receive_response() {
    spin_lock_irqsave(&dev->request_lock, flags);
    list_for_each_entry(request, &dev->req_list, list_ent) {
        if (request->request_msg.msg.init_req.req_id == *req_id) {
            found = true;
            break;
        }
    }
    spin_unlock_irqrestore(&dev->request_lock, flags);
    ...
}

If the host sends a duplicate RNDIS response with the same req_id, and the
first response has already woken the waiting thread, the waiting thread
can call put_rndis_request() to remove and free the request.

The duplicate response RX thread will then use the freed request pointer
and call netvsc_dma_unmap(), which calls kfree(packet->dma_range) without
checking for NULL.

For isolated VMs (like AMD SEV-SNP or Intel TDX), could an untrusted
Hyper-V host use this to compromise the guest?

>  static void dump_rndis_message(struct net_device *netdev,

[ ... ]

> @@ -221,27 +209,78 @@ static void dump_rndis_message(struct net_device *netdev,
>  	}
>  }
>  
> +int rndis_build_page_buffers(const void *data, u32 len,
> +			     struct hv_page_buffer *page_bufs,
> +			     u32 *page_buf_cnt)

[ ... ]

>  static int rndis_filter_send_request(struct rndis_device *dev,
>  				  struct rndis_request *req)
>  {
>  	struct hv_netvsc_packet *packet;
> -	struct hv_page_buffer pb;
> +	struct hv_page_buffer page_bufs[MAX_PAGE_BUFFER_COUNT];
> +	u32 page_buf_cnt;
>  	int ret;
>  
>  	/* Setup the packet to send it */
>  	packet = &req->pkt;
>  
>  	packet->total_data_buflen = req->request_msg.msg_len;
> -	packet->page_buf_cnt = 1;
> -
> -	pb.pfn = virt_to_phys(&req->request_msg) >> HV_HYP_PAGE_SHIFT;
> -	pb.len = req->request_msg.msg_len;
> -	pb.offset = offset_in_hvpage(&req->request_msg);
> +	ret = rndis_build_page_buffers(&req->request_msg,
> +				       req->request_msg.msg_len,
> +				       page_bufs, &page_buf_cnt);
> +	if (ret)
> +		return ret;
> +	packet->page_buf_cnt = page_buf_cnt;
>  
>  	trace_rndis_send(dev->ndev, 0, &req->request_msg);
>  
>  	rcu_read_lock_bh();
> -	ret = netvsc_send(dev->ndev, packet, NULL, &pb, NULL, false);
> +	ret = netvsc_send(dev->ndev, packet, NULL, page_bufs, NULL, false);
>  	rcu_read_unlock_bh();

[Severity: Medium]
Because page_buf_cnt can now be greater than 1, does this introduce a
resource leak if DMA mapping partially fails?

Inside netvsc_dma_map(), it iterates over page_buf_cnt to map the pages:

netvsc_dma_map() {
    ...
    for (i = 0; i < page_count; i++) {
        ...
        dma = dma_map_single(&hv_dev->device, src, len, DMA_TO_DEVICE);
        if (dma_mapping_error(&hv_dev->device, dma)) {
            kfree(packet->dma_range);
            return -ENOMEM;
        }
    }
    ...
}

If dma_map_single() fails on a later iteration (when i > 0), it returns
-ENOMEM but neglects to call dma_unmap_single() for the pages 0 to i - 1
that were successfully mapped in previous loop iterations.

> diff --git a/drivers/net/hyperv/rndis_request_test.c b/drivers/net/hyperv/rndis_request_test.c
> new file mode 100644
> index 0000000000000..6affa916e74e3
> --- /dev/null
> +++ b/drivers/net/hyperv/rndis_request_test.c

[ ... ]

> +static void rndis_request_size_premise_test(struct kunit *test)
> +{
> +	KUNIT_EXPECT_GT(test, sizeof(struct rndis_request),
> +			(size_t)KMALLOC_MAX_CACHE_SIZE);

[Severity: Medium]
Will this assertion unconditionally fail on architectures with page sizes
larger than 4KB?

KMALLOC_MAX_CACHE_SIZE scales with PAGE_SIZE (typically PAGE_SIZE * 2).
On architectures like ARM64 or PPC64 configured with 64KB pages,
KMALLOC_MAX_CACHE_SIZE is at least 64 KiB.

Since sizeof(struct rndis_request) is slightly larger than 8 KiB (because
RNDIS_EXT_LEN is fixed at 4096), this check will evaluate to roughly
8272 > 65536 on those platforms, causing the test suite to spuriously
fail.

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261007190752.336426-1-emersonbusson@gmail.com?part=12

  reply	other threads:[~2026-10-08 19:09 UTC|newest]

Thread overview: 27+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-07 19:07 [PATCH v2 0/14] hv: vmbus: make rings and host-visible buffers survive buddy fragmentation Emerson Busson
2026-10-07 19:07 ` [PATCH v2 01/14] hv: vmbus: convert ring backing through the chunk allocator Emerson Busson
2026-10-07 19:07 ` [PATCH v2 02/14] hv: vmbus: validate chunk buffer allocation and cleanup Emerson Busson
2026-10-08 21:17   ` kernel test robot
2026-10-07 19:07 ` [PATCH v2 03/14] uio: hv_generic: describe buffers for owned allocation Emerson Busson
2026-10-07 19:07 ` [PATCH v2 04/14] hv: vmbus: add KUnit tests for GPADL post failure injection Emerson Busson
2026-10-07 19:07 ` [PATCH v2 05/14] hv: vmbus: add KUnit test for order-zero allocation fallback Emerson Busson
2026-10-07 19:07 ` [PATCH v2 06/14] hv: vmbus: cover all shared-page policy combinations Emerson Busson
2026-10-07 19:07 ` [PATCH v2 07/14] hv: vmbus: distinguish host rescind from local channel unload Emerson Busson
2026-10-07 19:07 ` [PATCH v2 08/14] hv: vmbus: retain backing until ownership and references clear Emerson Busson
2026-10-08 19:09   ` sashiko-bot
2026-10-07 19:07 ` [PATCH v2 09/14] hv: use owned VMBus buffers in NetVSC and UIO Emerson Busson
2026-10-08 19:09   ` sashiko-bot
2026-10-07 19:07 ` [PATCH v2 10/14] hv: vmbus: pin buffer pages across UIO mmap to close the reclaim race Emerson Busson
2026-10-08 16:49   ` kernel test robot
2026-10-08 17:51     ` Nathan Chancellor
2026-10-08 17:02   ` kernel test robot
2026-10-08 19:09   ` sashiko-bot
2026-10-07 19:07 ` [PATCH v2 11/14] hv: vmbus: vmalloc requestor metadata Emerson Busson
2026-10-08 19:09   ` sashiko-bot
2026-10-07 19:07 ` [PATCH v2 12/14] hv: netvsc: allocate RNDIS request descriptors with kvzalloc_obj() Emerson Busson
2026-10-08 19:09   ` sashiko-bot [this message]
2026-10-07 19:07 ` [PATCH v2 13/14] hv: netvsc: handle a NULL request address on empty completions Emerson Busson
2026-10-08 19:09   ` sashiko-bot
2026-10-07 19:07 ` [PATCH v2 14/14] hv: netvsc: use kvzalloc for device state Emerson Busson
2026-10-08 19:09   ` sashiko-bot
2026-10-08 16:55 ` [PATCH v2 0/14] hv: vmbus: make rings and host-visible buffers survive buddy fragmentation Easwar Hariharan

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-164497@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=emersonbusson@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