From: Emerson Busson <emersonbusson@gmail.com>
To: mhklinux@outlook.com
Cc: kys@microsoft.com, haiyangz@microsoft.com, wei.liu@kernel.org,
decui@microsoft.com, andrew+netdev@lunn.ch, davem@davemloft.net,
edumazet@google.com, kuba@kernel.org, pabeni@redhat.com,
gregkh@linuxfoundation.org, linux-kernel@vger.kernel.org,
linux-hyperv@vger.kernel.org, netdev@vger.kernel.org
Subject: [PATCH v2 11/14] hv: vmbus: vmalloc requestor metadata
Date: Wed, 7 Oct 2026 16:07:49 -0300 [thread overview]
Message-ID: <20261007190752.336426-12-emersonbusson@gmail.com> (raw)
In-Reply-To: <20261007190752.336426-1-emersonbusson@gmail.com>
Channel opens allocate request-ID arrays from the ring size. With the
128-page default ring, kvcalloc() can request an order-7 allocation. The
bitmap created by bitmap_zalloc() is another physically contiguous
allocation; at normal NetVSC sizes it can require order-1 pages. Either
allocation can fail under buddy fragmentation while order-0 pages remain
available.
Always allocate both guest-private structures with vzalloc(). Reject a
zero requestor count and check byte-size calculations before allocating,
then pair the allocations with vfree() on rollback and teardown. Neither
structure is exposed to the host, so neither needs to be decrypted for
Confidential Computing.
Extend KUnit coverage to require vmalloc backing for a typical 8 KiB
requestor array, a bitmap larger than one page, and an array above
KMALLOC_MAX_SIZE. Existing requestor cases continue to cover ID lifecycle
and cleanup.
Rollback trigger: revert if requestor metadata is exposed to the host, if
request-ID allocation or teardown regresses on a healthy channel open, or
if hyperv-vmbus-buffer KUnit fails.
Signed-off-by: Emerson Busson <emersonbusson@gmail.com>
---
drivers/hv/channel.c | 39 ++++++--
drivers/hv/hyperv_vmbus.h | 11 +++
drivers/hv/vmbus_buffer_test.c | 160 +++++++++++++++++++++++++++++++++
3 files changed, 201 insertions(+), 9 deletions(-)
diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 2793ca1b7f32..c7f3f5c6c0d2 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -1411,14 +1411,26 @@ EXPORT_SYMBOL_GPL(vmbus_alloc_buffer);
* keeps track of the next available slot in the array. Initially, each
* slot points to the next one (as in a Linked List). The last slot
* does not point to anything, so its value is U64_MAX by default.
+ *
+ * Allocated with vzalloc() rather than kvcalloc(). kvcalloc() can use a
+ * higher-order kmalloc allocation for arrays up to KMALLOC_MAX_SIZE, so
+ * both small requestor arrays and the default 128-page ring array may need
+ * contiguous pages while opening a channel under buddy fragmentation. The
+ * array is guest-private request bookkeeping -- the host never sees the
+ * slot values -- so a vmalloc-backed mapping carries no Confidential
+ * Computing implication and needs no set_memory_decrypted().
* @size: The size of the array
*/
-static u64 *request_arr_init(u32 size)
+u64 *request_arr_init(u32 size)
{
- int i;
+ size_t bytes;
+ u32 i;
u64 *req_arr;
- req_arr = kcalloc(size, sizeof(u64), GFP_KERNEL);
+ if (!size || check_mul_overflow((size_t)size, sizeof(*req_arr), &bytes))
+ return NULL;
+
+ req_arr = vzalloc(bytes);
if (!req_arr)
return NULL;
@@ -1436,8 +1448,9 @@ static u64 *request_arr_init(u32 size)
* Index 0 is the first free slot
* @size: Size of the requestor array
*/
-static int vmbus_alloc_requestor(struct vmbus_requestor *rqstor, u32 size)
+int vmbus_alloc_requestor(struct vmbus_requestor *rqstor, u32 size)
{
+ size_t bitmap_longs, bitmap_bytes;
u64 *rqst_arr;
unsigned long *bitmap;
@@ -1445,9 +1458,17 @@ static int vmbus_alloc_requestor(struct vmbus_requestor *rqstor, u32 size)
if (!rqst_arr)
return -ENOMEM;
- bitmap = bitmap_zalloc(size, GFP_KERNEL);
+ bitmap_longs = size / BITS_PER_LONG;
+ if (size % BITS_PER_LONG)
+ bitmap_longs++;
+ if (check_mul_overflow(bitmap_longs, sizeof(*bitmap), &bitmap_bytes)) {
+ vfree(rqst_arr);
+ return -ENOMEM;
+ }
+
+ bitmap = vzalloc(bitmap_bytes);
if (!bitmap) {
- kfree(rqst_arr);
+ vfree(rqst_arr);
return -ENOMEM;
}
@@ -1464,10 +1485,10 @@ static int vmbus_alloc_requestor(struct vmbus_requestor *rqstor, u32 size)
* vmbus_free_requestor - Frees memory allocated for @rqstor
* @rqstor: Pointer to the requestor struct
*/
-static void vmbus_free_requestor(struct vmbus_requestor *rqstor)
+void vmbus_free_requestor(struct vmbus_requestor *rqstor)
{
- kfree(rqstor->req_arr);
- bitmap_free(rqstor->req_bitmap);
+ vfree(rqstor->req_arr);
+ vfree(rqstor->req_bitmap);
}
static int __vmbus_open(struct vmbus_channel *newchannel,
diff --git a/drivers/hv/hyperv_vmbus.h b/drivers/hv/hyperv_vmbus.h
index 2edeb7988bdc..9819a6b686ce 100644
--- a/drivers/hv/hyperv_vmbus.h
+++ b/drivers/hv/hyperv_vmbus.h
@@ -648,4 +648,15 @@ int vmbus_gpadl_teardown_request(struct vmbus_channel *channel,
void vmbus_complete_gpadl_teardown(struct vmbus_connection *connection,
struct vmbus_channel_gpadl_torndown *response);
+/*
+ * Requestor array lifetime helpers, shared with vmbus_buffer_test.c for
+ * the same reason as the sizing helpers. Defined in channel.c, unexported.
+ * request_arr_init() returns a vmalloc-backed array the caller must vfree();
+ * vmbus_alloc_requestor() owns both vmalloc-backed objects on success, and
+ * vmbus_free_requestor() releases them.
+ */
+u64 *request_arr_init(u32 size);
+int vmbus_alloc_requestor(struct vmbus_requestor *rqstor, u32 size);
+void vmbus_free_requestor(struct vmbus_requestor *rqstor);
+
#endif /* _HYPERV_VMBUS_H */
diff --git a/drivers/hv/vmbus_buffer_test.c b/drivers/hv/vmbus_buffer_test.c
index 5c8e70d861ad..d0102dabef73 100644
--- a/drivers/hv/vmbus_buffer_test.c
+++ b/drivers/hv/vmbus_buffer_test.c
@@ -10,6 +10,7 @@
#include <linux/completion.h>
#include <linux/hyperv.h>
#include <linux/mm.h>
+#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
@@ -776,6 +777,160 @@ static void vmbus_buffer_order_zero_allocation_test(struct kunit *test)
KUNIT_EXPECT_EQ(test, context.attempts, (unsigned int)MAX_PAGE_ORDER + 1);
}
+/*
+ * Requestor metadata must use vmalloc backing because both the array and its
+ * bitmap can require multiple pages. The requestor is guest-private
+ * bookkeeping: the host never sees the slot values, so this mapping has no
+ * Confidential Computing implication and needs no set_memory_decrypted().
+ * Cover a typical 8 KiB array, a requestor whose bitmap exceeds one page,
+ * and a size beyond KMALLOC_MAX_SIZE without memory pressure.
+ */
+static void vmbus_requestor_alloc_free_test(struct kunit *test)
+{
+ struct vmbus_requestor rqstor = {};
+
+ KUNIT_ASSERT_EQ(test, vmbus_alloc_requestor(&rqstor, 4), 0);
+ KUNIT_EXPECT_EQ(test, rqstor.size, 4U);
+ KUNIT_EXPECT_EQ(test, rqstor.next_request_id, 0U);
+ KUNIT_EXPECT_NOT_NULL(test, rqstor.req_arr);
+ KUNIT_EXPECT_NOT_NULL(test, rqstor.req_bitmap);
+
+ /* The free list links 0->1->2->3 and terminates in U64_MAX. */
+ KUNIT_EXPECT_EQ(test, rqstor.req_arr[0], 1U);
+ KUNIT_EXPECT_EQ(test, rqstor.req_arr[1], 2U);
+ KUNIT_EXPECT_EQ(test, rqstor.req_arr[2], 3U);
+ KUNIT_EXPECT_EQ(test, rqstor.req_arr[3], U64_MAX);
+
+ vmbus_free_requestor(&rqstor);
+}
+
+static void vmbus_requestor_vmalloc_backing_test(struct kunit *test)
+{
+ /*
+ * Cover the small array, a requestor whose bitmap exceeds one page,
+ * and a size beyond KMALLOC_MAX_SIZE. All requestor metadata must use
+ * vmalloc backing regardless of size or allocator pressure.
+ */
+ u32 small_size = 1024;
+ u32 bitmap_size = (PAGE_SIZE / sizeof(unsigned long)) * BITS_PER_LONG + 1;
+ u32 size = (KMALLOC_MAX_SIZE / sizeof(u64)) + 1;
+ struct vmbus_requestor rqstor = {};
+ u64 *req_arr;
+
+ KUNIT_EXPECT_PTR_EQ(test, request_arr_init(0), NULL);
+
+ req_arr = request_arr_init(small_size);
+ KUNIT_ASSERT_NOT_NULL(test, req_arr);
+ KUNIT_EXPECT_TRUE(test, is_vmalloc_addr(req_arr));
+ KUNIT_EXPECT_EQ(test, req_arr[0], 1U);
+ KUNIT_EXPECT_EQ(test, req_arr[small_size - 1], U64_MAX);
+ vfree(req_arr);
+
+ req_arr = request_arr_init(size);
+ KUNIT_ASSERT_NOT_NULL(test, req_arr);
+ KUNIT_EXPECT_TRUE(test, is_vmalloc_addr(req_arr));
+ KUNIT_EXPECT_EQ(test, req_arr[0], 1U);
+ KUNIT_EXPECT_EQ(test, req_arr[size - 1], U64_MAX);
+ vfree(req_arr);
+
+ KUNIT_ASSERT_EQ(test, vmbus_alloc_requestor(&rqstor, bitmap_size), 0);
+ KUNIT_EXPECT_TRUE(test, is_vmalloc_addr(rqstor.req_arr));
+ KUNIT_EXPECT_TRUE(test, is_vmalloc_addr(rqstor.req_bitmap));
+ vmbus_free_requestor(&rqstor);
+}
+
+static void vmbus_requestor_id_lifecycle_test(struct kunit *test)
+{
+ struct vmbus_channel channel = { .rqstor_size = 4 };
+ struct vmbus_requestor *rqstor = &channel.requestor;
+ u64 id0, id1, id2, id3, addr;
+
+ KUNIT_ASSERT_EQ(test, vmbus_alloc_requestor(rqstor, 4), 0);
+
+ /* IDs are 1-based; 0 is reserved for unsolicited host messages. */
+ id0 = vmbus_next_request_id(&channel, 0x1000);
+ KUNIT_EXPECT_EQ(test, id0, 1U);
+ id1 = vmbus_next_request_id(&channel, 0x2000);
+ KUNIT_EXPECT_EQ(test, id1, 2U);
+
+ /* Consumption returns the registered address and frees the slot. */
+ addr = vmbus_request_addr_match(&channel, id0, VMBUS_RQST_ADDR_ANY);
+ KUNIT_EXPECT_EQ(test, addr, 0x1000U);
+ addr = vmbus_request_addr_match(&channel, id1, 0x2000);
+ KUNIT_EXPECT_EQ(test, addr, 0x2000U);
+
+ /*
+ * Consumed slots are reusable. The free list is LIFO -- each
+ * consume pushes its slot onto the head -- so the slot freed last
+ * is the one handed out first. Freeing id0 then id1 leaves slot 1
+ * at the head, and the next ID is therefore id1 again, not id0.
+ */
+ id2 = vmbus_next_request_id(&channel, 0x3000);
+ KUNIT_EXPECT_EQ(test, id2, id1);
+ id3 = vmbus_next_request_id(&channel, 0x4000);
+ KUNIT_EXPECT_EQ(test, id3, id0);
+
+ vmbus_free_requestor(rqstor);
+}
+
+static void vmbus_requestor_invalid_ids_test(struct kunit *test)
+{
+ struct vmbus_channel channel = { .rqstor_size = 4 };
+ struct vmbus_requestor *rqstor = &channel.requestor;
+ u64 id, addr;
+
+ KUNIT_ASSERT_EQ(test, vmbus_alloc_requestor(rqstor, 4), 0);
+
+ /* ID 0 is the unsolicited-message sentinel and is never in the set. */
+ KUNIT_EXPECT_EQ(test, vmbus_request_addr_match(&channel, 0, 0),
+ VMBUS_RQST_ERROR);
+
+ /* Out-of-range IDs are refused, not wrapped. */
+ KUNIT_EXPECT_EQ(test, vmbus_request_addr_match(&channel, 5, 0),
+ VMBUS_RQST_ERROR);
+ KUNIT_EXPECT_EQ(test, vmbus_request_addr_match(&channel, U64_MAX, 0),
+ VMBUS_RQST_ERROR);
+
+ id = vmbus_next_request_id(&channel, 0xABCD);
+ KUNIT_ASSERT_EQ(test, id, 1U);
+
+ /* A wrong expected address does not consume the slot. */
+ addr = vmbus_request_addr_match(&channel, id, 0x9999);
+ KUNIT_EXPECT_EQ(test, addr, 0xABCDU);
+
+ /* The slot is still live and a matching lookup consumes it once. */
+ addr = vmbus_request_addr_match(&channel, id, 0xABCD);
+ KUNIT_EXPECT_EQ(test, addr, 0xABCDU);
+
+ /* Replay: the slot is gone. */
+ addr = vmbus_request_addr_match(&channel, id, VMBUS_RQST_ADDR_ANY);
+ KUNIT_EXPECT_EQ(test, addr, VMBUS_RQST_ERROR);
+
+ vmbus_free_requestor(rqstor);
+}
+
+static void vmbus_requestor_exhaustion_test(struct kunit *test)
+{
+ struct vmbus_channel channel = { .rqstor_size = 2 };
+ struct vmbus_requestor *rqstor = &channel.requestor;
+
+ KUNIT_ASSERT_EQ(test, vmbus_alloc_requestor(rqstor, 2), 0);
+
+ KUNIT_EXPECT_EQ(test, vmbus_next_request_id(&channel, 0x1), 1U);
+ KUNIT_EXPECT_EQ(test, vmbus_next_request_id(&channel, 0x2), 2U);
+
+ /* Both slots taken: the free list is empty and the API says so. */
+ KUNIT_EXPECT_EQ(test, vmbus_next_request_id(&channel, 0x3),
+ VMBUS_RQST_ERROR);
+
+ /* An uninitialized requestor is not a full one. */
+ channel.rqstor_size = 0;
+ KUNIT_EXPECT_EQ(test, vmbus_next_request_id(&channel, 0x4),
+ VMBUS_NO_RQSTOR);
+
+ vmbus_free_requestor(rqstor);
+}
+
static struct kunit_case vmbus_buffer_test_cases[] = {
KUNIT_CASE(vmbus_buffer_size_rounding_test),
KUNIT_CASE(vmbus_buffer_size_overflow_test),
@@ -805,6 +960,11 @@ static struct kunit_case vmbus_buffer_test_cases[] = {
KUNIT_CASE(vmbus_gpadl_post_success_test),
KUNIT_CASE(vmbus_gpadl_response_state_test),
KUNIT_CASE(vmbus_gpadl_teardown_post_failure_test),
+ KUNIT_CASE(vmbus_requestor_alloc_free_test),
+ KUNIT_CASE(vmbus_requestor_vmalloc_backing_test),
+ KUNIT_CASE(vmbus_requestor_id_lifecycle_test),
+ KUNIT_CASE(vmbus_requestor_invalid_ids_test),
+ KUNIT_CASE(vmbus_requestor_exhaustion_test),
{}
};
--
2.43.0
next prev parent reply other threads:[~2026-10-07 19:09 UTC|newest]
Thread overview: 15+ 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-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-07 19:07 ` [PATCH v2 09/14] hv: use owned VMBus buffers in NetVSC and UIO Emerson Busson
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-07 19:07 ` Emerson Busson [this message]
2026-10-07 19:07 ` [PATCH v2 12/14] hv: netvsc: allocate RNDIS request descriptors with kvzalloc_obj() Emerson Busson
2026-10-07 19:07 ` [PATCH v2 13/14] hv: netvsc: handle a NULL request address on empty completions Emerson Busson
2026-10-07 19:07 ` [PATCH v2 14/14] hv: netvsc: use kvzalloc for device state Emerson Busson
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=20261007190752.336426-12-emersonbusson@gmail.com \
--to=emersonbusson@gmail.com \
--cc=andrew+netdev@lunn.ch \
--cc=davem@davemloft.net \
--cc=decui@microsoft.com \
--cc=edumazet@google.com \
--cc=gregkh@linuxfoundation.org \
--cc=haiyangz@microsoft.com \
--cc=kuba@kernel.org \
--cc=kys@microsoft.com \
--cc=linux-hyperv@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=mhklinux@outlook.com \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=wei.liu@kernel.org \
/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
all inboxes | Powered by JetHome®