[PATCH 13/15] virtio_console: Add throttling support to prevent flooding ports

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Rogue processes on guests or hosts could pump in data
and cause an OOM condition. Add support for throttling
so that a limit to the number of outstanding buffers
can be specified.

Signed-off-by: Amit Shah <amit.shah@xxxxxxxxxx>
---
 drivers/char/virtio_console.c  |  102 +++++++++++++++++++++++++++++++++++++++-
 include/linux/virtio_console.h |    3 +
 2 files changed, 104 insertions(+), 1 deletions(-)

diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c
index f7db92a..c45b987 100644
--- a/drivers/char/virtio_console.c
+++ b/drivers/char/virtio_console.c
@@ -143,11 +143,32 @@ struct virtio_console_port {
 	 */
 	u32 vtermno;
 
+	/*
+	 * The Host can set some limit to the number of outstanding
+	 * buffers we can have in the readbuf_head list before we
+	 * should ask the Host to stop any more data from coming
+	 * in. This is to not allow a rogue process on the Host to OOM
+	 * the entire guest.
+	 */
+	u32 buffer_limit;
+
+	/*
+	 * The actual number of buffers we have pending in the
+	 * readbuf_head list
+	 */
+	u32 nr_buffers;
+
 	/* Is the host device open */
 	bool host_connected;
 
 	/* We should allow only one process to open a port */
 	bool guest_connected;
+
+	/* Does the Host not want to accept more data currently?  */
+	bool host_throttled;
+
+	/* Have we sent out a throttle request to the Host? */
+	bool guest_throttled;
 };
 
 struct virtio_console_struct virtconsole;
@@ -367,6 +388,7 @@ static ssize_t fill_readbuf(struct virtio_console_port *port,
 		if (buf->len - buf->offset == 0) {
 			spin_lock(&port->readbuf_list_lock);
 			list_del(&buf->list);
+			port->nr_buffers--;
 			spin_unlock(&port->readbuf_list_lock);
 			kfree(buf->buf);
 			kfree(buf);
@@ -374,6 +396,15 @@ static ssize_t fill_readbuf(struct virtio_console_port *port,
 		if (!out_count)
 			break;
 	}
+	if (port->guest_throttled && port->nr_buffers < port->buffer_limit) {
+		struct virtio_console_control cpkt;
+
+		cpkt.event = VIRTIO_CONSOLE_THROTTLE_PORT;
+		cpkt.value = false;
+		send_buf(port, (char *)&cpkt, sizeof(cpkt),
+			 VIRTIO_CONSOLE_ID_CONTROL, false);
+		port->guest_throttled = false;
+	}
 	return out_offset;
 }
 
@@ -430,6 +461,9 @@ static ssize_t virtconsole_write(struct file *filp, const char __user *ubuf,
 
 	port = filp->private_data;
 
+	if (port->host_throttled)
+		return -ENOSPC;
+
 	return send_buf(port, ubuf, count, 0, true);
 }
 
@@ -444,7 +478,7 @@ static unsigned int virtconsole_poll(struct file *filp, poll_table *wait)
 	ret = 0;
 	if (!list_empty(&port->readbuf_head))
 		ret |= POLLIN | POLLRDNORM;
-	if (port->host_connected)
+	if (port->host_connected && !port->host_throttled)
 		ret |= POLLOUT;
 	if (!port->host_connected)
 		ret |= POLLHUP;
@@ -715,6 +749,35 @@ static void handle_control_message(struct virtio_console_port *port,
 		port->hvc->irq_requested = 1;
 		virtcons_apply_config(port);
 		break;
+	case VIRTIO_CONSOLE_THROTTLE_PORT:
+		/*
+		 * Hosts can govern some policy to disallow rogue
+		 * processes write indefinitely to ports leading to
+		 * OOM situations.  If we receive this message here,
+		 * it means the Host side of the port either reached
+		 * its max. limit to cache data or signal to us that
+		 * the host is ready to accept more data.
+		 */
+		port->host_throttled = cpkt->value;
+		break;
+	case VIRTIO_CONSOLE_BUFFER_LIMIT:
+		/*
+		 * We can ask the Host to stop sending data to us in
+		 * case some rogue process on the Host injects data to
+		 * OOM the entire guest (as unread buffers keep piling
+		 * up in the kernel space). The Host, via this
+		 * message, can tell us the number of Mbytes to
+		 * buffer.
+		 *
+		 * The calculation is easy: we use 4K-sized pages in
+		 * our readbuf_head list. So on each buffer added to
+		 * the list, increment count by 1 (and decrement by 1
+		 * when one gets removed). So we just store the
+		 * (number-in-MB * 1024) / 4 to calculate the number of
+		 * buffers we can have in the list.
+		 */
+		port->buffer_limit = (u32)cpkt->value * 1024 / 4;
+		break;
 	}
 }
 
@@ -845,9 +908,31 @@ static void virtio_console_rx_work_handler(struct work_struct *work)
 			 */
 			port->host_connected = true;
 
+			if (port->guest_throttled) {
+				kfree(buf->buf);
+				kfree(buf);
+				continue;
+			}
 			spin_lock(&port->readbuf_list_lock);
 			list_add_tail(&buf->list, &port->readbuf_head);
+			port->nr_buffers++;
 			spin_unlock(&port->readbuf_list_lock);
+			/*
+			 * If we reached the throttle level, send out
+			 * a message to the host to ask it to stop
+			 * sending us any more data.
+			 */
+			if (virtio_has_feature(port->vcon->vdev,
+					       VIRTIO_CONSOLE_F_THROTTLE)
+			    && (port->nr_buffers >= port->buffer_limit)) {
+				struct virtio_console_control cpkt;
+
+				port->guest_throttled = true;
+				cpkt.event = VIRTIO_CONSOLE_THROTTLE_PORT;
+				cpkt.value = true;
+				send_buf(port, (char *)&cpkt, sizeof(cpkt),
+					 VIRTIO_CONSOLE_ID_CONTROL, false);
+			}
 		}
 		wake_up_interruptible(&port->waitqueue);
 
@@ -967,8 +1052,16 @@ static ssize_t virtcon_port_debugfs_read(struct file *filp, char __user *ubuf,
 	out_offset += snprintf(buf + out_offset, out_count - out_offset,
 			       "guest_connected: %d\n", port->guest_connected);
 	out_offset += snprintf(buf + out_offset, out_count - out_offset,
+			       "guest_throttled: %d\n", port->guest_throttled);
+	out_offset += snprintf(buf + out_offset, out_count - out_offset,
+			       "buffer_limit: %u\n", port->buffer_limit);
+	out_offset += snprintf(buf + out_offset, out_count - out_offset,
+			       "nr_buffers: %u\n", port->nr_buffers);
+	out_offset += snprintf(buf + out_offset, out_count - out_offset,
 			       "host_connected: %d\n", port->host_connected);
 	out_offset += snprintf(buf + out_offset, out_count - out_offset,
+			       "host_throttled: %d\n", port->host_throttled);
+	out_offset += snprintf(buf + out_offset, out_count - out_offset,
 			       "is_console: %d\n", port->hvc ? 1 : 0);
 	out_offset += snprintf(buf + out_offset, out_count - out_offset,
 			       "console_vtermno: %u\n", port->vtermno);
@@ -999,6 +1092,8 @@ static int virtconsole_add_port(u32 port_nr)
 	port->vcon = &virtconsole;
 	port->id = port_nr;
 
+	port->buffer_limit = ~(u32)0;
+
 	cdev_init(&port->cdev, &virtconsole_fops);
 
 	ret = alloc_chrdev_region(&devt, 0, 1, "virtio-console");
@@ -1144,6 +1239,10 @@ static int __devinit virtcons_probe(struct virtio_device *vdev)
 				  &virtconsole.config.nr_ports,
 				  sizeof(virtconsole.config.nr_ports));
 	}
+	if (virtio_has_feature(vdev, VIRTIO_CONSOLE_F_THROTTLE)) {
+		vdev->features[0] |= 1 << VIRTIO_CONSOLE_F_THROTTLE;
+		vdev->config->finalize_features(vdev);
+	}
 	/* Find the queues. */
 	/* FIXME: This is why we want to wean off hvc: we do nothing
 	 * when input comes in. */
@@ -1194,6 +1293,7 @@ static struct virtio_device_id id_table[] = {
 static unsigned int features[] = {
 	VIRTIO_CONSOLE_F_SIZE,
 	VIRTIO_CONSOLE_F_MULTIPORT,
+	VIRTIO_CONSOLE_F_THROTTLE,
 };
 
 static struct virtio_driver virtio_console = {
diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h
index b93dba4..5336a75 100644
--- a/include/linux/virtio_console.h
+++ b/include/linux/virtio_console.h
@@ -13,6 +13,7 @@
 /* Feature bits */
 #define VIRTIO_CONSOLE_F_SIZE	0	/* Does host provide console size? */
 #define VIRTIO_CONSOLE_F_MULTIPORT 1	/* Does host provide multiple ports? */
+#define VIRTIO_CONSOLE_F_THROTTLE  2	/* Do we throttle data to prevent OOM */
 
 struct virtio_console_config {
 	/* colums of the screens */
@@ -37,6 +38,8 @@ struct virtio_console_control {
 #define VIRTIO_CONSOLE_PORT_NAME	1
 #define VIRTIO_CONSOLE_CONSOLE_PORT	2
 #define VIRTIO_CONSOLE_RESIZE		3
+#define VIRTIO_CONSOLE_THROTTLE_PORT	4
+#define VIRTIO_CONSOLE_BUFFER_LIMIT	5
 
 /*
  * This struct is put in each buffer that gets passed to userspace and
-- 
1.6.2.5

_______________________________________________
Virtualization mailing list
Virtualization@xxxxxxxxxxxxxxxxxxxxxxxxxx
https://lists.linux-foundation.org/mailman/listinfo/virtualization

[Index of Archives]     [KVM Development]     [Libvirt Development]     [Libvirt Users]     [CentOS Virtualization]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite Forum]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux