mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: BlaisorBlade <blaisorblade_spam@yahoo.it>
To: Andrew Morton <akpm@osdl.org>
Cc: linux-kernel@vger.kernel.org
Subject: Re: [PATCH] Missing return value check on do_write_mem
Date: Thu, 25 Mar 2004 20:03:52 +0100	[thread overview]
Message-ID: <200403252003.52209.blaisorblade_spam@yahoo.it> (raw)
In-Reply-To: <20040313161500.72a7d098.akpm@osdl.org>

[-- Attachment #1: Type: text/plain, Size: 2758 bytes --]

Alle 01:15, domenica 14 marzo 2004, Andrew Morton ha scritto:
> BlaisorBlade <blaisorblade_spam@yahoo.it> wrote:
> > Alle 22:46, martedì 9 marzo 2004, Andrew Morton ha scritto:
> >  > BlaisorBlade <blaisorblade_spam@yahoo.it> wrote:
> >  > > In drivers/char/mem.c do_write_mem can return -EFAULT but write_kmem
> >  > > forgets this and goes blindly.
>
> Actually, you converted this code from "wrong" to "still wrong".  How's
> this?
[...]
Sorry for not answering soon, but there is a *bug*:
+                               written = copy_from_user(kbuf, buf, len);
+                               if (written != len) {

On success copy_from_user returns 0, so you should check
"if (written != 0)".

The attached patch addresses also this and renames the var to "unwritten".

> - Return correct value from kmem writes() when a fault is encountered.  A
>   write()-style syscall's return values are:
>
>    0: nothing was written and there was no error (someone tried to write
>    zero bytes)
>
>    >0: the number of bytes copied, whether or not there was an error.
>
>    Userspace detects errors by noting that the write() return value is less
>    than was requested.

- I've checked the code of 2.4 do_generic_file_write() which complies with 
your description, but man 2 write is not clear on this (this is possibly why 
who wrote that code missed this).

- Also, it seems read() should have the same return values (I checked 2.4 and 
2.6 generic_file_read() ); so even the read_kmem() and read_mem must be 
fixed. I'm attaching the fix which also creates a do_read_mem() like 
do_write_mem().

- More important, this check is at least strange:

#if defined(__sparc__) || (defined(__mc68000__) && defined(CONFIG_MMU))
                /* we don't have page 0 mapped on sparc and m68k.. */
                if (p < PAGE_SIZE) {

This is checked both when p is a physical address and when it is a virtual 
one, for both write() and read(). The non existing page 0 is the physical 
page 0 or the virtual page 0? I've not changed it, however.

- Also, generic_file_write() (both 2.4 and 2.6) calls down(&inode->i_sem);, 
while write_mem() and write_kmem() do not; shouldn't they be fixed?

- Finally, do_write_mem() does *ppos += written; since ppos could have been 
changed, this is wrong, or not? It has no effect for write_kmem() that sets a 
correct value after, but seems a bug for write_mem().

By the way: I've sent your first write() fix, just adapted a bit for 2.4, to 
Marcelo Tosatti. I'll post this second one for 2.4 if it's Ok for you.

CC me as I'm not subscribed, please.

Thanks a lot for your attention!
-- 
Paolo Giarrusso, aka Blaisorblade
Linux registered user n. 292729

[-- Attachment #2: do_write_mem-return-check.patch --]
[-- Type: text/x-diff, Size: 5557 bytes --]

--- ./drivers/char/mem.c.fix	2004-03-23 16:40:49.000000000 +0100
+++ ./drivers/char/mem.c	2004-03-25 20:01:05.000000000 +0100
@@ -105,35 +105,62 @@ static inline int valid_phys_addr_range(
 static ssize_t do_write_mem(void *p, unsigned long realp,
 			    const char * buf, size_t count, loff_t *ppos)
 {
-	ssize_t written;
-	unsigned long copied;
+	ssize_t written = 0;
+	unsigned long uncopied;
 
-	written = 0;
 #if defined(__sparc__) || (defined(__mc68000__) && defined(CONFIG_MMU))
 	/* we don't have page 0 mapped on sparc and m68k.. */
 	if (realp < PAGE_SIZE) {
-		unsigned long sz = PAGE_SIZE-realp;
-		if (sz > count) sz = count; 
+		unsigned long sz = PAGE_SIZE - realp;
+		if (sz > count)
+			sz = count; 
 		/* Hmm. Do something? */
-		buf+=sz;
-		p+=sz;
-		count-=sz;
-		written+=sz;
+		buf += sz;
+		p += sz;
+		count -= sz;
+		written += sz;
 	}
 #endif
-	copied = copy_from_user(p, buf, count);
-	if (copied) {
-		ssize_t ret = written + (count - copied);
-
-		if (ret)
-			return ret;
-		return -EFAULT;
+	uncopied = copy_from_user(p, buf, count);
+	if (uncopied) {
+		ssize_t ret = written + (count - uncopied);
+		return ret ? ret : -EFAULT;
 	}
 	written += count;
 	*ppos += written;
 	return written;
 }
 
+static ssize_t do_read_mem(void *p, unsigned long realp,
+			   char * buf, size_t count, loff_t *ppos)
+{
+	ssize_t read = 0;
+	unsigned long uncopied;
+
+#if defined(__sparc__) || (defined(__mc68000__) && defined(CONFIG_MMU))
+	/* we don't have page 0 mapped on sparc and m68k.. */
+	if (realp < PAGE_SIZE) {
+		size_t sz = PAGE_SIZE - realp;
+		if (sz > count)
+			sz = count;
+		if (clear_user(buf, sz))
+			return -EFAULT;
+		buf += sz;
+		p += sz;
+		count -= sz;
+		read += sz;
+	}
+#endif
+	uncopied = copy_to_user(buf, (char *)p, count);
+	if (uncopied) {
+		ssize_t ret = read + (count - uncopied);
+		return ret ? ret : -EFAULT;
+	}
+	read += count;
+	*ppos += read;
+	return read;
+}
+
 
 /*
  * This funcion reads the *physical* memory. The f_pos points directly to the 
@@ -143,32 +170,10 @@ static ssize_t read_mem(struct file * fi
 			size_t count, loff_t *ppos)
 {
 	unsigned long p = *ppos;
-	ssize_t read;
 
 	if (!valid_phys_addr_range(p, &count))
 		return -EFAULT;
-	read = 0;
-#if defined(__sparc__) || (defined(__mc68000__) && defined(CONFIG_MMU))
-	/* we don't have page 0 mapped on sparc and m68k.. */
-	if (p < PAGE_SIZE) {
-		unsigned long sz = PAGE_SIZE-p;
-		if (sz > count) 
-			sz = count; 
-		if (sz > 0) {
-			if (clear_user(buf, sz))
-				return -EFAULT;
-			buf += sz; 
-			p += sz; 
-			count -= sz; 
-			read += sz; 
-		}
-	}
-#endif
-	if (copy_to_user(buf, __va(p), count))
-		return -EFAULT;
-	read += count;
-	*ppos += read;
-	return read;
+	return do_read_mem(__va(p), p, buf, count, ppos);
 }
 
 static ssize_t write_mem(struct file * file, const char * buf, 
@@ -222,25 +227,14 @@ static ssize_t read_kmem(struct file *fi
 	char * kbuf; /* k-addr because vread() takes vmlist_lock rwlock */
 		
 	if (p < (unsigned long) high_memory) {
-		read = count;
+		ssize_t toread = count;
 		if (count > (unsigned long) high_memory - p)
-			read = (unsigned long) high_memory - p;
+			toread = (unsigned long) high_memory - p;
+
+		read = do_read_mem((void*)p, p, buf, toread, ppos);
+		if (read != toread)
+			return read;
 
-#if defined(__sparc__) || (defined(__mc68000__) && defined(CONFIG_MMU))
-		/* we don't have page 0 mapped on sparc and m68k.. */
-		if (p < PAGE_SIZE && read > 0) {
-			size_t tmp = PAGE_SIZE - p;
-			if (tmp > read) tmp = read;
-			if (clear_user(buf, tmp))
-				return -EFAULT;
-			buf += tmp;
-			p += tmp;
-			read -= tmp;
-			count -= tmp;
-		}
-#endif
-		if (copy_to_user(buf, (char *)p, read))
-			return -EFAULT;
 		p += read;
 		buf += read;
 		count -= read;
@@ -252,15 +246,20 @@ static ssize_t read_kmem(struct file *fi
 			return -ENOMEM;
 		while (count > 0) {
 			int len = count;
+			ssize_t unwritten;
 
 			if (len > PAGE_SIZE)
 				len = PAGE_SIZE;
 			len = vread(kbuf, (char *)p, len);
 			if (!len)
 				break;
-			if (copy_to_user(buf, kbuf, len)) {
+			unwritten = copy_to_user(buf, kbuf, len);
+			if (unwritten != 0) {
+				ssize_t ret;
+
 				free_page((unsigned long)kbuf);
-				return -EFAULT;
+				ret = read + virtr + (len - unwritten);
+				return ret ? ret : -EFAULT;
 			}
 			count -= len;
 			buf += len;
@@ -282,19 +281,19 @@ static ssize_t write_kmem(struct file * 
 	unsigned long p = *ppos;
 	ssize_t wrote = 0;
 	ssize_t virtr = 0;
-	ssize_t written;
 	char * kbuf; /* k-addr because vwrite() takes vmlist_lock rwlock */
 
 	if (p < (unsigned long) high_memory) {
+		ssize_t towrite;
 
-		wrote = count;
+		towrite = count;
 		if (count > (unsigned long) high_memory - p)
-			wrote = (unsigned long) high_memory - p;
+			towrite = (unsigned long) high_memory - p;
+
+		wrote = do_write_mem((void*)p, p, buf, towrite, ppos);
+		if (wrote != towrite)
+			return wrote;
 
-		written = do_write_mem((void*)p, p, buf, wrote, ppos);
-		if (written != wrote)
-			return written;
-		wrote = written;
 		p += wrote;
 		buf += wrote;
 		count -= wrote;
@@ -310,12 +309,12 @@ static ssize_t write_kmem(struct file * 
 			if (len > PAGE_SIZE)
 				len = PAGE_SIZE;
 			if (len) {
-				written = copy_from_user(kbuf, buf, len);
-				if (written != len) {
+				ssize_t unwritten = copy_from_user(kbuf, buf, len);
+				if (unwritten != 0) {
 					ssize_t ret;
 
 					free_page((unsigned long)kbuf);
-					ret = wrote + virtr + (len - written);
+					ret = wrote + virtr + (len - unwritten);
 					return ret ? ret : -EFAULT;
 				}
 			}

      reply	other threads:[~2004-03-26 19:41 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2004-03-08 11:46 BlaisorBlade
2004-03-09 21:46 ` Andrew Morton
2004-03-13 19:49   ` BlaisorBlade
2004-03-14  0:15     ` Andrew Morton
2004-03-25 19:03       ` BlaisorBlade [this message]

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=200403252003.52209.blaisorblade_spam@yahoo.it \
    --to=blaisorblade_spam@yahoo.it \
    --cc=akpm@osdl.org \
    --cc=linux-kernel@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®