| Seq | Timestamp | Type | Name | Duration |
|---|---|---|---|---|
| 0/0 | 2026/07/22 14:54 | flow | patch-triage |
1mArgs:null Results: {
"EnableConfigs": [
"HYPERV",
"HYPERV_NET"
],
"FocusSymbols": [
"vmbus_establish_gpadl_caller_decrypted",
"vmbus_establish_gpadl_and_decrypt",
"netvsc_alloc_buf_pages",
"netvsc_free_buf_pages",
"netvsc_init_buf",
"__vmbus_establish_gpadl"
],
"Reasoning": "The patch modifies the Hyper-V network driver (netvsc) and VMBus channel code to manage the encryption lifecycle of GPADL buffers, particularly for Confidential VMs. It introduces new functions to allocate/free buffers and establish GPADLs. These are functional changes to reachable driver logic.",
"WorthFuzzing": true
} |
| 1/1 | 2026/07/22 14:54 | action | read-patch-diff |
0mArgs:null Results: {
"PatchDiff": "commit 04b5fd86971f22061c09c88db5ecf759eb22cbd0\nAuthor: syz-cluster \u003ctriage@syzkaller.com\u003e\nDate: Wed Jul 22 14:54:06 2026 +0000\n\n syz-cluster: applied patch under review\n\ndiff --git a/drivers/hv/channel.c b/drivers/hv/channel.c\nindex 6821f225248b19..0166367a4df378 100644\n--- a/drivers/hv/channel.c\n+++ b/drivers/hv/channel.c\n@@ -426,7 +426,13 @@ static void vmbus_free_channel_msginfo(struct vmbus_channel_msginfo *msginfo)\n }\n \n /*\n- * __vmbus_establish_gpadl - Establish a GPADL for a buffer or ringbuffer\n+ * __vmbus_establish_gpadl - Establish a GPADL for a buffer or ringbuffer.\n+ *\n+ * This function only handles the GPADL handshake with the host. The caller\n+ * is responsible for ensuring that @kbuffer is in the encryption state the\n+ * host expects (host-visible / \"decrypted\" for confidential VMs); this\n+ * function never calls set_memory_decrypted() or set_memory_encrypted().\n+ * It also leaves gpadl-\u003edecrypted untouched, so the caller must set it.\n *\n * @channel: a channel\n * @type: the type of the corresponding GPADL, only meaningful for the guest.\n@@ -434,7 +440,7 @@ static void vmbus_free_channel_msginfo(struct vmbus_channel_msginfo *msginfo)\n * @size: page-size multiple\n * @send_offset: the offset (in bytes) where the send ring buffer starts,\n * should be 0 for BUFFER type gpadl\n- * @gpadl_handle: some funky thing\n+ * @gpadl: out parameter receiving the established GPADL handle/buffer/size\n */\n static int __vmbus_establish_gpadl(struct vmbus_channel *channel,\n \t\t\t\t enum hv_gpadl_type type, void *kbuffer,\n@@ -454,30 +460,8 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,\n \t\t(atomic_inc_return(\u0026vmbus_connection.next_gpadl_handle) - 1);\n \n \tret = create_gpadl_header(type, kbuffer, size, send_offset, \u0026msginfo);\n-\tif (ret) {\n-\t\tgpadl-\u003edecrypted = false;\n+\tif (ret)\n \t\treturn ret;\n-\t}\n-\n-\tgpadl-\u003edecrypted = !((channel-\u003eco_external_memory \u0026\u0026 type == HV_GPADL_BUFFER) ||\n-\t\t(channel-\u003eco_ring_buffer \u0026\u0026 type == HV_GPADL_RING));\n-\tif (gpadl-\u003edecrypted) {\n-\t\t/*\n-\t\t * The \"decrypted\" flag being true assumes that set_memory_decrypted() succeeds.\n-\t\t * But if it fails, the encryption state of the memory is unknown. In that case,\n-\t\t * leave \"decrypted\" as true to ensure the memory is leaked instead of going back\n-\t\t * on the free list.\n-\t\t */\n-\t\tret = set_memory_decrypted((unsigned long)kbuffer,\n-\t\t\t\t\tPFN_UP(size));\n-\t\tif (ret) {\n-\t\t\tdev_warn(\u0026channel-\u003edevice_obj-\u003edevice,\n-\t\t\t\t\"Failed to set host visibility for new GPADL %d.\\n\",\n-\t\t\t\tret);\n-\t\t\tvmbus_free_channel_msginfo(msginfo);\n-\t\t\treturn ret;\n-\t\t}\n-\t}\n \n \tinit_completion(\u0026msginfo-\u003ewaitevent);\n \tmsginfo-\u003ewaiting_channel = channel;\n@@ -553,19 +537,47 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,\n \tspin_unlock_irqrestore(\u0026vmbus_connection.channelmsg_lock, flags);\n \n \tvmbus_free_channel_msginfo(msginfo);\n+\treturn ret;\n+}\n \n-\tif (ret) {\n-\t\t/*\n-\t\t * If set_memory_encrypted() fails, the decrypted flag is\n-\t\t * left as true so the memory is leaked instead of being\n-\t\t * put back on the free list.\n-\t\t */\n-\t\tif (gpadl-\u003edecrypted) {\n-\t\t\tif (!set_memory_encrypted((unsigned long)kbuffer, PFN_UP(size)))\n-\t\t\t\tgpadl-\u003edecrypted = false;\n+/*\n+ * vmbus_establish_gpadl_and_decrypt - Manage the encryption lifecycle of\n+ * @kbuffer around a __vmbus_establish_gpadl() call.\n+ *\n+ * Decrypts @kbuffer (making it host-visible) unless the channel was created\n+ * with confidential memory for @type, then establishes the GPADL. On\n+ * establish failure the buffer is re-encrypted so the caller can free it.\n+ *\n+ * Sets gpadl-\u003edecrypted; see the comment on struct vmbus_gpadl::decrypted.\n+ */\n+static int vmbus_establish_gpadl_and_decrypt(struct vmbus_channel *channel,\n+\t\t\t\t\t enum hv_gpadl_type type,\n+\t\t\t\t\t void *kbuffer, u32 size,\n+\t\t\t\t\t u32 send_offset,\n+\t\t\t\t\t struct vmbus_gpadl *gpadl)\n+{\n+\tbool decrypt = !((channel-\u003eco_external_memory \u0026\u0026 type == HV_GPADL_BUFFER) ||\n+\t\t\t (channel-\u003eco_ring_buffer \u0026\u0026 type == HV_GPADL_RING));\n+\tint ret;\n+\n+\tif (decrypt) {\n+\t\tret = set_memory_decrypted((unsigned long)kbuffer, PFN_UP(size));\n+\t\tif (ret) {\n+\t\t\tdev_warn(\u0026channel-\u003edevice_obj-\u003edevice,\n+\t\t\t\t \"Failed to set host visibility for new GPADL %d.\\n\",\n+\t\t\t\t ret);\n+\t\t\t/* Encryption state unknown; signal caller to leak. */\n+\t\t\tgpadl-\u003edecrypted = true;\n+\t\t\treturn ret;\n \t\t}\n \t}\n \n+\tgpadl-\u003edecrypted = decrypt;\n+\tret = __vmbus_establish_gpadl(channel, type, kbuffer, size, send_offset,\n+\t\t\t\t gpadl);\n+\tif (ret \u0026\u0026 decrypt \u0026\u0026\n+\t !set_memory_encrypted((unsigned long)kbuffer, PFN_UP(size)))\n+\t\tgpadl-\u003edecrypted = false;\n \treturn ret;\n }\n \n@@ -580,11 +592,40 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,\n int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,\n \t\t\t u32 size, struct vmbus_gpadl *gpadl)\n {\n-\treturn __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER, kbuffer, size,\n-\t\t\t\t 0U, gpadl);\n+\treturn vmbus_establish_gpadl_and_decrypt(channel, HV_GPADL_BUFFER,\n+\t\t\t\t\t\t kbuffer, size, 0U, gpadl);\n }\n EXPORT_SYMBOL_GPL(vmbus_establish_gpadl);\n \n+/*\n+ * vmbus_establish_gpadl_caller_decrypted - Establish a GPADL for a buffer\n+ * whose encryption state is managed by the caller.\n+ *\n+ * @channel: a channel\n+ * @kbuffer: a buffer that the caller has already transitioned to host-visible\n+ * (decrypted) via set_memory_decrypted() or an equivalent mechanism.\n+ * @size: page-size multiple\n+ * @gpadl: out parameter receiving the established GPADL\n+ *\n+ * Unlike vmbus_establish_gpadl(), this function does not call\n+ * set_memory_decrypted() on @kbuffer, and the matching vmbus_teardown_gpadl()\n+ * call will not call set_memory_encrypted() on it. The caller is responsible\n+ * for the full encryption lifecycle of @kbuffer; on return gpadl-\u003edecrypted\n+ * is set to false to record that vmbus does not own the encryption state.\n+ */\n+int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,\n+\t\t\t\t\t void *kbuffer, u32 size,\n+\t\t\t\t\t struct vmbus_gpadl *gpadl)\n+{\n+\tint ret = __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER, kbuffer,\n+\t\t\t\t\t size, 0U, gpadl);\n+\n+\t/* Caller owns @kbuffer's encryption; teardown must not touch it. */\n+\tgpadl-\u003edecrypted = false;\n+\treturn ret;\n+}\n+EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted);\n+\n /**\n * request_arr_init - Allocates memory for the requestor array. Each slot\n * keeps track of the next available slot in the array. Initially, each\n@@ -685,11 +726,11 @@ static int __vmbus_open(struct vmbus_channel *newchannel,\n \t/* Establish the gpadl for the ring buffer */\n \tnewchannel-\u003eringbuffer_gpadlhandle.gpadl_handle = 0;\n \n-\terr = __vmbus_establish_gpadl(newchannel, HV_GPADL_RING,\n-\t\t\t\t page_address(newchannel-\u003eringbuffer_page),\n-\t\t\t\t (send_pages + recv_pages) \u003c\u003c PAGE_SHIFT,\n-\t\t\t\t newchannel-\u003eringbuffer_send_offset \u003c\u003c PAGE_SHIFT,\n-\t\t\t\t \u0026newchannel-\u003eringbuffer_gpadlhandle);\n+\terr = vmbus_establish_gpadl_and_decrypt(newchannel, HV_GPADL_RING,\n+\t\t\t\t\t\tpage_address(newchannel-\u003eringbuffer_page),\n+\t\t\t\t\t\t(send_pages + recv_pages) \u003c\u003c PAGE_SHIFT,\n+\t\t\t\t\t\tnewchannel-\u003eringbuffer_send_offset \u003c\u003c PAGE_SHIFT,\n+\t\t\t\t\t\t\u0026newchannel-\u003eringbuffer_gpadlhandle);\n \tif (err)\n \t\tgoto error_clean_ring;\n \ndiff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h\nindex 7397c693f984af..b0c4cb0f7a4ce4 100644\n--- a/drivers/net/hyperv/hyperv_net.h\n+++ b/drivers/net/hyperv/hyperv_net.h\n@@ -220,6 +220,8 @@ struct net_device_context;\n \n extern u32 netvsc_ring_bytes;\n \n+int netvsc_workqueue_init(void);\n+void netvsc_workqueue_destroy(void);\n struct netvsc_device *netvsc_device_add(struct hv_device *device,\n \t\t\t\t\tconst struct netvsc_device_info *info);\n int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx);\n@@ -1147,6 +1149,16 @@ struct netvsc_channel {\n \tstruct netvsc_stats_rx rx_stats;\n };\n \n+/* A physically-contiguous chunk of netvsc buffer.\n+ *\n+ * The chunk list is preserved so each chunk can be individually freed at\n+ * teardown.\n+ */\n+struct netvsc_buf_chunk {\n+\tstruct page *page;\n+\tunsigned int order;\n+};\n+\n /* Per netvsc device */\n struct netvsc_device {\n \tu32 nvsp_version;\n@@ -1158,6 +1170,8 @@ struct netvsc_device {\n \t/* Receive buffer allocated by us but manages by NetVSP */\n \tvoid *recv_buf;\n \tu32 recv_buf_size; /* allocated bytes */\n+\tstruct netvsc_buf_chunk *recv_buf_chunks;\n+\tu32 recv_buf_chunk_cnt;\n \tstruct vmbus_gpadl recv_buf_gpadl_handle;\n \tu32 recv_section_cnt;\n \tu32 recv_section_size;\n@@ -1166,6 +1180,8 @@ struct netvsc_device {\n \t/* Send buffer allocated by us */\n \tvoid *send_buf;\n \tu32 send_buf_size;\n+\tstruct netvsc_buf_chunk *send_buf_chunks;\n+\tu32 send_buf_chunk_cnt;\n \tstruct vmbus_gpadl send_buf_gpadl_handle;\n \tu32 send_section_cnt;\n \tu32 send_section_size;\n@@ -1193,7 +1209,7 @@ struct netvsc_device {\n \n \tstruct netvsc_channel chan_table[VRSS_CHANNEL_MAX];\n \n-\tstruct rcu_head rcu;\n+\tstruct rcu_work rwork;\n };\n \n /* NdisInitialize message */\ndiff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c\nindex 59e95341f9b1e5..2505d10e220102 100644\n--- a/drivers/net/hyperv/netvsc.c\n+++ b/drivers/net/hyperv/netvsc.c\n@@ -14,6 +14,8 @@\n #include \u003clinux/mm.h\u003e\n #include \u003clinux/delay.h\u003e\n #include \u003clinux/io.h\u003e\n+#include \u003clinux/log2.h\u003e\n+#include \u003clinux/set_memory.h\u003e\n #include \u003clinux/slab.h\u003e\n #include \u003clinux/netdevice.h\u003e\n #include \u003clinux/if_ether.h\u003e\n@@ -28,6 +30,8 @@\n #include \"hyperv_net.h\"\n #include \"netvsc_trace.h\"\n \n+static struct workqueue_struct *netvsc_wq;\n+\n /*\n * Switch the data path from the synthetic interface to the VF\n * interface.\n@@ -125,40 +129,163 @@ static void netvsc_subchan_work(struct work_struct *w)\n \trtnl_unlock();\n }\n \n-static struct netvsc_device *alloc_net_device(void)\n+/*\n+ * netvsc_free_buf_pages - release a netvsc send/receive buffer.\n+ *\n+ * @addr: buffer address, or NULL if none was allocated (e.g. cleanup from a\n+ * failed allocation)\n+ * @chunks: chunks array from netvsc_alloc_buf_pages(), or NULL\n+ * @chunk_cnt: number of entries in @chunks\n+ *\n+ * When @chunks is NULL the buffer is a plain vzalloc() allocation.\n+ *\n+ * Otherwise tear down the vmap, and for each chunk re-encrypt and free\n+ * the underlying pages. Any chunk that cannot be re-encrypted is leaked.\n+ */\n+static void netvsc_free_buf_pages(void *addr,\n+\t\t\t\t struct netvsc_buf_chunk *chunks,\n+\t\t\t\t u32 chunk_cnt)\n {\n-\tstruct netvsc_device *net_device;\n+\tu32 i;\n \n-\tnet_device = kzalloc_obj(struct netvsc_device);\n-\tif (!net_device)\n+\tif (!chunks) {\n+\t\tvfree(addr);\n+\t\treturn;\n+\t}\n+\n+\tvunmap(addr);\n+\n+\tfor (i = 0; i \u003c chunk_cnt; i++) {\n+\t\tunsigned long vaddr =\n+\t\t\t(unsigned long)page_address(chunks[i].page);\n+\t\tunsigned int order = chunks[i].order;\n+\n+\t\tif (set_memory_encrypted(vaddr, 1U \u003c\u003c order))\n+\t\t\tcontinue;\n+\t\t__free_pages(chunks[i].page, order);\n+\t}\n+\n+\tkvfree(chunks);\n+}\n+\n+/*\n+ * netvsc_alloc_buf_pages - allocate a virtually-contiguous, host-visible\n+ * buffer for the netvsc send/receive area.\n+ *\n+ * @node: NUMA node hint (NUMA_NO_NODE for any node)\n+ * @size: requested buffer size in bytes (rounded up to PAGE_SIZE)\n+ * @chunks_out: on success, set to the array of underlying chunks\n+ * @chunk_cnt_out: on success, set to the number of chunks\n+ *\n+ * Allocates the buffer as a series of physically-contiguous chunks,\n+ * starting at MAX_PAGE_ORDER and falling back to smaller orders on\n+ * allocation failure. Each chunk is transitioned to host-visible via\n+ * set_memory_decrypted() on its direct-map address, then all chunks are\n+ * combined into a virtually-contiguous range via vmap().\n+ *\n+ * Return: the vmap()ed virtual address, or NULL on failure.\n+ */\n+static void *netvsc_alloc_buf_pages(int node, u32 size,\n+\t\t\t\t struct netvsc_buf_chunk **chunks_out,\n+\t\t\t\t u32 *chunk_cnt_out)\n+{\n+\tu32 nr_pages = PFN_UP(size);\n+\tstruct netvsc_buf_chunk *chunks = NULL;\n+\tstruct page **pages = NULL;\n+\tunsigned int order;\n+\tu32 chunk_cnt = 0;\n+\tu32 page_idx = 0;\n+\tu32 remaining = nr_pages;\n+\tvoid *addr;\n+\tu32 i;\n+\tint ret;\n+\n+\t*chunks_out = NULL;\n+\t*chunk_cnt_out = 0;\n+\n+\tif (!nr_pages)\n \t\treturn NULL;\n \n-\tinit_waitqueue_head(\u0026net_device-\u003ewait_drain);\n-\tnet_device-\u003edestroy = false;\n-\tnet_device-\u003etx_disable = true;\n+\t/* Worst case: every chunk is a single page. */\n+\tchunks = kvmalloc_array(nr_pages, sizeof(*chunks),\n+\t\t\t\tGFP_KERNEL | __GFP_ZERO);\n+\tif (!chunks)\n+\t\tgoto err;\n \n-\tnet_device-\u003emax_pkt = RNDIS_MAX_PKT_DEFAULT;\n-\tnet_device-\u003epkt_align = RNDIS_PKT_ALIGN_DEFAULT;\n+\tpages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL);\n+\tif (!pages)\n+\t\tgoto err;\n \n-\tinit_completion(\u0026net_device-\u003echannel_init_wait);\n-\tinit_waitqueue_head(\u0026net_device-\u003esubchan_open);\n-\tINIT_WORK(\u0026net_device-\u003esubchan_work, netvsc_subchan_work);\n+\t/*\n+\t * @order monotonically decreases across iterations\n+\t *\n+\t * Use __GFP_NORETRY | __GFP_NOWARN to avoid OOM-killing, but try\n+\t * harder at order 0 since that is the final fallback.\n+\t */\n+\torder = min_t(unsigned int, MAX_PAGE_ORDER, ilog2(nr_pages));\n+\twhile (remaining) {\n+\t\tstruct page *page;\n+\t\tgfp_t gfp;\n+\n+\t\torder = min_t(unsigned int, order, ilog2(remaining));\n+\n+\t\tfor (;;) {\n+\t\t\tgfp = GFP_KERNEL | __GFP_ZERO;\n+\t\t\tif (order)\n+\t\t\t\tgfp |= __GFP_NORETRY | __GFP_NOWARN;\n+\t\t\tpage = alloc_pages_node(node, gfp, order);\n+\t\t\tif (page)\n+\t\t\t\tbreak;\n+\t\t\tif (!order)\n+\t\t\t\tgoto err;\n+\t\t\torder--;\n+\t\t}\n \n-\treturn net_device;\n+\t\tret = set_memory_decrypted((unsigned long)page_address(page),\n+\t\t\t\t\t 1U \u003c\u003c order);\n+\t\tif (ret) {\n+\t\t\t/*\n+\t\t\t * set_memory_decrypted() failed; the page state is\n+\t\t\t * unknown so it must be leaked rather than freed.\n+\t\t\t */\n+\t\t\tgoto err;\n+\t\t}\n+\n+\t\tchunks[chunk_cnt].page = page;\n+\t\tchunks[chunk_cnt].order = order;\n+\t\tchunk_cnt++;\n+\n+\t\tfor (i = 0; i \u003c (1U \u003c\u003c order); i++)\n+\t\t\tpages[page_idx++] = page + i;\n+\n+\t\tremaining -= 1U \u003c\u003c order;\n+\t}\n+\n+\taddr = vmap(pages, nr_pages, VM_MAP, pgprot_decrypted(PAGE_KERNEL));\n+\tif (!addr)\n+\t\tgoto err;\n+\n+\tkvfree(pages);\n+\t*chunks_out = chunks;\n+\t*chunk_cnt_out = chunk_cnt;\n+\treturn addr;\n+\n+err:\n+\tkvfree(pages);\n+\tnetvsc_free_buf_pages(NULL, chunks, chunk_cnt);\n+\treturn NULL;\n }\n \n-static void free_netvsc_device(struct rcu_head *head)\n+static void __free_netvsc_device(struct netvsc_device *nvdev)\n {\n-\tstruct netvsc_device *nvdev\n-\t\t= container_of(head, struct netvsc_device, rcu);\n \tint i;\n \n \tkfree(nvdev-\u003eextension);\n \n-\tif (!nvdev-\u003erecv_buf_gpadl_handle.decrypted)\n-\t\tvfree(nvdev-\u003erecv_buf);\n-\tif (!nvdev-\u003esend_buf_gpadl_handle.decrypted)\n-\t\tvfree(nvdev-\u003esend_buf);\n+\tnetvsc_free_buf_pages(nvdev-\u003erecv_buf, nvdev-\u003erecv_buf_chunks,\n+\t\t\t nvdev-\u003erecv_buf_chunk_cnt);\n+\tnetvsc_free_buf_pages(nvdev-\u003esend_buf, nvdev-\u003esend_buf_chunks,\n+\t\t\t nvdev-\u003esend_buf_chunk_cnt);\n \tbitmap_free(nvdev-\u003esend_section_map);\n \n \tfor (i = 0; i \u003c VRSS_CHANNEL_MAX; i++) {\n@@ -170,9 +297,56 @@ static void free_netvsc_device(struct rcu_head *head)\n \tkfree(nvdev);\n }\n \n+static void free_netvsc_device(struct work_struct *w)\n+{\n+\tstruct rcu_work *rwork = to_rcu_work(w);\n+\n+\t__free_netvsc_device(container_of(rwork, struct netvsc_device, rwork));\n+}\n+\n+int netvsc_workqueue_init(void)\n+{\n+\tnetvsc_wq = alloc_workqueue(\"hv_netvsc\", WQ_UNBOUND, 0);\n+\n+\treturn netvsc_wq ? 0 : -ENOMEM;\n+}\n+\n+void netvsc_workqueue_destroy(void)\n+{\n+\trcu_barrier();\n+\tdestroy_workqueue(netvsc_wq);\n+}\n+\n+static struct netvsc_device *alloc_net_device(void)\n+{\n+\tstruct netvsc_device *net_device;\n+\n+\tnet_device = kzalloc_obj(struct netvsc_device);\n+\tif (!net_device)\n+\t\treturn NULL;\n+\n+\tinit_waitqueue_head(\u0026net_device-\u003ewait_drain);\n+\tnet_device-\u003edestroy = false;\n+\tnet_device-\u003etx_disable = true;\n+\n+\tnet_device-\u003emax_pkt = RNDIS_MAX_PKT_DEFAULT;\n+\tnet_device-\u003epkt_align = RNDIS_PKT_ALIGN_DEFAULT;\n+\n+\tinit_completion(\u0026net_device-\u003echannel_init_wait);\n+\tinit_waitqueue_head(\u0026net_device-\u003esubchan_open);\n+\tINIT_WORK(\u0026net_device-\u003esubchan_work, netvsc_subchan_work);\n+\tINIT_RCU_WORK(\u0026net_device-\u003erwork, free_netvsc_device);\n+\n+\treturn net_device;\n+}\n+\n static void free_netvsc_device_rcu(struct netvsc_device *nvdev)\n {\n-\tcall_rcu(\u0026nvdev-\u003ercu, free_netvsc_device);\n+\t/*\n+\t * Defer the actual free to process context: vunmap() and\n+\t * set_memory_encrypted() cannot run from RCU softirq context.\n+\t */\n+\tqueue_rcu_work(netvsc_wq, \u0026nvdev-\u003erwork);\n }\n \n static void netvsc_revoke_recv_buf(struct hv_device *device,\n@@ -351,7 +525,20 @@ static int netvsc_init_buf(struct hv_device *device,\n \t\tbuf_size = min_t(unsigned int, buf_size,\n \t\t\t\t NETVSC_RECEIVE_BUFFER_SIZE_LEGACY);\n \n-\tnet_device-\u003erecv_buf = vzalloc(buf_size);\n+\tif (device-\u003echannel-\u003eco_external_memory) {\n+\t\t/* Confidential VM Bus leaves buffer encrypted */\n+\t\tnet_device-\u003erecv_buf_chunks = NULL;\n+\t\tnet_device-\u003erecv_buf_chunk_cnt = 0;\n+\t\tnet_device-\u003erecv_buf = vzalloc(buf_size);\n+\t} else {\n+\t\t/* Otherwise, allocate decrypted buffer */\n+\t\tnet_device-\u003erecv_buf =\n+\t\t\tnetvsc_alloc_buf_pages(cpu_to_node(device-\u003echannel-\u003etarget_cpu),\n+\t\t\t\t\t buf_size,\n+\t\t\t\t\t \u0026net_device-\u003erecv_buf_chunks,\n+\t\t\t\t\t \u0026net_device-\u003erecv_buf_chunk_cnt);\n+\t}\n+\n \tif (!net_device-\u003erecv_buf) {\n \t\tnetdev_err(ndev,\n \t\t\t \"unable to allocate receive buffer of size %u\\n\",\n@@ -367,9 +554,10 @@ static int netvsc_init_buf(struct hv_device *device,\n \t * channel. Note: This call uses the vmbus connection rather\n \t * than the channel to establish the gpadl handle.\n \t */\n-\tret = vmbus_establish_gpadl(device-\u003echannel, net_device-\u003erecv_buf,\n-\t\t\t\t buf_size,\n-\t\t\t\t \u0026net_device-\u003erecv_buf_gpadl_handle);\n+\tret = vmbus_establish_gpadl_caller_decrypted(device-\u003echannel,\n+\t\t\t\t\t\t net_device-\u003erecv_buf,\n+\t\t\t\t\t\t buf_size,\n+\t\t\t\t\t\t \u0026net_device-\u003erecv_buf_gpadl_handle);\n \tif (ret != 0) {\n \t\tnetdev_err(ndev,\n \t\t\t\"unable to establish receive buffer's gpadl\\n\");\n@@ -457,7 +645,19 @@ static int netvsc_init_buf(struct hv_device *device,\n \tbuf_size = device_info-\u003esend_sections * device_info-\u003esend_section_size;\n \tbuf_size = round_up(buf_size, PAGE_SIZE);\n \n-\tnet_device-\u003esend_buf = vzalloc(buf_size);\n+\tif (device-\u003echannel-\u003eco_external_memory) {\n+\t\t/* Confidential VM Bus leaves buffer encrypted */\n+\t\tnet_device-\u003esend_buf_chunks = NULL;\n+\t\tnet_device-\u003esend_buf_chunk_cnt = 0;\n+\t\tnet_device-\u003esend_buf = vzalloc(buf_size);\n+\t} else {\n+\t\t/* Otherwise, allocate decrypted buffer */\n+\t\tnet_device-\u003esend_buf =\n+\t\t\tnetvsc_alloc_buf_pages(cpu_to_node(device-\u003echannel-\u003etarget_cpu),\n+\t\t\t\t\t buf_size,\n+\t\t\t\t\t \u0026net_device-\u003esend_buf_chunks,\n+\t\t\t\t\t \u0026net_device-\u003esend_buf_chunk_cnt);\n+\t}\n \tif (!net_device-\u003esend_buf) {\n \t\tnetdev_err(ndev, \"unable to allocate send buffer of size %u\\n\",\n \t\t\t buf_size);\n@@ -470,9 +670,10 @@ static int netvsc_init_buf(struct hv_device *device,\n \t * channel. Note: This call uses the vmbus connection rather\n \t * than the channel to establish the gpadl handle.\n \t */\n-\tret = vmbus_establish_gpadl(device-\u003echannel, net_device-\u003esend_buf,\n-\t\t\t\t buf_size,\n-\t\t\t\t \u0026net_device-\u003esend_buf_gpadl_handle);\n+\tret = vmbus_establish_gpadl_caller_decrypted(device-\u003echannel,\n+\t\t\t\t\t\t net_device-\u003esend_buf,\n+\t\t\t\t\t\t buf_size,\n+\t\t\t\t\t\t \u0026net_device-\u003esend_buf_gpadl_handle);\n \tif (ret != 0) {\n \t\tnetdev_err(ndev,\n \t\t\t \"unable to establish send buffer's gpadl\\n\");\n@@ -1863,7 +2064,11 @@ struct netvsc_device *netvsc_device_add(struct hv_device *device,\n \tnetif_napi_del(\u0026net_device-\u003echan_table[0].napi);\n \n cleanup2:\n-\tfree_netvsc_device(\u0026net_device-\u003ercu);\n+\t/*\n+\t * net_device was never published, so we don't need to wait for an\n+\t * RCU grace period -- call the free routine synchronously.\n+\t */\n+\t__free_netvsc_device(net_device);\n \n \treturn ERR_PTR(ret);\n }\ndiff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c\nindex ee5ab5ceb2be2d..1d43c73fd73f10 100644\n--- a/drivers/net/hyperv/netvsc_drv.c\n+++ b/drivers/net/hyperv/netvsc_drv.c\n@@ -2867,12 +2867,17 @@ static void __exit netvsc_drv_exit(void)\n {\n \tunregister_netdevice_notifier(\u0026netvsc_netdev_notifier);\n \tvmbus_driver_unregister(\u0026netvsc_drv);\n+\tnetvsc_workqueue_destroy();\n }\n \n static int __init netvsc_drv_init(void)\n {\n \tint ret;\n \n+\tret = netvsc_workqueue_init();\n+\tif (ret)\n+\t\treturn ret;\n+\n \tif (ring_size \u003c RING_SIZE_MIN) {\n \t\tring_size = RING_SIZE_MIN;\n \t\tpr_info(\"Increased ring_size to %u (min allowed)\\n\",\n@@ -2890,6 +2895,7 @@ static int __init netvsc_drv_init(void)\n \n err_vmbus_reg:\n \tunregister_netdevice_notifier(\u0026netvsc_netdev_notifier);\n+\tnetvsc_workqueue_destroy();\n \treturn ret;\n }\n \ndiff --git a/include/linux/hyperv.h b/include/linux/hyperv.h\nindex 964f1be8150c59..9c6b1b7794b48e 100644\n--- a/include/linux/hyperv.h\n+++ b/include/linux/hyperv.h\n@@ -781,6 +781,12 @@ struct vmbus_gpadl {\n \tu32 gpadl_handle;\n \tu32 size;\n \tvoid *buffer;\n+\t/*\n+\t * Only used if the vmbus layer owns the encryption lifecycle of\n+\t * @buffer.\n+\t * Indicates @buffer must be re-encrypted at teardown or that it\n+\t * must be leaked because re-encryption failed.\n+\t */\n \tbool decrypted;\n };\n \n@@ -1205,6 +1211,11 @@ extern int vmbus_establish_gpadl(struct vmbus_channel *channel,\n \t\t\t\t u32 size,\n \t\t\t\t struct vmbus_gpadl *gpadl);\n \n+extern int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,\n+\t\t\t\t\t\t void *kbuffer,\n+\t\t\t\t\t\t u32 size,\n+\t\t\t\t\t\t struct vmbus_gpadl *gpadl);\n+\n extern int vmbus_teardown_gpadl(struct vmbus_channel *channel,\n \t\t\t\t struct vmbus_gpadl *gpadl);\n \n"
} |
| 2/1 | 2026/07/22 14:54 | agent | patch-evaluator |
1mModel:best-expensive Args: null Results: {
"EnableConfigs": [
"HYPERV",
"HYPERV_NET"
],
"FocusSymbols": [
"vmbus_establish_gpadl_caller_decrypted",
"vmbus_establish_gpadl_and_decrypt",
"netvsc_alloc_buf_pages",
"netvsc_free_buf_pages",
"netvsc_init_buf",
"__vmbus_establish_gpadl"
],
"Reasoning": "The patch modifies the Hyper-V network driver (netvsc) and VMBus channel code to manage the encryption lifecycle of GPADL buffers, particularly for Confidential VMs. It introduces new functions to allocate/free buffers and establish GPADLs. These are functional changes to reachable driver logic.",
"WorthFuzzing": true
}Instruction: You are an expert Linux kernel maintainer. Your job is to review a provided patch series and determine if it makes functional changes to the kernel that should be fuzzed. IMPORTANT: The changes have ALREADY been applied and committed as the HEAD commit in your workspace. Do NOT rely on your internal knowledge of the kernel. You must actively use your code access tools to examine the actual source code and confirm any assumptions. Return WorthFuzzing=false if the patch only contains: - Modifications to Documentation/, Kconfig files, or code comments. - Purely decorative changes, such as logging (e.g., pr_err, printk) or tracepoints. - Changes to numeric constants or macros that do not functionally alter execution flow. - Code paths that are impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware (e.g., usb gadget, mac80211_hwsim). If it modifies reachable core kernel logic, drivers, or architectures, use your code search tools to verify the code can be executed, then return WorthFuzzing=true. When returning WorthFuzzing=true, you MUST ALSO: 1. Extract any specific kernel functions that should be heavily fuzzed into FocusSymbols. Avoid listing generic hot-path functions to prevent skewed test distributions. 2. Identify any specific CONFIG_ options required to properly test this new/modified feature. Go and look into the Kconfig files and check for ifdefs around the code, do not make assumptions. Do not list too generic configs (we already have them enabled). Only list those that specifically cover the modified code. List them in the EnableConfigs output array, and DO NOT add a 'CONFIG_' prefix (e.g., return "NET_IPV4" instead of "CONFIG_NET_IPV4"). Prefer calling several tools at the same time to save round-trips. Use set-results tool to provide results of the analysis. It must be called exactly once before the final reply. Ignore results of this tool. Prompt: For your convenience, here is the diff of the changes:
commit 04b5fd86971f22061c09c88db5ecf759eb22cbd0
Author: syz-cluster <triage@syzkaller.com>
Date: Wed Jul 22 14:54:06 2026 +0000
syz-cluster: applied patch under review
diff --git a/drivers/hv/channel.c b/drivers/hv/channel.c
index 6821f225248b19..0166367a4df378 100644
--- a/drivers/hv/channel.c
+++ b/drivers/hv/channel.c
@@ -426,7 +426,13 @@ static void vmbus_free_channel_msginfo(struct vmbus_channel_msginfo *msginfo)
}
/*
- * __vmbus_establish_gpadl - Establish a GPADL for a buffer or ringbuffer
+ * __vmbus_establish_gpadl - Establish a GPADL for a buffer or ringbuffer.
+ *
+ * This function only handles the GPADL handshake with the host. The caller
+ * is responsible for ensuring that @kbuffer is in the encryption state the
+ * host expects (host-visible / "decrypted" for confidential VMs); this
+ * function never calls set_memory_decrypted() or set_memory_encrypted().
+ * It also leaves gpadl->decrypted untouched, so the caller must set it.
*
* @channel: a channel
* @type: the type of the corresponding GPADL, only meaningful for the guest.
@@ -434,7 +440,7 @@ static void vmbus_free_channel_msginfo(struct vmbus_channel_msginfo *msginfo)
* @size: page-size multiple
* @send_offset: the offset (in bytes) where the send ring buffer starts,
* should be 0 for BUFFER type gpadl
- * @gpadl_handle: some funky thing
+ * @gpadl: out parameter receiving the established GPADL handle/buffer/size
*/
static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
enum hv_gpadl_type type, void *kbuffer,
@@ -454,30 +460,8 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
(atomic_inc_return(&vmbus_connection.next_gpadl_handle) - 1);
ret = create_gpadl_header(type, kbuffer, size, send_offset, &msginfo);
- if (ret) {
- gpadl->decrypted = false;
+ if (ret)
return ret;
- }
-
- gpadl->decrypted = !((channel->co_external_memory && type == HV_GPADL_BUFFER) ||
- (channel->co_ring_buffer && type == HV_GPADL_RING));
- if (gpadl->decrypted) {
- /*
- * The "decrypted" flag being true assumes that set_memory_decrypted() succeeds.
- * But if it fails, the encryption state of the memory is unknown. In that case,
- * leave "decrypted" as true to ensure the memory is leaked instead of going back
- * on the free list.
- */
- ret = set_memory_decrypted((unsigned long)kbuffer,
- PFN_UP(size));
- if (ret) {
- dev_warn(&channel->device_obj->device,
- "Failed to set host visibility for new GPADL %d.\n",
- ret);
- vmbus_free_channel_msginfo(msginfo);
- return ret;
- }
- }
init_completion(&msginfo->waitevent);
msginfo->waiting_channel = channel;
@@ -553,19 +537,47 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
spin_unlock_irqrestore(&vmbus_connection.channelmsg_lock, flags);
vmbus_free_channel_msginfo(msginfo);
+ return ret;
+}
- if (ret) {
- /*
- * If set_memory_encrypted() fails, the decrypted flag is
- * left as true so the memory is leaked instead of being
- * put back on the free list.
- */
- if (gpadl->decrypted) {
- if (!set_memory_encrypted((unsigned long)kbuffer, PFN_UP(size)))
- gpadl->decrypted = false;
+/*
+ * vmbus_establish_gpadl_and_decrypt - Manage the encryption lifecycle of
+ * @kbuffer around a __vmbus_establish_gpadl() call.
+ *
+ * Decrypts @kbuffer (making it host-visible) unless the channel was created
+ * with confidential memory for @type, then establishes the GPADL. On
+ * establish failure the buffer is re-encrypted so the caller can free it.
+ *
+ * Sets gpadl->decrypted; see the comment on struct vmbus_gpadl::decrypted.
+ */
+static int vmbus_establish_gpadl_and_decrypt(struct vmbus_channel *channel,
+ enum hv_gpadl_type type,
+ void *kbuffer, u32 size,
+ u32 send_offset,
+ struct vmbus_gpadl *gpadl)
+{
+ bool decrypt = !((channel->co_external_memory && type == HV_GPADL_BUFFER) ||
+ (channel->co_ring_buffer && type == HV_GPADL_RING));
+ int ret;
+
+ if (decrypt) {
+ ret = set_memory_decrypted((unsigned long)kbuffer, PFN_UP(size));
+ if (ret) {
+ dev_warn(&channel->device_obj->device,
+ "Failed to set host visibility for new GPADL %d.\n",
+ ret);
+ /* Encryption state unknown; signal caller to leak. */
+ gpadl->decrypted = true;
+ return ret;
}
}
+ gpadl->decrypted = decrypt;
+ ret = __vmbus_establish_gpadl(channel, type, kbuffer, size, send_offset,
+ gpadl);
+ if (ret && decrypt &&
+ !set_memory_encrypted((unsigned long)kbuffer, PFN_UP(size)))
+ gpadl->decrypted = false;
return ret;
}
@@ -580,11 +592,40 @@ static int __vmbus_establish_gpadl(struct vmbus_channel *channel,
int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,
u32 size, struct vmbus_gpadl *gpadl)
{
- return __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER, kbuffer, size,
- 0U, gpadl);
+ return vmbus_establish_gpadl_and_decrypt(channel, HV_GPADL_BUFFER,
+ kbuffer, size, 0U, gpadl);
}
EXPORT_SYMBOL_GPL(vmbus_establish_gpadl);
+/*
+ * vmbus_establish_gpadl_caller_decrypted - Establish a GPADL for a buffer
+ * whose encryption state is managed by the caller.
+ *
+ * @channel: a channel
+ * @kbuffer: a buffer that the caller has already transitioned to host-visible
+ * (decrypted) via set_memory_decrypted() or an equivalent mechanism.
+ * @size: page-size multiple
+ * @gpadl: out parameter receiving the established GPADL
+ *
+ * Unlike vmbus_establish_gpadl(), this function does not call
+ * set_memory_decrypted() on @kbuffer, and the matching vmbus_teardown_gpadl()
+ * call will not call set_memory_encrypted() on it. The caller is responsible
+ * for the full encryption lifecycle of @kbuffer; on return gpadl->decrypted
+ * is set to false to record that vmbus does not own the encryption state.
+ */
+int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
+ void *kbuffer, u32 size,
+ struct vmbus_gpadl *gpadl)
+{
+ int ret = __vmbus_establish_gpadl(channel, HV_GPADL_BUFFER, kbuffer,
+ size, 0U, gpadl);
+
+ /* Caller owns @kbuffer's encryption; teardown must not touch it. */
+ gpadl->decrypted = false;
+ return ret;
+}
+EXPORT_SYMBOL_GPL(vmbus_establish_gpadl_caller_decrypted);
+
/**
* request_arr_init - Allocates memory for the requestor array. Each slot
* keeps track of the next available slot in the array. Initially, each
@@ -685,11 +726,11 @@ static int __vmbus_open(struct vmbus_channel *newchannel,
/* Establish the gpadl for the ring buffer */
newchannel->ringbuffer_gpadlhandle.gpadl_handle = 0;
- err = __vmbus_establish_gpadl(newchannel, HV_GPADL_RING,
- page_address(newchannel->ringbuffer_page),
- (send_pages + recv_pages) << PAGE_SHIFT,
- newchannel->ringbuffer_send_offset << PAGE_SHIFT,
- &newchannel->ringbuffer_gpadlhandle);
+ err = vmbus_establish_gpadl_and_decrypt(newchannel, HV_GPADL_RING,
+ page_address(newchannel->ringbuffer_page),
+ (send_pages + recv_pages) << PAGE_SHIFT,
+ newchannel->ringbuffer_send_offset << PAGE_SHIFT,
+ &newchannel->ringbuffer_gpadlhandle);
if (err)
goto error_clean_ring;
diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h
index 7397c693f984af..b0c4cb0f7a4ce4 100644
--- a/drivers/net/hyperv/hyperv_net.h
+++ b/drivers/net/hyperv/hyperv_net.h
@@ -220,6 +220,8 @@ struct net_device_context;
extern u32 netvsc_ring_bytes;
+int netvsc_workqueue_init(void);
+void netvsc_workqueue_destroy(void);
struct netvsc_device *netvsc_device_add(struct hv_device *device,
const struct netvsc_device_info *info);
int netvsc_alloc_recv_comp_ring(struct netvsc_device *net_device, u32 q_idx);
@@ -1147,6 +1149,16 @@ struct netvsc_channel {
struct netvsc_stats_rx rx_stats;
};
+/* A physically-contiguous chunk of netvsc buffer.
+ *
+ * The chunk list is preserved so each chunk can be individually freed at
+ * teardown.
+ */
+struct netvsc_buf_chunk {
+ struct page *page;
+ unsigned int order;
+};
+
/* Per netvsc device */
struct netvsc_device {
u32 nvsp_version;
@@ -1158,6 +1170,8 @@ struct netvsc_device {
/* Receive buffer allocated by us but manages by NetVSP */
void *recv_buf;
u32 recv_buf_size; /* allocated bytes */
+ struct netvsc_buf_chunk *recv_buf_chunks;
+ u32 recv_buf_chunk_cnt;
struct vmbus_gpadl recv_buf_gpadl_handle;
u32 recv_section_cnt;
u32 recv_section_size;
@@ -1166,6 +1180,8 @@ struct netvsc_device {
/* Send buffer allocated by us */
void *send_buf;
u32 send_buf_size;
+ struct netvsc_buf_chunk *send_buf_chunks;
+ u32 send_buf_chunk_cnt;
struct vmbus_gpadl send_buf_gpadl_handle;
u32 send_section_cnt;
u32 send_section_size;
@@ -1193,7 +1209,7 @@ struct netvsc_device {
struct netvsc_channel chan_table[VRSS_CHANNEL_MAX];
- struct rcu_head rcu;
+ struct rcu_work rwork;
};
/* NdisInitialize message */
diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c
index 59e95341f9b1e5..2505d10e220102 100644
--- a/drivers/net/hyperv/netvsc.c
+++ b/drivers/net/hyperv/netvsc.c
@@ -14,6 +14,8 @@
#include <linux/mm.h>
#include <linux/delay.h>
#include <linux/io.h>
+#include <linux/log2.h>
+#include <linux/set_memory.h>
#include <linux/slab.h>
#include <linux/netdevice.h>
#include <linux/if_ether.h>
@@ -28,6 +30,8 @@
#include "hyperv_net.h"
#include "netvsc_trace.h"
+static struct workqueue_struct *netvsc_wq;
+
/*
* Switch the data path from the synthetic interface to the VF
* interface.
@@ -125,40 +129,163 @@ static void netvsc_subchan_work(struct work_struct *w)
rtnl_unlock();
}
-static struct netvsc_device *alloc_net_device(void)
+/*
+ * netvsc_free_buf_pages - release a netvsc send/receive buffer.
+ *
+ * @addr: buffer address, or NULL if none was allocated (e.g. cleanup from a
+ * failed allocation)
+ * @chunks: chunks array from netvsc_alloc_buf_pages(), or NULL
+ * @chunk_cnt: number of entries in @chunks
+ *
+ * When @chunks is NULL the buffer is a plain vzalloc() allocation.
+ *
+ * Otherwise tear down the vmap, and for each chunk re-encrypt and free
+ * the underlying pages. Any chunk that cannot be re-encrypted is leaked.
+ */
+static void netvsc_free_buf_pages(void *addr,
+ struct netvsc_buf_chunk *chunks,
+ u32 chunk_cnt)
{
- struct netvsc_device *net_device;
+ u32 i;
- net_device = kzalloc_obj(struct netvsc_device);
- if (!net_device)
+ if (!chunks) {
+ vfree(addr);
+ return;
+ }
+
+ vunmap(addr);
+
+ for (i = 0; i < chunk_cnt; i++) {
+ unsigned long vaddr =
+ (unsigned long)page_address(chunks[i].page);
+ unsigned int order = chunks[i].order;
+
+ if (set_memory_encrypted(vaddr, 1U << order))
+ continue;
+ __free_pages(chunks[i].page, order);
+ }
+
+ kvfree(chunks);
+}
+
+/*
+ * netvsc_alloc_buf_pages - allocate a virtually-contiguous, host-visible
+ * buffer for the netvsc send/receive area.
+ *
+ * @node: NUMA node hint (NUMA_NO_NODE for any node)
+ * @size: requested buffer size in bytes (rounded up to PAGE_SIZE)
+ * @chunks_out: on success, set to the array of underlying chunks
+ * @chunk_cnt_out: on success, set to the number of chunks
+ *
+ * Allocates the buffer as a series of physically-contiguous chunks,
+ * starting at MAX_PAGE_ORDER and falling back to smaller orders on
+ * allocation failure. Each chunk is transitioned to host-visible via
+ * set_memory_decrypted() on its direct-map address, then all chunks are
+ * combined into a virtually-contiguous range via vmap().
+ *
+ * Return: the vmap()ed virtual address, or NULL on failure.
+ */
+static void *netvsc_alloc_buf_pages(int node, u32 size,
+ struct netvsc_buf_chunk **chunks_out,
+ u32 *chunk_cnt_out)
+{
+ u32 nr_pages = PFN_UP(size);
+ struct netvsc_buf_chunk *chunks = NULL;
+ struct page **pages = NULL;
+ unsigned int order;
+ u32 chunk_cnt = 0;
+ u32 page_idx = 0;
+ u32 remaining = nr_pages;
+ void *addr;
+ u32 i;
+ int ret;
+
+ *chunks_out = NULL;
+ *chunk_cnt_out = 0;
+
+ if (!nr_pages)
return NULL;
- init_waitqueue_head(&net_device->wait_drain);
- net_device->destroy = false;
- net_device->tx_disable = true;
+ /* Worst case: every chunk is a single page. */
+ chunks = kvmalloc_array(nr_pages, sizeof(*chunks),
+ GFP_KERNEL | __GFP_ZERO);
+ if (!chunks)
+ goto err;
- net_device->max_pkt = RNDIS_MAX_PKT_DEFAULT;
- net_device->pkt_align = RNDIS_PKT_ALIGN_DEFAULT;
+ pages = kvmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL);
+ if (!pages)
+ goto err;
- init_completion(&net_device->channel_init_wait);
- init_waitqueue_head(&net_device->subchan_open);
- INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);
+ /*
+ * @order monotonically decreases across iterations
+ *
+ * Use __GFP_NORETRY | __GFP_NOWARN to avoid OOM-killing, but try
+ * harder at order 0 since that is the final fallback.
+ */
+ order = min_t(unsigned int, MAX_PAGE_ORDER, ilog2(nr_pages));
+ while (remaining) {
+ struct page *page;
+ gfp_t gfp;
+
+ order = min_t(unsigned int, order, ilog2(remaining));
+
+ for (;;) {
+ gfp = GFP_KERNEL | __GFP_ZERO;
+ if (order)
+ gfp |= __GFP_NORETRY | __GFP_NOWARN;
+ page = alloc_pages_node(node, gfp, order);
+ if (page)
+ break;
+ if (!order)
+ goto err;
+ order--;
+ }
- return net_device;
+ ret = set_memory_decrypted((unsigned long)page_address(page),
+ 1U << order);
+ if (ret) {
+ /*
+ * set_memory_decrypted() failed; the page state is
+ * unknown so it must be leaked rather than freed.
+ */
+ goto err;
+ }
+
+ chunks[chunk_cnt].page = page;
+ chunks[chunk_cnt].order = order;
+ chunk_cnt++;
+
+ for (i = 0; i < (1U << order); i++)
+ pages[page_idx++] = page + i;
+
+ remaining -= 1U << order;
+ }
+
+ addr = vmap(pages, nr_pages, VM_MAP, pgprot_decrypted(PAGE_KERNEL));
+ if (!addr)
+ goto err;
+
+ kvfree(pages);
+ *chunks_out = chunks;
+ *chunk_cnt_out = chunk_cnt;
+ return addr;
+
+err:
+ kvfree(pages);
+ netvsc_free_buf_pages(NULL, chunks, chunk_cnt);
+ return NULL;
}
-static void free_netvsc_device(struct rcu_head *head)
+static void __free_netvsc_device(struct netvsc_device *nvdev)
{
- struct netvsc_device *nvdev
- = container_of(head, struct netvsc_device, rcu);
int i;
kfree(nvdev->extension);
- if (!nvdev->recv_buf_gpadl_handle.decrypted)
- vfree(nvdev->recv_buf);
- if (!nvdev->send_buf_gpadl_handle.decrypted)
- vfree(nvdev->send_buf);
+ netvsc_free_buf_pages(nvdev->recv_buf, nvdev->recv_buf_chunks,
+ nvdev->recv_buf_chunk_cnt);
+ netvsc_free_buf_pages(nvdev->send_buf, nvdev->send_buf_chunks,
+ nvdev->send_buf_chunk_cnt);
bitmap_free(nvdev->send_section_map);
for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
@@ -170,9 +297,56 @@ static void free_netvsc_device(struct rcu_head *head)
kfree(nvdev);
}
+static void free_netvsc_device(struct work_struct *w)
+{
+ struct rcu_work *rwork = to_rcu_work(w);
+
+ __free_netvsc_device(container_of(rwork, struct netvsc_device, rwork));
+}
+
+int netvsc_workqueue_init(void)
+{
+ netvsc_wq = alloc_workqueue("hv_netvsc", WQ_UNBOUND, 0);
+
+ return netvsc_wq ? 0 : -ENOMEM;
+}
+
+void netvsc_workqueue_destroy(void)
+{
+ rcu_barrier();
+ destroy_workqueue(netvsc_wq);
+}
+
+static struct netvsc_device *alloc_net_device(void)
+{
+ struct netvsc_device *net_device;
+
+ net_device = kzalloc_obj(struct netvsc_device);
+ if (!net_device)
+ return NULL;
+
+ init_waitqueue_head(&net_device->wait_drain);
+ net_device->destroy = false;
+ net_device->tx_disable = true;
+
+ net_device->max_pkt = RNDIS_MAX_PKT_DEFAULT;
+ net_device->pkt_align = RNDIS_PKT_ALIGN_DEFAULT;
+
+ init_completion(&net_device->channel_init_wait);
+ init_waitqueue_head(&net_device->subchan_open);
+ INIT_WORK(&net_device->subchan_work, netvsc_subchan_work);
+ INIT_RCU_WORK(&net_device->rwork, free_netvsc_device);
+
+ return net_device;
+}
+
static void free_netvsc_device_rcu(struct netvsc_device *nvdev)
{
- call_rcu(&nvdev->rcu, free_netvsc_device);
+ /*
+ * Defer the actual free to process context: vunmap() and
+ * set_memory_encrypted() cannot run from RCU softirq context.
+ */
+ queue_rcu_work(netvsc_wq, &nvdev->rwork);
}
static void netvsc_revoke_recv_buf(struct hv_device *device,
@@ -351,7 +525,20 @@ static int netvsc_init_buf(struct hv_device *device,
buf_size = min_t(unsigned int, buf_size,
NETVSC_RECEIVE_BUFFER_SIZE_LEGACY);
- net_device->recv_buf = vzalloc(buf_size);
+ if (device->channel->co_external_memory) {
+ /* Confidential VM Bus leaves buffer encrypted */
+ net_device->recv_buf_chunks = NULL;
+ net_device->recv_buf_chunk_cnt = 0;
+ net_device->recv_buf = vzalloc(buf_size);
+ } else {
+ /* Otherwise, allocate decrypted buffer */
+ net_device->recv_buf =
+ netvsc_alloc_buf_pages(cpu_to_node(device->channel->target_cpu),
+ buf_size,
+ &net_device->recv_buf_chunks,
+ &net_device->recv_buf_chunk_cnt);
+ }
+
if (!net_device->recv_buf) {
netdev_err(ndev,
"unable to allocate receive buffer of size %u\n",
@@ -367,9 +554,10 @@ static int netvsc_init_buf(struct hv_device *device,
* channel. Note: This call uses the vmbus connection rather
* than the channel to establish the gpadl handle.
*/
- ret = vmbus_establish_gpadl(device->channel, net_device->recv_buf,
- buf_size,
- &net_device->recv_buf_gpadl_handle);
+ ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
+ net_device->recv_buf,
+ buf_size,
+ &net_device->recv_buf_gpadl_handle);
if (ret != 0) {
netdev_err(ndev,
"unable to establish receive buffer's gpadl\n");
@@ -457,7 +645,19 @@ static int netvsc_init_buf(struct hv_device *device,
buf_size = device_info->send_sections * device_info->send_section_size;
buf_size = round_up(buf_size, PAGE_SIZE);
- net_device->send_buf = vzalloc(buf_size);
+ if (device->channel->co_external_memory) {
+ /* Confidential VM Bus leaves buffer encrypted */
+ net_device->send_buf_chunks = NULL;
+ net_device->send_buf_chunk_cnt = 0;
+ net_device->send_buf = vzalloc(buf_size);
+ } else {
+ /* Otherwise, allocate decrypted buffer */
+ net_device->send_buf =
+ netvsc_alloc_buf_pages(cpu_to_node(device->channel->target_cpu),
+ buf_size,
+ &net_device->send_buf_chunks,
+ &net_device->send_buf_chunk_cnt);
+ }
if (!net_device->send_buf) {
netdev_err(ndev, "unable to allocate send buffer of size %u\n",
buf_size);
@@ -470,9 +670,10 @@ static int netvsc_init_buf(struct hv_device *device,
* channel. Note: This call uses the vmbus connection rather
* than the channel to establish the gpadl handle.
*/
- ret = vmbus_establish_gpadl(device->channel, net_device->send_buf,
- buf_size,
- &net_device->send_buf_gpadl_handle);
+ ret = vmbus_establish_gpadl_caller_decrypted(device->channel,
+ net_device->send_buf,
+ buf_size,
+ &net_device->send_buf_gpadl_handle);
if (ret != 0) {
netdev_err(ndev,
"unable to establish send buffer's gpadl\n");
@@ -1863,7 +2064,11 @@ struct netvsc_device *netvsc_device_add(struct hv_device *device,
netif_napi_del(&net_device->chan_table[0].napi);
cleanup2:
- free_netvsc_device(&net_device->rcu);
+ /*
+ * net_device was never published, so we don't need to wait for an
+ * RCU grace period -- call the free routine synchronously.
+ */
+ __free_netvsc_device(net_device);
return ERR_PTR(ret);
}
diff --git a/drivers/net/hyperv/netvsc_drv.c b/drivers/net/hyperv/netvsc_drv.c
index ee5ab5ceb2be2d..1d43c73fd73f10 100644
--- a/drivers/net/hyperv/netvsc_drv.c
+++ b/drivers/net/hyperv/netvsc_drv.c
@@ -2867,12 +2867,17 @@ static void __exit netvsc_drv_exit(void)
{
unregister_netdevice_notifier(&netvsc_netdev_notifier);
vmbus_driver_unregister(&netvsc_drv);
+ netvsc_workqueue_destroy();
}
static int __init netvsc_drv_init(void)
{
int ret;
+ ret = netvsc_workqueue_init();
+ if (ret)
+ return ret;
+
if (ring_size < RING_SIZE_MIN) {
ring_size = RING_SIZE_MIN;
pr_info("Increased ring_size to %u (min allowed)\n",
@@ -2890,6 +2895,7 @@ static int __init netvsc_drv_init(void)
err_vmbus_reg:
unregister_netdevice_notifier(&netvsc_netdev_notifier);
+ netvsc_workqueue_destroy();
return ret;
}
diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h
index 964f1be8150c59..9c6b1b7794b48e 100644
--- a/include/linux/hyperv.h
+++ b/include/linux/hyperv.h
@@ -781,6 +781,12 @@ struct vmbus_gpadl {
u32 gpadl_handle;
u32 size;
void *buffer;
+ /*
+ * Only used if the vmbus layer owns the encryption lifecycle of
+ * @buffer.
+ * Indicates @buffer must be re-encrypted at teardown or that it
+ * must be leaked because re-encryption failed.
+ */
bool decrypted;
};
@@ -1205,6 +1211,11 @@ extern int vmbus_establish_gpadl(struct vmbus_channel *channel,
u32 size,
struct vmbus_gpadl *gpadl);
+extern int vmbus_establish_gpadl_caller_decrypted(struct vmbus_channel *channel,
+ void *kbuffer,
+ u32 size,
+ struct vmbus_gpadl *gpadl);
+
extern int vmbus_teardown_gpadl(struct vmbus_channel *channel,
struct vmbus_gpadl *gpadl);
|
| 3/2 | 2026/07/22 14:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 9880 output: 21 thoughts: 500 Thoughts: **Analyzing the Potential for Fuzzing this Hyper-V Network Driver Patch** Okay, so I've got this patch in front of me that's tweaking the Hyper-V network driver, specifically the `netvsc` and VMBus channel code. It looks like it's introducing a new function, `vmbus_establish_gpadl_caller_decrypted`, and fundamentally changing how `netvsc` handles its send/receive buffers. The key here is the use of `set_memory_decrypted` and `set_memory_encrypted` functions. Those are big flags for Confidential VMs, like AMD SEV-SNP or Intel TDX, which is important to consider. The patch also introduces `netvsc_alloc_buf_pages` and `netvsc_free_buf_pages`. This seems to be about allocating physically contiguous memory chunks and vmap-ing them, instead of relying on `vzalloc`. Additionally, there is a new workqueue, `netvsc_wq`, added for deferring the device freeing. Now, the critical question is: can this be reached in a virtualized environment? The prompt specifically mentioned QEMU and GCE as potential barriers, but I know the Hyper-V drivers (`hv_netvsc`, `hv_vmbus`) are actively used when Linux runs as a guest on Hyper-V. The prompt also notes that it's "impossible to reach in virtualized environments like GCE or QEMU, even when utilizing software-emulated hardware". Syzkaller *should* be able to fuzz Hyper-V drivers if it's running on Hyper-V or if there's some sort of emulation. Wait a second, wasn't there some Hyper-V fuzzing support in syzkaller? Let's see...I remember seeing things like `syz_vmbus_establish_gpadl` in the syzlang, so there might be a VMBus fuzzer that runs in QEMU with a mock VMBus implementation, or potentially even running on Hyper-V. This warrants a deeper look; I need to verify if `hv_netvsc` itself is targeted by fuzzing. Okay, let's start searching. I'll search the kernel and syzkaller codebases for "netvsc". Wait, the instructions say that if this modifies reachable core kernel logic, drivers, or architectures, I need to use code search tools to verify if the code *can* be executed. Okay, that's what I'll do. Alright, time to check if `CONFIG_HYPERV_NET` and `CONFIG_HYPERV` are enabled, and therefore whether we can fuzz it at all. Then I'll search for `netvsc_alloc_buf_pages` and `netvsc_device_add` to find the relevant code paths. The whole point is to determine if this code is actually *executable* and therefore WorthFuzzing. |
| 4/2 | 2026/07/22 14:54 | tool | grepper |
0mArgs:{
"Expression": "netvsc_device_add"
}Results: {
"Output": "drivers/net/hyperv/hyperv_net.h=224=void netvsc_workqueue_destroy(void);\ndrivers/net/hyperv/hyperv_net.h:225:struct netvsc_device *netvsc_device_add(struct hv_device *device,\ndrivers/net/hyperv/hyperv_net.h-226-\t\t\t\t\tconst struct netvsc_device_info *info);\n--\ndrivers/net/hyperv/netvsc.c=1941=void netvsc_channel_cb(void *context)\n--\ndrivers/net/hyperv/netvsc.c-1958-/*\ndrivers/net/hyperv/netvsc.c:1959: * netvsc_device_add - Callback when the device belonging to this\ndrivers/net/hyperv/netvsc.c-1960- * driver is added\ndrivers/net/hyperv/netvsc.c-1961- */\ndrivers/net/hyperv/netvsc.c:1962:struct netvsc_device *netvsc_device_add(struct hv_device *device,\ndrivers/net/hyperv/netvsc.c-1963-\t\t\t\tconst struct netvsc_device_info *device_info)\n--\ndrivers/net/hyperv/rndis_filter.c=1470=struct netvsc_device *rndis_filter_device_add(struct hv_device *dev,\n--\ndrivers/net/hyperv/rndis_filter.c-1490-\t */\ndrivers/net/hyperv/rndis_filter.c:1491:\tnet_device = netvsc_device_add(dev, device_info);\ndrivers/net/hyperv/rndis_filter.c-1492-\tif (IS_ERR(net_device)) {\n"
} |
| 5/2 | 2026/07/22 14:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 11503 output: 23 thoughts: 19 Thoughts: Okay, here's my attempt at summarizing that thought process, keeping in mind I'm speaking to an expert: **Investigating `netvsc_alloc_buf_pages`** Right, let's zero in on `netvsc_alloc_buf_pages`. My initial thought is to check its invocation points. I need to understand its call stack and how it's being used. I'm assuming it's related to buffer allocation within the network virtual service context, but I need to confirm that and get a feel for the specific circumstances that trigger it. I'll start with a quick search to identify all call sites. From there, I'll trace through the code, ideally understanding the logical flow that leads to `netvsc_alloc_buf_pages` being called. Knowing the surrounding context will allow me to properly interpret the significance of the calls. I'll need to pay attention to any error handling around the calls as well. My main goal is to fully understand its allocation patterns and dependencies. |
| 6/2 | 2026/07/22 14:54 | tool | grepper |
0mArgs:{
"Expression": "netvsc_alloc_buf_pages"
}Results: {
"Output": "drivers/net/hyperv/netvsc.c=101=static void netvsc_subchan_work(struct work_struct *w)\n--\ndrivers/net/hyperv/netvsc.c-136- * failed allocation)\ndrivers/net/hyperv/netvsc.c:137: * @chunks: chunks array from netvsc_alloc_buf_pages(), or NULL\ndrivers/net/hyperv/netvsc.c-138- * @chunk_cnt: number of entries in @chunks\n--\ndrivers/net/hyperv/netvsc.c=145=static void netvsc_free_buf_pages(void *addr,\n--\ndrivers/net/hyperv/netvsc.c-171-/*\ndrivers/net/hyperv/netvsc.c:172: * netvsc_alloc_buf_pages - allocate a virtually-contiguous, host-visible\ndrivers/net/hyperv/netvsc.c-173- * buffer for the netvsc send/receive area.\n--\ndrivers/net/hyperv/netvsc.c-187- */\ndrivers/net/hyperv/netvsc.c:188:static void *netvsc_alloc_buf_pages(int node, u32 size,\ndrivers/net/hyperv/netvsc.c-189-\t\t\t\t struct netvsc_buf_chunk **chunks_out,\n--\ndrivers/net/hyperv/netvsc.c=509=static int netvsc_init_buf(struct hv_device *device,\n--\ndrivers/net/hyperv/netvsc.c-535-\t\tnet_device-\u003erecv_buf =\ndrivers/net/hyperv/netvsc.c:536:\t\t\tnetvsc_alloc_buf_pages(cpu_to_node(device-\u003echannel-\u003etarget_cpu),\ndrivers/net/hyperv/netvsc.c-537-\t\t\t\t\t buf_size,\n--\ndrivers/net/hyperv/netvsc.c-655-\t\tnet_device-\u003esend_buf =\ndrivers/net/hyperv/netvsc.c:656:\t\t\tnetvsc_alloc_buf_pages(cpu_to_node(device-\u003echannel-\u003etarget_cpu),\ndrivers/net/hyperv/netvsc.c-657-\t\t\t\t\t buf_size,\n"
} |
| 7/2 | 2026/07/22 14:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 12347 output: 18 thoughts: 91 Thoughts: **Analyzing the Proposed NetVSC Patch** Okay, so I'm looking at this patch and my initial thought is, "Where's the beef?" It seems to be touching `netvsc_init_buf` and `netvsc_device_add`, both in `drivers/net/hyperv/netvsc.c`. That rings some bells - probably related to the initial buffer setup and device addition within the network virtual service client. And then it also seems to be modifying `__vmbus_establish_gpadl` in `drivers/hv/channel.c`. That's a low-level channel function, related to setting up the Guest Physical Address List. Definitely interesting. Before I dive too deep into the code changes themselves, I need to make a quick sanity check. I need to confirm that this patch is correctly guarded by the relevant configurations. I'll need to confirm that `CONFIG_HYPERV` and `CONFIG_HYPERV_NET` are the correct pre-requisites for this particular area of the code. Let's see if those are sufficient. If not, I'm going to have to dig deeper into the Kconfig files. |
| 8/2 | 2026/07/22 14:54 | tool | grepper |
0mArgs:{
"Expression": "config HYPERV"
}Results: {
"Output": "arch/x86/Kconfig=784=config SCHED_OMIT_FRAME_POINTER\n--\narch/x86/Kconfig-795-\narch/x86/Kconfig:796:menuconfig HYPERVISOR_GUEST\narch/x86/Kconfig-797-\tbool \"Linux guest support\"\n--\ndrivers/hv/Kconfig=3=menu \"Microsoft Hyper-V guest support\"\ndrivers/hv/Kconfig-4-\ndrivers/hv/Kconfig:5:config HYPERV\ndrivers/hv/Kconfig-6-\tbool \"Microsoft Hyper-V core hypervisor support\"\n--\ndrivers/hv/Kconfig-16-\ndrivers/hv/Kconfig:17:config HYPERV_VTL_MODE\ndrivers/hv/Kconfig-18-\tbool \"Enable Linux to boot in VTL context\"\n--\ndrivers/hv/Kconfig-42-\ndrivers/hv/Kconfig:43:config HYPERV_TIMER\ndrivers/hv/Kconfig-44-\tdef_bool HYPERV \u0026\u0026 X86\ndrivers/hv/Kconfig-45-\ndrivers/hv/Kconfig:46:config HYPERV_UTILS\ndrivers/hv/Kconfig-47-\ttristate \"Microsoft Hyper-V Utilities driver\"\n--\ndrivers/hv/Kconfig-52-\ndrivers/hv/Kconfig:53:config HYPERV_BALLOON\ndrivers/hv/Kconfig-54-\ttristate \"Microsoft Hyper-V Balloon driver\"\n--\ndrivers/hv/Kconfig-59-\ndrivers/hv/Kconfig:60:config HYPERV_VMBUS\ndrivers/hv/Kconfig-61-\ttristate \"Microsoft Hyper-V VMBus driver\"\n--\ndrivers/input/serio/Kconfig=254=config SERIO_OLPC_APSP\n--\ndrivers/input/serio/Kconfig-263-\ndrivers/input/serio/Kconfig:264:config HYPERV_KEYBOARD\ndrivers/input/serio/Kconfig-265-\ttristate \"Microsoft Synthetic Keyboard driver\"\n--\ndrivers/net/hyperv/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/hyperv/Kconfig:2:config HYPERV_NET\ndrivers/net/hyperv/Kconfig-3-\ttristate \"Microsoft Hyper-V virtual network driver\"\n--\ndrivers/scsi/Kconfig=578=config XEN_SCSI_FRONTEND\n--\ndrivers/scsi/Kconfig-587-\ndrivers/scsi/Kconfig:588:config HYPERV_STORAGE\ndrivers/scsi/Kconfig-589-\ttristate \"Microsoft Hyper-V virtual storage driver\"\n--\nlib/Kconfig.debug=3527=config MEMTEST\n--\nlib/Kconfig.debug-3540-\nlib/Kconfig.debug:3541:config HYPERV_TESTING\nlib/Kconfig.debug-3542-\tbool \"Microsoft Hyper-V driver testing\"\n--\nnet/vmw_vsock/Kconfig=66=config VIRTIO_VSOCKETS_COMMON\n--\nnet/vmw_vsock/Kconfig-72-\nnet/vmw_vsock/Kconfig:73:config HYPERV_VSOCKETS\nnet/vmw_vsock/Kconfig-74-\ttristate \"Hyper-V transport for Virtual Sockets\"\n"
} |
| 9/2 | 2026/07/22 14:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13561 output: 20 thoughts: 19 Thoughts: Here's my attempt at summarizing the thought process, tailored for an expert audience: **Confirming the Hyper-V Network Configuration** Okay, so I need to verify this `CONFIG_HYPERV_NET` setting. My initial assessment suggests this is the correct kernel configuration option for enabling Hyper-V network support, but let's confirm this rigorously before proceeding. I need to make sure I haven't overlooked any nuances in the dependencies or conflicting settings that might impact the intended network functionality within the Hyper-V environment. A quick check of the Kconfig documentation and any relevant build logs should be enough to solidify this particular choice, I'll need to keep an eye out for any interactions with `CONFIG_VIRTIO_NET` or `CONFIG_E1000E`. It's always best to be precise here, and avoid any unwanted surprises down the road. |
| 10/2 | 2026/07/22 14:54 | tool | grepper |
0mArgs:{
"Expression": "obj-.*netvsc"
}Results: {
"Output": "drivers/net/hyperv/Makefile-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/hyperv/Makefile:2:obj-$(CONFIG_HYPERV_NET) += hv_netvsc.o\ndrivers/net/hyperv/Makefile-3-\n"
} |
| 11/2 | 2026/07/22 14:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 13864 output: 17 thoughts: 86 Thoughts: Here's my thought process, as it stands: **Navigating the Hyper-V Network Driver Patch** Okay, so I'm looking at this patch and it seems like `CONFIG_HYPERV_NET` is the key configuration option. That's the starting point. The patch itself touches `drivers/net/hyperv/netvsc.c` and `drivers/hv/channel.c`. Now, `netvsc.c` I recognize, but `channel.c`... that rings some bells with the broader Hyper-V infrastructure, specifically the VMBus. I need to make sure I know how this ties in with the existing configuration options. Is `channel.c` part of `CONFIG_HYPERV_VMBUS` or maybe just the general `CONFIG_HYPERV`? I need to get some more context, so I'll check the `drivers/hv/Makefile` to confirm the dependencies and placement of this file. That should give me a clearer picture of the scope of this change and how it impacts the overall Hyper-V driver configuration. |
| 12/2 | 2026/07/22 14:54 | tool | grepper |
0mArgs:{
"Expression": "channel\\.o"
}Results: {
"Output": "Documentation/networking/ppp_generic.rst=49=Each channel has to provide two functions to the generic PPP layer,\nDocumentation/networking/ppp_generic.rst:50:via the ppp_channel.ops pointer:\nDocumentation/networking/ppp_generic.rst-51-\n--\ndrivers/gpu/host1x/Makefile=2=host1x-y = \\\n--\ndrivers/gpu/host1x/Makefile-7-\tcdma.o \\\ndrivers/gpu/host1x/Makefile:8:\tchannel.o \\\ndrivers/gpu/host1x/Makefile-9-\tjob.o \\\n--\ndrivers/hv/Makefile=11=hv_vmbus-y := vmbus_drv.o \\\ndrivers/hv/Makefile:12:\t\t hv.o connection.o channel.o \\\ndrivers/hv/Makefile-13-\t\t channel_mgmt.o ring_buffer.o hv_trace.o\n--\ndrivers/media/test-drivers/vidtv/Makefile=5=dvb-vidtv-bridge-objs := vidtv_bridge.o vidtv_common.o vidtv_ts.o vidtv_psi.o \\\ndrivers/media/test-drivers/vidtv/Makefile:6:\t\t\t vidtv_pes.o vidtv_s302m.o vidtv_channel.o vidtv_mux.o\ndrivers/media/test-drivers/vidtv/Makefile-7-\n--\ndrivers/misc/sgi-xp/Makefile=9=obj-$(CONFIG_SGI_XP)\t\t+= xpc.o\ndrivers/misc/sgi-xp/Makefile:10:xpc-y\t\t\t\t:= xpc_main.o xpc_channel.o xpc_partition.o \\\ndrivers/misc/sgi-xp/Makefile-11-\t\t\t\t xpc_uv.o\n--\ndrivers/net/ethernet/microsoft/mana/Makefile=5=obj-$(CONFIG_MICROSOFT_MANA) += mana.o\ndrivers/net/ethernet/microsoft/mana/Makefile:6:mana-objs := gdma_main.o shm_channel.o hw_channel.o mana_en.o mana_ethtool.o mana_bpf.o\n--\ndrivers/net/wireless/ath/ath9k/Makefile=2=ath9k-y +=\tbeacon.o \\\n--\ndrivers/net/wireless/ath/ath9k/Makefile-9-\t\tantenna.o \\\ndrivers/net/wireless/ath/ath9k/Makefile:10:\t\tchannel.o\ndrivers/net/wireless/ath/ath9k/Makefile-11-\n--\ndrivers/net/wireless/broadcom/brcm80211/brcmsmac/Makefile=23=brcmsmac-y := \\\n--\ndrivers/net/wireless/broadcom/brcm80211/brcmsmac/Makefile-27-\tantsel.o \\\ndrivers/net/wireless/broadcom/brcm80211/brcmsmac/Makefile:28:\tchannel.o \\\ndrivers/net/wireless/broadcom/brcm80211/brcmsmac/Makefile-29-\tmain.o \\\n--\ndrivers/net/wireless/mediatek/mt76/Makefile=11=mt76-y := \\\ndrivers/net/wireless/mediatek/mt76/Makefile-12-\tmmio.o util.o trace.o dma.o mac80211.o debugfs.o eeprom.o \\\ndrivers/net/wireless/mediatek/mt76/Makefile:13:\ttx.o agg-rx.o mcu.o wed.o scan.o channel.o\ndrivers/net/wireless/mediatek/mt76/Makefile-14-\n--\nnet/mac80211/Makefile=5=mac80211-y := \\\n--\nnet/mac80211/Makefile-11-\twpa.o \\\nnet/mac80211/Makefile:12:\tscan.o offchannel.o \\\nnet/mac80211/Makefile-13-\tht.o agg-tx.o agg-rx.o \\\n--\nnet/sunrpc/xprtrdma/Makefile=4=rpcrdma-y := transport.o rpc_rdma.o verbs.o frwr_ops.o ib_client.o \\\nnet/sunrpc/xprtrdma/Makefile:5:\tsvc_rdma.o svc_rdma_backchannel.o svc_rdma_transport.o \\\nnet/sunrpc/xprtrdma/Makefile-6-\tsvc_rdma_sendto.o svc_rdma_recvfrom.o svc_rdma_rw.o \\\nnet/sunrpc/xprtrdma/Makefile-7-\tsvc_rdma_pcl.o module.o\nnet/sunrpc/xprtrdma/Makefile:8:rpcrdma-$(CONFIG_SUNRPC_BACKCHANNEL) += backchannel.o\n"
} |
| 13/2 | 2026/07/22 14:54 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 15394 output: 23 thoughts: 410 Thoughts: **Analyzing the Hyper-V VMBus Module and Associated Network Driver** Okay, so I'm looking at this `hv_vmbus` module, and it's clearly tied to `CONFIG_HYPERV`. Now, a key question is: are `CONFIG_HYPERV` and specifically `CONFIG_HYPERV_NET` even accessible within the syzkaller environment? I know syzkaller has pseudo-syscalls like `syz_vmbus_establish_gpadl`, which suggests vmbus is supported. Hmm, I have to remember that note about pseudo-syscalls. It says I can't search for 'syz_' expressions because those aren't directly in the kernel source. But I know syzkaller *does* have vmbus support, so I can put that aside for now. Now, this `netvsc` is the network driver, and that opens up the possibility of fuzzing it if the right device is present. The patch seems to be modifying a number of key functions: `vmbus_establish_gpadl_caller_decrypted`, `vmbus_establish_gpadl_and_decrypt`, `netvsc_alloc_buf_pages`, `netvsc_free_buf_pages`, `netvsc_workqueue_init`, `netvsc_workqueue_destroy`, and `netvsc_device_add`. Looks like functional changes all around. Let's nail down what the important FocusSymbols are here. I'm focusing on: `vmbus_establish_gpadl_caller_decrypted`, `vmbus_establish_gpadl_and_decrypt`, `netvsc_alloc_buf_pages`, `netvsc_free_buf_pages`, `netvsc_device_add`, `netvsc_init_buf` and `__vmbus_establish_gpadl`. Alright, I'm going to dig into `drivers/net/hyperv/netvsc.c` and take a closer look at `netvsc_alloc_buf_pages` and `netvsc_free_buf_pages`. |
| 14/2 | 2026/07/22 14:55 | tool | grepper |
0mArgs:{
"Expression": "netvsc_alloc_buf_pages"
}Results: {
"Output": "drivers/net/hyperv/netvsc.c=101=static void netvsc_subchan_work(struct work_struct *w)\n--\ndrivers/net/hyperv/netvsc.c-136- * failed allocation)\ndrivers/net/hyperv/netvsc.c:137: * @chunks: chunks array from netvsc_alloc_buf_pages(), or NULL\ndrivers/net/hyperv/netvsc.c-138- * @chunk_cnt: number of entries in @chunks\n--\ndrivers/net/hyperv/netvsc.c=145=static void netvsc_free_buf_pages(void *addr,\n--\ndrivers/net/hyperv/netvsc.c-171-/*\ndrivers/net/hyperv/netvsc.c:172: * netvsc_alloc_buf_pages - allocate a virtually-contiguous, host-visible\ndrivers/net/hyperv/netvsc.c-173- * buffer for the netvsc send/receive area.\n--\ndrivers/net/hyperv/netvsc.c-187- */\ndrivers/net/hyperv/netvsc.c:188:static void *netvsc_alloc_buf_pages(int node, u32 size,\ndrivers/net/hyperv/netvsc.c-189-\t\t\t\t struct netvsc_buf_chunk **chunks_out,\n--\ndrivers/net/hyperv/netvsc.c=509=static int netvsc_init_buf(struct hv_device *device,\n--\ndrivers/net/hyperv/netvsc.c-535-\t\tnet_device-\u003erecv_buf =\ndrivers/net/hyperv/netvsc.c:536:\t\t\tnetvsc_alloc_buf_pages(cpu_to_node(device-\u003echannel-\u003etarget_cpu),\ndrivers/net/hyperv/netvsc.c-537-\t\t\t\t\t buf_size,\n--\ndrivers/net/hyperv/netvsc.c-655-\t\tnet_device-\u003esend_buf =\ndrivers/net/hyperv/netvsc.c:656:\t\t\tnetvsc_alloc_buf_pages(cpu_to_node(device-\u003echannel-\u003etarget_cpu),\ndrivers/net/hyperv/netvsc.c-657-\t\t\t\t\t buf_size,\n"
} |
| 15/2 | 2026/07/22 14:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 16884 output: 27 thoughts: 110 Thoughts: **Focusing on Key Functions** Okay, so I need to zero in on a few crucial functions in this process. My initial assessment points me towards these: `vmbus_establish_gpadl_caller_decrypted`, `vmbus_establish_gpadl_and_decrypt`, `netvsc_alloc_buf_pages`, `netvsc_free_buf_pages`, and `netvsc_init_buf`. These seem like the core building blocks for the data transfer and memory management within the virtualized network interface. I'm particularly curious about how the GPADL establishment and decryption are handled, so I'll start by confirming the location of `vmbus_establish_gpadl_and_decrypt`. Based on my experience, I'm pretty sure it's in the `drivers/hv/channel.c` file, but it's always best to verify. Let's make sure. |
| 16/2 | 2026/07/22 14:55 | tool | grepper |
0mArgs:{
"Expression": "vmbus_establish_gpadl_and_decrypt"
}Results: {
"Output": "drivers/hv/channel.c=445=static int __vmbus_establish_gpadl(struct vmbus_channel *channel,\n--\ndrivers/hv/channel.c-543-/*\ndrivers/hv/channel.c:544: * vmbus_establish_gpadl_and_decrypt - Manage the encryption lifecycle of\ndrivers/hv/channel.c-545- * @kbuffer around a __vmbus_establish_gpadl() call.\n--\ndrivers/hv/channel.c-552- */\ndrivers/hv/channel.c:553:static int vmbus_establish_gpadl_and_decrypt(struct vmbus_channel *channel,\ndrivers/hv/channel.c-554-\t\t\t\t\t enum hv_gpadl_type type,\n--\ndrivers/hv/channel.c=592=int vmbus_establish_gpadl(struct vmbus_channel *channel, void *kbuffer,\n--\ndrivers/hv/channel.c-594-{\ndrivers/hv/channel.c:595:\treturn vmbus_establish_gpadl_and_decrypt(channel, HV_GPADL_BUFFER,\ndrivers/hv/channel.c-596-\t\t\t\t\t\t kbuffer, size, 0U, gpadl);\n--\ndrivers/hv/channel.c=693=static int __vmbus_open(struct vmbus_channel *newchannel,\n--\ndrivers/hv/channel.c-728-\ndrivers/hv/channel.c:729:\terr = vmbus_establish_gpadl_and_decrypt(newchannel, HV_GPADL_RING,\ndrivers/hv/channel.c-730-\t\t\t\t\t\tpage_address(newchannel-\u003eringbuffer_page),\n"
} |
| 17/2 | 2026/07/22 14:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 17667 output: 20 thoughts: 197 Thoughts: **Patch Analysis and Fuzzing Strategy** Okay, so I've examined this patch, and my initial impression is that it's definitely touching some critical Hyper-V networking and VMBus core logic. Based on this, I'm confident it warrants a fuzzing effort. Therefore, I'm going to set `WorthFuzzing=true`. Now, let's zero in on the key symbols this patch modifies: I'm particularly interested in `vmbus_establish_gpadl_caller_decrypted`, `vmbus_establish_gpadl_and_decrypt`, `netvsc_alloc_buf_pages`, `netvsc_free_buf_pages`, `netvsc_init_buf`, and `__vmbus_establish_gpadl`. These are the areas where I'd anticipate the most interesting and potentially vulnerable interactions. These will be my focus symbols. I need to confirm the appropriate configurations are enabled for this to be active. I see it's linked to `HYPERV` and `HYPERV_NET`. Let me double-check that `HYPERV_NET` is the only config for netvsc. Yes, that's correct. `CONFIG_HYPERV_NET` it is! And `HYPERV` is for vmbus. I'll take a quick scan to see if there are any other related configurations that are relevant, just to be thorough. |
| 18/2 | 2026/07/22 14:55 | tool | grepper |
0mArgs:{
"Expression": "config HYPERV_NET"
}Results: {
"Output": "drivers/net/hyperv/Kconfig-1-# SPDX-License-Identifier: GPL-2.0-only\ndrivers/net/hyperv/Kconfig:2:config HYPERV_NET\ndrivers/net/hyperv/Kconfig-3-\ttristate \"Microsoft Hyper-V virtual network driver\"\n"
} |
| 19/2 | 2026/07/22 14:55 | llm | patch-evaluator |
0mModel:gemini-3.1-pro-preview Args: null Results: null Tokens: input: 18278 output: 183 thoughts: 138 Thoughts: **My Reasoning on the Hyper-V Network Driver Patch** Okay, so there isn't any other configuration to consider here; it's a focused change. My immediate assessment is that this patch primarily revolves around enhancements for Confidential VMs within the Hyper-V environment. It's drilling into the `netvsc` network driver and the VMBus channel code. The core logic change seems to be around managing the encryption lifecycle of GPADL buffers. Specifically, I see the introduction of new functions. It looks like `netvsc_alloc_buf_pages` and `netvsc_free_buf_pages` handle the allocation and deallocation of buffer pages. That's pretty standard for memory management, of course. Then, things get more interesting with `vmbus_establish_gpadl_caller_decrypted` and `vmbus_establish_gpadl_and_decrypt`. These strongly suggest functional changes to the way GPADLs are established, and decryption is managed for this driver logic. This would all be reachable driver logic, of course, as the functions are intended to perform an operation. Now to set the results. |
| 20/2 | 2026/07/22 14:55 | tool | set-results |
0mArgs:{
"EnableConfigs": [
"HYPERV",
"HYPERV_NET"
],
"FocusSymbols": [
"vmbus_establish_gpadl_caller_decrypted",
"vmbus_establish_gpadl_and_decrypt",
"netvsc_alloc_buf_pages",
"netvsc_free_buf_pages",
"netvsc_init_buf",
"__vmbus_establish_gpadl"
],
"Reasoning": "The patch modifies the Hyper-V network driver (netvsc) and VMBus channel code to manage the encryption lifecycle of GPADL buffers, particularly for Confidential VMs. It introduces new functions to allocate/free buffers and establish GPADLs. These are functional changes to reachable driver logic.",
"WorthFuzzing": true
}Results: {
"EnableConfigs": [
"HYPERV",
"HYPERV_NET"
],
"FocusSymbols": [
"vmbus_establish_gpadl_caller_decrypted",
"vmbus_establish_gpadl_and_decrypt",
"netvsc_alloc_buf_pages",
"netvsc_free_buf_pages",
"netvsc_init_buf",
"__vmbus_establish_gpadl"
],
"Reasoning": "The patch modifies the Hyper-V network driver (netvsc) and VMBus channel code to manage the encryption lifecycle of GPADL buffers, particularly for Confidential VMs. It introduces new functions to allocate/free buffers and establish GPADLs. These are functional changes to reachable driver logic.",
"WorthFuzzing": true
} |
| Total Calls | Total Tokens | Avg Tokens | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|
| Total Calls | Total Duration (Seconds) | Avg Duration (Seconds) |
|---|