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.s...@redhat.com> --- drivers/char/virtio_console.c | 78 ++++++++++++++++++++++++++++++++++++++- include/linux/virtio_console.h | 2 + 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/drivers/char/virtio_console.c b/drivers/char/virtio_console.c index d0bdc18..fe8f14f 100644 --- a/drivers/char/virtio_console.c +++ b/drivers/char/virtio_console.c @@ -169,11 +169,32 @@ struct port { /* The 'id' to identify the port with the Host */ u32 id; + /* + * 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; }; /* This is the very early arch-specified put chars function. */ @@ -383,6 +404,7 @@ static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count, if (buf->len - buf->offset == 0) { spin_lock_irqsave(&port->readbuf_list_lock, flags); list_del(&buf->list); + port->nr_buffers--; spin_unlock_irqrestore(&port->readbuf_list_lock, flags); kfree(buf->buf); kfree(buf); @@ -390,6 +412,10 @@ static ssize_t fill_readbuf(struct port *port, char *out_buf, size_t out_count, if (!out_count) break; } + if (port->guest_throttled && port->nr_buffers < port->buffer_limit) { + send_control_msg(port, VIRTIO_CONSOLE_THROTTLE_PORT, 0); + port->guest_throttled = false; + } return out_offset; } @@ -446,6 +472,9 @@ static ssize_t port_fops_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); } @@ -460,7 +489,7 @@ static unsigned int port_fops_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; @@ -811,6 +840,35 @@ static void handle_control_message(struct port *port, struct port_buffer *buf) err); 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; } } @@ -881,10 +939,24 @@ static void rx_work_handler(struct work_struct *work) */ port->host_connected = true; + if (port->guest_throttled) { + kfree(buf->buf); + kfree(buf); + continue; + } spin_lock_irq(&port->readbuf_list_lock); list_add_tail(&buf->list, &port->readbuf_head); + port->nr_buffers++; spin_unlock_irq(&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 (port->nr_buffers >= port->buffer_limit) { + send_control_msg(port, VIRTIO_CONSOLE_THROTTLE_PORT, 1); + port->guest_throttled = true; + } wake_up_interruptible(&port->waitqueue); if (is_console_port(port)) @@ -974,6 +1046,8 @@ static int __devinit add_port(struct ports_device *portdev, u32 id) port->portdev = portdev; port->id = id; + port->buffer_limit = ~(u32)0; + cdev_init(&port->cdev, &port_fops); devt = MKDEV(portdev->chr_major, id); diff --git a/include/linux/virtio_console.h b/include/linux/virtio_console.h index d65fdb5..2aa2b42 100644 --- a/include/linux/virtio_console.h +++ b/include/linux/virtio_console.h @@ -38,6 +38,8 @@ struct virtio_console_control { #define VIRTIO_CONSOLE_RESIZE 2 #define VIRTIO_CONSOLE_PORT_OPEN 3 #define VIRTIO_CONSOLE_PORT_NAME 4 +#define VIRTIO_CONSOLE_THROTTLE_PORT 5 +#define VIRTIO_CONSOLE_BUFFER_LIMIT 6 /* * This struct is put at the start of each buffer that gets passed to -- 1.6.2.5 _______________________________________________ Virtualization mailing list Virtualization@lists.linux-foundation.org https://lists.linux-foundation.org/mailman/listinfo/virtualization