Commit adae1e93 authored by Andres Beltran's avatar Andres Beltran Committed by Wei Liu
Browse files

Drivers: hv: vmbus: Copy packets sent by Hyper-V out of the ring buffer



Pointers to ring-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, return a copy of the incoming VMBus packet
after validating its length and offset fields in hv_pkt_iter_first().
In this way, the packet can no longer be modified by the host.

Signed-off-by: default avatarAndres Beltran <lkmlabelt@gmail.com>
Co-developed-by: default avatarAndrea Parri (Microsoft) <parri.andrea@gmail.com>
Signed-off-by: default avatarAndrea Parri (Microsoft) <parri.andrea@gmail.com>
Reviewed-by: default avatarMichael Kelley <mikelley@microsoft.com>
Link: https://lore.kernel.org/r/20210408161439.341988-1-parri.andrea@gmail.com


Signed-off-by: default avatarWei Liu <wei.liu@kernel.org>
parent 03b30cc3
Loading
Loading
Loading
Loading
+6 −3
Original line number Diff line number Diff line
@@ -662,12 +662,15 @@ static int __vmbus_open(struct vmbus_channel *newchannel,
	newchannel->onchannel_callback = onchannelcallback;
	newchannel->channel_callback_context = context;

	err = hv_ringbuffer_init(&newchannel->outbound, page, send_pages);
	if (!newchannel->max_pkt_size)
		newchannel->max_pkt_size = VMBUS_DEFAULT_MAX_PKT_SIZE;

	err = hv_ringbuffer_init(&newchannel->outbound, page, send_pages, 0);
	if (err)
		goto error_clean_ring;

	err = hv_ringbuffer_init(&newchannel->inbound,
				 &page[send_pages], recv_pages);
	err = hv_ringbuffer_init(&newchannel->inbound, &page[send_pages],
				 recv_pages, newchannel->max_pkt_size);
	if (err)
		goto error_clean_ring;

+1 −0
Original line number Diff line number Diff line
@@ -349,6 +349,7 @@ int hv_fcopy_init(struct hv_util_service *srv)
{
	recv_buffer = srv->recv_buffer;
	fcopy_transaction.recv_channel = srv->channel;
	fcopy_transaction.recv_channel->max_pkt_size = HV_HYP_PAGE_SIZE * 2;

	/*
	 * When this driver loads, the user level daemon that
+1 −0
Original line number Diff line number Diff line
@@ -757,6 +757,7 @@ hv_kvp_init(struct hv_util_service *srv)
{
	recv_buffer = srv->recv_buffer;
	kvp_transaction.recv_channel = srv->channel;
	kvp_transaction.recv_channel->max_pkt_size = HV_HYP_PAGE_SIZE * 4;

	/*
	 * When this driver loads, the user level daemon that
+1 −1
Original line number Diff line number Diff line
@@ -174,7 +174,7 @@ extern int hv_synic_cleanup(unsigned int cpu);
void hv_ringbuffer_pre_init(struct vmbus_channel *channel);

int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
		       struct page *pages, u32 pagecnt);
		       struct page *pages, u32 pagecnt, u32 max_pkt_size);

void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info);

+71 −11
Original line number Diff line number Diff line
@@ -181,7 +181,7 @@ void hv_ringbuffer_pre_init(struct vmbus_channel *channel)

/* Initialize the ring buffer. */
int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
		       struct page *pages, u32 page_cnt)
		       struct page *pages, u32 page_cnt, u32 max_pkt_size)
{
	int i;
	struct page **pages_wraparound;
@@ -223,6 +223,14 @@ int hv_ringbuffer_init(struct hv_ring_buffer_info *ring_info,
		sizeof(struct hv_ring_buffer);
	ring_info->priv_read_index = 0;

	/* Initialize buffer that holds copies of incoming packets */
	if (max_pkt_size) {
		ring_info->pkt_buffer = kzalloc(max_pkt_size, GFP_KERNEL);
		if (!ring_info->pkt_buffer)
			return -ENOMEM;
		ring_info->pkt_buffer_size = max_pkt_size;
	}

	spin_lock_init(&ring_info->ring_lock);

	return 0;
@@ -235,6 +243,9 @@ void hv_ringbuffer_cleanup(struct hv_ring_buffer_info *ring_info)
	vunmap(ring_info->ring_buffer);
	ring_info->ring_buffer = NULL;
	mutex_unlock(&ring_info->ring_buffer_mutex);

	kfree(ring_info->pkt_buffer);
	ring_info->pkt_buffer_size = 0;
}

/* Write to the ring buffer. */
@@ -375,7 +386,7 @@ int hv_ringbuffer_read(struct vmbus_channel *channel,
	memcpy(buffer, (const char *)desc + offset, packetlen);

	/* Advance ring index to next packet descriptor */
	__hv_pkt_iter_next(channel, desc);
	__hv_pkt_iter_next(channel, desc, true);

	/* Notify host of update */
	hv_pkt_iter_close(channel);
@@ -401,6 +412,22 @@ static u32 hv_pkt_iter_avail(const struct hv_ring_buffer_info *rbi)
		return (rbi->ring_datasize - priv_read_loc) + write_loc;
}

/*
 * Get first vmbus packet without copying it out of the ring buffer
 */
struct vmpacket_descriptor *hv_pkt_iter_first_raw(struct vmbus_channel *channel)
{
	struct hv_ring_buffer_info *rbi = &channel->inbound;

	hv_debug_delay_test(channel, MESSAGE_DELAY);

	if (hv_pkt_iter_avail(rbi) < sizeof(struct vmpacket_descriptor))
		return NULL;

	return (struct vmpacket_descriptor *)(hv_get_ring_buffer(rbi) + rbi->priv_read_index);
}
EXPORT_SYMBOL_GPL(hv_pkt_iter_first_raw);

/*
 * Get first vmbus packet from ring buffer after read_index
 *
@@ -409,17 +436,49 @@ static u32 hv_pkt_iter_avail(const struct hv_ring_buffer_info *rbi)
struct vmpacket_descriptor *hv_pkt_iter_first(struct vmbus_channel *channel)
{
	struct hv_ring_buffer_info *rbi = &channel->inbound;
	struct vmpacket_descriptor *desc;
	struct vmpacket_descriptor *desc, *desc_copy;
	u32 bytes_avail, pkt_len, pkt_offset;

	hv_debug_delay_test(channel, MESSAGE_DELAY);
	if (hv_pkt_iter_avail(rbi) < sizeof(struct vmpacket_descriptor))
	desc = hv_pkt_iter_first_raw(channel);
	if (!desc)
		return NULL;

	desc = hv_get_ring_buffer(rbi) + rbi->priv_read_index;
	if (desc)
		prefetch((char *)desc + (desc->len8 << 3));
	bytes_avail = min(rbi->pkt_buffer_size, hv_pkt_iter_avail(rbi));

	/*
	 * Ensure the compiler does not use references to incoming Hyper-V values (which
	 * could change at any moment) when reading local variables later in the code
	 */
	pkt_len = READ_ONCE(desc->len8) << 3;
	pkt_offset = READ_ONCE(desc->offset8) << 3;

	/*
	 * If pkt_len is invalid, set it to the smaller of hv_pkt_iter_avail() and
	 * rbi->pkt_buffer_size
	 */
	if (pkt_len < sizeof(struct vmpacket_descriptor) || pkt_len > bytes_avail)
		pkt_len = bytes_avail;

	/*
	 * If pkt_offset is invalid, arbitrarily set it to
	 * the size of vmpacket_descriptor
	 */
	if (pkt_offset < sizeof(struct vmpacket_descriptor) || pkt_offset > pkt_len)
		pkt_offset = sizeof(struct vmpacket_descriptor);

	/* Copy the Hyper-V packet out of the ring buffer */
	desc_copy = (struct vmpacket_descriptor *)rbi->pkt_buffer;
	memcpy(desc_copy, desc, pkt_len);

	/*
	 * Hyper-V could still change len8 and offset8 after the earlier read.
	 * Ensure that desc_copy has legal values for len8 and offset8 that
	 * are consistent with the copy we just made
	 */
	desc_copy->len8 = pkt_len >> 3;
	desc_copy->offset8 = pkt_offset >> 3;

	return desc;
	return desc_copy;
}
EXPORT_SYMBOL_GPL(hv_pkt_iter_first);

@@ -431,7 +490,8 @@ EXPORT_SYMBOL_GPL(hv_pkt_iter_first);
 */
struct vmpacket_descriptor *
__hv_pkt_iter_next(struct vmbus_channel *channel,
		   const struct vmpacket_descriptor *desc)
		   const struct vmpacket_descriptor *desc,
		   bool copy)
{
	struct hv_ring_buffer_info *rbi = &channel->inbound;
	u32 packetlen = desc->len8 << 3;
@@ -444,7 +504,7 @@ __hv_pkt_iter_next(struct vmbus_channel *channel,
		rbi->priv_read_index -= dsize;

	/* more data? */
	return hv_pkt_iter_first(channel);
	return copy ? hv_pkt_iter_first(channel) : hv_pkt_iter_first_raw(channel);
}
EXPORT_SYMBOL_GPL(__hv_pkt_iter_next);

Loading