|
|
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 |
|