diff --git a/SOURCES/kvm-CVE-2015-1779-incrementally-decode-websocket-frames.patch b/SOURCES/kvm-CVE-2015-1779-incrementally-decode-websocket-frames.patch new file mode 100644 index 0000000..234ec1e --- /dev/null +++ b/SOURCES/kvm-CVE-2015-1779-incrementally-decode-websocket-frames.patch @@ -0,0 +1,253 @@ +From 2eae7bb4e94710164926c670334a83bf9d347c2f Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Tue, 22 Sep 2015 17:44:53 +0200 +Subject: [PATCH 1/2] CVE-2015-1779: incrementally decode websocket frames + +Message-id: <1442943894-7638-2-git-send-email-kraxel@redhat.com> +Patchwork-id: 67884 +O-Subject: [RHEL-7.1.z qemu-kvm PATCH 1/2] CVE-2015-1779: incrementally decode websocket frames +Bugzilla: 1205050 +RH-Acked-by: Miroslav Rezanina +RH-Acked-by: Thomas Huth +RH-Acked-by: Petr Matousek + +From: "Daniel P. Berrange" + +The logic for decoding websocket frames wants to fully +decode the frame header and payload, before allowing the +VNC server to see any of the payload data. There is no +size limit on websocket payloads, so this allows a +malicious network client to consume 2^64 bytes in memory +in QEMU. It can trigger this denial of service before +the VNC server even performs any authentication. + +The fix is to decode the header, and then incrementally +decode the payload data as it is needed. With this fix +the websocket decoder will allow at most 4k of data to +be buffered before decoding and processing payload. + +Signed-off-by: Daniel P. Berrange + +[ kraxel: fix frequent spurious disconnects, suggested by Peter Maydell ] + + @@ -361,7 +361,7 @@ int vncws_decode_frame_payload(Buffer *input, + - *payload_size = input->offset; + + *payload_size = *payload_remain; + +[ kraxel: fix 32bit build ] + + @@ -306,7 +306,7 @@ struct VncState + - uint64_t ws_payload_remain; + + size_t ws_payload_remain; + +Signed-off-by: Gerd Hoffmann +(cherry picked from commit a2bebfd6e09d285aa793cae3fb0fc3a39a9fee6e) +Signed-off-by: Miroslav Rezanina +--- + ui/vnc-ws.c | 105 ++++++++++++++++++++++++++++++++++++++++-------------------- + ui/vnc-ws.h | 9 ++++-- + ui/vnc.h | 2 ++ + 3 files changed, 80 insertions(+), 36 deletions(-) + +diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c +index df89315..a7d457c 100644 +--- a/ui/vnc-ws.c ++++ b/ui/vnc-ws.c +@@ -114,7 +114,7 @@ long vnc_client_read_ws(VncState *vs) + { + int ret, err; + uint8_t *payload; +- size_t payload_size, frame_size; ++ size_t payload_size, header_size; + VNC_DEBUG("Read websocket %p size %zd offset %zd\n", vs->ws_input.buffer, + vs->ws_input.capacity, vs->ws_input.offset); + buffer_reserve(&vs->ws_input, 4096); +@@ -124,18 +124,39 @@ long vnc_client_read_ws(VncState *vs) + } + vs->ws_input.offset += ret; + +- /* make sure that nothing is left in the ws_input buffer */ ++ ret = 0; ++ /* consume as much of ws_input buffer as possible */ + do { +- err = vncws_decode_frame(&vs->ws_input, &payload, +- &payload_size, &frame_size); +- if (err <= 0) { +- return err; ++ if (vs->ws_payload_remain == 0) { ++ err = vncws_decode_frame_header(&vs->ws_input, ++ &header_size, ++ &vs->ws_payload_remain, ++ &vs->ws_payload_mask); ++ if (err <= 0) { ++ return err; ++ } ++ ++ buffer_advance(&vs->ws_input, header_size); + } ++ if (vs->ws_payload_remain != 0) { ++ err = vncws_decode_frame_payload(&vs->ws_input, ++ &vs->ws_payload_remain, ++ &vs->ws_payload_mask, ++ &payload, ++ &payload_size); ++ if (err < 0) { ++ return err; ++ } ++ if (err == 0) { ++ return ret; ++ } ++ ret += err; + +- buffer_reserve(&vs->input, payload_size); +- buffer_append(&vs->input, payload, payload_size); ++ buffer_reserve(&vs->input, payload_size); ++ buffer_append(&vs->input, payload, payload_size); + +- buffer_advance(&vs->ws_input, frame_size); ++ buffer_advance(&vs->ws_input, payload_size); ++ } + } while (vs->ws_input.offset > 0); + + return ret; +@@ -273,15 +294,14 @@ void vncws_encode_frame(Buffer *output, const void *payload, + buffer_append(output, payload, payload_size); + } + +-int vncws_decode_frame(Buffer *input, uint8_t **payload, +- size_t *payload_size, size_t *frame_size) ++int vncws_decode_frame_header(Buffer *input, ++ size_t *header_size, ++ size_t *payload_remain, ++ WsMask *payload_mask) + { + unsigned char opcode = 0, fin = 0, has_mask = 0; +- size_t header_size = 0; +- uint32_t *payload32; ++ size_t payload_len; + WsHeader *header = (WsHeader *)input->buffer; +- WsMask mask; +- int i; + + if (input->offset < WS_HEAD_MIN_LEN + 4) { + /* header not complete */ +@@ -291,7 +311,7 @@ int vncws_decode_frame(Buffer *input, uint8_t **payload, + fin = (header->b0 & 0x80) >> 7; + opcode = header->b0 & 0x0f; + has_mask = (header->b1 & 0x80) >> 7; +- *payload_size = header->b1 & 0x7f; ++ payload_len = header->b1 & 0x7f; + + if (opcode == WS_OPCODE_CLOSE) { + /* disconnect */ +@@ -308,40 +328,57 @@ int vncws_decode_frame(Buffer *input, uint8_t **payload, + return -2; + } + +- if (*payload_size < 126) { +- header_size = 6; +- mask = header->u.m; +- } else if (*payload_size == 126 && input->offset >= 8) { +- *payload_size = be16_to_cpu(header->u.s16.l16); +- header_size = 8; +- mask = header->u.s16.m16; +- } else if (*payload_size == 127 && input->offset >= 14) { +- *payload_size = be64_to_cpu(header->u.s64.l64); +- header_size = 14; +- mask = header->u.s64.m64; ++ if (payload_len < 126) { ++ *payload_remain = payload_len; ++ *header_size = 6; ++ *payload_mask = header->u.m; ++ } else if (payload_len == 126 && input->offset >= 8) { ++ *payload_remain = be16_to_cpu(header->u.s16.l16); ++ *header_size = 8; ++ *payload_mask = header->u.s16.m16; ++ } else if (payload_len == 127 && input->offset >= 14) { ++ *payload_remain = be64_to_cpu(header->u.s64.l64); ++ *header_size = 14; ++ *payload_mask = header->u.s64.m64; + } else { + /* header not complete */ + return 0; + } + +- *frame_size = header_size + *payload_size; ++ return 1; ++} ++ ++int vncws_decode_frame_payload(Buffer *input, ++ size_t *payload_remain, WsMask *payload_mask, ++ uint8_t **payload, size_t *payload_size) ++{ ++ size_t i; ++ uint32_t *payload32; + +- if (input->offset < *frame_size) { +- /* frame not complete */ ++ *payload = input->buffer; ++ /* If we aren't at the end of the payload, then drop ++ * off the last bytes, so we're always multiple of 4 ++ * for purpose of unmasking, except at end of payload ++ */ ++ if (input->offset < *payload_remain) { ++ *payload_size = input->offset - (input->offset % 4); ++ } else { ++ *payload_size = *payload_remain; ++ } ++ if (*payload_size == 0) { + return 0; + } +- +- *payload = input->buffer + header_size; ++ *payload_remain -= *payload_size; + + /* unmask frame */ + /* process 1 frame (32 bit op) */ + payload32 = (uint32_t *)(*payload); + for (i = 0; i < *payload_size / 4; i++) { +- payload32[i] ^= mask.u; ++ payload32[i] ^= payload_mask->u; + } + /* process the remaining bytes (if any) */ + for (i *= 4; i < *payload_size; i++) { +- (*payload)[i] ^= mask.c[i % 4]; ++ (*payload)[i] ^= payload_mask->c[i % 4]; + } + + return 1; +diff --git a/ui/vnc-ws.h b/ui/vnc-ws.h +index 95c1b0a..6e93fa0 100644 +--- a/ui/vnc-ws.h ++++ b/ui/vnc-ws.h +@@ -83,7 +83,12 @@ long vnc_client_read_ws(VncState *vs); + void vncws_process_handshake(VncState *vs, uint8_t *line, size_t size); + void vncws_encode_frame(Buffer *output, const void *payload, + const size_t payload_size); +-int vncws_decode_frame(Buffer *input, uint8_t **payload, +- size_t *payload_size, size_t *frame_size); ++int vncws_decode_frame_header(Buffer *input, ++ size_t *header_size, ++ size_t *payload_remain, ++ WsMask *payload_mask); ++int vncws_decode_frame_payload(Buffer *input, ++ size_t *payload_remain, WsMask *payload_mask, ++ uint8_t **payload, size_t *payload_size); + + #endif /* __QEMU_UI_VNC_WS_H */ +diff --git a/ui/vnc.h b/ui/vnc.h +index 6e99213..0efc5c6 100644 +--- a/ui/vnc.h ++++ b/ui/vnc.h +@@ -290,6 +290,8 @@ struct VncState + #ifdef CONFIG_VNC_WS + Buffer ws_input; + Buffer ws_output; ++ size_t ws_payload_remain; ++ WsMask ws_payload_mask; + #endif + /* current output mode information */ + VncWritePixels *write_pixels; +-- +1.8.3.1 + diff --git a/SOURCES/kvm-CVE-2015-1779-limit-size-of-HTTP-headers-from-websoc.patch b/SOURCES/kvm-CVE-2015-1779-limit-size-of-HTTP-headers-from-websoc.patch new file mode 100644 index 0000000..4d3de63 --- /dev/null +++ b/SOURCES/kvm-CVE-2015-1779-limit-size-of-HTTP-headers-from-websoc.patch @@ -0,0 +1,70 @@ +From 7721e2e58f7cd2fcf835800622b8a7e1cdeb4557 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Tue, 22 Sep 2015 17:44:54 +0200 +Subject: [PATCH 2/2] CVE-2015-1779: limit size of HTTP headers from websockets + clients + +Message-id: <1442943894-7638-3-git-send-email-kraxel@redhat.com> +Patchwork-id: 67885 +O-Subject: [RHEL-7.1.z qemu-kvm PATCH 2/2] CVE-2015-1779: limit size of HTTP headers from websockets clients +Bugzilla: 1205050 +RH-Acked-by: Miroslav Rezanina +RH-Acked-by: Thomas Huth +RH-Acked-by: Petr Matousek + +From: "Daniel P. Berrange" + +The VNC server websockets decoder will read and buffer data from +websockets clients until it sees the end of the HTTP headers, +as indicated by \r\n\r\n. In theory this allows a malicious to +trick QEMU into consuming an arbitrary amount of RAM. In practice, +because QEMU runs g_strstr_len() across the buffered header data, +it will spend increasingly long burning CPU time searching for +the substring match and less & less time reading data. So while +this does cause arbitrary memory growth, the bigger problem is +that QEMU will be burning 100% of available CPU time. + +A novnc websockets client typically sends headers of around +512 bytes in length. As such it is reasonable to place a 4096 +byte limit on the amount of data buffered while searching for +the end of HTTP headers. + +Signed-off-by: Daniel P. Berrange +Signed-off-by: Gerd Hoffmann +(cherry picked from commit 2cdb5e142fb93e875fa53c52864ef5eb8d5d8b41) +Signed-off-by: Miroslav Rezanina +--- + ui/vnc-ws.c | 10 ++++++++-- + 1 file changed, 8 insertions(+), 2 deletions(-) + +diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c +index a7d457c..7133be9 100644 +--- a/ui/vnc-ws.c ++++ b/ui/vnc-ws.c +@@ -88,8 +88,11 @@ void vncws_handshake_read(void *opaque) + VncState *vs = opaque; + uint8_t *handshake_end; + long ret; +- buffer_reserve(&vs->ws_input, 4096); +- ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), 4096); ++ /* Typical HTTP headers from novnc are 512 bytes, so limiting ++ * total header size to 4096 is easily enough. */ ++ size_t want = 4096 - vs->ws_input.offset; ++ buffer_reserve(&vs->ws_input, want); ++ ret = vnc_client_read_buf(vs, buffer_end(&vs->ws_input), want); + + if (!ret) { + if (vs->csock == -1) { +@@ -106,6 +109,9 @@ void vncws_handshake_read(void *opaque) + vncws_process_handshake(vs, vs->ws_input.buffer, vs->ws_input.offset); + buffer_advance(&vs->ws_input, handshake_end - vs->ws_input.buffer + + strlen(WS_HANDSHAKE_END)); ++ } else if (vs->ws_input.offset >= 4096) { ++ VNC_DEBUG("End of headers not found in first 4096 bytes\n"); ++ vnc_client_error(vs); + } + } + +-- +1.8.3.1 + diff --git a/SOURCES/kvm-qtest-ide-test-disable-flush-test.patch b/SOURCES/kvm-qtest-ide-test-disable-flush-test.patch new file mode 100644 index 0000000..7624cff --- /dev/null +++ b/SOURCES/kvm-qtest-ide-test-disable-flush-test.patch @@ -0,0 +1,86 @@ +From 699e404550228859f73ce42f36c6da538e1b0fb1 Mon Sep 17 00:00:00 2001 +Message-Id: <699e404550228859f73ce42f36c6da538e1b0fb1.1445289321.git.jen@redhat.com> +From: John Snow +Date: Mon, 19 Oct 2015 17:58:34 -0400 +Subject: [CHANGE] qtest/ide-test: disable flush-test +To: rhvirt-patches@redhat.com, + jen@redhat.com + +RH-Author: John Snow +Message-id: <1445277514-26179-2-git-send-email-jsnow@redhat.com> +Patchwork-id: 68184 +O-Subject: [RHEL-7.1.z qemu-kvm PATCH 1/1] qtest/ide-test: disable flush-test +Bugzilla: 1273098 +RH-Acked-by: Jeff Nelson +RH-Acked-by: Wei Huang +RH-Acked-by: Laszlo Ersek + +One of the tests downstream causes a race that can result in build +failures. For 7.3, we intend to fix the test properly, but for 7.1.z +and 7.2.*, we disable the test as a workaround. + +Signed-off-by: John Snow +Signed-off-by: Jeff E. Nelson +--- + tests/ide-test.c | 38 -------------------------------------- + 1 file changed, 38 deletions(-) + +diff --git a/tests/ide-test.c b/tests/ide-test.c +index 51f9239..43b7fd6 100644 +--- a/tests/ide-test.c ++++ b/tests/ide-test.c +@@ -425,43 +425,6 @@ static void test_identify(void) + ide_test_quit(); + } + +-static void test_flush(void) +-{ +- uint8_t data; +- +- ide_test_start( +- "-vnc none " +- "-drive file=blkdebug::%s,if=ide,cache=writeback", +- tmp_path); +- +- /* Delay the completion of the flush request until we explicitly do it */ +- qmp("{'execute':'human-monitor-command', 'arguments': { " +- "'command-line': 'qemu-io ide0-hd0 \"break flush_to_os A\"'} }"); +- +- /* FLUSH CACHE command on device 0*/ +- outb(IDE_BASE + reg_device, 0); +- outb(IDE_BASE + reg_command, CMD_FLUSH_CACHE); +- +- /* Check status while request is in flight*/ +- data = inb(IDE_BASE + reg_status); +- assert_bit_set(data, BSY | DRDY); +- assert_bit_clear(data, DF | ERR | DRQ); +- +- /* Complete the command */ +- qmp("{'execute':'human-monitor-command', 'arguments': { " +- "'command-line': 'qemu-io ide0-hd0 \"resume A\"'} }"); +- +- /* Check registers */ +- data = inb(IDE_BASE + reg_device); +- g_assert_cmpint(data & DEV, ==, 0); +- +- data = inb(IDE_BASE + reg_status); +- assert_bit_set(data, DRDY); +- assert_bit_clear(data, BSY | DF | ERR | DRQ); +- +- ide_test_quit(); +-} +- + static void test_flush_nodev(void) + { + ide_test_start(""); +@@ -505,7 +468,6 @@ int main(int argc, char **argv) + qtest_add_func("/ide/bmdma/long_prdt", test_bmdma_long_prdt); + qtest_add_func("/ide/bmdma/teardown", test_bmdma_teardown); + +- qtest_add_func("/ide/flush", test_flush); + qtest_add_func("/ide/flush_nodev", test_flush_nodev); + + ret = g_test_run(); +-- +2.4.3 + diff --git a/SPECS/qemu-kvm.spec b/SPECS/qemu-kvm.spec index 86f4a7a..0820ccb 100644 --- a/SPECS/qemu-kvm.spec +++ b/SPECS/qemu-kvm.spec @@ -72,7 +72,7 @@ Obsoletes: %1 < %{obsoletes_version} \ Summary: QEMU is a FAST! processor emulator Name: %{pkgname}%{?pkgsuffix} Version: 1.5.3 -Release: 86%{?dist}.6 +Release: 86%{?dist}.8 # Epoch because we pushed a qemu-1.0 package. AIUI this can't ever be dropped Epoch: 10 License: GPLv2+ and LGPLv2+ and BSD @@ -2859,6 +2859,12 @@ Patch1403: kvm-rtl8139-check-IP-Total-Length-field-CVE-2015-5165.patch Patch1404: kvm-rtl8139-skip-offload-on-short-TCP-header-CVE-2015-51.patch # For bz#1248764 - CVE-2015-5165 qemu-kvm: Qemu: rtl8139 uninitialized heap memory information leakage to guest [rhel-7.1.z] Patch1405: kvm-rtl8139-check-TCP-Data-Offset-field-CVE-2015-5165.patch +# For bz#1205050 - CVE-2015-1779 qemu-kvm: qemu: vnc: insufficient resource limiting in VNC websockets decoder [rhel-7.1.z] +Patch1406: kvm-CVE-2015-1779-incrementally-decode-websocket-frames.patch +# For bz#1205050 - CVE-2015-1779 qemu-kvm: qemu: vnc: insufficient resource limiting in VNC websockets decoder [rhel-7.1.z] +Patch1407: kvm-CVE-2015-1779-limit-size-of-HTTP-headers-from-websoc.patch +# For bz#1273098 - qemu-kvm build failure race condition in tests/ide-test +Patch1408: kvm-qtest-ide-test-disable-flush-test.patch BuildRequires: zlib-devel @@ -4472,6 +4478,9 @@ cp %{SOURCE18} pc-bios # keep "make check" happy %patch1403 -p1 %patch1404 -p1 %patch1405 -p1 +%patch1406 -p1 +%patch1407 -p1 +%patch1408 -p1 %build buildarch="%{kvm_target}-softmmu" @@ -4916,6 +4925,17 @@ sh %{_sysconfdir}/sysconfig/modules/kvm.modules &> /dev/null || : %{_libdir}/pkgconfig/libcacard.pc %changelog +* Mon Oct 19 2015 Jeff E. Nelson - 1.5.3-86.el7_1.8 +- kvm-qtest-ide-test-disable-flush-test.patch [bz#1273098] +- Resolves: bz#1273098 + (qemu-kvm build failure race condition in tests/ide-test) + +* Fri Oct 09 2015 Miroslav Rezanina - 1.5.3-86.el7_1.7 +- kvm-CVE-2015-1779-incrementally-decode-websocket-frames.patch [bz#1205050] +- kvm-CVE-2015-1779-limit-size-of-HTTP-headers-from-websoc.patch [bz#1205050] +- Resolves: bz#1205050 + (CVE-2015-1779 qemu-kvm: qemu: vnc: insufficient resource limiting in VNC websockets decoder [rhel-7.1.z]) + * Thu Aug 06 2015 Miroslav Rezanina - 1.5.3-86.el7_1.6 - kvm-rtl8139-avoid-nested-ifs-in-IP-header-parsing-CVE-20.patch [bz#1248764] - kvm-rtl8139-drop-tautologous-if-ip-.-statement-CVE-2015-.patch [bz#1248764]