diff mbox

usb: check RNDIS buffer offsets & length

Message ID 1454669651-29411-1-git-send-email-ppandit@redhat.com
State New
Headers show

Commit Message

Prasad Pandit Feb. 5, 2016, 10:54 a.m. UTC
From: Prasad J Pandit <pjp@fedoraproject.org>

When processing remote NDIS control message packets, the USB Net
device emulator uses a fixed length(4096) data buffer. The incoming
informationBufferOffset & Length combination could cross that range.
Check control message buffer offsets and length to avoid it.

Reported-by: Qinghao Tang <luodalongde@gmail.com>
Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
---
 hw/usb/core.c        | 25 ++++++++++++++++---------
 hw/usb/dev-network.c |  9 ++++++---
 2 files changed, 22 insertions(+), 12 deletions(-)

Comments

Prasad Pandit Feb. 9, 2016, 11:01 a.m. UTC | #1
+-- On Fri, 5 Feb 2016, P J P wrote --+
| From: Prasad J Pandit <pjp@fedoraproject.org>
| 
| When processing remote NDIS control message packets, the USB Net
| device emulator uses a fixed length(4096) data buffer. The incoming
| informationBufferOffset & Length combination could cross that range.
| Check control message buffer offsets and length to avoid it.
| 
| Reported-by: Qinghao Tang <luodalongde@gmail.com>

...ping!
--
 - P J P
47AF CE69 3A90 54AA 9045 1053 DD13 3D32 FE5B 041F
Prasad Pandit Feb. 15, 2016, 4:26 a.m. UTC | #2
+-- On Tue, 9 Feb 2016, P J P wrote --+
| +-- On Fri, 5 Feb 2016, P J P wrote --+
| | From: Prasad J Pandit <pjp@fedoraproject.org>
| | 
| | When processing remote NDIS control message packets, the USB Net
| | device emulator uses a fixed length(4096) data buffer. The incoming
| | informationBufferOffset & Length combination could cross that range.
| | Check control message buffer offsets and length to avoid it.
| | 
| | Reported-by: Qinghao Tang <luodalongde@gmail.com>
| 
| ...ping!

Ping...Gerd?
--
 - P J P
47AF CE69 3A90 54AA 9045 1053 DD13 3D32 FE5B 041F
Gerd Hoffmann Feb. 15, 2016, 9:41 a.m. UTC | #3
On Mo, 2016-02-15 at 09:56 +0530, P J P wrote:
> +-- On Tue, 9 Feb 2016, P J P wrote --+
> | +-- On Fri, 5 Feb 2016, P J P wrote --+
> | | From: Prasad J Pandit <pjp@fedoraproject.org>
> | | 
> | | When processing remote NDIS control message packets, the USB Net
> | | device emulator uses a fixed length(4096) data buffer. The incoming
> | | informationBufferOffset & Length combination could cross that range.
> | | Check control message buffer offsets and length to avoid it.
> | | 
> | | Reported-by: Qinghao Tang <luodalongde@gmail.com>
> | 
> | ...ping!
> 
> Ping...Gerd?

Was offline for a week, will look soonish (have a email backlog to
process now ...)

cheers,
  Gerd
Gerd Hoffmann Feb. 16, 2016, 2:33 p.m. UTC | #4
> diff --git a/hw/usb/core.c b/hw/usb/core.c
> index d0025db..9d90ec7 100644
> --- a/hw/usb/core.c
> +++ b/hw/usb/core.c
> @@ -128,9 +128,16 @@ static void do_token_setup(USBDevice *s, USBPacket *p)
>      }
>  
>      usb_packet_copy(p, s->setup_buf, p->iov.size);
> +    s->setup_index = 0;
>      p->actual_length = 0;
>      s->setup_len   = (s->setup_buf[7] << 8) | s->setup_buf[6];
> -    s->setup_index = 0;
> +    if (s->setup_len > sizeof(s->data_buf)) {
> +        fprintf(stderr,
> +                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
> +                s->setup_len, sizeof(s->data_buf));
> +        p->status = USB_RET_STALL;
> +        return;
> +    }
>  
>      request = (s->setup_buf[0] << 8) | s->setup_buf[1];
>      value   = (s->setup_buf[3] << 8) | s->setup_buf[2];
> @@ -151,13 +158,6 @@ static void do_token_setup(USBDevice *s, USBPacket *p)
>          }
>          s->setup_state = SETUP_STATE_DATA;
>      } else {
> -        if (s->setup_len > sizeof(s->data_buf)) {
> -            fprintf(stderr,
> -                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
> -                s->setup_len, sizeof(s->data_buf));
> -            p->status = USB_RET_STALL;
> -            return;
> -        }
>          if (s->setup_len == 0)
>              s->setup_state = SETUP_STATE_ACK;
>          else

Moves up the check so it is done for every control xfer.  Good.

> @@ -172,11 +172,18 @@ static void do_token_in(USBDevice *s, USBPacket *p)
>      int request, value, index;
>  
>      assert(p->ep->nr == 0);
> +    if (s->setup_len > sizeof(s->data_buf)) {
> +        fprintf(stderr,
> +                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
> +                s->setup_len, sizeof(s->data_buf));
> +        p->status = USB_RET_STALL;
> +        return;
> +    }

Why this is needed?  All control transfers go through do_token_setup
first, so with the check moved in do_token_setup we should never ever
trigger it here ...

> -    if (bufoffs + buflen > length)
> +    if (buflen > length || bufoffs >= length || bufoffs + buflen > length) {
>          return USB_RET_STALL;
> +    }

What is this?  Not mentioned in the commit message.  Looks like integer
overflow prevention to me (if correct: separate patch with proper commit
message please).

thanks,
  Gerd
Prasad Pandit Feb. 16, 2016, 7 p.m. UTC | #5
Hello Gerd,

+-- On Tue, 16 Feb 2016, Gerd Hoffmann wrote --+
| Moves up the check so it is done for every control xfer.  Good.
 ... 
| Why this is needed?  All control transfers go through do_token_setup
| first, so with the check moved in do_token_setup we should never ever
| trigger it here ...

  I see, okay.

| > -    if (bufoffs + buflen > length)
| > +    if (buflen > length || bufoffs >= length || bufoffs + buflen > length) {
| >          return USB_RET_STALL;
| > +    }
| 
| What is this?  Not mentioned in the commit message.  Looks like integer
| overflow prevention to me (if correct: separate patch with proper commit
| message please).

  That's right. I've sent separate revised patches for the above two changes.

Thank you.
--
Prasad J Pandit / Red Hat Product Security Team
47AF CE69 3A90 54AA 9045 1053 DD13 3D32 FE5B 041F
Prasad Pandit Feb. 17, 2016, 8:25 a.m. UTC | #6
+-- On Tue, 16 Feb 2016, Gerd Hoffmann wrote --+
| > @@ -172,11 +172,18 @@ static void do_token_in(USBDevice *s, USBPacket *p)
| >      assert(p->ep->nr == 0);
| > +    if (s->setup_len > sizeof(s->data_buf)) {
| > +        fprintf(stderr,
| > +                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
| > +                s->setup_len, sizeof(s->data_buf));
| > +        p->status = USB_RET_STALL;
| > +        return;
| > +    }
| 
| Why this is needed?  All control transfers go through do_token_setup
| first, so with the check moved in do_token_setup we should never ever
| trigger it here ...

  usb_handle_packet
   -> usb_process_one
    -> do_token_in

  Is it possible for a guest to call do_token_in, without calling 
do_token_setup first? Most drivers seem to have their own 'usb_packet_setup' 
routine. (to confirm)

Thank you.
--
Prasad J Pandit / Red Hat Product Security Team
47AF CE69 3A90 54AA 9045 1053 DD13 3D32 FE5B 041F
Gerd Hoffmann Feb. 17, 2016, 2:46 p.m. UTC | #7
On Mi, 2016-02-17 at 13:55 +0530, P J P wrote:
> +-- On Tue, 16 Feb 2016, Gerd Hoffmann wrote --+
> | > @@ -172,11 +172,18 @@ static void do_token_in(USBDevice *s, USBPacket *p)
> | >      assert(p->ep->nr == 0);
> | > +    if (s->setup_len > sizeof(s->data_buf)) {
> | > +        fprintf(stderr,
> | > +                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
> | > +                s->setup_len, sizeof(s->data_buf));
> | > +        p->status = USB_RET_STALL;
> | > +        return;
> | > +    }
> | 
> | Why this is needed?  All control transfers go through do_token_setup
> | first, so with the check moved in do_token_setup we should never ever
> | trigger it here ...
> 
>   usb_handle_packet
>    -> usb_process_one
>     -> do_token_in
> 
>   Is it possible for a guest to call do_token_in, without calling 
> do_token_setup first?

For anything interesting to happen in do_token_in() setup_state must be
set to either ACK or DATA, and for that to be the case do_token_setup()
must run first.  I don't think the guest can trick qemu to go straight
to do_token_in().

Also s->setup_len is set in do_token_setup() only, verifying it once
(after setting it from guest-supplied data) should be enough.

cheers,
  Gerd
diff mbox

Patch

diff --git a/hw/usb/core.c b/hw/usb/core.c
index d0025db..9d90ec7 100644
--- a/hw/usb/core.c
+++ b/hw/usb/core.c
@@ -128,9 +128,16 @@  static void do_token_setup(USBDevice *s, USBPacket *p)
     }
 
     usb_packet_copy(p, s->setup_buf, p->iov.size);
+    s->setup_index = 0;
     p->actual_length = 0;
     s->setup_len   = (s->setup_buf[7] << 8) | s->setup_buf[6];
-    s->setup_index = 0;
+    if (s->setup_len > sizeof(s->data_buf)) {
+        fprintf(stderr,
+                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
+                s->setup_len, sizeof(s->data_buf));
+        p->status = USB_RET_STALL;
+        return;
+    }
 
     request = (s->setup_buf[0] << 8) | s->setup_buf[1];
     value   = (s->setup_buf[3] << 8) | s->setup_buf[2];
@@ -151,13 +158,6 @@  static void do_token_setup(USBDevice *s, USBPacket *p)
         }
         s->setup_state = SETUP_STATE_DATA;
     } else {
-        if (s->setup_len > sizeof(s->data_buf)) {
-            fprintf(stderr,
-                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
-                s->setup_len, sizeof(s->data_buf));
-            p->status = USB_RET_STALL;
-            return;
-        }
         if (s->setup_len == 0)
             s->setup_state = SETUP_STATE_ACK;
         else
@@ -172,11 +172,18 @@  static void do_token_in(USBDevice *s, USBPacket *p)
     int request, value, index;
 
     assert(p->ep->nr == 0);
+    if (s->setup_len > sizeof(s->data_buf)) {
+        fprintf(stderr,
+                "usb_generic_handle_packet: ctrl buffer too small (%d > %zu)\n",
+                s->setup_len, sizeof(s->data_buf));
+        p->status = USB_RET_STALL;
+        return;
+    }
 
     request = (s->setup_buf[0] << 8) | s->setup_buf[1];
     value   = (s->setup_buf[3] << 8) | s->setup_buf[2];
     index   = (s->setup_buf[5] << 8) | s->setup_buf[4];
- 
+
     switch(s->setup_state) {
     case SETUP_STATE_ACK:
         if (!(s->setup_buf[0] & USB_DIR_IN)) {
diff --git a/hw/usb/dev-network.c b/hw/usb/dev-network.c
index 7800cee..ba3c7a7 100644
--- a/hw/usb/dev-network.c
+++ b/hw/usb/dev-network.c
@@ -914,8 +914,9 @@  static int rndis_query_response(USBNetState *s,
 
     bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8;
     buflen = le32_to_cpu(buf->InformationBufferLength);
-    if (bufoffs + buflen > length)
+    if (buflen > length || bufoffs >= length || bufoffs + buflen > length) {
         return USB_RET_STALL;
+    }
 
     infobuflen = ndis_query(s, le32_to_cpu(buf->OID),
                             bufoffs + (uint8_t *) buf, buflen, infobuf,
@@ -960,8 +961,9 @@  static int rndis_set_response(USBNetState *s,
 
     bufoffs = le32_to_cpu(buf->InformationBufferOffset) + 8;
     buflen = le32_to_cpu(buf->InformationBufferLength);
-    if (bufoffs + buflen > length)
+    if (buflen > length || bufoffs >= length || bufoffs + buflen > length) {
         return USB_RET_STALL;
+    }
 
     ret = ndis_set(s, le32_to_cpu(buf->OID),
                     bufoffs + (uint8_t *) buf, buflen);
@@ -1211,8 +1213,9 @@  static void usb_net_handle_dataout(USBNetState *s, USBPacket *p)
     if (le32_to_cpu(msg->MessageType) == RNDIS_PACKET_MSG) {
         uint32_t offs = 8 + le32_to_cpu(msg->DataOffset);
         uint32_t size = le32_to_cpu(msg->DataLength);
-        if (offs + size <= len)
+        if (offs < len && size < len && offs + size <= len) {
             qemu_send_packet(qemu_get_queue(s->nic), s->out_buf + offs, size);
+        }
     }
     s->out_ptr -= len;
     memmove(s->out_buf, &s->out_buf[len], s->out_ptr);