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