diff mbox

fs/coda: potential buffer overflow in coda_psdev_write()

Message ID 20180712123255.rkcszmwdmrloxaki@kili.mountain (mailing list archive)
State New, archived
Headers show

Commit Message

Dan Carpenter July 12, 2018, 12:32 p.m. UTC
"dcbuf" is a union that is "size" bytes large.  We ensure that "nbytes"
is large enough to hold the smallest member of the union, but if we
require a larger union member then then we could access beyond the end
of the allocated memory in coda_downcall().

The union is quite small so we can allocate enough space so everything
fits.  The CODA_ALLOC() macro calls kzalloc() which means the extra
memory is just zeroed and it's fine.

Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>

Comments

Jan Harkes July 12, 2018, 3:31 p.m. UTC | #1
On Thu, Jul 12, 2018 at 03:32:56PM +0300, Dan Carpenter wrote:
> "dcbuf" is a union that is "size" bytes large.  We ensure that "nbytes"
> is large enough to hold the smallest member of the union, but if we
> require a larger union member then then we could access beyond the end
> of the allocated memory in coda_downcall().

I don't see where nbytes is set to hold the smallest member of the
union.

    // nbytes is how much userspace is trying to write
    union outputArgs *dcbuf;
    int size = sizeof(*dcbuf);      // maximum size of the union
    ...
    if (nbytes > size) {
        ...
        nbytes = size;              // truncate nbytes if the write was
                                    // larger than our buffer
    }
    CODA_ALLOC(*dcbuf, union outputArgs *, nbytes);
    copy_from_user(dcbuf, buf, nbytes);


The test between the sizeof and the truncation of nbytes in the
ellipsized part of the code does test for the smallest size of the
union, but it has a 'goto out;' when it is hit because if the received
message is smaller than the message header, the code that would run
after the copy_from_user would look at fields that were never passed by
userspace.

Even if we allocate size instead of nbytes, we still wouldn't
copy_from_user more than nbytes anyway.

Jan

> The union is quite small so we can allocate enough space so everything
> fits.  The CODA_ALLOC() macro calls kzalloc() which means the extra
> memory is just zeroed and it's fine.
> 
> Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
> 
> diff --git a/fs/coda/psdev.c b/fs/coda/psdev.c
> index c5234c21b539..910d57e576e2 100644
> --- a/fs/coda/psdev.c
> +++ b/fs/coda/psdev.c
> @@ -124,7 +124,7 @@ static ssize_t coda_psdev_write(struct file *file, const char __user *buf,
>  				hdr.opcode, hdr.unique);
>  		        nbytes = size;
>  		}
> -		CODA_ALLOC(dcbuf, union outputArgs *, nbytes);
> +		CODA_ALLOC(dcbuf, union outputArgs *, size);
>  		if (copy_from_user(dcbuf, buf, nbytes)) {
>  			CODA_FREE(dcbuf, nbytes);
>  			retval = -EFAULT;
>
Dan Carpenter July 12, 2018, 3:50 p.m. UTC | #2
You misread my commit message, but I have spotted another thing I want
to change so I will resend the patch tomorrow.

On Thu, Jul 12, 2018 at 11:31:00AM -0400, Jan Harkes wrote:
> On Thu, Jul 12, 2018 at 03:32:56PM +0300, Dan Carpenter wrote:
> > "dcbuf" is a union that is "size" bytes large.  We ensure that "nbytes"
> > is large enough to hold the smallest member of the union, but if we
> > require a larger union member then then we could access beyond the end
> > of the allocated memory in coda_downcall().
> 
> I don't see where nbytes is set to hold the smallest member of the
> union.

It's not *always* the smallest member.  It's *at least* the smallest
member (but not necessarily large enough for the largest).

> 
>     // nbytes is how much userspace is trying to write
>     union outputArgs *dcbuf;
>     int size = sizeof(*dcbuf);      // maximum size of the union
>     ...
>     if (nbytes > size) {
>         ...
>         nbytes = size;              // truncate nbytes if the write was
>                                     // larger than our buffer
>     }
>     CODA_ALLOC(*dcbuf, union outputArgs *, nbytes);
>     copy_from_user(dcbuf, buf, nbytes);
> 
> 
> The test between the sizeof and the truncation of nbytes in the
> ellipsized part of the code does test for the smallest size of the
> union, but it has a 'goto out;' when it is hit because if the received
> message is smaller than the message header, the code that would run
> after the copy_from_user would look at fields that were never passed by
> userspace.
> 
> Even if we allocate size instead of nbytes, we still wouldn't
> copy_from_user more than nbytes anyway.
> 

The copy is not the problem, it's coda_downcall().  I did mention that
in my commit message...  Here is how the code looks:

fs/coda/psdev.c
    97  static ssize_t coda_psdev_write(struct file *file, const char __user *buf, 
    98                                  size_t nbytes, loff_t *off)
    99  {
   100          struct venus_comm *vcp = (struct venus_comm *) file->private_data;
   101          struct upc_req *req = NULL;
   102          struct upc_req *tmp;
   103          struct list_head *lh;
   104          struct coda_in_hdr hdr;
   105          ssize_t retval = 0, count = 0;
   106          int error;
   107  
   108          /* Peek at the opcode, uniquefier */
   109          if (copy_from_user(&hdr, buf, 2 * sizeof(u_long)))
   110                  return -EFAULT;
   111  
   112          if (DOWNCALL(hdr.opcode)) {
   113                  union outputArgs *dcbuf;
   114                  int size = sizeof(*dcbuf);
                        ^^^^^^^^^^^^^^^^^^^^^^^^^
size is the whole union.

   115  
   116                  if  ( nbytes < sizeof(struct coda_out_hdr) ) {
                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

struct coda_out_hdr is the smallest member.  We are assuming that
nbytes == sizeof(struct coda_out_hdr) so this test is fine.

   117                          pr_warn("coda_downcall opc %d uniq %d, not enough!\n",
   118                                  hdr.opcode, hdr.unique);
   119                          count = nbytes;
   120                          goto out;
   121                  }
   122                  if ( nbytes > size ) {
                             ^^^^^^^^^^^^^
It's not too large.  That's fine.

   123                          pr_warn("downcall opc %d, uniq %d, too much!",
   124                                  hdr.opcode, hdr.unique);
   125                          nbytes = size;
   126                  }
   127                  CODA_ALLOC(dcbuf, union outputArgs *, nbytes);
                                                              ^^^^^^
We allocate enough space for the smallest member.

   128                  if (copy_from_user(dcbuf, buf, nbytes)) {
   129                          CODA_FREE(dcbuf, nbytes);
   130                          retval = -EFAULT;
   131                          goto out;
   132                  }
   133  
   134                  /* what downcall errors does Venus handle ? */
   135                  error = coda_downcall(vcp, hdr.opcode, dcbuf);
                                                               ^^^^^
But coda_downcall() assumes everything is checked properly.  The
references to CodaFid could be out of bounds, for example.
"fid = &out->coda_zapdir.CodaFid;"

   136  
   137                  CODA_FREE(dcbuf, nbytes);
                                         ^^^^^^
Let me update this...  It's not used, but I guess it's uglier to be
wrong as well as unused.  I will resend tomorrow.

regards,
dan carpenter
Dan Carpenter July 13, 2018, 7:05 p.m. UTC | #3
I added the linux-fsdevel list back to the CC.

On Fri, Jul 13, 2018 at 12:16:30PM -0400, Jan Harkes wrote:
> On Fri, Jul 13, 2018 at 06:10:17PM +0300, Dan Carpenter wrote:
> > "dcbuf" is a union that is "size" bytes large.  We ensure that "nbytes"
> > is large enough to hold the smallest member of the union, but the
> > problem is that we might try to use a larger member.  If "nbytes" is
> > set to sizeof(struct coda_out_hdr) that would cause a problem in
> > coda_downcall() when we try to access &out->coda_zapdir.CodaFid;
> > 
> > The union is quite small so we can allocate enough space so everything
> > fits.  The CODA_ALLOC() macro calls kzalloc() which means the extra
> > memory is just zeroed and it's fine.
> > 
> > Signed-off-by: Dan Carpenter <dan.carpenter@oracle.com>
> > ---
> > v2: I forgot to update CODA_FREE() in my first patch.
> 
> You also seem to have missed my comments on your first patch. The
> smallest size test is only to make sure we at least get a complete
> message header.
> 
> If your concern is that a userspace client might send a CODA_ZAPDIR
> downcall but doesn't actually include enough data for the message to
> contain the file identifier to be zapped. I don't think this shouldn't
> be papered over by just passing along some extra zero bytes.
> How would we know these extra zero bytes are fine? How does the
> developer implementing this broken Coda client find out that he is not
> actually sending a properly formatted message and the intended cache
> flush never happens?
> 
> The proper fix should be to check that we received at least enough data
> to fully read the received downcall message based on the opcode in the
> received message header and log/return an error if it doesn't match.
> 

I just wanted to solve the memory corruption without breaking user
space.  What you're proposing sounds more complicated and probably
someone should test it.  Can you fix it and give me a Reported-by tag?

regards,
dan carpenter
Jan Harkes July 13, 2018, 7:08 p.m. UTC | #4
On Fri, Jul 13, 2018 at 10:05:03PM +0300, Dan Carpenter wrote:
> > The proper fix should be to check that we received at least enough data
> > to fully read the received downcall message based on the opcode in the
> > received message header and log/return an error if it doesn't match.
> 
> I just wanted to solve the memory corruption without breaking user
> space.  What you're proposing sounds more complicated and probably
> someone should test it.  Can you fix it and give me a Reported-by tag?

Should not be too hard and I am in the best position to test it, so yes
I will do that.

Jan
diff mbox

Patch

diff --git a/fs/coda/psdev.c b/fs/coda/psdev.c
index c5234c21b539..910d57e576e2 100644
--- a/fs/coda/psdev.c
+++ b/fs/coda/psdev.c
@@ -124,7 +124,7 @@  static ssize_t coda_psdev_write(struct file *file, const char __user *buf,
 				hdr.opcode, hdr.unique);
 		        nbytes = size;
 		}
-		CODA_ALLOC(dcbuf, union outputArgs *, nbytes);
+		CODA_ALLOC(dcbuf, union outputArgs *, size);
 		if (copy_from_user(dcbuf, buf, nbytes)) {
 			CODA_FREE(dcbuf, nbytes);
 			retval = -EFAULT;