yeahuh / rpms / qemu-kvm

Forked from rpms/qemu-kvm 2 years ago
Clone

Blame SOURCES/kvm-CVE-2015-1779-incrementally-decode-websocket-frames.patch

9ae3a8
From 67c87cd508385158a8a0fb12a430dd19d2883974 Mon Sep 17 00:00:00 2001
9ae3a8
From: Gerd Hoffmann <kraxel@redhat.com>
9ae3a8
Date: Wed, 20 May 2015 08:39:08 +0200
9ae3a8
Subject: [PATCH 1/6] CVE-2015-1779: incrementally decode websocket frames
9ae3a8
9ae3a8
Message-id: <1432111149-11644-2-git-send-email-kraxel@redhat.com>
9ae3a8
Patchwork-id: 65099
9ae3a8
O-Subject: [RHEL-7.2 qemu-kvm PATCH 1/2] CVE-2015-1779: incrementally decode websocket frames
9ae3a8
Bugzilla: 1206497
9ae3a8
RH-Acked-by: Thomas Huth <thuth@redhat.com>
9ae3a8
RH-Acked-by: Petr Matousek <pmatouse@redhat.com>
9ae3a8
RH-Acked-by: Daniel P. Berrange <berrange@redhat.com>
9ae3a8
9ae3a8
From: "Daniel P. Berrange" <berrange@redhat.com>
9ae3a8
9ae3a8
The logic for decoding websocket frames wants to fully
9ae3a8
decode the frame header and payload, before allowing the
9ae3a8
VNC server to see any of the payload data. There is no
9ae3a8
size limit on websocket payloads, so this allows a
9ae3a8
malicious network client to consume 2^64 bytes in memory
9ae3a8
in QEMU. It can trigger this denial of service before
9ae3a8
the VNC server even performs any authentication.
9ae3a8
9ae3a8
The fix is to decode the header, and then incrementally
9ae3a8
decode the payload data as it is needed. With this fix
9ae3a8
the websocket decoder will allow at most 4k of data to
9ae3a8
be buffered before decoding and processing payload.
9ae3a8
9ae3a8
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
9ae3a8
9ae3a8
[ kraxel: fix frequent spurious disconnects, suggested by Peter Maydell ]
9ae3a8
9ae3a8
  @@ -361,7 +361,7 @@ int vncws_decode_frame_payload(Buffer *input,
9ae3a8
  -        *payload_size = input->offset;
9ae3a8
  +        *payload_size = *payload_remain;
9ae3a8
9ae3a8
[ kraxel: fix 32bit build ]
9ae3a8
9ae3a8
  @@ -306,7 +306,7 @@ struct VncState
9ae3a8
  -    uint64_t ws_payload_remain;
9ae3a8
  +    size_t ws_payload_remain;
9ae3a8
9ae3a8
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
9ae3a8
(cherry picked from commit a2bebfd6e09d285aa793cae3fb0fc3a39a9fee6e)
9ae3a8
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
9ae3a8
---
9ae3a8
 ui/vnc-ws.c | 105 ++++++++++++++++++++++++++++++++++++++++--------------------
9ae3a8
 ui/vnc-ws.h |   9 ++++--
9ae3a8
 ui/vnc.h    |   2 ++
9ae3a8
 3 files changed, 80 insertions(+), 36 deletions(-)
9ae3a8
9ae3a8
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
9ae3a8
index df89315..a7d457c 100644
9ae3a8
--- a/ui/vnc-ws.c
9ae3a8
+++ b/ui/vnc-ws.c
9ae3a8
@@ -114,7 +114,7 @@ long vnc_client_read_ws(VncState *vs)
9ae3a8
 {
9ae3a8
     int ret, err;
9ae3a8
     uint8_t *payload;
9ae3a8
-    size_t payload_size, frame_size;
9ae3a8
+    size_t payload_size, header_size;
9ae3a8
     VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer,
9ae3a8
             vs->ws_input.capacity, vs->ws_input.offset);
9ae3a8
     buffer_reserve(&vs->ws_input, 4096);
9ae3a8
@@ -124,18 +124,39 @@ long vnc_client_read_ws(VncState *vs)
9ae3a8
     }
9ae3a8
     vs->ws_input.offset += ret;
9ae3a8
 
9ae3a8
-    /* make sure that nothing is left in the ws_input buffer */
9ae3a8
+    ret = 0;
9ae3a8
+    /* consume as much of ws_input buffer as possible */
9ae3a8
     do {
9ae3a8
-        err = vncws_decode_frame(&vs->ws_input, &payload,
9ae3a8
-                              &payload_size, &frame_size);
9ae3a8
-        if (err <= 0) {
9ae3a8
-            return err;
9ae3a8
+        if (vs->ws_payload_remain == 0) {
9ae3a8
+            err = vncws_decode_frame_header(&vs->ws_input,
9ae3a8
+                                            &header_size,
9ae3a8
+                                            &vs->ws_payload_remain,
9ae3a8
+                                            &vs->ws_payload_mask);
9ae3a8
+            if (err <= 0) {
9ae3a8
+                return err;
9ae3a8
+            }
9ae3a8
+
9ae3a8
+            buffer_advance(&vs->ws_input, header_size);
9ae3a8
         }
9ae3a8
+        if (vs->ws_payload_remain != 0) {
9ae3a8
+            err = vncws_decode_frame_payload(&vs->ws_input,
9ae3a8
+                                             &vs->ws_payload_remain,
9ae3a8
+                                             &vs->ws_payload_mask,
9ae3a8
+                                             &payload,
9ae3a8
+                                             &payload_size);
9ae3a8
+            if (err < 0) {
9ae3a8
+                return err;
9ae3a8
+            }
9ae3a8
+            if (err == 0) {
9ae3a8
+                return ret;
9ae3a8
+            }
9ae3a8
+            ret += err;
9ae3a8
 
9ae3a8
-        buffer_reserve(&vs->input, payload_size);
9ae3a8
-        buffer_append(&vs->input, payload, payload_size);
9ae3a8
+            buffer_reserve(&vs->input, payload_size);
9ae3a8
+            buffer_append(&vs->input, payload, payload_size);
9ae3a8
 
9ae3a8
-        buffer_advance(&vs->ws_input, frame_size);
9ae3a8
+            buffer_advance(&vs->ws_input, payload_size);
9ae3a8
+        }
9ae3a8
     } while (vs->ws_input.offset > 0);
9ae3a8
 
9ae3a8
     return ret;
9ae3a8
@@ -273,15 +294,14 @@ void vncws_encode_frame(Buffer *output, const void *payload,
9ae3a8
     buffer_append(output, payload, payload_size);
9ae3a8
 }
9ae3a8
 
9ae3a8
-int vncws_decode_frame(Buffer *input, uint8_t **payload,
9ae3a8
-                           size_t *payload_size, size_t *frame_size)
9ae3a8
+int vncws_decode_frame_header(Buffer *input,
9ae3a8
+                              size_t *header_size,
9ae3a8
+                              size_t *payload_remain,
9ae3a8
+                              WsMask *payload_mask)
9ae3a8
 {
9ae3a8
     unsigned char opcode = 0, fin = 0, has_mask = 0;
9ae3a8
-    size_t header_size = 0;
9ae3a8
-    uint32_t *payload32;
9ae3a8
+    size_t payload_len;
9ae3a8
     WsHeader *header = (WsHeader *)input->buffer;
9ae3a8
-    WsMask mask;
9ae3a8
-    int i;
9ae3a8
 
9ae3a8
     if (input->offset < WS_HEAD_MIN_LEN + 4) {
9ae3a8
         /* header not complete */
9ae3a8
@@ -291,7 +311,7 @@ int vncws_decode_frame(Buffer *input, uint8_t **payload,
9ae3a8
     fin = (header->b0 & 0x80) >> 7;
9ae3a8
     opcode = header->b0 & 0x0f;
9ae3a8
     has_mask = (header->b1 & 0x80) >> 7;
9ae3a8
-    *payload_size = header->b1 & 0x7f;
9ae3a8
+    payload_len = header->b1 & 0x7f;
9ae3a8
 
9ae3a8
     if (opcode == WS_OPCODE_CLOSE) {
9ae3a8
         /* disconnect */
9ae3a8
@@ -308,40 +328,57 @@ int vncws_decode_frame(Buffer *input, uint8_t **payload,
9ae3a8
         return -2;
9ae3a8
     }
9ae3a8
 
9ae3a8
-    if (*payload_size < 126) {
9ae3a8
-        header_size = 6;
9ae3a8
-        mask = header->u.m;
9ae3a8
-    } else if (*payload_size == 126 && input->offset >= 8) {
9ae3a8
-        *payload_size = be16_to_cpu(header->u.s16.l16);
9ae3a8
-        header_size = 8;
9ae3a8
-        mask = header->u.s16.m16;
9ae3a8
-    } else if (*payload_size == 127 && input->offset >= 14) {
9ae3a8
-        *payload_size = be64_to_cpu(header->u.s64.l64);
9ae3a8
-        header_size = 14;
9ae3a8
-        mask = header->u.s64.m64;
9ae3a8
+    if (payload_len < 126) {
9ae3a8
+        *payload_remain = payload_len;
9ae3a8
+        *header_size = 6;
9ae3a8
+        *payload_mask = header->u.m;
9ae3a8
+    } else if (payload_len == 126 && input->offset >= 8) {
9ae3a8
+        *payload_remain = be16_to_cpu(header->u.s16.l16);
9ae3a8
+        *header_size = 8;
9ae3a8
+        *payload_mask = header->u.s16.m16;
9ae3a8
+    } else if (payload_len == 127 && input->offset >= 14) {
9ae3a8
+        *payload_remain = be64_to_cpu(header->u.s64.l64);
9ae3a8
+        *header_size = 14;
9ae3a8
+        *payload_mask = header->u.s64.m64;
9ae3a8
     } else {
9ae3a8
         /* header not complete */
9ae3a8
         return 0;
9ae3a8
     }
9ae3a8
 
9ae3a8
-    *frame_size = header_size + *payload_size;
9ae3a8
+    return 1;
9ae3a8
+}
9ae3a8
+
9ae3a8
+int vncws_decode_frame_payload(Buffer *input,
9ae3a8
+                               size_t *payload_remain, WsMask *payload_mask,
9ae3a8
+                               uint8_t **payload, size_t *payload_size)
9ae3a8
+{
9ae3a8
+    size_t i;
9ae3a8
+    uint32_t *payload32;
9ae3a8
 
9ae3a8
-    if (input->offset < *frame_size) {
9ae3a8
-        /* frame not complete */
9ae3a8
+    *payload = input->buffer;
9ae3a8
+    /* If we aren't at the end of the payload, then drop
9ae3a8
+     * off the last bytes, so we're always multiple of 4
9ae3a8
+     * for purpose of unmasking, except at end of payload
9ae3a8
+     */
9ae3a8
+    if (input->offset < *payload_remain) {
9ae3a8
+        *payload_size = input->offset - (input->offset % 4);
9ae3a8
+    } else {
9ae3a8
+        *payload_size = *payload_remain;
9ae3a8
+    }
9ae3a8
+    if (*payload_size == 0) {
9ae3a8
         return 0;
9ae3a8
     }
9ae3a8
-
9ae3a8
-    *payload = input->buffer + header_size;
9ae3a8
+    *payload_remain -= *payload_size;
9ae3a8
 
9ae3a8
     /* unmask frame */
9ae3a8
     /* process 1 frame (32 bit op) */
9ae3a8
     payload32 = (uint32_t *)(*payload);
9ae3a8
     for (i = 0; i < *payload_size / 4; i++) {
9ae3a8
-        payload32[i] ^= mask.u;
9ae3a8
+        payload32[i] ^= payload_mask->u;
9ae3a8
     }
9ae3a8
     /* process the remaining bytes (if any) */
9ae3a8
     for (i *= 4; i < *payload_size; i++) {
9ae3a8
-        (*payload)[i] ^= mask.c[i % 4];
9ae3a8
+        (*payload)[i] ^= payload_mask->c[i % 4];
9ae3a8
     }
9ae3a8
 
9ae3a8
     return 1;
9ae3a8
diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
9ae3a8
index 95c1b0a..6e93fa0 100644
9ae3a8
--- a/ui/vnc-ws.h
9ae3a8
+++ b/ui/vnc-ws.h
9ae3a8
@@ -83,7 +83,12 @@ long vnc_client_read_ws(VncState *vs);
9ae3a8
 void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size);
9ae3a8
 void vncws_encode_frame(Buffer *output, const void *payload,
9ae3a8
             const size_t payload_size);
9ae3a8
-int vncws_decode_frame(Buffer *input, uint8_t **payload,
9ae3a8
-                               size_t *payload_size, size_t *frame_size);
9ae3a8
+int vncws_decode_frame_header(Buffer *input,
9ae3a8
+                              size_t *header_size,
9ae3a8
+                              size_t *payload_remain,
9ae3a8
+                              WsMask *payload_mask);
9ae3a8
+int vncws_decode_frame_payload(Buffer *input,
9ae3a8
+                               size_t *payload_remain, WsMask *payload_mask,
9ae3a8
+                               uint8_t **payload, size_t *payload_size);
9ae3a8
 
9ae3a8
 #endif /* __QEMU_UI_VNC_WS_H */
9ae3a8
diff --git a/ui/vnc.h b/ui/vnc.h
9ae3a8
index 6e99213..0efc5c6 100644
9ae3a8
--- a/ui/vnc.h
9ae3a8
+++ b/ui/vnc.h
9ae3a8
@@ -290,6 +290,8 @@ struct VncState
9ae3a8
 #ifdef CONFIG_VNC_WS
9ae3a8
     Buffer ws_input;
9ae3a8
     Buffer ws_output;
9ae3a8
+    size_t ws_payload_remain;
9ae3a8
+    WsMask ws_payload_mask;
9ae3a8
 #endif
9ae3a8
     /* current output mode information */
9ae3a8
     VncWritePixels *write_pixels;
9ae3a8
-- 
9ae3a8
1.8.3.1
9ae3a8