From c7e74ca2f09c682535cd2874085f534efd35c6f5 Mon Sep 17 00:00:00 2001 From: Andrew Morton Date: Mon, 15 Mar 2004 15:29:15 -0800 Subject: [PATCH] [PATCH] Fix hvc console sleep in spinlock bug From: Jeremy Kerr This patch fixes the sleep in spinlock hvc bug in hvc_write(). The code is a little longer, but protects against large amounts of memory being kmalloc()ed by userspace, and minimises calls to copy_from_user(). --- drivers/char/hvc_console.c | 70 ++++++++++++++++++++++++++++---------- 1 file changed, 52 insertions(+), 18 deletions(-) diff --git a/drivers/char/hvc_console.c b/drivers/char/hvc_console.c index 99d77ca8840d5..d4bf205fd8821 100644 --- a/drivers/char/hvc_console.c +++ b/drivers/char/hvc_console.c @@ -131,31 +131,65 @@ static int hvc_write(struct tty_struct *tty, int from_user, const unsigned char *buf, int count) { struct hvc_struct *hp = tty->driver_data; - char *p; - int todo, written = 0; + char *tbuf, *p; + int tbsize, rsize, written = 0; unsigned long flags; - spin_lock_irqsave(&hp->lock, flags); - while (count > 0 && (todo = N_OUTBUF - hp->n_outbuf) > 0) { - if (todo > count) - todo = count; - p = hp->outbuf + hp->n_outbuf; - if (from_user) { - todo -= copy_from_user(p, buf, todo); - if (todo == 0) { + if (from_user) { + tbsize = min(count, (int)PAGE_SIZE); + if (!(tbuf = kmalloc(tbsize, GFP_KERNEL))) + return -ENOMEM; + + while ((rsize = count - written) > 0) { + int wsize; + if (rsize > tbsize) + rsize = tbsize; + + p = tbuf; + rsize -= copy_from_user(p, buf, rsize); + if (!rsize) { if (written == 0) written = -EFAULT; break; } - } else - memcpy(p, buf, todo); - count -= todo; - buf += todo; - hp->n_outbuf += todo; - written += todo; - hvc_push(hp); + buf += rsize; + written += rsize; + + spin_lock_irqsave(&hp->lock, flags); + for (wsize = N_OUTBUF - hp->n_outbuf; rsize && wsize; + wsize = N_OUTBUF - hp->n_outbuf) { + if (wsize > rsize) + wsize = rsize; + memcpy(hp->outbuf + hp->n_outbuf, p, wsize); + hp->n_outbuf += wsize; + hvc_push(hp); + rsize -= wsize; + p += wsize; + } + spin_unlock_irqrestore(&hp->lock, flags); + + if (rsize) + break; + + if (count < tbsize) + tbsize = count; + } + + kfree(tbuf); + } else { + spin_lock_irqsave(&hp->lock, flags); + while (count > 0 && (rsize = N_OUTBUF - hp->n_outbuf) > 0) { + if (rsize > count) + rsize = count; + memcpy(hp->outbuf + hp->n_outbuf, buf, rsize); + count -= rsize; + buf += rsize; + hp->n_outbuf += rsize; + written += rsize; + hvc_push(hp); + } + spin_unlock_irqrestore(&hp->lock, flags); } - spin_unlock_irqrestore(&hp->lock, flags); return written; } -- 2.53.0