Skip to content

Commit

Permalink
hv_netvsc: Copy packets sent by Hyper-V out of the receive buffer
Browse files Browse the repository at this point in the history
Pointers to receive-buffer packets sent by Hyper-V are used within the
guest VM.  Hyper-V can send packets with erroneous values or modify
packet fields after they are processed by the guest.  To defend against
these scenarios, copy (sections of) the incoming packet after validating
their length and offset fields in netvsc_filter_receive().  In this way,
the packet can no longer be modified by the host.

Reported-by: Juan Vazquez <juvazq@microsoft.com>
Signed-off-by: Andrea Parri (Microsoft) <parri.andrea@gmail.com>
Link: https://lore.kernel.org/r/20210126162907.21056-1-parri.andrea@gmail.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
  • Loading branch information
Andrea Parri (Microsoft) authored and Jakub Kicinski committed Jan 30, 2021
1 parent 46eb3c1 commit 0ba35fe
Show file tree
Hide file tree
Showing 4 changed files with 150 additions and 86 deletions.
93 changes: 48 additions & 45 deletions drivers/net/hyperv/hyperv_net.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,43 @@ struct ndis_recv_scale_param { /* NDIS_RECEIVE_SCALE_PARAMETERS */
u32 processor_masks_entry_size;
};

/* Fwd declaration */
struct ndis_tcp_ip_checksum_info;
struct ndis_pkt_8021q_info;
struct ndis_tcp_ip_checksum_info {
union {
struct {
u32 is_ipv4:1;
u32 is_ipv6:1;
u32 tcp_checksum:1;
u32 udp_checksum:1;
u32 ip_header_checksum:1;
u32 reserved:11;
u32 tcp_header_offset:10;
} transmit;
struct {
u32 tcp_checksum_failed:1;
u32 udp_checksum_failed:1;
u32 ip_checksum_failed:1;
u32 tcp_checksum_succeeded:1;
u32 udp_checksum_succeeded:1;
u32 ip_checksum_succeeded:1;
u32 loopback:1;
u32 tcp_checksum_value_invalid:1;
u32 ip_checksum_value_invalid:1;
} receive;
u32 value;
};
};

struct ndis_pkt_8021q_info {
union {
struct {
u32 pri:3; /* User Priority */
u32 cfi:1; /* Canonical Format ID */
u32 vlanid:12; /* VLAN ID */
u32 reserved:16;
};
u32 value;
};
};

/*
* Represent netvsc packet which contains 1 RNDIS and 1 ethernet frame
Expand Down Expand Up @@ -194,7 +228,8 @@ int netvsc_send(struct net_device *net,
struct sk_buff *skb,
bool xdp_tx);
void netvsc_linkstatus_callback(struct net_device *net,
struct rndis_message *resp);
struct rndis_message *resp,
void *data);
int netvsc_recv_callback(struct net_device *net,
struct netvsc_device *nvdev,
struct netvsc_channel *nvchan);
Expand Down Expand Up @@ -884,16 +919,21 @@ struct multi_recv_comp {
#define NVSP_RSC_MAX 562 /* Max #RSC frags in a vmbus xfer page pkt */

struct nvsc_rsc {
const struct ndis_pkt_8021q_info *vlan;
const struct ndis_tcp_ip_checksum_info *csum_info;
const u32 *hash_info;
struct ndis_pkt_8021q_info vlan;
struct ndis_tcp_ip_checksum_info csum_info;
u32 hash_info;
u8 ppi_flags; /* valid/present bits for the above PPIs */
u8 is_last; /* last RNDIS msg in a vmtransfer_page */
u32 cnt; /* #fragments in an RSC packet */
u32 pktlen; /* Full packet length */
void *data[NVSP_RSC_MAX];
u32 len[NVSP_RSC_MAX];
};

#define NVSC_RSC_VLAN BIT(0) /* valid/present bit for 'vlan' */
#define NVSC_RSC_CSUM_INFO BIT(1) /* valid/present bit for 'csum_info' */
#define NVSC_RSC_HASH_INFO BIT(2) /* valid/present bit for 'hash_info' */

struct netvsc_stats {
u64 packets;
u64 bytes;
Expand Down Expand Up @@ -1002,6 +1042,7 @@ struct net_device_context {
struct netvsc_channel {
struct vmbus_channel *channel;
struct netvsc_device *net_device;
void *recv_buf; /* buffer to copy packets out from the receive buffer */
const struct vmpacket_descriptor *desc;
struct napi_struct napi;
struct multi_send_data msd;
Expand Down Expand Up @@ -1234,18 +1275,6 @@ struct rndis_pktinfo_id {
u16 pkt_id;
};

struct ndis_pkt_8021q_info {
union {
struct {
u32 pri:3; /* User Priority */
u32 cfi:1; /* Canonical Format ID */
u32 vlanid:12; /* VLAN ID */
u32 reserved:16;
};
u32 value;
};
};

struct ndis_object_header {
u8 type;
u8 revision;
Expand Down Expand Up @@ -1436,32 +1465,6 @@ struct ndis_offload_params {
};
};

struct ndis_tcp_ip_checksum_info {
union {
struct {
u32 is_ipv4:1;
u32 is_ipv6:1;
u32 tcp_checksum:1;
u32 udp_checksum:1;
u32 ip_header_checksum:1;
u32 reserved:11;
u32 tcp_header_offset:10;
} transmit;
struct {
u32 tcp_checksum_failed:1;
u32 udp_checksum_failed:1;
u32 ip_checksum_failed:1;
u32 tcp_checksum_succeeded:1;
u32 udp_checksum_succeeded:1;
u32 ip_checksum_succeeded:1;
u32 loopback:1;
u32 tcp_checksum_value_invalid:1;
u32 ip_checksum_value_invalid:1;
} receive;
u32 value;
};
};

struct ndis_tcp_lso_info {
union {
struct {
Expand Down
20 changes: 20 additions & 0 deletions drivers/net/hyperv/netvsc.c
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ static void free_netvsc_device(struct rcu_head *head)

for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
xdp_rxq_info_unreg(&nvdev->chan_table[i].xdp_rxq);
kfree(nvdev->chan_table[i].recv_buf);
vfree(nvdev->chan_table[i].mrc.slots);
}

Expand Down Expand Up @@ -1284,6 +1285,19 @@ static int netvsc_receive(struct net_device *ndev,
continue;
}

/* We're going to copy (sections of) the packet into nvchan->recv_buf;
* make sure that nvchan->recv_buf is large enough to hold the packet.
*/
if (unlikely(buflen > net_device->recv_section_size)) {
nvchan->rsc.cnt = 0;
status = NVSP_STAT_FAIL;
netif_err(net_device_ctx, rx_err, ndev,
"Packet too big: buflen=%u recv_section_size=%u\n",
buflen, net_device->recv_section_size);

continue;
}

data = recv_buf + offset;

nvchan->rsc.is_last = (i == count - 1);
Expand Down Expand Up @@ -1535,6 +1549,12 @@ struct netvsc_device *netvsc_device_add(struct hv_device *device,
for (i = 0; i < VRSS_CHANNEL_MAX; i++) {
struct netvsc_channel *nvchan = &net_device->chan_table[i];

nvchan->recv_buf = kzalloc(device_info->recv_section_size, GFP_KERNEL);
if (nvchan->recv_buf == NULL) {
ret = -ENOMEM;
goto cleanup2;
}

nvchan->channel = device->channel;
nvchan->net_device = net_device;
u64_stats_init(&nvchan->tx_stats.syncp);
Expand Down
24 changes: 14 additions & 10 deletions drivers/net/hyperv/netvsc_drv.c
Original file line number Diff line number Diff line change
Expand Up @@ -743,7 +743,8 @@ static netdev_tx_t netvsc_start_xmit(struct sk_buff *skb,
* netvsc_linkstatus_callback - Link up/down notification
*/
void netvsc_linkstatus_callback(struct net_device *net,
struct rndis_message *resp)
struct rndis_message *resp,
void *data)
{
struct rndis_indicate_status *indicate = &resp->msg.indicate_status;
struct net_device_context *ndev_ctx = netdev_priv(net);
Expand All @@ -757,6 +758,9 @@ void netvsc_linkstatus_callback(struct net_device *net,
return;
}

/* Copy the RNDIS indicate status into nvchan->recv_buf */
memcpy(indicate, data + RNDIS_HEADER_SIZE, sizeof(*indicate));

/* Update the physical link speed when changing to another vSwitch */
if (indicate->status == RNDIS_STATUS_LINK_SPEED_CHANGE) {
u32 speed;
Expand All @@ -771,8 +775,7 @@ void netvsc_linkstatus_callback(struct net_device *net,
return;
}

speed = *(u32 *)((void *)indicate
+ indicate->status_buf_offset) / 10000;
speed = *(u32 *)(data + RNDIS_HEADER_SIZE + indicate->status_buf_offset) / 10000;
ndev_ctx->speed = speed;
return;
}
Expand Down Expand Up @@ -827,10 +830,11 @@ static struct sk_buff *netvsc_alloc_recv_skb(struct net_device *net,
struct xdp_buff *xdp)
{
struct napi_struct *napi = &nvchan->napi;
const struct ndis_pkt_8021q_info *vlan = nvchan->rsc.vlan;
const struct ndis_pkt_8021q_info *vlan = &nvchan->rsc.vlan;
const struct ndis_tcp_ip_checksum_info *csum_info =
nvchan->rsc.csum_info;
const u32 *hash_info = nvchan->rsc.hash_info;
&nvchan->rsc.csum_info;
const u32 *hash_info = &nvchan->rsc.hash_info;
u8 ppi_flags = nvchan->rsc.ppi_flags;
struct sk_buff *skb;
void *xbuf = xdp->data_hard_start;
int i;
Expand Down Expand Up @@ -874,7 +878,7 @@ static struct sk_buff *netvsc_alloc_recv_skb(struct net_device *net,
* We compute it here if the flags are set, because on Linux, the IP
* checksum is always checked.
*/
if (csum_info && csum_info->receive.ip_checksum_value_invalid &&
if ((ppi_flags & NVSC_RSC_CSUM_INFO) && csum_info->receive.ip_checksum_value_invalid &&
csum_info->receive.ip_checksum_succeeded &&
skb->protocol == htons(ETH_P_IP)) {
/* Check that there is enough space to hold the IP header. */
Expand All @@ -886,16 +890,16 @@ static struct sk_buff *netvsc_alloc_recv_skb(struct net_device *net,
}

/* Do L4 checksum offload if enabled and present. */
if (csum_info && (net->features & NETIF_F_RXCSUM)) {
if ((ppi_flags & NVSC_RSC_CSUM_INFO) && (net->features & NETIF_F_RXCSUM)) {
if (csum_info->receive.tcp_checksum_succeeded ||
csum_info->receive.udp_checksum_succeeded)
skb->ip_summed = CHECKSUM_UNNECESSARY;
}

if (hash_info && (net->features & NETIF_F_RXHASH))
if ((ppi_flags & NVSC_RSC_HASH_INFO) && (net->features & NETIF_F_RXHASH))
skb_set_hash(skb, *hash_info, PKT_HASH_TYPE_L4);

if (vlan) {
if (ppi_flags & NVSC_RSC_VLAN) {
u16 vlan_tci = vlan->vlanid | (vlan->pri << VLAN_PRIO_SHIFT) |
(vlan->cfi ? VLAN_CFI_MASK : 0);

Expand Down
Loading

0 comments on commit 0ba35fe

Please sign in to comment.