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(). --- 25-akpm/drivers/char/hvc_console.c | 70 +++++++++++++++++++++++++++---------- 1 files changed, 52 insertions(+), 18 deletions(-) diff -puN drivers/char/hvc_console.c~ppc64-hvc-sleep_in_spinlock drivers/char/hvc_console.c --- 25/drivers/char/hvc_console.c~ppc64-hvc-sleep_in_spinlock 2004-03-14 15:35:17.384295768 -0800 +++ 25-akpm/drivers/char/hvc_console.c 2004-03-14 15:35:17.386295464 -0800 @@ -131,31 +131,65 @@ static int hvc_write(struct tty_struct * 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; } _