Currently, pointers to guest memory are passed to Hyper-V as transaction IDs in netvsc. In the face of errors or malicious behavior in Hyper-V, netvsc should not expose or trust the transaction IDs returned by Hyper-V to be valid guest memory addresses. Instead, use small integers generated by vmbus_requestor as requests (transaction) IDs.
Cc: David S. Miller <da...@davemloft.net> Cc: Jakub Kicinski <k...@kernel.org> Cc: net...@vger.kernel.org Signed-off-by: Andres Beltran <lkmlab...@gmail.com> --- Changes in v2: - Add casts to unsigned long to fix warnings on 32bit. - Use an inline function to get the requestor size. drivers/net/hyperv/hyperv_net.h | 13 +++++ drivers/net/hyperv/netvsc.c | 79 +++++++++++++++++++++++++------ drivers/net/hyperv/rndis_filter.c | 1 + include/linux/hyperv.h | 1 + 4 files changed, 80 insertions(+), 14 deletions(-) diff --git a/drivers/net/hyperv/hyperv_net.h b/drivers/net/hyperv/hyperv_net.h index abda736e7c7d..f43b614f2345 100644 --- a/drivers/net/hyperv/hyperv_net.h +++ b/drivers/net/hyperv/hyperv_net.h @@ -847,6 +847,19 @@ struct nvsp_message { #define NETVSC_XDP_HDRM 256 +#define NETVSC_MIN_OUT_MSG_SIZE (sizeof(struct vmpacket_descriptor) + \ + sizeof(struct nvsp_message)) +#define NETVSC_MIN_IN_MSG_SIZE sizeof(struct vmpacket_descriptor) + +/* Estimated requestor size: + * out_ring_size/min_out_msg_size + in_ring_size/min_in_msg_size + */ +static inline u32 netvsc_rqstor_size(unsigned long ringbytes) +{ + return ringbytes / NETVSC_MIN_OUT_MSG_SIZE + + ringbytes / NETVSC_MIN_IN_MSG_SIZE; +} + struct multi_send_data { struct sk_buff *skb; /* skb containing the pkt */ struct hv_netvsc_packet *pkt; /* netvsc pkt pending */ diff --git a/drivers/net/hyperv/netvsc.c b/drivers/net/hyperv/netvsc.c index 41f5cf0bb997..79b907a29433 100644 --- a/drivers/net/hyperv/netvsc.c +++ b/drivers/net/hyperv/netvsc.c @@ -50,7 +50,7 @@ void netvsc_switch_datapath(struct net_device *ndev, bool vf) vmbus_sendpacket(dev->channel, init_pkt, sizeof(struct nvsp_message), - (unsigned long)init_pkt, + VMBUS_RQST_ID_NO_RESPONSE, VM_PKT_DATA_INBAND, 0); } @@ -163,7 +163,7 @@ static void netvsc_revoke_recv_buf(struct hv_device *device, ret = vmbus_sendpacket(device->channel, revoke_packet, sizeof(struct nvsp_message), - (unsigned long)revoke_packet, + VMBUS_RQST_ID_NO_RESPONSE, VM_PKT_DATA_INBAND, 0); /* If the failure is because the channel is rescinded; * ignore the failure since we cannot send on a rescinded @@ -213,7 +213,7 @@ static void netvsc_revoke_send_buf(struct hv_device *device, ret = vmbus_sendpacket(device->channel, revoke_packet, sizeof(struct nvsp_message), - (unsigned long)revoke_packet, + VMBUS_RQST_ID_NO_RESPONSE, VM_PKT_DATA_INBAND, 0); /* If the failure is because the channel is rescinded; @@ -304,6 +304,7 @@ static int netvsc_init_buf(struct hv_device *device, unsigned int buf_size; size_t map_words; int ret = 0; + u64 rqst_id; /* Get receive buffer area. */ buf_size = device_info->recv_sections * device_info->recv_section_size; @@ -350,13 +351,22 @@ static int netvsc_init_buf(struct hv_device *device, trace_nvsp_send(ndev, init_packet); + rqst_id = vmbus_next_request_id(&device->channel->requestor, + (unsigned long)init_packet); + if (rqst_id == VMBUS_RQST_ERROR) { + netdev_err(ndev, "No request id available\n"); + goto cleanup; + } + /* Send the gpadl notification request */ ret = vmbus_sendpacket(device->channel, init_packet, sizeof(struct nvsp_message), - (unsigned long)init_packet, + rqst_id, VM_PKT_DATA_INBAND, VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); if (ret != 0) { + /* Reclaim request ID to avoid leak of IDs */ + vmbus_request_addr(&device->channel->requestor, rqst_id); netdev_err(ndev, "unable to send receive buffer's gpadl to netvsp\n"); goto cleanup; @@ -432,13 +442,22 @@ static int netvsc_init_buf(struct hv_device *device, trace_nvsp_send(ndev, init_packet); + rqst_id = vmbus_next_request_id(&device->channel->requestor, + (unsigned long)init_packet); + if (rqst_id == VMBUS_RQST_ERROR) { + netdev_err(ndev, "No request id available\n"); + goto cleanup; + } + /* Send the gpadl notification request */ ret = vmbus_sendpacket(device->channel, init_packet, sizeof(struct nvsp_message), - (unsigned long)init_packet, + rqst_id, VM_PKT_DATA_INBAND, VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); if (ret != 0) { + /* Reclaim request ID to avoid leak of IDs */ + vmbus_request_addr(&device->channel->requestor, rqst_id); netdev_err(ndev, "unable to send send buffer's gpadl to netvsp\n"); goto cleanup; @@ -496,6 +515,7 @@ static int negotiate_nvsp_ver(struct hv_device *device, { struct net_device *ndev = hv_get_drvdata(device); int ret; + u64 rqst_id; memset(init_packet, 0, sizeof(struct nvsp_message)); init_packet->hdr.msg_type = NVSP_MSG_TYPE_INIT; @@ -503,15 +523,25 @@ static int negotiate_nvsp_ver(struct hv_device *device, init_packet->msg.init_msg.init.max_protocol_ver = nvsp_ver; trace_nvsp_send(ndev, init_packet); + rqst_id = vmbus_next_request_id(&device->channel->requestor, + (unsigned long)init_packet); + if (rqst_id == VMBUS_RQST_ERROR) { + netdev_err(ndev, "No request id available\n"); + return -EAGAIN; + } + /* Send the init request */ ret = vmbus_sendpacket(device->channel, init_packet, sizeof(struct nvsp_message), - (unsigned long)init_packet, + rqst_id, VM_PKT_DATA_INBAND, VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); - if (ret != 0) + if (ret != 0) { + /* Reclaim request ID to avoid leak of IDs */ + vmbus_request_addr(&device->channel->requestor, rqst_id); return ret; + } wait_for_completion(&net_device->channel_init_wait); @@ -542,7 +572,7 @@ static int negotiate_nvsp_ver(struct hv_device *device, ret = vmbus_sendpacket(device->channel, init_packet, sizeof(struct nvsp_message), - (unsigned long)init_packet, + VMBUS_RQST_ID_NO_RESPONSE, VM_PKT_DATA_INBAND, 0); return ret; @@ -599,7 +629,7 @@ static int netvsc_connect_vsp(struct hv_device *device, /* Send the init request */ ret = vmbus_sendpacket(device->channel, init_packet, sizeof(struct nvsp_message), - (unsigned long)init_packet, + VMBUS_RQST_ID_NO_RESPONSE, VM_PKT_DATA_INBAND, 0); if (ret != 0) goto cleanup; @@ -680,10 +710,19 @@ static void netvsc_send_tx_complete(struct net_device *ndev, const struct vmpacket_descriptor *desc, int budget) { - struct sk_buff *skb = (struct sk_buff *)(unsigned long)desc->trans_id; + struct sk_buff *skb; struct net_device_context *ndev_ctx = netdev_priv(ndev); u16 q_idx = 0; int queue_sends; + u64 cmd_rqst; + + cmd_rqst = vmbus_request_addr(&channel->requestor, (u64)desc->trans_id); + if (cmd_rqst == VMBUS_RQST_ERROR) { + netdev_err(ndev, "Incorrect transaction id\n"); + return; + } + + skb = (struct sk_buff *)(unsigned long)cmd_rqst; /* Notify the layer above us */ if (likely(skb)) { @@ -822,7 +861,7 @@ static inline int netvsc_send_pkt( struct net_device *ndev = hv_get_drvdata(device); struct net_device_context *ndev_ctx = netdev_priv(ndev); struct netdev_queue *txq = netdev_get_tx_queue(ndev, packet->q_idx); - u64 req_id; + u64 rqst_id; int ret; u32 ring_avail = hv_get_avail_to_write_percent(&out_channel->outbound); @@ -838,13 +877,19 @@ static inline int netvsc_send_pkt( else rpkt->send_buf_section_size = packet->total_data_buflen; - req_id = (ulong)skb; if (out_channel->rescind) return -ENODEV; trace_nvsp_send_pkt(ndev, out_channel, rpkt); + rqst_id = vmbus_next_request_id(&out_channel->requestor, + (unsigned long)skb); + if (rqst_id == VMBUS_RQST_ERROR) { + ret = -EAGAIN; + goto ret_check; + } + if (packet->page_buf_cnt) { if (packet->cp_partial) pb += packet->rmsg_pgcnt; @@ -852,14 +897,15 @@ static inline int netvsc_send_pkt( ret = vmbus_sendpacket_pagebuffer(out_channel, pb, packet->page_buf_cnt, &nvmsg, sizeof(nvmsg), - req_id); + rqst_id); } else { ret = vmbus_sendpacket(out_channel, &nvmsg, sizeof(nvmsg), - req_id, VM_PKT_DATA_INBAND, + rqst_id, VM_PKT_DATA_INBAND, VMBUS_DATA_PACKET_FLAG_COMPLETION_REQUESTED); } +ret_check: if (ret == 0) { atomic_inc_return(&nvchan->queue_sends); @@ -868,9 +914,13 @@ static inline int netvsc_send_pkt( ndev_ctx->eth_stats.stop_queue++; } } else if (ret == -EAGAIN) { + /* Reclaim request ID to avoid leak of IDs */ + vmbus_request_addr(&out_channel->requestor, rqst_id); netif_tx_stop_queue(txq); ndev_ctx->eth_stats.stop_queue++; } else { + /* Reclaim request ID to avoid leak of IDs */ + vmbus_request_addr(&out_channel->requestor, rqst_id); netdev_err(ndev, "Unable to send packet pages %u len %u, ret %d\n", packet->page_buf_cnt, packet->total_data_buflen, @@ -1422,6 +1472,7 @@ struct netvsc_device *netvsc_device_add(struct hv_device *device, netvsc_poll, NAPI_POLL_WEIGHT); /* Open the channel */ + device->channel->rqstor_size = netvsc_rqstor_size(netvsc_ring_bytes); ret = vmbus_open(device->channel, netvsc_ring_bytes, netvsc_ring_bytes, NULL, 0, netvsc_channel_cb, net_device->chan_table); diff --git a/drivers/net/hyperv/rndis_filter.c b/drivers/net/hyperv/rndis_filter.c index b81ceba38218..10489ba44a09 100644 --- a/drivers/net/hyperv/rndis_filter.c +++ b/drivers/net/hyperv/rndis_filter.c @@ -1114,6 +1114,7 @@ static void netvsc_sc_open(struct vmbus_channel *new_sc) /* Set the channel before opening.*/ nvchan->channel = new_sc; + new_sc->rqstor_size = netvsc_rqstor_size(netvsc_ring_bytes); ret = vmbus_open(new_sc, netvsc_ring_bytes, netvsc_ring_bytes, NULL, 0, netvsc_channel_cb, nvchan); diff --git a/include/linux/hyperv.h b/include/linux/hyperv.h index c509d20ab7db..d8194924983d 100644 --- a/include/linux/hyperv.h +++ b/include/linux/hyperv.h @@ -730,6 +730,7 @@ struct vmbus_requestor { }; #define VMBUS_RQST_ERROR U64_MAX +#define VMBUS_RQST_ID_NO_RESPONSE (U64_MAX - 1) struct vmbus_device { u16 dev_type; -- 2.25.1