1 From a2bebfd6e09d285aa793cae3fb0fc3a39a9fee6e Mon Sep 17 00:00:00 2001
2 From: "Daniel P. Berrange" <berrange@redhat.com>
3 Date: Mon, 23 Mar 2015 22:58:21 +0000
4 Subject: [PATCH 1/2] CVE-2015-1779: incrementally decode websocket frames
6 The logic for decoding websocket frames wants to fully
7 decode the frame header and payload, before allowing the
8 VNC server to see any of the payload data. There is no
9 size limit on websocket payloads, so this allows a
10 malicious network client to consume 2^64 bytes in memory
11 in QEMU. It can trigger this denial of service before
12 the VNC server even performs any authentication.
14 The fix is to decode the header, and then incrementally
15 decode the payload data as it is needed. With this fix
16 the websocket decoder will allow at most 4k of data to
17 be buffered before decoding and processing payload.
19 Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
21 [ kraxel: fix frequent spurious disconnects, suggested by Peter Maydell ]
23 @@ -361,7 +361,7 @@ int vncws_decode_frame_payload(Buffer *input,
24 - *payload_size = input->offset;
25 + *payload_size = *payload_remain;
27 [ kraxel: fix 32bit build ]
29 @@ -306,7 +306,7 @@ struct VncState
30 - uint64_t ws_payload_remain;
31 + size_t ws_payload_remain;
33 Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
35 ui/vnc-ws.c | 105 ++++++++++++++++++++++++++++++++++++++++--------------------
36 ui/vnc-ws.h | 9 ++++--
38 3 files changed, 80 insertions(+), 36 deletions(-)
40 diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
41 index 85dbb7e..0b7de4e 100644
44 @@ -107,7 +107,7 @@ long vnc_client_read_ws(VncState *vs)
48 - size_t payload_size, frame_size;
49 + size_t payload_size, header_size;
50 VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer,
51 vs->ws_input.capacity, vs->ws_input.offset);
52 buffer_reserve(&vs->ws_input, 4096);
53 @@ -117,18 +117,39 @@ long vnc_client_read_ws(VncState *vs)
55 vs->ws_input.offset += ret;
57 - /* make sure that nothing is left in the ws_input buffer */
59 + /* consume as much of ws_input buffer as possible */
61 - err = vncws_decode_frame(&vs->ws_input, &payload,
62 - &payload_size, &frame_size);
65 + if (vs->ws_payload_remain == 0) {
66 + err = vncws_decode_frame_header(&vs->ws_input,
68 + &vs->ws_payload_remain,
69 + &vs->ws_payload_mask);
74 + buffer_advance(&vs->ws_input, header_size);
76 + if (vs->ws_payload_remain != 0) {
77 + err = vncws_decode_frame_payload(&vs->ws_input,
78 + &vs->ws_payload_remain,
79 + &vs->ws_payload_mask,
90 - buffer_reserve(&vs->input, payload_size);
91 - buffer_append(&vs->input, payload, payload_size);
92 + buffer_reserve(&vs->input, payload_size);
93 + buffer_append(&vs->input, payload, payload_size);
95 - buffer_advance(&vs->ws_input, frame_size);
96 + buffer_advance(&vs->ws_input, payload_size);
98 } while (vs->ws_input.offset > 0);
101 @@ -265,15 +286,14 @@ void vncws_encode_frame(Buffer *output, const void *payload,
102 buffer_append(output, payload, payload_size);
105 -int vncws_decode_frame(Buffer *input, uint8_t **payload,
106 - size_t *payload_size, size_t *frame_size)
107 +int vncws_decode_frame_header(Buffer *input,
108 + size_t *header_size,
109 + size_t *payload_remain,
110 + WsMask *payload_mask)
112 unsigned char opcode = 0, fin = 0, has_mask = 0;
113 - size_t header_size = 0;
114 - uint32_t *payload32;
115 + size_t payload_len;
116 WsHeader *header = (WsHeader *)input->buffer;
120 if (input->offset < WS_HEAD_MIN_LEN + 4) {
121 /* header not complete */
122 @@ -283,7 +303,7 @@ int vncws_decode_frame(Buffer *input, uint8_t **payload,
123 fin = (header->b0 & 0x80) >> 7;
124 opcode = header->b0 & 0x0f;
125 has_mask = (header->b1 & 0x80) >> 7;
126 - *payload_size = header->b1 & 0x7f;
127 + payload_len = header->b1 & 0x7f;
129 if (opcode == WS_OPCODE_CLOSE) {
131 @@ -300,40 +320,57 @@ int vncws_decode_frame(Buffer *input, uint8_t **payload,
135 - if (*payload_size < 126) {
137 - mask = header->u.m;
138 - } else if (*payload_size == 126 && input->offset >= 8) {
139 - *payload_size = be16_to_cpu(header->u.s16.l16);
141 - mask = header->u.s16.m16;
142 - } else if (*payload_size == 127 && input->offset >= 14) {
143 - *payload_size = be64_to_cpu(header->u.s64.l64);
145 - mask = header->u.s64.m64;
146 + if (payload_len < 126) {
147 + *payload_remain = payload_len;
149 + *payload_mask = header->u.m;
150 + } else if (payload_len == 126 && input->offset >= 8) {
151 + *payload_remain = be16_to_cpu(header->u.s16.l16);
153 + *payload_mask = header->u.s16.m16;
154 + } else if (payload_len == 127 && input->offset >= 14) {
155 + *payload_remain = be64_to_cpu(header->u.s64.l64);
157 + *payload_mask = header->u.s64.m64;
159 /* header not complete */
163 - *frame_size = header_size + *payload_size;
167 +int vncws_decode_frame_payload(Buffer *input,
168 + size_t *payload_remain, WsMask *payload_mask,
169 + uint8_t **payload, size_t *payload_size)
172 + uint32_t *payload32;
174 - if (input->offset < *frame_size) {
175 - /* frame not complete */
176 + *payload = input->buffer;
177 + /* If we aren't at the end of the payload, then drop
178 + * off the last bytes, so we're always multiple of 4
179 + * for purpose of unmasking, except at end of payload
181 + if (input->offset < *payload_remain) {
182 + *payload_size = input->offset - (input->offset % 4);
184 + *payload_size = *payload_remain;
186 + if (*payload_size == 0) {
190 - *payload = input->buffer + header_size;
191 + *payload_remain -= *payload_size;
194 /* process 1 frame (32 bit op) */
195 payload32 = (uint32_t *)(*payload);
196 for (i = 0; i < *payload_size / 4; i++) {
197 - payload32[i] ^= mask.u;
198 + payload32[i] ^= payload_mask->u;
200 /* process the remaining bytes (if any) */
201 for (i *= 4; i < *payload_size; i++) {
202 - (*payload)[i] ^= mask.c[i % 4];
203 + (*payload)[i] ^= payload_mask->c[i % 4];
207 diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h
208 index ef229b7..14d4230 100644
211 @@ -83,7 +83,12 @@ long vnc_client_read_ws(VncState *vs);
212 void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size);
213 void vncws_encode_frame(Buffer *output, const void *payload,
214 const size_t payload_size);
215 -int vncws_decode_frame(Buffer *input, uint8_t **payload,
216 - size_t *payload_size, size_t *frame_size);
217 +int vncws_decode_frame_header(Buffer *input,
218 + size_t *header_size,
219 + size_t *payload_remain,
220 + WsMask *payload_mask);
221 +int vncws_decode_frame_payload(Buffer *input,
222 + size_t *payload_remain, WsMask *payload_mask,
223 + uint8_t **payload, size_t *payload_size);
225 #endif /* __QEMU_UI_VNC_WS_H */
226 diff --git a/ui/vnc.h b/ui/vnc.h
227 index e19ac39..3f7c6a9 100644
230 @@ -306,6 +306,8 @@ struct VncState
234 + size_t ws_payload_remain;
235 + WsMask ws_payload_mask;
237 /* current output mode information */
238 VncWritePixels *write_pixels;